diff options
Diffstat (limited to 'drivers/scsi/libsas/sas_discover.c')
| -rw-r--r-- | drivers/scsi/libsas/sas_discover.c | 376 | 
1 files changed, 289 insertions, 87 deletions
diff --git a/drivers/scsi/libsas/sas_discover.c b/drivers/scsi/libsas/sas_discover.c index f5831930df9..62b58d38ce2 100644 --- a/drivers/scsi/libsas/sas_discover.c +++ b/drivers/scsi/libsas/sas_discover.c @@ -24,35 +24,32 @@  #include <linux/scatterlist.h>  #include <linux/slab.h> +#include <linux/async.h>  #include <scsi/scsi_host.h>  #include <scsi/scsi_eh.h>  #include "sas_internal.h"  #include <scsi/scsi_transport.h>  #include <scsi/scsi_transport_sas.h> +#include <scsi/sas_ata.h>  #include "../scsi_sas_internal.h"  /* ---------- Basic task processing for discovery purposes ---------- */  void sas_init_dev(struct domain_device *dev)  { -        INIT_LIST_HEAD(&dev->siblings); -        INIT_LIST_HEAD(&dev->dev_list_node); -        switch (dev->dev_type) { -        case SAS_END_DEV: -                break; -        case EDGE_DEV: -        case FANOUT_DEV: -                INIT_LIST_HEAD(&dev->ex_dev.children); -                break; -        case SATA_DEV: -        case SATA_PM: -        case SATA_PM_PORT: -                INIT_LIST_HEAD(&dev->sata_dev.children); -                break; -        default: -                break; -        } +	switch (dev->dev_type) { +	case SAS_END_DEVICE: +		INIT_LIST_HEAD(&dev->ssp_dev.eh_list_node); +		break; +	case SAS_EDGE_EXPANDER_DEVICE: +	case SAS_FANOUT_EXPANDER_DEVICE: +		INIT_LIST_HEAD(&dev->ex_dev.children); +		mutex_init(&dev->ex_dev.cmd_mutex); +		break; +	default: +		break; +	}  }  /* ---------- Domain device discovery ---------- */ @@ -68,19 +65,19 @@ void sas_init_dev(struct domain_device *dev)   */  static int sas_get_port_device(struct asd_sas_port *port)  { -	unsigned long flags;  	struct asd_sas_phy *phy;  	struct sas_rphy *rphy;  	struct domain_device *dev; +	int rc = -ENODEV; -	dev = kzalloc(sizeof(*dev), GFP_KERNEL); +	dev = sas_alloc_device();  	if (!dev)  		return -ENOMEM; -	spin_lock_irqsave(&port->phy_list_lock, flags); +	spin_lock_irq(&port->phy_list_lock);  	if (list_empty(&port->phy_list)) { -		spin_unlock_irqrestore(&port->phy_list_lock, flags); -		kfree(dev); +		spin_unlock_irq(&port->phy_list_lock); +		sas_put_device(dev);  		return -ENODEV;  	}  	phy = container_of(port->phy_list.next, struct asd_sas_phy, port_phy_el); @@ -88,7 +85,7 @@ static int sas_get_port_device(struct asd_sas_port *port)  	memcpy(dev->frame_rcvd, phy->frame_rcvd, min(sizeof(dev->frame_rcvd),  					     (size_t)phy->frame_rcvd_size));  	spin_unlock(&phy->frame_rcvd_lock); -	spin_unlock_irqrestore(&port->phy_list_lock, flags); +	spin_unlock_irq(&port->phy_list_lock);  	if (dev->frame_rcvd[0] == 0x34 && port->oob_mode == SATA_OOB_MODE) {  		struct dev_to_host_fis *fis = @@ -96,9 +93,9 @@ static int sas_get_port_device(struct asd_sas_port *port)  		if (fis->interrupt_reason == 1 && fis->lbal == 1 &&  		    fis->byte_count_low==0x69 && fis->byte_count_high == 0x96  		    && (fis->device & ~0x10) == 0) -			dev->dev_type = SATA_PM; +			dev->dev_type = SAS_SATA_PM;  		else -			dev->dev_type = SATA_DEV; +			dev->dev_type = SAS_SATA_DEV;  		dev->tproto = SAS_PROTOCOL_SATA;  	} else {  		struct sas_identify_frame *id = @@ -110,16 +107,23 @@ static int sas_get_port_device(struct asd_sas_port *port)  	sas_init_dev(dev); +	dev->port = port;  	switch (dev->dev_type) { -	case SAS_END_DEV: -	case SATA_DEV: +	case SAS_SATA_DEV: +		rc = sas_ata_init(dev); +		if (rc) { +			rphy = NULL; +			break; +		} +		/* fall through */ +	case SAS_END_DEVICE:  		rphy = sas_end_device_alloc(port->port);  		break; -	case EDGE_DEV: +	case SAS_EDGE_EXPANDER_DEVICE:  		rphy = sas_expander_alloc(port->port,  					  SAS_EDGE_EXPANDER_DEVICE);  		break; -	case FANOUT_DEV: +	case SAS_FANOUT_EXPANDER_DEVICE:  		rphy = sas_expander_alloc(port->port,  					  SAS_FANOUT_EXPANDER_DEVICE);  		break; @@ -130,15 +134,15 @@ static int sas_get_port_device(struct asd_sas_port *port)  	}  	if (!rphy) { -		kfree(dev); -		return -ENODEV; +		sas_put_device(dev); +		return rc;  	} +  	rphy->identify.phy_identifier = phy->phy->identify.phy_identifier;  	memcpy(dev->sas_addr, port->attached_sas_addr, SAS_ADDR_SIZE);  	sas_fill_in_rphy(dev, rphy);  	sas_hash_addr(dev->hashed_sas_addr, dev->sas_addr);  	port->port_dev = dev; -	dev->port = port;  	dev->linkrate = port->linkrate;  	dev->min_linkrate = port->linkrate;  	dev->max_linkrate = port->linkrate; @@ -147,11 +151,23 @@ static int sas_get_port_device(struct asd_sas_port *port)  	memset(port->disc.eeds_a, 0, SAS_ADDR_SIZE);  	memset(port->disc.eeds_b, 0, SAS_ADDR_SIZE);  	port->disc.max_level = 0; +	sas_device_set_phy(dev, port->port);  	dev->rphy = rphy; -	spin_lock_irq(&port->dev_list_lock); -	list_add_tail(&dev->dev_list_node, &port->dev_list); -	spin_unlock_irq(&port->dev_list_lock); +	get_device(&dev->rphy->dev); + +	if (dev_is_sata(dev) || dev->dev_type == SAS_END_DEVICE) +		list_add_tail(&dev->disco_list_node, &port->disco_list); +	else { +		spin_lock_irq(&port->dev_list_lock); +		list_add_tail(&dev->dev_list_node, &port->dev_list); +		spin_unlock_irq(&port->dev_list_lock); +	} + +	spin_lock_irq(&port->phy_list_lock); +	list_for_each_entry(phy, &port->phy_list, port_phy_el) +		sas_phy_set_target(phy, dev); +	spin_unlock_irq(&port->phy_list_lock);  	return 0;  } @@ -165,15 +181,18 @@ int sas_notify_lldd_dev_found(struct domain_device *dev)  	struct Scsi_Host *shost = sas_ha->core.shost;  	struct sas_internal *i = to_sas_internal(shost->transportt); -	if (i->dft->lldd_dev_found) { -		res = i->dft->lldd_dev_found(dev); -		if (res) { -			printk("sas: driver on pcidev %s cannot handle " -			       "device %llx, error:%d\n", -			       dev_name(sas_ha->dev), -			       SAS_ADDR(dev->sas_addr), res); -		} +	if (!i->dft->lldd_dev_found) +		return 0; + +	res = i->dft->lldd_dev_found(dev); +	if (res) { +		printk("sas: driver on pcidev %s cannot handle " +		       "device %llx, error:%d\n", +		       dev_name(sas_ha->dev), +		       SAS_ADDR(dev->sas_addr), res);  	} +	set_bit(SAS_DEV_FOUND, &dev->state); +	kref_get(&dev->kref);  	return res;  } @@ -184,12 +203,83 @@ void sas_notify_lldd_dev_gone(struct domain_device *dev)  	struct Scsi_Host *shost = sas_ha->core.shost;  	struct sas_internal *i = to_sas_internal(shost->transportt); -	if (i->dft->lldd_dev_gone) +	if (!i->dft->lldd_dev_gone) +		return; + +	if (test_and_clear_bit(SAS_DEV_FOUND, &dev->state)) {  		i->dft->lldd_dev_gone(dev); +		sas_put_device(dev); +	} +} + +static void sas_probe_devices(struct work_struct *work) +{ +	struct domain_device *dev, *n; +	struct sas_discovery_event *ev = to_sas_discovery_event(work); +	struct asd_sas_port *port = ev->port; + +	clear_bit(DISCE_PROBE, &port->disc.pending); + +	/* devices must be domain members before link recovery and probe */ +	list_for_each_entry(dev, &port->disco_list, disco_list_node) { +		spin_lock_irq(&port->dev_list_lock); +		list_add_tail(&dev->dev_list_node, &port->dev_list); +		spin_unlock_irq(&port->dev_list_lock); +	} + +	sas_probe_sata(port); + +	list_for_each_entry_safe(dev, n, &port->disco_list, disco_list_node) { +		int err; + +		err = sas_rphy_add(dev->rphy); +		if (err) +			sas_fail_probe(dev, __func__, err); +		else +			list_del_init(&dev->disco_list_node); +	}  } -/* ---------- Common/dispatchers ---------- */ +static void sas_suspend_devices(struct work_struct *work) +{ +	struct asd_sas_phy *phy; +	struct domain_device *dev; +	struct sas_discovery_event *ev = to_sas_discovery_event(work); +	struct asd_sas_port *port = ev->port; +	struct Scsi_Host *shost = port->ha->core.shost; +	struct sas_internal *si = to_sas_internal(shost->transportt); + +	clear_bit(DISCE_SUSPEND, &port->disc.pending); + +	sas_suspend_sata(port); + +	/* lldd is free to forget the domain_device across the +	 * suspension, we force the issue here to keep the reference +	 * counts aligned +	 */ +	list_for_each_entry(dev, &port->dev_list, dev_list_node) +		sas_notify_lldd_dev_gone(dev); + +	/* we are suspending, so we know events are disabled and +	 * phy_list is not being mutated +	 */ +	list_for_each_entry(phy, &port->phy_list, port_phy_el) { +		if (si->dft->lldd_port_formed) +			si->dft->lldd_port_deformed(phy); +		phy->suspended = 1; +		port->suspended = 1; +	} +} + +static void sas_resume_devices(struct work_struct *work) +{ +	struct sas_discovery_event *ev = to_sas_discovery_event(work); +	struct asd_sas_port *port = ev->port; +	clear_bit(DISCE_RESUME, &port->disc.pending); + +	sas_resume_sata(port); +}  /**   * sas_discover_end_dev -- discover an end device (SSP, etc) @@ -203,58 +293,138 @@ int sas_discover_end_dev(struct domain_device *dev)  	res = sas_notify_lldd_dev_found(dev);  	if (res) -		goto out_err2; - -	res = sas_rphy_add(dev->rphy); -	if (res) -		goto out_err; +		return res; +	sas_discover_event(dev->port, DISCE_PROBE);  	return 0; - -out_err: -	sas_notify_lldd_dev_gone(dev); -out_err2: -	return res;  }  /* ---------- Device registration and unregistration ---------- */ -static inline void sas_unregister_common_dev(struct domain_device *dev) +void sas_free_device(struct kref *kref) +{ +	struct domain_device *dev = container_of(kref, typeof(*dev), kref); + +	put_device(&dev->rphy->dev); +	dev->rphy = NULL; + +	if (dev->parent) +		sas_put_device(dev->parent); + +	sas_port_put_phy(dev->phy); +	dev->phy = NULL; + +	/* remove the phys and ports, everything else should be gone */ +	if (dev->dev_type == SAS_EDGE_EXPANDER_DEVICE || dev->dev_type == SAS_FANOUT_EXPANDER_DEVICE) +		kfree(dev->ex_dev.ex_phy); + +	if (dev_is_sata(dev) && dev->sata_dev.ap) { +		ata_sas_port_destroy(dev->sata_dev.ap); +		dev->sata_dev.ap = NULL; +	} + +	kfree(dev); +} + +static void sas_unregister_common_dev(struct asd_sas_port *port, struct domain_device *dev)  { +	struct sas_ha_struct *ha = port->ha; +  	sas_notify_lldd_dev_gone(dev);  	if (!dev->parent)  		dev->port->port_dev = NULL;  	else  		list_del_init(&dev->siblings); + +	spin_lock_irq(&port->dev_list_lock);  	list_del_init(&dev->dev_list_node); +	if (dev_is_sata(dev)) +		sas_ata_end_eh(dev->sata_dev.ap); +	spin_unlock_irq(&port->dev_list_lock); + +	spin_lock_irq(&ha->lock); +	if (dev->dev_type == SAS_END_DEVICE && +	    !list_empty(&dev->ssp_dev.eh_list_node)) { +		list_del_init(&dev->ssp_dev.eh_list_node); +		ha->eh_active--; +	} +	spin_unlock_irq(&ha->lock); + +	sas_put_device(dev);  } -void sas_unregister_dev(struct domain_device *dev) +static void sas_destruct_devices(struct work_struct *work)  { -	if (dev->rphy) { +	struct domain_device *dev, *n; +	struct sas_discovery_event *ev = to_sas_discovery_event(work); +	struct asd_sas_port *port = ev->port; + +	clear_bit(DISCE_DESTRUCT, &port->disc.pending); + +	list_for_each_entry_safe(dev, n, &port->destroy_list, disco_list_node) { +		list_del_init(&dev->disco_list_node); +  		sas_remove_children(&dev->rphy->dev);  		sas_rphy_delete(dev->rphy); -		dev->rphy = NULL; +		sas_unregister_common_dev(port, dev);  	} -	if (dev->dev_type == EDGE_DEV || dev->dev_type == FANOUT_DEV) { -		/* remove the phys and ports, everything else should be gone */ -		kfree(dev->ex_dev.ex_phy); -		dev->ex_dev.ex_phy = NULL; +} + +void sas_unregister_dev(struct asd_sas_port *port, struct domain_device *dev) +{ +	if (!test_bit(SAS_DEV_DESTROY, &dev->state) && +	    !list_empty(&dev->disco_list_node)) { +		/* this rphy never saw sas_rphy_add */ +		list_del_init(&dev->disco_list_node); +		sas_rphy_free(dev->rphy); +		sas_unregister_common_dev(port, dev); +		return; +	} + +	if (!test_and_set_bit(SAS_DEV_DESTROY, &dev->state)) { +		sas_rphy_unlink(dev->rphy); +		list_move_tail(&dev->disco_list_node, &port->destroy_list); +		sas_discover_event(dev->port, DISCE_DESTRUCT);  	} -	sas_unregister_common_dev(dev);  } -void sas_unregister_domain_devices(struct asd_sas_port *port) +void sas_unregister_domain_devices(struct asd_sas_port *port, int gone)  {  	struct domain_device *dev, *n; -	list_for_each_entry_safe_reverse(dev,n,&port->dev_list,dev_list_node) -		sas_unregister_dev(dev); +	list_for_each_entry_safe_reverse(dev, n, &port->dev_list, dev_list_node) { +		if (gone) +			set_bit(SAS_DEV_GONE, &dev->state); +		sas_unregister_dev(port, dev); +	} + +	list_for_each_entry_safe(dev, n, &port->disco_list, disco_list_node) +		sas_unregister_dev(port, dev);  	port->port->rphy = NULL;  } +void sas_device_set_phy(struct domain_device *dev, struct sas_port *port) +{ +	struct sas_ha_struct *ha; +	struct sas_phy *new_phy; + +	if (!dev) +		return; + +	ha = dev->port->ha; +	new_phy = sas_port_get_phy(port); + +	/* pin and record last seen phy */ +	spin_lock_irq(&ha->phy_port_lock); +	if (new_phy) { +		sas_port_put_phy(dev->phy); +		dev->phy = new_phy; +	} +	spin_unlock_irq(&ha->phy_port_lock); +} +  /* ---------- Discovery and Revalidation ---------- */  /** @@ -270,12 +440,10 @@ 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; -	sas_begin_event(DISCE_DISCOVER_DOMAIN, &port->disc.disc_event_lock, -			&port->disc.pending); +	clear_bit(DISCE_DISCOVER_DOMAIN, &port->disc.pending);  	if (port->port_dev)  		return; @@ -289,15 +457,15 @@ static void sas_discover_domain(struct work_struct *work)  		    task_pid_nr(current));  	switch (dev->dev_type) { -	case SAS_END_DEV: +	case SAS_END_DEVICE:  		error = sas_discover_end_dev(dev);  		break; -	case EDGE_DEV: -	case FANOUT_DEV: +	case SAS_EDGE_EXPANDER_DEVICE: +	case SAS_FANOUT_EXPANDER_DEVICE:  		error = sas_discover_root_expander(dev);  		break; -	case SATA_DEV: -	case SATA_PM: +	case SAS_SATA_DEV: +	case SAS_SATA_PM:  #ifdef CONFIG_SCSI_SAS_ATA  		error = sas_discover_sata(dev);  		break; @@ -313,13 +481,12 @@ static void sas_discover_domain(struct work_struct *work)  	if (error) {  		sas_rphy_free(dev->rphy); -		dev->rphy = NULL; - +		list_del_init(&dev->disco_list_node);  		spin_lock_irq(&port->dev_list_lock);  		list_del_init(&dev->dev_list_node);  		spin_unlock_irq(&port->dev_list_lock); -		kfree(dev); /* not kobject_register-ed yet */ +		sas_put_device(dev);  		port->port_dev = NULL;  	} @@ -330,24 +497,57 @@ 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; + +	/* prevent revalidation from finding sata links in recovery */ +	mutex_lock(&ha->disco_mutex); +	if (test_bit(SAS_HA_ATA_EH_ACTIVE, &ha->state)) { +		SAS_DPRINTK("REVALIDATION DEFERRED on port %d, pid:%d\n", +			    port->id, task_pid_nr(current)); +		goto out; +	} -	sas_begin_event(DISCE_REVALIDATE_DOMAIN, &port->disc.disc_event_lock, -			&port->disc.pending); +	clear_bit(DISCE_REVALIDATE_DOMAIN, &port->disc.pending);  	SAS_DPRINTK("REVALIDATING DOMAIN on port %d, pid:%d\n", port->id,  		    task_pid_nr(current)); +  	if (port->port_dev)  		res = sas_ex_revalidate_domain(port->port_dev);  	SAS_DPRINTK("done REVALIDATING DOMAIN on port %d, pid:%d, res 0x%x\n",  		    port->id, task_pid_nr(current), res); + out: +	mutex_unlock(&ha->disco_mutex);  }  /* ---------- Events ---------- */ +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, 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 sas_work *sw, +			    struct sas_ha_struct *ha) +{ +	if (!test_and_set_bit(event, pending)) { +		unsigned long flags; + +		spin_lock_irqsave(&ha->lock, flags); +		sas_chain_work(ha, sw); +		spin_unlock_irqrestore(&ha->lock, flags); +	} +} +  int sas_discover_event(struct asd_sas_port *port, enum discover_event ev)  {  	struct sas_discovery *disc; @@ -358,8 +558,7 @@ int sas_discover_event(struct asd_sas_port *port, enum discover_event ev)  	BUG_ON(ev >= DISC_NUM_EVENTS); -	sas_queue_event(ev, &disc->disc_event_lock, &disc->pending, -			&disc->disc_work[ev].work, port->ha); +	sas_chain_event(ev, &disc->pending, &disc->disc_work[ev].work, port->ha);  	return 0;  } @@ -377,12 +576,15 @@ void sas_init_disc(struct sas_discovery *disc, struct asd_sas_port *port)  	static const work_func_t sas_event_fns[DISC_NUM_EVENTS] = {  		[DISCE_DISCOVER_DOMAIN] = sas_discover_domain,  		[DISCE_REVALIDATE_DOMAIN] = sas_revalidate_domain, +		[DISCE_PROBE] = sas_probe_devices, +		[DISCE_SUSPEND] = sas_suspend_devices, +		[DISCE_RESUME] = sas_resume_devices, +		[DISCE_DESTRUCT] = sas_destruct_devices,  	}; -	spin_lock_init(&disc->disc_event_lock);  	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;  	}  }  | 
