Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 22b9153f authored by Dan Williams's avatar Dan Williams Committed by James Bottomley
Browse files

[SCSI] libsas: introduce sas_work to fix sas_drain_work vs sas_queue_work



When requeuing work to a draining workqueue the last work instance may
not be idle, so sas_queue_work() must not touch work->entry.  Introduce
sas_work with a drain_node list_head to have a private list for
collecting work deferred due to drain collision.

Fixes reports like:
  BUG: unable to handle kernel NULL pointer dereference at           (null)
  IP: [<ffffffff810410d4>] process_one_work+0x2e/0x338

Signed-off-by: default avatarDan Williams <dan.j.williams@intel.com>
Signed-off-by: default avatarJames Bottomley <JBottomley@Parallels.com>
parent f8fc75dc
Loading
Loading
Loading
Loading
+14 −14
Original line number Diff line number Diff line
@@ -205,8 +205,7 @@ void sas_notify_lldd_dev_gone(struct domain_device *dev)
static void sas_probe_devices(struct work_struct *work)
{
	struct domain_device *dev, *n;
	struct sas_discovery_event *ev =
		container_of(work, struct sas_discovery_event, work);
	struct sas_discovery_event *ev = to_sas_discovery_event(work);
	struct asd_sas_port *port = ev->port;

	clear_bit(DISCE_PROBE, &port->disc.pending);
@@ -291,8 +290,7 @@ static void sas_unregister_common_dev(struct asd_sas_port *port, struct domain_d
static void sas_destruct_devices(struct work_struct *work)
{
	struct domain_device *dev, *n;
	struct sas_discovery_event *ev =
		container_of(work, struct sas_discovery_event, work);
	struct sas_discovery_event *ev = to_sas_discovery_event(work);
	struct asd_sas_port *port = ev->port;

	clear_bit(DISCE_DESTRUCT, &port->disc.pending);
@@ -377,8 +375,7 @@ static void sas_discover_domain(struct work_struct *work)
{
	struct domain_device *dev;
	int error = 0;
	struct sas_discovery_event *ev =
		container_of(work, struct sas_discovery_event, work);
	struct sas_discovery_event *ev = to_sas_discovery_event(work);
	struct asd_sas_port *port = ev->port;

	clear_bit(DISCE_DISCOVER_DOMAIN, &port->disc.pending);
@@ -437,8 +434,7 @@ static void sas_discover_domain(struct work_struct *work)
static void sas_revalidate_domain(struct work_struct *work)
{
	int res = 0;
	struct sas_discovery_event *ev =
		container_of(work, struct sas_discovery_event, work);
	struct sas_discovery_event *ev = to_sas_discovery_event(work);
	struct asd_sas_port *port = ev->port;
	struct sas_ha_struct *ha = port->ha;

@@ -466,21 +462,25 @@ static void sas_revalidate_domain(struct work_struct *work)

/* ---------- Events ---------- */

static void sas_chain_work(struct sas_ha_struct *ha, struct work_struct *work)
static void sas_chain_work(struct sas_ha_struct *ha, struct sas_work *sw)
{
	/* chained work is not subject to SA_HA_DRAINING or SAS_HA_REGISTERED */
	scsi_queue_work(ha->core.shost, work);
	/* chained work is not subject to SA_HA_DRAINING or
	 * SAS_HA_REGISTERED, because it is either submitted in the
	 * workqueue, or known to be submitted from a context that is
	 * not racing against draining
	 */
	scsi_queue_work(ha->core.shost, &sw->work);
}

static void sas_chain_event(int event, unsigned long *pending,
			    struct work_struct *work,
			    struct sas_work *sw,
			    struct sas_ha_struct *ha)
{
	if (!test_and_set_bit(event, pending)) {
		unsigned long flags;

		spin_lock_irqsave(&ha->state_lock, flags);
		sas_chain_work(ha, work);
		sas_chain_work(ha, sw);
		spin_unlock_irqrestore(&ha->state_lock, flags);
	}
}
@@ -519,7 +519,7 @@ void sas_init_disc(struct sas_discovery *disc, struct asd_sas_port *port)

	disc->pending = 0;
	for (i = 0; i < DISC_NUM_EVENTS; i++) {
		INIT_WORK(&disc->disc_work[i].work, sas_event_fns[i]);
		INIT_SAS_WORK(&disc->disc_work[i].work, sas_event_fns[i]);
		disc->disc_work[i].port = port;
	}
}
+13 −11
Original line number Diff line number Diff line
@@ -27,19 +27,21 @@
#include "sas_internal.h"
#include "sas_dump.h"

void sas_queue_work(struct sas_ha_struct *ha, struct work_struct *work)
void sas_queue_work(struct sas_ha_struct *ha, struct sas_work *sw)
{
	if (!test_bit(SAS_HA_REGISTERED, &ha->state))
		return;

	if (test_bit(SAS_HA_DRAINING, &ha->state))
		list_add(&work->entry, &ha->defer_q);
	else
		scsi_queue_work(ha->core.shost, work);
	if (test_bit(SAS_HA_DRAINING, &ha->state)) {
		/* add it to the defer list, if not already pending */
		if (list_empty(&sw->drain_node))
			list_add(&sw->drain_node, &ha->defer_q);
	} else
		scsi_queue_work(ha->core.shost, &sw->work);
}

static void sas_queue_event(int event, unsigned long *pending,
			    struct work_struct *work,
			    struct sas_work *work,
			    struct sas_ha_struct *ha)
{
	if (!test_and_set_bit(event, pending)) {
@@ -55,7 +57,7 @@ static void sas_queue_event(int event, unsigned long *pending,
void __sas_drain_work(struct sas_ha_struct *ha)
{
	struct workqueue_struct *wq = ha->core.shost->work_q;
	struct work_struct *w, *_w;
	struct sas_work *sw, *_sw;

	set_bit(SAS_HA_DRAINING, &ha->state);
	/* flush submitters */
@@ -66,9 +68,9 @@ void __sas_drain_work(struct sas_ha_struct *ha)

	spin_lock_irq(&ha->state_lock);
	clear_bit(SAS_HA_DRAINING, &ha->state);
	list_for_each_entry_safe(w, _w, &ha->defer_q, entry) {
		list_del_init(&w->entry);
		sas_queue_work(ha, w);
	list_for_each_entry_safe(sw, _sw, &ha->defer_q, drain_node) {
		list_del_init(&sw->drain_node);
		sas_queue_work(ha, sw);
	}
	spin_unlock_irq(&ha->state_lock);
}
@@ -151,7 +153,7 @@ int sas_init_events(struct sas_ha_struct *sas_ha)
	int i;

	for (i = 0; i < HA_NUM_EVENTS; i++) {
		INIT_WORK(&sas_ha->ha_events[i].work, sas_ha_event_fns[i]);
		INIT_SAS_WORK(&sas_ha->ha_events[i].work, sas_ha_event_fns[i]);
		sas_ha->ha_events[i].ha = sas_ha;
	}

+5 −6
Original line number Diff line number Diff line
@@ -94,8 +94,7 @@ void sas_hash_addr(u8 *hashed, const u8 *sas_addr)

void sas_hae_reset(struct work_struct *work)
{
	struct sas_ha_event *ev =
		container_of(work, struct sas_ha_event, work);
	struct sas_ha_event *ev = to_sas_ha_event(work);
	struct sas_ha_struct *ha = ev->ha;

	clear_bit(HAE_RESET, &ha->pending);
@@ -369,14 +368,14 @@ static void sas_phy_release(struct sas_phy *phy)

static void phy_reset_work(struct work_struct *work)
{
	struct sas_phy_data *d = container_of(work, typeof(*d), reset_work);
	struct sas_phy_data *d = container_of(work, typeof(*d), reset_work.work);

	d->reset_result = transport_sas_phy_reset(d->phy, d->hard_reset);
}

static void phy_enable_work(struct work_struct *work)
{
	struct sas_phy_data *d = container_of(work, typeof(*d), enable_work);
	struct sas_phy_data *d = container_of(work, typeof(*d), enable_work.work);

	d->enable_result = sas_phy_enable(d->phy, d->enable);
}
@@ -389,8 +388,8 @@ static int sas_phy_setup(struct sas_phy *phy)
		return -ENOMEM;

	mutex_init(&d->event_lock);
	INIT_WORK(&d->reset_work, phy_reset_work);
	INIT_WORK(&d->enable_work, phy_enable_work);
	INIT_SAS_WORK(&d->reset_work, phy_reset_work);
	INIT_SAS_WORK(&d->enable_work, phy_enable_work);
	d->phy = phy;
	phy->hostdata = d;

+3 −3
Original line number Diff line number Diff line
@@ -45,10 +45,10 @@ struct sas_phy_data {
	struct mutex event_lock;
	int hard_reset;
	int reset_result;
	struct work_struct reset_work;
	struct sas_work reset_work;
	int enable;
	int enable_result;
	struct work_struct enable_work;
	struct sas_work enable_work;
};

void sas_scsi_recover_host(struct Scsi_Host *shost);
@@ -80,7 +80,7 @@ void sas_porte_broadcast_rcvd(struct work_struct *work);
void sas_porte_link_reset_err(struct work_struct *work);
void sas_porte_timer_event(struct work_struct *work);
void sas_porte_hard_reset(struct work_struct *work);
void sas_queue_work(struct sas_ha_struct *ha, struct work_struct *work);
void sas_queue_work(struct sas_ha_struct *ha, struct sas_work *sw);

int sas_notify_lldd_dev_found(struct domain_device *);
void sas_notify_lldd_dev_gone(struct domain_device *);
+7 −14
Original line number Diff line number Diff line
@@ -32,8 +32,7 @@

static void sas_phye_loss_of_signal(struct work_struct *work)
{
	struct asd_sas_event *ev =
		container_of(work, struct asd_sas_event, work);
	struct asd_sas_event *ev = to_asd_sas_event(work);
	struct asd_sas_phy *phy = ev->phy;

	clear_bit(PHYE_LOSS_OF_SIGNAL, &phy->phy_events_pending);
@@ -43,8 +42,7 @@ static void sas_phye_loss_of_signal(struct work_struct *work)

static void sas_phye_oob_done(struct work_struct *work)
{
	struct asd_sas_event *ev =
		container_of(work, struct asd_sas_event, work);
	struct asd_sas_event *ev = to_asd_sas_event(work);
	struct asd_sas_phy *phy = ev->phy;

	clear_bit(PHYE_OOB_DONE, &phy->phy_events_pending);
@@ -53,8 +51,7 @@ static void sas_phye_oob_done(struct work_struct *work)

static void sas_phye_oob_error(struct work_struct *work)
{
	struct asd_sas_event *ev =
		container_of(work, struct asd_sas_event, work);
	struct asd_sas_event *ev = to_asd_sas_event(work);
	struct asd_sas_phy *phy = ev->phy;
	struct sas_ha_struct *sas_ha = phy->ha;
	struct asd_sas_port *port = phy->port;
@@ -85,8 +82,7 @@ static void sas_phye_oob_error(struct work_struct *work)

static void sas_phye_spinup_hold(struct work_struct *work)
{
	struct asd_sas_event *ev =
		container_of(work, struct asd_sas_event, work);
	struct asd_sas_event *ev = to_asd_sas_event(work);
	struct asd_sas_phy *phy = ev->phy;
	struct sas_ha_struct *sas_ha = phy->ha;
	struct sas_internal *i =
@@ -127,14 +123,12 @@ int sas_register_phys(struct sas_ha_struct *sas_ha)
		phy->error = 0;
		INIT_LIST_HEAD(&phy->port_phy_el);
		for (k = 0; k < PORT_NUM_EVENTS; k++) {
			INIT_WORK(&phy->port_events[k].work,
				  sas_port_event_fns[k]);
			INIT_SAS_WORK(&phy->port_events[k].work, sas_port_event_fns[k]);
			phy->port_events[k].phy = phy;
		}

		for (k = 0; k < PHY_NUM_EVENTS; k++) {
			INIT_WORK(&phy->phy_events[k].work,
				  sas_phy_event_fns[k]);
			INIT_SAS_WORK(&phy->phy_events[k].work, sas_phy_event_fns[k]);
			phy->phy_events[k].phy = phy;
		}

@@ -144,8 +138,7 @@ int sas_register_phys(struct sas_ha_struct *sas_ha)
		spin_lock_init(&phy->sas_prim_lock);
		phy->frame_rcvd_size = 0;

		phy->phy = sas_phy_alloc(&sas_ha->core.shost->shost_gendev,
					 i);
		phy->phy = sas_phy_alloc(&sas_ha->core.shost->shost_gendev, i);
		if (!phy->phy)
			return -ENOMEM;

Loading