From 735f7d2fedf57380214221be7bed7f62d729e262 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 17 Nov 2011 17:59:47 -0800 Subject: [SCSI] libsas: fix domain_device leak Arrange for the deallocation of a struct domain_device object when it no longer has: 1/ any children 2/ references by any scsi_targets 3/ references by a lldd The comment about domain_device lifetime in Documentation/scsi/libsas.txt is stale as it appears mainline never had a version of a struct domain_device that was registered as a kobject. We now manage domain_device reference counts on behalf of external agents. Reviewed-by: Jack Wang Signed-off-by: Dan Williams Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_expander.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers/scsi/libsas/sas_expander.c') diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index 1b831c55ec6..15d2239a378 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -657,10 +657,11 @@ static struct domain_device *sas_ex_discover_end_dev( if (phy->attached_sata_host || phy->attached_sata_ps) return NULL; - child = kzalloc(sizeof(*child), GFP_KERNEL); + child = sas_alloc_device(); if (!child) return NULL; + kref_get(&parent->kref); child->parent = parent; child->port = parent->port; child->iproto = phy->attached_iproto; @@ -762,7 +763,7 @@ static struct domain_device *sas_ex_discover_end_dev( sas_port_delete(phy->port); out_err: phy->port = NULL; - kfree(child); + sas_put_device(child); return NULL; } @@ -809,7 +810,7 @@ static struct domain_device *sas_ex_discover_expander( phy->attached_phy_id); return NULL; } - child = kzalloc(sizeof(*child), GFP_KERNEL); + child = sas_alloc_device(); if (!child) return NULL; @@ -835,6 +836,7 @@ static struct domain_device *sas_ex_discover_expander( child->rphy = rphy; edev = rphy_to_expander_device(rphy); child->dev_type = phy->attached_dev_type; + kref_get(&parent->kref); child->parent = parent; child->port = port; child->iproto = phy->attached_iproto; @@ -858,7 +860,7 @@ static struct domain_device *sas_ex_discover_expander( spin_lock_irq(&parent->port->dev_list_lock); list_del(&child->dev_list_node); spin_unlock_irq(&parent->port->dev_list_lock); - kfree(child); + sas_put_device(child); return NULL; } list_add_tail(&child->siblings, &parent->ex_dev.children); -- cgit v1.2.3-70-g09d2 From e139942d77a6e3ac83bc322e826668054a8601d6 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sat, 7 Jan 2012 08:52:39 +0000 Subject: [SCSI] libsas: convert dev->gone to flags In preparation for adding tracking of another device state "destroy". Signed-off-by: Dan Williams Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_ata.c | 2 +- drivers/scsi/libsas/sas_expander.c | 6 +++--- drivers/scsi/libsas/sas_port.c | 2 +- drivers/scsi/libsas/sas_scsi_host.c | 2 +- include/scsi/libsas.h | 7 +++++-- 5 files changed, 11 insertions(+), 8 deletions(-) (limited to 'drivers/scsi/libsas/sas_expander.c') diff --git a/drivers/scsi/libsas/sas_ata.c b/drivers/scsi/libsas/sas_ata.c index 81ce39d166d..2fc5a3961ca 100644 --- a/drivers/scsi/libsas/sas_ata.c +++ b/drivers/scsi/libsas/sas_ata.c @@ -184,7 +184,7 @@ static unsigned int sas_ata_qc_issue(struct ata_queued_cmd *qc) spin_unlock(ap->lock); /* If the device fell off, no sense in issuing commands */ - if (dev->gone) + if (test_bit(SAS_DEV_GONE, &dev->state)) goto out; task = sas_alloc_task(GFP_ATOMIC); diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index 15d2239a378..f33d0c9911c 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -1750,7 +1750,7 @@ static void sas_unregister_ex_tree(struct asd_sas_port *port, struct domain_devi struct domain_device *child, *n; list_for_each_entry_safe(child, n, &ex->children, siblings) { - child->gone = 1; + set_bit(SAS_DEV_GONE, &child->state); if (child->dev_type == EDGE_DEV || child->dev_type == FANOUT_DEV) sas_unregister_ex_tree(port, child); @@ -1771,7 +1771,7 @@ static void sas_unregister_devs_sas_addr(struct domain_device *parent, &ex_dev->children, siblings) { if (SAS_ADDR(child->sas_addr) == SAS_ADDR(phy->attached_sas_addr)) { - child->gone = 1; + set_bit(SAS_DEV_GONE, &child->state); if (child->dev_type == EDGE_DEV || child->dev_type == FANOUT_DEV) sas_unregister_ex_tree(parent->port, child); @@ -1780,7 +1780,7 @@ static void sas_unregister_devs_sas_addr(struct domain_device *parent, break; } } - parent->gone = 1; + set_bit(SAS_DEV_GONE, &parent->state); sas_disable_routing(parent, phy->attached_sas_addr); } memset(phy->attached_sas_addr, 0, SAS_ADDR_SIZE); diff --git a/drivers/scsi/libsas/sas_port.c b/drivers/scsi/libsas/sas_port.c index a47c7a75327..d88e55f9732 100644 --- a/drivers/scsi/libsas/sas_port.c +++ b/drivers/scsi/libsas/sas_port.c @@ -171,7 +171,7 @@ void sas_deform_port(struct asd_sas_phy *phy, int gone) if (port->num_phys == 1) { if (dev && gone) - dev->gone = 1; + set_bit(SAS_DEV_GONE, &dev->state); sas_unregister_domain_devices(port); sas_port_delete(port->port); port->port = NULL; diff --git a/drivers/scsi/libsas/sas_scsi_host.c b/drivers/scsi/libsas/sas_scsi_host.c index fd60465d4b2..15533a17eb9 100644 --- a/drivers/scsi/libsas/sas_scsi_host.c +++ b/drivers/scsi/libsas/sas_scsi_host.c @@ -192,7 +192,7 @@ int sas_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd) int res = 0; /* If the device fell off, no sense in issuing commands */ - if (dev->gone) { + if (test_bit(SAS_DEV_GONE, &dev->state)) { cmd->result = DID_BAD_TARGET << 16; goto out_done; } diff --git a/include/scsi/libsas.h b/include/scsi/libsas.h index 42900fa95a0..d792b13cfcf 100644 --- a/include/scsi/libsas.h +++ b/include/scsi/libsas.h @@ -173,7 +173,10 @@ struct sata_device { struct ata_taskfile tf; }; -/* ---------- Domain device ---------- */ +enum { + SAS_DEV_GONE, +}; + struct domain_device { enum sas_dev_type dev_type; @@ -205,7 +208,7 @@ struct domain_device { }; void *lldd_dev; - int gone; + unsigned long state; struct kref kref; }; -- cgit v1.2.3-70-g09d2 From 87c8331fcf72e501c3a3c0cdc5c9391ec72f7cf2 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 17 Nov 2011 17:59:51 -0800 Subject: [SCSI] libsas: prevent domain rediscovery competing with ata error handling libata error handling provides for a timeout for link recovery. libsas must not rescan for previously known devices in this interval otherwise it may remove a device that is simply waiting for its link to recover. Let libata-eh make the determination of when the link is stable and prevent libsas (host workqueue) from taking action while this determination is pending. Using a mutex (ha->disco_mutex) to flush and disable revalidation while eh is running requires any discovery action that may block on eh be moved to its own context outside the lock. Probing ATA devices explicitly waits on ata-eh and the cache-flush-io issued during device removal may also pend awaiting eh completion. Essentially any rphy add/remove activity needs to run outside the lock. This adds two new cleanup states for sas_unregister_domain_devices() 'allocated-but-not-probed', and 'flagged-for-destruction'. In the 'allocated-but-not-probed' state dev->rphy points to a rphy that is known to have not been through a sas_rphy_add() event. At domain teardown check if this device is still pending probe and cleanup accordingly. Similarly if a device has already been queued for removal then sas_unregister_domain_devices has nothing to do. Signed-off-by: Dan Williams Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_ata.c | 55 ++++++++++++++++++++++++++++++--- drivers/scsi/libsas/sas_discover.c | 63 ++++++++++++++++++++++++++++++++++---- drivers/scsi/libsas/sas_event.c | 26 ++++++++++++++++ drivers/scsi/libsas/sas_expander.c | 5 ++- drivers/scsi/libsas/sas_init.c | 2 ++ drivers/scsi/libsas/sas_internal.h | 3 ++ drivers/scsi/libsas/sas_port.c | 2 ++ drivers/scsi/scsi_transport_sas.c | 18 +++++++++-- include/scsi/libsas.h | 12 ++++++-- include/scsi/sas_ata.h | 5 +++ include/scsi/scsi_transport_sas.h | 1 + 11 files changed, 174 insertions(+), 18 deletions(-) (limited to 'drivers/scsi/libsas/sas_expander.c') diff --git a/drivers/scsi/libsas/sas_ata.c b/drivers/scsi/libsas/sas_ata.c index 2fc5a3961ca..4b6365c6410 100644 --- a/drivers/scsi/libsas/sas_ata.c +++ b/drivers/scsi/libsas/sas_ata.c @@ -758,6 +758,35 @@ static int sas_discover_sata_pm(struct domain_device *dev) return -ENODEV; } +void sas_probe_sata(struct work_struct *work) +{ + struct domain_device *dev, *n; + struct sas_discovery_event *ev = + container_of(work, struct sas_discovery_event, work); + struct asd_sas_port *port = ev->port; + + clear_bit(DISCE_PROBE, &port->disc.pending); + + list_for_each_entry_safe(dev, n, &port->disco_list, disco_list_node) { + int err; + + spin_lock_irq(&port->dev_list_lock); + list_add_tail(&dev->dev_list_node, &port->dev_list); + spin_unlock_irq(&port->dev_list_lock); + + err = sas_rphy_add(dev->rphy); + + if (err) { + SAS_DPRINTK("%s: for %s device %16llx returned %d\n", + __func__, dev->parent ? "exp-attached" : + "direct-attached", + SAS_ADDR(dev->sas_addr), err); + sas_unregister_dev(port, dev); + } else + list_del_init(&dev->disco_list_node); + } +} + /** * sas_discover_sata -- discover an STP/SATA domain device * @dev: pointer to struct domain_device of interest @@ -794,10 +823,15 @@ int sas_discover_sata(struct domain_device *dev) break; } sas_notify_lldd_dev_gone(dev); - if (!res) { - sas_notify_lldd_dev_found(dev); - res = sas_rphy_add(dev->rphy); - } + + if (res) + return res; + + res = sas_notify_lldd_dev_found(dev); + if (res) + return res; + + sas_discover_event(dev->port, DISCE_PROBE); return res; } @@ -805,6 +839,17 @@ int sas_discover_sata(struct domain_device *dev) void sas_ata_strategy_handler(struct Scsi_Host *shost) { struct scsi_device *sdev; + struct sas_ha_struct *sas_ha = SHOST_TO_SAS_HA(shost); + + /* it's ok to defer revalidation events during ata eh, these + * disks are in one of three states: + * 1/ present for initial domain discovery, and these + * resets will cause bcn flutters + * 2/ hot removed, we'll discover that after eh fails + * 3/ hot added after initial discovery, lost the race, and need + * to catch the next train. + */ + sas_disable_revalidation(sas_ha); shost_for_each_device(sdev, shost) { struct domain_device *ddev = sdev_to_domain_dev(sdev); @@ -816,6 +861,8 @@ void sas_ata_strategy_handler(struct Scsi_Host *shost) ata_port_printk(ap, KERN_DEBUG, "sas eh calling libata port error handler"); ata_scsi_port_error_handler(shost, ap); } + + sas_enable_revalidation(sas_ha); } int sas_ata_timed_out(struct scsi_cmnd *cmd, struct sas_task *task, diff --git a/drivers/scsi/libsas/sas_discover.c b/drivers/scsi/libsas/sas_discover.c index 32e01176604..7e8fdcb202b 100644 --- a/drivers/scsi/libsas/sas_discover.c +++ b/drivers/scsi/libsas/sas_discover.c @@ -148,9 +148,14 @@ static int sas_get_port_device(struct asd_sas_port *port) port->disc.max_level = 0; 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); + + if (dev_is_sata(dev)) + 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); + } return 0; } @@ -255,14 +260,43 @@ static void sas_unregister_common_dev(struct asd_sas_port *port, struct domain_d sas_put_device(dev); } -void sas_unregister_dev(struct asd_sas_port *port, 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 = + container_of(work, struct 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); + + sas_put_device(dev); + } +} + +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); + dev->rphy = NULL; + sas_unregister_common_dev(port, dev); + } + + if (dev->rphy && !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(port, dev); } void sas_unregister_domain_devices(struct asd_sas_port *port) @@ -271,6 +305,8 @@ void sas_unregister_domain_devices(struct asd_sas_port *port) list_for_each_entry_safe_reverse(dev, n, &port->dev_list, dev_list_node) 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; @@ -335,6 +371,7 @@ static void sas_discover_domain(struct work_struct *work) 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); @@ -353,16 +390,28 @@ static void sas_revalidate_domain(struct work_struct *work) struct sas_discovery_event *ev = container_of(work, struct 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; + } 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 ---------- */ @@ -414,6 +463,8 @@ 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_sata, + [DISCE_DESTRUCT] = sas_destruct_devices, }; disc->pending = 0; diff --git a/drivers/scsi/libsas/sas_event.c b/drivers/scsi/libsas/sas_event.c index e5035aa4c2a..933d757499b 100644 --- a/drivers/scsi/libsas/sas_event.c +++ b/drivers/scsi/libsas/sas_event.c @@ -81,6 +81,32 @@ int sas_drain_work(struct sas_ha_struct *ha) } EXPORT_SYMBOL_GPL(sas_drain_work); +void sas_disable_revalidation(struct sas_ha_struct *ha) +{ + mutex_lock(&ha->disco_mutex); + set_bit(SAS_HA_ATA_EH_ACTIVE, &ha->state); + mutex_unlock(&ha->disco_mutex); +} + +void sas_enable_revalidation(struct sas_ha_struct *ha) +{ + int i; + + mutex_lock(&ha->disco_mutex); + clear_bit(SAS_HA_ATA_EH_ACTIVE, &ha->state); + for (i = 0; i < ha->num_phys; i++) { + struct asd_sas_port *port = ha->sas_port[i]; + const int ev = DISCE_REVALIDATE_DOMAIN; + struct sas_discovery *d = &port->disc; + + if (!test_and_clear_bit(ev, &d->pending)) + continue; + + sas_queue_event(ev, &d->pending, &d->disc_work[ev].work, ha); + } + mutex_unlock(&ha->disco_mutex); +} + static void notify_ha_event(struct sas_ha_struct *sas_ha, enum ha_event event) { BUG_ON(event >= HA_NUM_EVENTS); diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index f33d0c9911c..e45b259dac4 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -704,9 +704,7 @@ static struct domain_device *sas_ex_discover_end_dev( child->rphy = rphy; - spin_lock_irq(&parent->port->dev_list_lock); - list_add_tail(&child->dev_list_node, &parent->port->dev_list); - spin_unlock_irq(&parent->port->dev_list_lock); + list_add_tail(&child->disco_list_node, &parent->port->disco_list); res = sas_discover_sata(child); if (res) { @@ -756,6 +754,7 @@ static struct domain_device *sas_ex_discover_end_dev( sas_rphy_free(child->rphy); child->rphy = NULL; + list_del(&child->disco_list_node); spin_lock_irq(&parent->port->dev_list_lock); list_del(&child->dev_list_node); spin_unlock_irq(&parent->port->dev_list_lock); diff --git a/drivers/scsi/libsas/sas_init.c b/drivers/scsi/libsas/sas_init.c index 572b943d760..52cd11d7666 100644 --- a/drivers/scsi/libsas/sas_init.c +++ b/drivers/scsi/libsas/sas_init.c @@ -104,6 +104,7 @@ int sas_register_ha(struct sas_ha_struct *sas_ha) { int error = 0; + mutex_init(&sas_ha->disco_mutex); spin_lock_init(&sas_ha->phy_port_lock); sas_hash_addr(sas_ha->hashed_sas_addr, sas_ha->sas_addr); @@ -168,6 +169,7 @@ int sas_unregister_ha(struct sas_ha_struct *sas_ha) sas_drain_work(sas_ha); sas_unregister_ports(sas_ha); + sas_drain_work(sas_ha); if (sas_ha->lldd_max_execute_num > 1) { sas_shutdown_queue(sas_ha); diff --git a/drivers/scsi/libsas/sas_internal.h b/drivers/scsi/libsas/sas_internal.h index 948ea64cc2e..ebe9b81ddef 100644 --- a/drivers/scsi/libsas/sas_internal.h +++ b/drivers/scsi/libsas/sas_internal.h @@ -56,6 +56,8 @@ enum blk_eh_timer_return sas_scsi_timed_out(struct scsi_cmnd *); int sas_init_queue(struct sas_ha_struct *sas_ha); int sas_init_events(struct sas_ha_struct *sas_ha); void sas_shutdown_queue(struct sas_ha_struct *sas_ha); +void sas_disable_revalidation(struct sas_ha_struct *ha); +void sas_enable_revalidation(struct sas_ha_struct *ha); void sas_deform_port(struct asd_sas_phy *phy, int gone); @@ -138,6 +140,7 @@ static inline struct domain_device *sas_alloc_device(void) if (dev) { INIT_LIST_HEAD(&dev->siblings); INIT_LIST_HEAD(&dev->dev_list_node); + INIT_LIST_HEAD(&dev->disco_list_node); kref_init(&dev->kref); } return dev; diff --git a/drivers/scsi/libsas/sas_port.c b/drivers/scsi/libsas/sas_port.c index d88e55f9732..2980bde4e34 100644 --- a/drivers/scsi/libsas/sas_port.c +++ b/drivers/scsi/libsas/sas_port.c @@ -277,6 +277,8 @@ static void sas_init_port(struct asd_sas_port *port, memset(port, 0, sizeof(*port)); port->id = i; INIT_LIST_HEAD(&port->dev_list); + INIT_LIST_HEAD(&port->disco_list); + INIT_LIST_HEAD(&port->destroy_list); spin_lock_init(&port->phy_list_lock); INIT_LIST_HEAD(&port->phy_list); port->ha = sas_ha; diff --git a/drivers/scsi/scsi_transport_sas.c b/drivers/scsi/scsi_transport_sas.c index 9d9330ae421..9421bae8af1 100644 --- a/drivers/scsi/scsi_transport_sas.c +++ b/drivers/scsi/scsi_transport_sas.c @@ -1602,6 +1602,20 @@ sas_rphy_delete(struct sas_rphy *rphy) } EXPORT_SYMBOL(sas_rphy_delete); +/** + * sas_rphy_unlink - unlink SAS remote PHY + * @rphy: SAS remote phy to unlink from its parent port + * + * Removes port reference to an rphy + */ +void sas_rphy_unlink(struct sas_rphy *rphy) +{ + struct sas_port *parent = dev_to_sas_port(rphy->dev.parent); + + parent->rphy = NULL; +} +EXPORT_SYMBOL(sas_rphy_unlink); + /** * sas_rphy_remove - remove SAS remote PHY * @rphy: SAS remote phy to remove @@ -1612,7 +1626,6 @@ void sas_rphy_remove(struct sas_rphy *rphy) { struct device *dev = &rphy->dev; - struct sas_port *parent = dev_to_sas_port(dev->parent); switch (rphy->identify.device_type) { case SAS_END_DEVICE: @@ -1626,10 +1639,9 @@ sas_rphy_remove(struct sas_rphy *rphy) break; } + sas_rphy_unlink(rphy); transport_remove_device(dev); device_del(dev); - - parent->rphy = NULL; } EXPORT_SYMBOL(sas_rphy_remove); diff --git a/include/scsi/libsas.h b/include/scsi/libsas.h index d792b13cfcf..bd6e89ece2a 100644 --- a/include/scsi/libsas.h +++ b/include/scsi/libsas.h @@ -86,7 +86,9 @@ enum discover_event { DISCE_DISCOVER_DOMAIN = 0U, DISCE_REVALIDATE_DOMAIN = 1, DISCE_PORT_GONE = 2, - DISC_NUM_EVENTS = 3, + DISCE_PROBE = 3, + DISCE_DESTRUCT = 4, + DISC_NUM_EVENTS = 5, }; /* ---------- Expander Devices ---------- */ @@ -175,6 +177,7 @@ struct sata_device { enum { SAS_DEV_GONE, + SAS_DEV_DESTROY, }; struct domain_device { @@ -191,6 +194,7 @@ struct domain_device { struct asd_sas_port *port; /* shortcut to root of the tree */ struct list_head dev_list_node; + struct list_head disco_list_node; /* awaiting probe or destruct */ enum sas_protocol iproto; enum sas_protocol tproto; @@ -226,7 +230,6 @@ struct sas_discovery { int max_level; }; - /* The port struct is Class:RW, driver:RO */ struct asd_sas_port { /* private: */ @@ -236,6 +239,8 @@ struct asd_sas_port { struct domain_device *port_dev; spinlock_t dev_list_lock; struct list_head dev_list; + struct list_head disco_list; + struct list_head destroy_list; enum sas_linkrate linkrate; struct sas_phy *phy; @@ -334,6 +339,7 @@ struct sas_ha_event { enum sas_ha_state { SAS_HA_REGISTERED, SAS_HA_DRAINING, + SAS_HA_ATA_EH_ACTIVE, }; struct sas_ha_struct { @@ -346,6 +352,8 @@ struct sas_ha_struct { unsigned long state; spinlock_t state_lock; + struct mutex disco_mutex; + struct scsi_core core; /* public: */ diff --git a/include/scsi/sas_ata.h b/include/scsi/sas_ata.h index 7d5013f8653..557fc9a8559 100644 --- a/include/scsi/sas_ata.h +++ b/include/scsi/sas_ata.h @@ -45,6 +45,7 @@ int sas_ata_timed_out(struct scsi_cmnd *cmd, struct sas_task *task, enum blk_eh_timer_return *rtn); int sas_ata_eh(struct Scsi_Host *shost, struct list_head *work_q, struct list_head *done_q); +void sas_probe_sata(struct work_struct *work); #else @@ -78,6 +79,10 @@ static inline int sas_ata_eh(struct Scsi_Host *shost, struct list_head *work_q, return 0; } +static inline void sas_probe_sata(struct work_struct *work) +{ +} + #endif #endif /* _SAS_ATA_H_ */ diff --git a/include/scsi/scsi_transport_sas.h b/include/scsi/scsi_transport_sas.h index ffeebc34a4f..6d14daac758 100644 --- a/include/scsi/scsi_transport_sas.h +++ b/include/scsi/scsi_transport_sas.h @@ -194,6 +194,7 @@ void sas_rphy_free(struct sas_rphy *); extern int sas_rphy_add(struct sas_rphy *); extern void sas_rphy_remove(struct sas_rphy *); extern void sas_rphy_delete(struct sas_rphy *); +extern void sas_rphy_unlink(struct sas_rphy *); extern int scsi_is_sas_rphy(const struct device *); struct sas_port *sas_port_alloc(struct device *, int); -- cgit v1.2.3-70-g09d2 From b52df4174dff7e587f6fbfb21e3c2cb57109e5cf Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 30 Nov 2011 23:23:33 -0800 Subject: [SCSI] libsas: use libata-eh-reset for sata rediscovery fis transmit failures Since sata devices can take several seconds to recover the link on reset the 0.5 seconds that libsas currently waits may not be enough. Instead if we are rediscovering a phy that was previously attached to a sata device let libata handle any resets to encourage the device to transmit the initial fis. Once sas_ata_hard_reset() and lldds learn how to honor 'deadline' libsas should stop encountering phys in an intermediate state, until then this will loop until the fis is transmitted or ->attached_sas_addr gets cleared, but in the more likely initial discovery case we keep existing behavior. Signed-off-by: Dan Williams Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_ata.c | 19 ++++++++++++++++ drivers/scsi/libsas/sas_expander.c | 44 +++++++++++++++++++++++++++++++++----- include/scsi/sas_ata.h | 6 +++++- 3 files changed, 63 insertions(+), 6 deletions(-) (limited to 'drivers/scsi/libsas/sas_expander.c') diff --git a/drivers/scsi/libsas/sas_ata.c b/drivers/scsi/libsas/sas_ata.c index a8ace8d24e6..48cadf88c39 100644 --- a/drivers/scsi/libsas/sas_ata.c +++ b/drivers/scsi/libsas/sas_ata.c @@ -679,3 +679,22 @@ int sas_ata_eh(struct Scsi_Host *shost, struct list_head *work_q, return rtn; } + +void sas_ata_schedule_reset(struct domain_device *dev) +{ + struct ata_eh_info *ehi; + struct ata_port *ap; + unsigned long flags; + + if (!dev_is_sata(dev)) + return; + + ap = dev->sata_dev.ap; + ehi = &ap->link.eh_info; + + spin_lock_irqsave(ap->lock, flags); + ehi->err_mask |= AC_ERR_TIMEOUT; + ehi->action |= ATA_EH_RESET; + ata_port_schedule_eh(ap); + spin_unlock_irqrestore(ap->lock, flags); +} diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index e45b259dac4..f4894b0f537 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -28,6 +28,7 @@ #include "sas_internal.h" +#include #include #include #include "../scsi_sas_internal.h" @@ -226,12 +227,35 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id, return; } +/* check if we have an existing attached ata device on this expander phy */ +static struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id) +{ + struct ex_phy *ex_phy = &ex_dev->ex_dev.ex_phy[phy_id]; + struct domain_device *dev; + struct sas_rphy *rphy; + + if (!ex_phy->port) + return NULL; + + rphy = ex_phy->port->rphy; + if (!rphy) + return NULL; + + dev = sas_find_dev_by_rphy(rphy); + + if (dev && dev_is_sata(dev)) + return dev; + + return NULL; +} + #define DISCOVER_REQ_SIZE 16 #define DISCOVER_RESP_SIZE 56 static int sas_ex_phy_discover_helper(struct domain_device *dev, u8 *disc_req, u8 *disc_resp, int single) { + struct domain_device *ata_dev = sas_ex_to_ata(dev, single); int i, res; disc_req[9] = single; @@ -242,20 +266,30 @@ static int sas_ex_phy_discover_helper(struct domain_device *dev, u8 *disc_req, disc_resp, DISCOVER_RESP_SIZE); if (res) return res; - /* This is detecting a failure to transmit initial - * dev to host FIS as described in section G.5 of - * sas-2 r 04b */ dr = &((struct smp_resp *)disc_resp)->disc; if (memcmp(dev->sas_addr, dr->attached_sas_addr, SAS_ADDR_SIZE) == 0) { sas_printk("Found loopback topology, just ignore it!\n"); return 0; } + + /* This is detecting a failure to transmit initial + * dev to host FIS as described in section J.5 of + * sas-2 r16 + */ if (!(dr->attached_dev_type == 0 && dr->attached_sata_dev)) break; - /* In order to generate the dev to host FIS, we - * send a link reset to the expander port */ + + /* In order to generate the dev to host FIS, we send a + * link reset to the expander port. If a device was + * previously detected on this port we ask libata to + * manage the reset and link recovery. + */ + if (ata_dev) { + sas_ata_schedule_reset(ata_dev); + break; + } sas_smp_phy_control(dev, single, PHY_FUNC_LINK_RESET, NULL); /* Wait for the reset to trigger the negotiation */ msleep(500); diff --git a/include/scsi/sas_ata.h b/include/scsi/sas_ata.h index 9f7a23d1146..c0bcd30eec5 100644 --- a/include/scsi/sas_ata.h +++ b/include/scsi/sas_ata.h @@ -44,7 +44,7 @@ void sas_ata_strategy_handler(struct Scsi_Host *shost); int sas_ata_eh(struct Scsi_Host *shost, struct list_head *work_q, struct list_head *done_q); void sas_probe_sata(struct work_struct *work); - +void sas_ata_schedule_reset(struct domain_device *dev); #else @@ -75,6 +75,10 @@ static inline void sas_probe_sata(struct work_struct *work) { } +static inline void sas_ata_schedule_reset(struct domain_device *dev) +{ +} + #endif #endif /* _SAS_ATA_H_ */ -- cgit v1.2.3-70-g09d2 From 81c757bc696284f39f07766f0c2ca67af64ce9bd Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 2 Dec 2011 16:07:01 -0800 Subject: [SCSI] libsas: execute transport link resets with libata-eh via host workqueue Link resets leave ata affiliations intact, so arrange for libsas to make an effort to avoid dropping the device due to a slow-to-recover link. Towards this end carry out reset in the host workqueue so that it can check for ata devices and kick the reset request to libata. Hard resets, in contrast, bypass libata since they are meant for associating an ata device with another initiator in the domain (tears down affiliations). Need to add a new transport_sas_phy_reset() since the current sas_phy_reset() is a utility function to libsas lldds. They are not prepared for it to loop back into eh. Signed-off-by: Dan Williams Signed-off-by: James Bottomley --- drivers/ata/libata-eh.c | 1 + drivers/ata/libata.h | 1 - drivers/scsi/libsas/sas_ata.c | 11 ++++++++ drivers/scsi/libsas/sas_expander.c | 2 +- drivers/scsi/libsas/sas_init.c | 56 +++++++++++++++++++++++++++++++++++++- drivers/scsi/libsas/sas_internal.h | 1 + include/linux/libata.h | 1 + include/scsi/sas_ata.h | 4 +++ 8 files changed, 74 insertions(+), 3 deletions(-) (limited to 'drivers/scsi/libsas/sas_expander.c') diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index a9b28203800..c61316e9d2f 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -863,6 +863,7 @@ void ata_port_wait_eh(struct ata_port *ap) goto retry; } } +EXPORT_SYMBOL_GPL(ata_port_wait_eh); static int ata_eh_nr_in_flight(struct ata_port *ap) { diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h index 814486d35c4..1fab235ee51 100644 --- a/drivers/ata/libata.h +++ b/drivers/ata/libata.h @@ -151,7 +151,6 @@ extern void ata_eh_acquire(struct ata_port *ap); extern void ata_eh_release(struct ata_port *ap); extern enum blk_eh_timer_return ata_scsi_timed_out(struct scsi_cmnd *cmd); extern void ata_scsi_error(struct Scsi_Host *host); -extern void ata_port_wait_eh(struct ata_port *ap); extern void ata_eh_fastdrain_timerfn(unsigned long arg); extern void ata_qc_schedule_eh(struct ata_queued_cmd *qc); extern void ata_dev_disable(struct ata_device *dev); diff --git a/drivers/scsi/libsas/sas_ata.c b/drivers/scsi/libsas/sas_ata.c index 48cadf88c39..03930a04a67 100644 --- a/drivers/scsi/libsas/sas_ata.c +++ b/drivers/scsi/libsas/sas_ata.c @@ -698,3 +698,14 @@ void sas_ata_schedule_reset(struct domain_device *dev) ata_port_schedule_eh(ap); spin_unlock_irqrestore(ap->lock, flags); } + +void sas_ata_wait_eh(struct domain_device *dev) +{ + struct ata_port *ap; + + if (!dev_is_sata(dev)) + return; + + ap = dev->sata_dev.ap; + ata_port_wait_eh(ap); +} diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index f4894b0f537..d3c1a29b8a2 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -228,7 +228,7 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id, } /* check if we have an existing attached ata device on this expander phy */ -static struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id) +struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id) { struct ex_phy *ex_phy = &ex_dev->ex_dev.ex_phy[phy_id]; struct domain_device *dev; diff --git a/drivers/scsi/libsas/sas_init.c b/drivers/scsi/libsas/sas_init.c index cb65adf4ab1..a15fb861dab 100644 --- a/drivers/scsi/libsas/sas_init.c +++ b/drivers/scsi/libsas/sas_init.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -195,6 +196,59 @@ static int sas_get_linkerrors(struct sas_phy *phy) return sas_smp_get_phy_events(phy); } +/** + * transport_sas_phy_reset - reset a phy and permit libata to manage the link + * + * phy reset request via sysfs in host workqueue context so we know we + * can block on eh and safely traverse the domain_device topology + */ +static int transport_sas_phy_reset(struct sas_phy *phy, int hard_reset) +{ + int ret; + enum phy_func reset_type; + + if (hard_reset) + reset_type = PHY_FUNC_HARD_RESET; + else + reset_type = PHY_FUNC_LINK_RESET; + + if (scsi_is_sas_phy_local(phy)) { + struct Scsi_Host *shost = dev_to_shost(phy->dev.parent); + struct sas_ha_struct *sas_ha = SHOST_TO_SAS_HA(shost); + struct asd_sas_phy *asd_phy = sas_ha->sas_phy[phy->number]; + struct sas_internal *i = + to_sas_internal(sas_ha->core.shost->transportt); + struct domain_device *dev = NULL; + + if (asd_phy->port) + dev = asd_phy->port->port_dev; + + /* validate that dev has been probed */ + if (dev) + dev = sas_find_dev_by_rphy(dev->rphy); + + if (dev && dev_is_sata(dev) && !hard_reset) { + sas_ata_schedule_reset(dev); + sas_ata_wait_eh(dev); + ret = 0; + } else + ret = i->dft->lldd_control_phy(asd_phy, reset_type, NULL); + } else { + struct sas_rphy *rphy = dev_to_rphy(phy->dev.parent); + struct domain_device *ddev = sas_find_dev_by_rphy(rphy); + struct domain_device *ata_dev = sas_ex_to_ata(ddev, phy->number); + + if (ata_dev && !hard_reset) { + sas_ata_schedule_reset(ata_dev); + sas_ata_wait_eh(ata_dev); + ret = 0; + } else + ret = sas_smp_phy_control(ddev, phy->number, reset_type, NULL); + } + + return ret; +} + int sas_phy_enable(struct sas_phy *phy, int enable) { int ret; @@ -300,7 +354,7 @@ static void phy_reset_work(struct work_struct *work) { struct sas_phy_data *d = container_of(work, typeof(*d), reset_work); - d->reset_result = sas_phy_reset(d->phy, d->hard_reset); + d->reset_result = transport_sas_phy_reset(d->phy, d->hard_reset); } static int sas_phy_setup(struct sas_phy *phy) diff --git a/drivers/scsi/libsas/sas_internal.h b/drivers/scsi/libsas/sas_internal.h index 9ba65e0c6f9..ae9698d9d85 100644 --- a/drivers/scsi/libsas/sas_internal.h +++ b/drivers/scsi/libsas/sas_internal.h @@ -85,6 +85,7 @@ int sas_smp_phy_control(struct domain_device *dev, int phy_id, int sas_smp_get_phy_events(struct sas_phy *phy); struct domain_device *sas_find_dev_by_rphy(struct sas_rphy *rphy); +struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id); void sas_hae_reset(struct work_struct *work); diff --git a/include/linux/libata.h b/include/linux/libata.h index cafc09a64fe..aa427047756 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -1147,6 +1147,7 @@ static inline int ata_acpi_cbl_80wire(struct ata_port *ap, * EH - drivers/ata/libata-eh.c */ extern void ata_port_schedule_eh(struct ata_port *ap); +extern void ata_port_wait_eh(struct ata_port *ap); extern int ata_link_abort(struct ata_link *link); extern int ata_port_abort(struct ata_port *ap); extern int ata_port_freeze(struct ata_port *ap); diff --git a/include/scsi/sas_ata.h b/include/scsi/sas_ata.h index c0bcd30eec5..da3f3772738 100644 --- a/include/scsi/sas_ata.h +++ b/include/scsi/sas_ata.h @@ -45,6 +45,7 @@ int sas_ata_eh(struct Scsi_Host *shost, struct list_head *work_q, struct list_head *done_q); void sas_probe_sata(struct work_struct *work); void sas_ata_schedule_reset(struct domain_device *dev); +void sas_ata_wait_eh(struct domain_device *dev); #else @@ -79,6 +80,9 @@ static inline void sas_ata_schedule_reset(struct domain_device *dev) { } +static inline void sas_ata_wait_eh(struct domain_device *dev) +{ +} #endif #endif /* _SAS_ATA_H_ */ -- cgit v1.2.3-70-g09d2 From 89d3cf6ac3cdc4f15a82709f8c78ed169a98be5b Mon Sep 17 00:00:00 2001 From: Jeff Skirvin Date: Wed, 16 Nov 2011 09:44:13 +0000 Subject: [SCSI] libsas: add mutex for SMP task execution SAS does not tag SMP requests, and at least one lldd (isci) does not permit more than one in-flight request at a time. [jejb: fix sas_init_dev tab issues while we're at it] Signed-off-by: Jeff Skirvin Signed-off-by: Dan Williams Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_discover.c | 31 ++++++++++++++++--------------- drivers/scsi/libsas/sas_expander.c | 29 ++++++++++++++++------------- include/scsi/libsas.h | 2 ++ 3 files changed, 34 insertions(+), 28 deletions(-) (limited to 'drivers/scsi/libsas/sas_expander.c') diff --git a/drivers/scsi/libsas/sas_discover.c b/drivers/scsi/libsas/sas_discover.c index bad5eba4a92..c56cc640081 100644 --- a/drivers/scsi/libsas/sas_discover.c +++ b/drivers/scsi/libsas/sas_discover.c @@ -37,21 +37,22 @@ void sas_init_dev(struct domain_device *dev) { - 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_DEV: + break; + case EDGE_DEV: + case FANOUT_DEV: + INIT_LIST_HEAD(&dev->ex_dev.children); + mutex_init(&dev->ex_dev.cmd_mutex); + break; + case SATA_DEV: + case SATA_PM: + case SATA_PM_PORT: + INIT_LIST_HEAD(&dev->sata_dev.children); + break; + default: + break; + } } /* ---------- Domain device discovery ---------- */ diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index d3c1a29b8a2..7c59f97c028 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -72,11 +72,13 @@ static int smp_execute_task(struct domain_device *dev, void *req, int req_size, struct sas_internal *i = to_sas_internal(dev->port->ha->core.shost->transportt); + mutex_lock(&dev->ex_dev.cmd_mutex); for (retry = 0; retry < 3; retry++) { task = sas_alloc_task(GFP_KERNEL); - if (!task) - return -ENOMEM; - + if (!task) { + res = -ENOMEM; + break; + } task->dev = dev; task->task_proto = dev->tproto; sg_init_one(&task->smp_task.smp_req, req, req_size); @@ -94,7 +96,7 @@ static int smp_execute_task(struct domain_device *dev, void *req, int req_size, if (res) { del_timer(&task->timer); SAS_DPRINTK("executing SMP task failed:%d\n", res); - goto ex_err; + break; } wait_for_completion(&task->completion); @@ -104,21 +106,23 @@ static int smp_execute_task(struct domain_device *dev, void *req, int req_size, i->dft->lldd_abort_task(task); if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) { SAS_DPRINTK("SMP task aborted and not done\n"); - goto ex_err; + break; } } if (task->task_status.resp == SAS_TASK_COMPLETE && task->task_status.stat == SAM_STAT_GOOD) { res = 0; break; - } if (task->task_status.resp == SAS_TASK_COMPLETE && - task->task_status.stat == SAS_DATA_UNDERRUN) { + } + if (task->task_status.resp == SAS_TASK_COMPLETE && + task->task_status.stat == SAS_DATA_UNDERRUN) { /* no error, but return the number of bytes of * underrun */ res = task->task_status.residual; break; - } if (task->task_status.resp == SAS_TASK_COMPLETE && - task->task_status.stat == SAS_DATA_OVERRUN) { + } + if (task->task_status.resp == SAS_TASK_COMPLETE && + task->task_status.stat == SAS_DATA_OVERRUN) { res = -EMSGSIZE; break; } else { @@ -131,11 +135,10 @@ static int smp_execute_task(struct domain_device *dev, void *req, int req_size, task = NULL; } } -ex_err: + mutex_unlock(&dev->ex_dev.cmd_mutex); + BUG_ON(retry == 3 && task != NULL); - if (task != NULL) { - sas_free_task(task); - } + sas_free_task(task); return res; } diff --git a/include/scsi/libsas.h b/include/scsi/libsas.h index f388ba53612..18704a2e4f0 100644 --- a/include/scsi/libsas.h +++ b/include/scsi/libsas.h @@ -153,6 +153,8 @@ struct expander_device { struct ex_phy *ex_phy; struct sas_port *parent_port; + + struct mutex cmd_mutex; }; /* ---------- SATA device ---------- */ -- cgit v1.2.3-70-g09d2 From 36a399473902a57218dc493c5a814708a56b73ab Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 17 Nov 2011 17:59:54 -0800 Subject: [SCSI] libsas: poll for ata device readiness after reset Use ata_wait_after_reset() to poll for link recovery after a reset. This combined with sas_ha->eh_mutex prevents expander rediscovery from probing phys in an intermediate state. Local discovery does not have a mechanism to filter link status changes during this timeout, so it remains the responsibility of lldds to prevent premature port teardown. Although once all lldd's support ->lldd_ata_check_ready() that could be used as a gate to local port teardown. The signature fis is re-transmitted when the link comes back so we should be revalidating the ata device class, but that is left to a future patch. Signed-off-by: Dan Williams Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_ata.c | 104 ++++++++++++++++++++++++++----------- drivers/scsi/libsas/sas_expander.c | 10 ++-- drivers/scsi/libsas/sas_internal.h | 3 +- include/scsi/libsas.h | 1 + 4 files changed, 83 insertions(+), 35 deletions(-) (limited to 'drivers/scsi/libsas/sas_expander.c') diff --git a/drivers/scsi/libsas/sas_ata.c b/drivers/scsi/libsas/sas_ata.c index 4beca66728b..5fdb63ad94b 100644 --- a/drivers/scsi/libsas/sas_ata.c +++ b/drivers/scsi/libsas/sas_ata.c @@ -272,39 +272,84 @@ static bool sas_ata_qc_fill_rtf(struct ata_queued_cmd *qc) return true; } -static int sas_ata_hard_reset(struct ata_link *link, unsigned int *class, - unsigned long deadline) +static struct sas_internal *dev_to_sas_internal(struct domain_device *dev) +{ + return to_sas_internal(dev->port->ha->core.shost->transportt); +} + +static int smp_ata_check_ready(struct ata_link *link) { + int res; + u8 addr[8]; struct ata_port *ap = link->ap; struct domain_device *dev = ap->private_data; - struct sas_internal *i = - to_sas_internal(dev->port->ha->core.shost->transportt); - int res = TMF_RESP_FUNC_FAILED; - int ret = 0; + struct domain_device *ex_dev = dev->parent; + struct sas_phy *phy = sas_find_local_phy(dev); - if (i->dft->lldd_I_T_nexus_reset) - res = i->dft->lldd_I_T_nexus_reset(dev); + res = sas_get_phy_attached_sas_addr(ex_dev, phy->number, addr); + /* break the wait early if the expander is unreachable, + * otherwise keep polling + */ + if (res == -ECOMM) + return res; + if (res != SMP_RESP_FUNC_ACC || SAS_ADDR(addr) == 0) + return 0; + else + return 1; +} - if (res != TMF_RESP_FUNC_COMPLETE) { - SAS_DPRINTK("%s: Unable to reset I T nexus?\n", __func__); - ret = -EAGAIN; +static int local_ata_check_ready(struct ata_link *link) +{ + struct ata_port *ap = link->ap; + struct domain_device *dev = ap->private_data; + struct sas_internal *i = dev_to_sas_internal(dev); + + if (i->dft->lldd_ata_check_ready) + return i->dft->lldd_ata_check_ready(dev); + else { + /* lldd's that don't implement 'ready' checking get the + * old default behavior of not coordinating reset + * recovery with libata + */ + return 1; } +} +static int sas_ata_hard_reset(struct ata_link *link, unsigned int *class, + unsigned long deadline) +{ + int ret = 0, res; + struct ata_port *ap = link->ap; + int (*check_ready)(struct ata_link *link); + struct domain_device *dev = ap->private_data; + struct sas_phy *phy = sas_find_local_phy(dev); + struct sas_internal *i = dev_to_sas_internal(dev); + + res = i->dft->lldd_I_T_nexus_reset(dev); + + if (res != TMF_RESP_FUNC_COMPLETE) + SAS_DPRINTK("%s: Unable to reset ata device?\n", __func__); + + if (scsi_is_sas_phy_local(phy)) + check_ready = local_ata_check_ready; + else + check_ready = smp_ata_check_ready; + + ret = ata_wait_after_reset(link, deadline, check_ready); + if (ret && ret != -EAGAIN) + ata_link_err(link, "COMRESET failed (errno=%d)\n", ret); + + /* XXX: if the class changes during the reset the upper layer + * should be informed, if the device has gone away we assume + * libsas will eventually delete it + */ switch (dev->sata_dev.command_set) { - case ATA_COMMAND_SET: - SAS_DPRINTK("%s: Found ATA device.\n", __func__); - *class = ATA_DEV_ATA; - break; - case ATAPI_COMMAND_SET: - SAS_DPRINTK("%s: Found ATAPI device.\n", __func__); - *class = ATA_DEV_ATAPI; - break; - default: - SAS_DPRINTK("%s: Unknown SATA command set: %d.\n", - __func__, - dev->sata_dev.command_set); - *class = ATA_DEV_UNKNOWN; - break; + case ATA_COMMAND_SET: + *class = ATA_DEV_ATA; + break; + case ATAPI_COMMAND_SET: + *class = ATA_DEV_ATAPI; + break; } ap->cbl = ATA_CBL_SATA; @@ -316,8 +361,7 @@ static int sas_ata_soft_reset(struct ata_link *link, unsigned int *class, { struct ata_port *ap = link->ap; struct domain_device *dev = ap->private_data; - struct sas_internal *i = - to_sas_internal(dev->port->ha->core.shost->transportt); + struct sas_internal *i = dev_to_sas_internal(dev); int res = TMF_RESP_FUNC_FAILED; int ret = 0; @@ -355,8 +399,7 @@ static int sas_ata_soft_reset(struct ata_link *link, unsigned int *class, */ static void sas_ata_internal_abort(struct sas_task *task) { - struct sas_internal *si = - to_sas_internal(task->dev->port->ha->core.shost->transportt); + struct sas_internal *si = dev_to_sas_internal(task->dev); unsigned long flags; int res; @@ -425,8 +468,7 @@ static void sas_ata_post_internal(struct ata_queued_cmd *qc) static void sas_ata_set_dmamode(struct ata_port *ap, struct ata_device *ata_dev) { struct domain_device *dev = ap->private_data; - struct sas_internal *i = - to_sas_internal(dev->port->ha->core.shost->transportt); + struct sas_internal *i = dev_to_sas_internal(dev); if (i->dft->lldd_ata_set_dmamode) i->dft->lldd_ata_set_dmamode(dev); diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index 7c59f97c028..32e417e6c2f 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -125,7 +125,11 @@ static int smp_execute_task(struct domain_device *dev, void *req, int req_size, task->task_status.stat == SAS_DATA_OVERRUN) { res = -EMSGSIZE; break; - } else { + } + if (task->task_status.resp == SAS_TASK_UNDELIVERED && + task->task_status.stat == SAS_DEVICE_UNKNOWN) + break; + else { SAS_DPRINTK("%s: task to dev %016llx response: 0x%x " "status 0x%x\n", __func__, SAS_ADDR(dev->sas_addr), @@ -1648,8 +1652,8 @@ static int sas_get_phy_change_count(struct domain_device *dev, return res; } -static int sas_get_phy_attached_sas_addr(struct domain_device *dev, - int phy_id, u8 *attached_sas_addr) +int sas_get_phy_attached_sas_addr(struct domain_device *dev, int phy_id, + u8 *attached_sas_addr) { int res; struct smp_resp *disc_resp; diff --git a/drivers/scsi/libsas/sas_internal.h b/drivers/scsi/libsas/sas_internal.h index 9e960b2d535..a9a3bb94c1b 100644 --- a/drivers/scsi/libsas/sas_internal.h +++ b/drivers/scsi/libsas/sas_internal.h @@ -89,7 +89,8 @@ int sas_smp_get_phy_events(struct sas_phy *phy); struct domain_device *sas_find_dev_by_rphy(struct sas_rphy *rphy); struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id); - +int sas_get_phy_attached_sas_addr(struct domain_device *dev, int phy_id, + u8 *attached_sas_addr); void sas_hae_reset(struct work_struct *work); void sas_free_device(struct kref *kref); diff --git a/include/scsi/libsas.h b/include/scsi/libsas.h index 18704a2e4f0..2079b18467a 100644 --- a/include/scsi/libsas.h +++ b/include/scsi/libsas.h @@ -615,6 +615,7 @@ struct sas_domain_function_template { int (*lldd_clear_task_set)(struct domain_device *, u8 *lun); int (*lldd_I_T_nexus_reset)(struct domain_device *); int (*lldd_ata_soft_reset)(struct domain_device *); + int (*lldd_ata_check_ready)(struct domain_device *); void (*lldd_ata_set_dmamode)(struct domain_device *); int (*lldd_lu_reset)(struct domain_device *, u8 *lun); int (*lldd_query_task)(struct sas_task *); -- cgit v1.2.3-70-g09d2 From 0508c2f3b701f3cd8ed52b2a4abbb2a670f69ce2 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 21 Dec 2011 14:55:38 -0800 Subject: [SCSI] libsas: don't mark expanders as gone when a child device is removed Commit 56dd2c06 "[SCSI] libsas: Don't issue commands to devices that have been hot-removed" marked the parent device of an end-device as gone when all the phys to the end device have been deleted. The expander device is still present until its parent is removed. This is a benign change until the smp_execute_task() path is taught to check ->gone. Signed-off-by: Dan Williams Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_expander.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/scsi/libsas/sas_expander.c') diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index 32e417e6c2f..7701ab58840 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -1820,7 +1820,6 @@ static void sas_unregister_devs_sas_addr(struct domain_device *parent, break; } } - set_bit(SAS_DEV_GONE, &parent->state); sas_disable_routing(parent, phy->attached_sas_addr); } memset(phy->attached_sas_addr, 0, SAS_ADDR_SIZE); -- cgit v1.2.3-70-g09d2 From 3a9c5560f677690f65038f399f4f598c79b83186 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 21 Dec 2011 15:19:56 -0800 Subject: [SCSI] libsas: check for 'gone' expanders in smp_execute_task() No sense in issuing or retrying commands to an expander that has been removed. Signed-off-by: Dan Williams Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_expander.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers/scsi/libsas/sas_expander.c') diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index 7701ab58840..6fb1f3afd1e 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -74,6 +74,11 @@ static int smp_execute_task(struct domain_device *dev, void *req, int req_size, mutex_lock(&dev->ex_dev.cmd_mutex); for (retry = 0; retry < 3; retry++) { + if (test_bit(SAS_DEV_GONE, &dev->state)) { + res = -ECOMM; + break; + } + task = sas_alloc_task(GFP_KERNEL); if (!task) { res = -ENOMEM; -- cgit v1.2.3-70-g09d2 From f41a0c441c3fe43e79ebeb75584dbb5bfa83e5cd Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 21 Dec 2011 21:33:17 -0800 Subject: [SCSI] libsas: fix sas_find_local_phy(), take phy references In the direct-attached case this routine returns the phy on which this device was first discovered. Which is broken if we want to support wide-targets, as this phy reference can become stale even though the port is still active. In the expander-attached case this routine tries to lookup the phy by scanning the attached sas addresses of the parent expander, and BUG_ONs if it can't find it. However since eh and the libsas workqueue run independently we can still be attempting device recovery via eh after libsas has recorded the device as detached. This is even easier to hit now that eh is blocked while device domain rediscovery takes place, and that libata is fed more timed out commands increasing the chances that it will try to recover the ata device. Arrange for dev->phy to always point to a last known good phy, it may be stale after the port is torn down, but it will catch up for wide port reconfigurations, and never be NULL. Signed-off-by: Dan Williams Signed-off-by: James Bottomley --- drivers/scsi/aic94xx/aic94xx_tmf.c | 9 ++++++--- drivers/scsi/isci/task.c | 9 +++++---- drivers/scsi/libsas/sas_ata.c | 7 +++++-- drivers/scsi/libsas/sas_discover.c | 24 +++++++++++++++++++++++ drivers/scsi/libsas/sas_expander.c | 5 ++++- drivers/scsi/libsas/sas_internal.h | 1 + drivers/scsi/libsas/sas_port.c | 7 +++---- drivers/scsi/libsas/sas_scsi_host.c | 38 ++++++++++++++++++------------------- drivers/scsi/mvsas/mv_sas.c | 3 ++- drivers/scsi/pm8001/pm8001_sas.c | 19 ++++++++++++------- drivers/scsi/scsi_transport_sas.c | 23 ++++++++++++++++++++++ include/scsi/libsas.h | 9 +++++++-- include/scsi/scsi_transport_sas.h | 6 ++++++ 13 files changed, 116 insertions(+), 44 deletions(-) (limited to 'drivers/scsi/libsas/sas_expander.c') diff --git a/drivers/scsi/aic94xx/aic94xx_tmf.c b/drivers/scsi/aic94xx/aic94xx_tmf.c index 0add73bdf2a..50b914ffab9 100644 --- a/drivers/scsi/aic94xx/aic94xx_tmf.c +++ b/drivers/scsi/aic94xx/aic94xx_tmf.c @@ -181,7 +181,7 @@ static int asd_clear_nexus_I_T(struct domain_device *dev, int asd_I_T_nexus_reset(struct domain_device *dev) { int res, tmp_res, i; - struct sas_phy *phy = sas_find_local_phy(dev); + struct sas_phy *phy = sas_get_local_phy(dev); /* Standard mandates link reset for ATA (type 0) and * hard reset for SSP (type 1) */ int reset_type = (dev->dev_type == SATA_DEV || @@ -201,7 +201,7 @@ int asd_I_T_nexus_reset(struct domain_device *dev) for (i = 0 ; i < 3; i++) { tmp_res = asd_clear_nexus_I_T(dev, NEXUS_PHASE_RESUME); if (tmp_res == TC_RESUME) - return res; + goto out; msleep(500); } @@ -211,7 +211,10 @@ int asd_I_T_nexus_reset(struct domain_device *dev) dev_printk(KERN_ERR, &phy->dev, "Failed to resume nexus after reset 0x%x\n", tmp_res); - return TMF_RESP_FUNC_FAILED; + res = TMF_RESP_FUNC_FAILED; + out: + sas_put_local_phy(phy); + return res; } static int asd_clear_nexus_I_T_L(struct domain_device *dev, u8 *lun) diff --git a/drivers/scsi/isci/task.c b/drivers/scsi/isci/task.c index 4bd88ef83cd..b96e6044eda 100644 --- a/drivers/scsi/isci/task.c +++ b/drivers/scsi/isci/task.c @@ -1332,7 +1332,7 @@ isci_task_request_complete(struct isci_host *ihost, static int isci_reset_device(struct isci_host *ihost, struct isci_remote_device *idev) { - struct sas_phy *phy = sas_find_local_phy(idev->domain_dev); + struct sas_phy *phy = sas_get_local_phy(idev->domain_dev); enum sci_status status; unsigned long flags; int rc; @@ -1347,8 +1347,8 @@ static int isci_reset_device(struct isci_host *ihost, dev_dbg(&ihost->pdev->dev, "%s: sci_remote_device_reset(%p) returned %d!\n", __func__, idev, status); - - return TMF_RESP_FUNC_FAILED; + rc = TMF_RESP_FUNC_FAILED; + goto out; } spin_unlock_irqrestore(&ihost->scic_lock, flags); @@ -1369,7 +1369,8 @@ static int isci_reset_device(struct isci_host *ihost, } dev_dbg(&ihost->pdev->dev, "%s: idev %p complete.\n", __func__, idev); - + out: + sas_put_local_phy(phy); return rc; } diff --git a/drivers/scsi/libsas/sas_ata.c b/drivers/scsi/libsas/sas_ata.c index 5fdb63ad94b..92f7e78a096 100644 --- a/drivers/scsi/libsas/sas_ata.c +++ b/drivers/scsi/libsas/sas_ata.c @@ -284,9 +284,10 @@ static int smp_ata_check_ready(struct ata_link *link) struct ata_port *ap = link->ap; struct domain_device *dev = ap->private_data; struct domain_device *ex_dev = dev->parent; - struct sas_phy *phy = sas_find_local_phy(dev); + struct sas_phy *phy = sas_get_local_phy(dev); res = sas_get_phy_attached_sas_addr(ex_dev, phy->number, addr); + sas_put_local_phy(phy); /* break the wait early if the expander is unreachable, * otherwise keep polling */ @@ -319,10 +320,10 @@ static int sas_ata_hard_reset(struct ata_link *link, unsigned int *class, unsigned long deadline) { int ret = 0, res; + struct sas_phy *phy; struct ata_port *ap = link->ap; int (*check_ready)(struct ata_link *link); struct domain_device *dev = ap->private_data; - struct sas_phy *phy = sas_find_local_phy(dev); struct sas_internal *i = dev_to_sas_internal(dev); res = i->dft->lldd_I_T_nexus_reset(dev); @@ -330,10 +331,12 @@ static int sas_ata_hard_reset(struct ata_link *link, unsigned int *class, if (res != TMF_RESP_FUNC_COMPLETE) SAS_DPRINTK("%s: Unable to reset ata device?\n", __func__); + phy = sas_get_local_phy(dev); if (scsi_is_sas_phy_local(phy)) check_ready = local_ata_check_ready; else check_ready = smp_ata_check_ready; + sas_put_local_phy(phy); ret = ata_wait_after_reset(link, deadline, check_ready); if (ret && ret != -EAGAIN) diff --git a/drivers/scsi/libsas/sas_discover.c b/drivers/scsi/libsas/sas_discover.c index c56cc640081..789b50861bb 100644 --- a/drivers/scsi/libsas/sas_discover.c +++ b/drivers/scsi/libsas/sas_discover.c @@ -147,6 +147,7 @@ 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; @@ -234,6 +235,9 @@ void sas_free_device(struct kref *kref) 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 == EDGE_DEV || dev->dev_type == FANOUT_DEV) kfree(dev->ex_dev.ex_phy); @@ -308,6 +312,26 @@ void sas_unregister_domain_devices(struct asd_sas_port *port) } +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 ---------- */ /** diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index 6fb1f3afd1e..68a80a00f73 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -723,6 +723,7 @@ static struct domain_device *sas_ex_discover_end_dev( } } sas_ex_get_linkrate(parent, child, phy); + sas_device_set_phy(child, phy->port); #ifdef CONFIG_SCSI_SAS_ATA if ((phy->attached_tproto & SAS_PROTOCOL_STP) || phy->attached_sata_dev) { @@ -1810,7 +1811,7 @@ static void sas_unregister_devs_sas_addr(struct domain_device *parent, { struct expander_device *ex_dev = &parent->ex_dev; struct ex_phy *phy = &ex_dev->ex_phy[phy_id]; - struct domain_device *child, *n; + struct domain_device *child, *n, *found = NULL; if (last) { list_for_each_entry_safe(child, n, &ex_dev->children, siblings) { @@ -1822,6 +1823,7 @@ static void sas_unregister_devs_sas_addr(struct domain_device *parent, sas_unregister_ex_tree(parent->port, child); else sas_unregister_dev(parent->port, child); + found = child; break; } } @@ -1830,6 +1832,7 @@ static void sas_unregister_devs_sas_addr(struct domain_device *parent, memset(phy->attached_sas_addr, 0, SAS_ADDR_SIZE); if (phy->port) { sas_port_delete_phy(phy->port, phy->phy); + sas_device_set_phy(found, phy->port); if (phy->port->num_phys == 0) sas_port_delete(phy->port); phy->port = NULL; diff --git a/drivers/scsi/libsas/sas_internal.h b/drivers/scsi/libsas/sas_internal.h index a9a3bb94c1b..c8febc71c40 100644 --- a/drivers/scsi/libsas/sas_internal.h +++ b/drivers/scsi/libsas/sas_internal.h @@ -87,6 +87,7 @@ int sas_smp_phy_control(struct domain_device *dev, int phy_id, enum phy_func phy_func, struct sas_phy_linkrates *); int sas_smp_get_phy_events(struct sas_phy *phy); +void sas_device_set_phy(struct domain_device *dev, struct sas_port *port); struct domain_device *sas_find_dev_by_rphy(struct sas_rphy *rphy); struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id); int sas_get_phy_attached_sas_addr(struct domain_device *dev, int phy_id, diff --git a/drivers/scsi/libsas/sas_port.c b/drivers/scsi/libsas/sas_port.c index 2980bde4e34..31adcd1b419 100644 --- a/drivers/scsi/libsas/sas_port.c +++ b/drivers/scsi/libsas/sas_port.c @@ -108,9 +108,6 @@ static void sas_form_port(struct asd_sas_phy *phy) port->num_phys++; port->phy_mask |= (1U << phy->id); - if (!port->phy) - port->phy = phy->phy; - if (*(u64 *)port->attached_sas_addr == 0) { port->class = phy->class; memcpy(port->attached_sas_addr, phy->attached_sas_addr, @@ -175,8 +172,10 @@ void sas_deform_port(struct asd_sas_phy *phy, int gone) sas_unregister_domain_devices(port); sas_port_delete(port->port); port->port = NULL; - } else + } else { sas_port_delete_phy(port->port, phy->phy); + sas_device_set_phy(dev, port->port); + } if (si->dft->lldd_port_deformed) si->dft->lldd_port_deformed(phy); diff --git a/drivers/scsi/libsas/sas_scsi_host.c b/drivers/scsi/libsas/sas_scsi_host.c index 5cc44fddfe9..94ef76316c3 100644 --- a/drivers/scsi/libsas/sas_scsi_host.c +++ b/drivers/scsi/libsas/sas_scsi_host.c @@ -439,30 +439,26 @@ static int sas_recover_I_T(struct domain_device *dev) return res; } -/* Find the sas_phy that's attached to this device */ -struct sas_phy *sas_find_local_phy(struct domain_device *dev) +/* take a reference on the last known good phy for this device */ +struct sas_phy *sas_get_local_phy(struct domain_device *dev) { - struct domain_device *pdev = dev->parent; - struct ex_phy *exphy = NULL; - int i; + struct sas_ha_struct *ha = dev->port->ha; + struct sas_phy *phy; + unsigned long flags; - /* Directly attached device */ - if (!pdev) - return dev->port->phy; + /* a published domain device always has a valid phy, it may be + * stale, but it is never NULL + */ + BUG_ON(!dev->phy); - /* Otherwise look in the expander */ - for (i = 0; i < pdev->ex_dev.num_phys; i++) - if (!memcmp(dev->sas_addr, - pdev->ex_dev.ex_phy[i].attached_sas_addr, - SAS_ADDR_SIZE)) { - exphy = &pdev->ex_dev.ex_phy[i]; - break; - } + spin_lock_irqsave(&ha->phy_port_lock, flags); + phy = dev->phy; + get_device(&phy->dev); + spin_unlock_irqrestore(&ha->phy_port_lock, flags); - BUG_ON(!exphy); - return exphy->phy; + return phy; } -EXPORT_SYMBOL_GPL(sas_find_local_phy); +EXPORT_SYMBOL_GPL(sas_get_local_phy); /* Attempt to send a LUN reset message to a device */ int sas_eh_device_reset_handler(struct scsi_cmnd *cmd) @@ -489,7 +485,7 @@ int sas_eh_device_reset_handler(struct scsi_cmnd *cmd) int sas_eh_bus_reset_handler(struct scsi_cmnd *cmd) { struct domain_device *dev = cmd_to_domain_dev(cmd); - struct sas_phy *phy = sas_find_local_phy(dev); + struct sas_phy *phy = sas_get_local_phy(dev); int res; res = sas_phy_reset(phy, 1); @@ -497,6 +493,8 @@ int sas_eh_bus_reset_handler(struct scsi_cmnd *cmd) SAS_DPRINTK("Bus reset of %s failed 0x%x\n", kobject_name(&phy->dev.kobj), res); + sas_put_local_phy(phy); + if (res == TMF_RESP_FUNC_SUCC || res == TMF_RESP_FUNC_COMPLETE) return SUCCESS; diff --git a/drivers/scsi/mvsas/mv_sas.c b/drivers/scsi/mvsas/mv_sas.c index cd882230591..b68a65390f0 100644 --- a/drivers/scsi/mvsas/mv_sas.c +++ b/drivers/scsi/mvsas/mv_sas.c @@ -1474,10 +1474,11 @@ static int mvs_debug_issue_ssp_tmf(struct domain_device *dev, static int mvs_debug_I_T_nexus_reset(struct domain_device *dev) { int rc; - struct sas_phy *phy = sas_find_local_phy(dev); + struct sas_phy *phy = sas_get_local_phy(dev); int reset_type = (dev->dev_type == SATA_DEV || (dev->tproto & SAS_PROTOCOL_STP)) ? 0 : 1; rc = sas_phy_reset(phy, reset_type); + sas_put_local_phy(phy); msleep(2000); return rc; } diff --git a/drivers/scsi/pm8001/pm8001_sas.c b/drivers/scsi/pm8001/pm8001_sas.c index 310860e37d9..3b11edd4a50 100644 --- a/drivers/scsi/pm8001/pm8001_sas.c +++ b/drivers/scsi/pm8001/pm8001_sas.c @@ -967,12 +967,14 @@ int pm8001_I_T_nexus_reset(struct domain_device *dev) pm8001_dev = dev->lldd_dev; pm8001_ha = pm8001_find_ha_by_dev(dev); - phy = sas_find_local_phy(dev); + phy = sas_get_local_phy(dev); if (dev_is_sata(dev)) { DECLARE_COMPLETION_ONSTACK(completion_setstate); - if (scsi_is_sas_phy_local(phy)) - return 0; + if (scsi_is_sas_phy_local(phy)) { + rc = 0; + goto out; + } rc = sas_phy_reset(phy, 1); msleep(2000); rc = pm8001_exec_internal_task_abort(pm8001_ha, pm8001_dev , @@ -981,12 +983,14 @@ int pm8001_I_T_nexus_reset(struct domain_device *dev) rc = PM8001_CHIP_DISP->set_dev_state_req(pm8001_ha, pm8001_dev, 0x01); wait_for_completion(&completion_setstate); - } else{ - rc = sas_phy_reset(phy, 1); - msleep(2000); + } else { + rc = sas_phy_reset(phy, 1); + msleep(2000); } PM8001_EH_DBG(pm8001_ha, pm8001_printk(" for device[%x]:rc=%d\n", pm8001_dev->device_id, rc)); + out: + sas_put_local_phy(phy); return rc; } @@ -998,10 +1002,11 @@ int pm8001_lu_reset(struct domain_device *dev, u8 *lun) struct pm8001_device *pm8001_dev = dev->lldd_dev; struct pm8001_hba_info *pm8001_ha = pm8001_find_ha_by_dev(dev); if (dev_is_sata(dev)) { - struct sas_phy *phy = sas_find_local_phy(dev); + struct sas_phy *phy = sas_get_local_phy(dev); rc = pm8001_exec_internal_task_abort(pm8001_ha, pm8001_dev , dev, 1, 0); rc = sas_phy_reset(phy, 1); + sas_put_local_phy(phy); rc = PM8001_CHIP_DISP->set_dev_state_req(pm8001_ha, pm8001_dev, 0x01); msleep(2000); diff --git a/drivers/scsi/scsi_transport_sas.c b/drivers/scsi/scsi_transport_sas.c index ab3bd0b5ffd..7d69a25d200 100644 --- a/drivers/scsi/scsi_transport_sas.c +++ b/drivers/scsi/scsi_transport_sas.c @@ -1059,6 +1059,29 @@ int scsi_is_sas_port(const struct device *dev) } EXPORT_SYMBOL(scsi_is_sas_port); +/** + * sas_port_get_phy - try to take a reference on a port member + * @port: port to check + */ +struct sas_phy *sas_port_get_phy(struct sas_port *port) +{ + struct sas_phy *phy; + + mutex_lock(&port->phy_list_mutex); + if (list_empty(&port->phy_list)) + phy = NULL; + else { + struct list_head *ent = port->phy_list.next; + + phy = list_entry(ent, typeof(*phy), port_siblings); + get_device(&phy->dev); + } + mutex_unlock(&port->phy_list_mutex); + + return phy; +} +EXPORT_SYMBOL(sas_port_get_phy); + /** * sas_port_add_phy - add another phy to a port to form a wide port * @port: port to add the phy to diff --git a/include/scsi/libsas.h b/include/scsi/libsas.h index 2079b18467a..55bab863380 100644 --- a/include/scsi/libsas.h +++ b/include/scsi/libsas.h @@ -192,6 +192,7 @@ struct domain_device { struct domain_device *parent; struct list_head siblings; /* devices on the same level */ struct asd_sas_port *port; /* shortcut to root of the tree */ + struct sas_phy *phy; struct list_head dev_list_node; struct list_head disco_list_node; /* awaiting probe or destruct */ @@ -243,7 +244,6 @@ struct asd_sas_port { struct list_head destroy_list; enum sas_linkrate linkrate; - struct sas_phy *phy; struct work_struct work; /* public: */ @@ -429,6 +429,11 @@ static inline unsigned int to_sas_gpio_od(int device, int bit) return 3 * device + bit; } +static inline void sas_put_local_phy(struct sas_phy *phy) +{ + put_device(&phy->dev); +} + #ifdef CONFIG_SCSI_SAS_HOST_SMP int try_test_sas_gpio_gp_bit(unsigned int od, u8 *data, u8 index, u8 count); #else @@ -684,7 +689,7 @@ extern int sas_smp_handler(struct Scsi_Host *shost, struct sas_rphy *rphy, extern void sas_ssp_task_response(struct device *dev, struct sas_task *task, struct ssp_response_iu *iu); -struct sas_phy *sas_find_local_phy(struct domain_device *dev); +struct sas_phy *sas_get_local_phy(struct domain_device *dev); int sas_request_addr(struct Scsi_Host *shost, u8 *addr); diff --git a/include/scsi/scsi_transport_sas.h b/include/scsi/scsi_transport_sas.h index 42817facaed..98b3a20a010 100644 --- a/include/scsi/scsi_transport_sas.h +++ b/include/scsi/scsi_transport_sas.h @@ -209,6 +209,12 @@ void sas_port_add_phy(struct sas_port *, struct sas_phy *); void sas_port_delete_phy(struct sas_port *, struct sas_phy *); void sas_port_mark_backlink(struct sas_port *); int scsi_is_sas_port(const struct device *); +struct sas_phy *sas_port_get_phy(struct sas_port *port); +static inline void sas_port_put_phy(struct sas_phy *phy) +{ + if (phy) + put_device(&phy->dev); +} extern struct scsi_transport_template * sas_attach_transport(struct sas_function_template *); -- cgit v1.2.3-70-g09d2 From d214d81e883b6fc6f11cc772ff88585565d45cce Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Mon, 16 Jan 2012 11:56:50 -0800 Subject: [SCSI] libsas: improve debug statements It's difficult to determine which domain_device is triggering error recovery, so convert messages like: sas: ex 5001b4da000e703f phy08:T attached: 5001b4da000e7028 sas: ex 5001b4da000e703f phy09:T attached: 5001b4da000e7029 ... ata7: sas eh calling libata port error handler ata8: sas eh calling libata port error handler ...into: sas: ex 5001517e85cfefff phy05:T:9 attached: 5001517e85cfefe5 (stp) sas: ex 5001517e3b0af0bf phy11:T:8 attached: 5001517e3b0af0ab (stp) ... sas: ata7: end_device-21:1: dev error handler sas: ata8: end_device-20:0:5: dev error handler which shows attached link rate, device type, and associates a domain_device with its ata_port id to correlate messages emitted from libata-eh. As Doug notes, we can also take the opportunity to clarify expander phy routing capabilities. [dgilbert@interlog.com: clarify table2table with 'U'] Signed-off-by: Dan Williams Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_ata.c | 43 ++++++++++++++++------ drivers/scsi/libsas/sas_expander.c | 74 ++++++++++++++++++++++++++------------ 2 files changed, 85 insertions(+), 32 deletions(-) (limited to 'drivers/scsi/libsas/sas_expander.c') diff --git a/drivers/scsi/libsas/sas_ata.c b/drivers/scsi/libsas/sas_ata.c index 40edf520d69..ba1ebfe991d 100644 --- a/drivers/scsi/libsas/sas_ata.c +++ b/drivers/scsi/libsas/sas_ata.c @@ -317,6 +317,28 @@ static int local_ata_check_ready(struct ata_link *link) } } +static int sas_ata_printk(const char *level, const struct domain_device *ddev, + const char *fmt, ...) +{ + struct ata_port *ap = ddev->sata_dev.ap; + struct device *dev = &ddev->rphy->dev; + struct va_format vaf; + va_list args; + int r; + + va_start(args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + + r = printk("%ssas: ata%u: %s: %pV", + level, ap->print_id, dev_name(dev), &vaf); + + va_end(args); + + return r; +} + static int sas_ata_hard_reset(struct ata_link *link, unsigned int *class, unsigned long deadline) { @@ -333,7 +355,7 @@ static int sas_ata_hard_reset(struct ata_link *link, unsigned int *class, res = i->dft->lldd_I_T_nexus_reset(dev); if (res != TMF_RESP_FUNC_COMPLETE) - SAS_DPRINTK("%s: Unable to reset ata device?\n", __func__); + sas_ata_printk(KERN_DEBUG, dev, "Unable to reset ata device?\n"); phy = sas_get_local_phy(dev); if (scsi_is_sas_phy_local(phy)) @@ -344,7 +366,7 @@ static int sas_ata_hard_reset(struct ata_link *link, unsigned int *class, ret = ata_wait_after_reset(link, deadline, check_ready); if (ret && ret != -EAGAIN) - ata_link_err(link, "COMRESET failed (errno=%d)\n", ret); + sas_ata_printk(KERN_ERR, dev, "reset failed (errno=%d)\n", ret); /* XXX: if the class changes during the reset the upper layer * should be informed, if the device has gone away we assume @@ -665,7 +687,7 @@ static void async_sas_ata_eh(void *data, async_cookie_t cookie) * remove once all commands are completed */ kref_get(&dev->kref); - ata_port_printk(ap, KERN_DEBUG, "sas eh calling libata port error handler"); + sas_ata_printk(KERN_DEBUG, dev, "dev error handler\n"); ata_scsi_port_error_handler(ha->core.shost, ap); sas_put_device(dev); } @@ -703,26 +725,27 @@ void sas_ata_eh(struct Scsi_Host *shost, struct list_head *work_q, struct list_head *done_q) { struct scsi_cmnd *cmd, *n; - struct ata_port *ap; + struct domain_device *eh_dev; do { LIST_HEAD(sata_q); - - ap = NULL; + eh_dev = NULL; list_for_each_entry_safe(cmd, n, work_q, eh_entry) { struct domain_device *ddev = cmd_to_domain_dev(cmd); if (!dev_is_sata(ddev) || TO_SAS_TASK(cmd)) continue; - if (ap && ap != ddev->sata_dev.ap) + if (eh_dev && eh_dev != ddev) continue; - ap = ddev->sata_dev.ap; + eh_dev = ddev; list_move(&cmd->eh_entry, &sata_q); } if (!list_empty(&sata_q)) { - ata_port_printk(ap, KERN_DEBUG, "sas eh calling libata cmd error handler\n"); + struct ata_port *ap = eh_dev->sata_dev.ap; + + sas_ata_printk(KERN_DEBUG, eh_dev, "cmd error handler\n"); ata_scsi_cmd_error_handler(shost, ap, &sata_q); /* * ata's error handler may leave the cmd on the list @@ -738,7 +761,7 @@ void sas_ata_eh(struct Scsi_Host *shost, struct list_head *work_q, while (!list_empty(&sata_q)) list_del_init(sata_q.next); } - } while (ap); + } while (eh_dev); } void sas_ata_schedule_reset(struct domain_device *dev) diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index 68a80a00f73..4b2ecd35dc5 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -166,7 +166,22 @@ static inline void *alloc_smp_resp(int size) return kzalloc(size, GFP_KERNEL); } -/* ---------- Expander configuration ---------- */ +static char sas_route_char(struct domain_device *dev, struct ex_phy *phy) +{ + switch (phy->routing_attr) { + case TABLE_ROUTING: + if (dev->ex_dev.t2t_supp) + return 'U'; + else + return 'T'; + case DIRECT_ROUTING: + return 'D'; + case SUBTRACTIVE_ROUTING: + return 'S'; + default: + return '?'; + } +} static void sas_set_ex_phy(struct domain_device *dev, int phy_id, void *disc_resp) @@ -176,9 +191,10 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id, struct smp_resp *resp = disc_resp; struct discover_resp *dr = &resp->disc; struct sas_rphy *rphy = dev->rphy; - int rediscover = (phy->phy != NULL); + bool new_phy = !phy->phy; + char *type; - if (!rediscover) { + if (new_phy) { phy->phy = sas_phy_alloc(&rphy->dev, phy_id); /* FIXME: error_handling */ @@ -223,20 +239,41 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id, phy->phy->maximum_linkrate = dr->pmax_linkrate; phy->phy->negotiated_linkrate = phy->linkrate; - if (!rediscover) + if (new_phy) if (sas_phy_add(phy->phy)) { sas_phy_free(phy->phy); return; } - SAS_DPRINTK("ex %016llx phy%02d:%c attached: %016llx\n", - SAS_ADDR(dev->sas_addr), phy->phy_id, - phy->routing_attr == TABLE_ROUTING ? 'T' : - phy->routing_attr == DIRECT_ROUTING ? 'D' : - phy->routing_attr == SUBTRACTIVE_ROUTING ? 'S' : '?', - SAS_ADDR(phy->attached_sas_addr)); + switch (phy->attached_dev_type) { + case NO_DEVICE: + type = "no device"; + break; + case SAS_END_DEV: + if (phy->attached_iproto) { + if (phy->attached_tproto) + type = "host+target"; + else + type = "host"; + } else { + if (dr->attached_sata_dev) + type = "stp"; + else + type = "ssp"; + } + break; + case EDGE_DEV: + case FANOUT_DEV: + type = "smp"; + break; + default: + type = "unknown"; + } - return; + SAS_DPRINTK("ex %016llx phy%02d:%c:%X attached: %016llx (%s)\n", + SAS_ADDR(dev->sas_addr), phy->phy_id, + sas_route_char(dev, phy), phy->linkrate, + SAS_ADDR(phy->attached_sas_addr), type); } /* check if we have an existing attached ata device on this expander phy */ @@ -1176,32 +1213,25 @@ static void sas_print_parent_topology_bug(struct domain_device *child, struct ex_phy *parent_phy, struct ex_phy *child_phy) { - static const char ra_char[] = { - [DIRECT_ROUTING] = 'D', - [SUBTRACTIVE_ROUTING] = 'S', - [TABLE_ROUTING] = 'T', - }; static const char *ex_type[] = { [EDGE_DEV] = "edge", [FANOUT_DEV] = "fanout", }; struct domain_device *parent = child->parent; - sas_printk("%s ex %016llx (T2T supp:%d) phy 0x%x <--> %s ex %016llx " - "(T2T supp:%d) phy 0x%x has %c:%c routing link!\n", + sas_printk("%s ex %016llx phy 0x%x <--> %s ex %016llx " + "phy 0x%x has %c:%c routing link!\n", ex_type[parent->dev_type], SAS_ADDR(parent->sas_addr), - parent->ex_dev.t2t_supp, parent_phy->phy_id, ex_type[child->dev_type], SAS_ADDR(child->sas_addr), - child->ex_dev.t2t_supp, child_phy->phy_id, - ra_char[parent_phy->routing_attr], - ra_char[child_phy->routing_attr]); + sas_route_char(parent, parent_phy), + sas_route_char(child, child_phy)); } static int sas_check_eeds(struct domain_device *child, -- cgit v1.2.3-70-g09d2 From 354cf82980e2449e71fdaa3c6f170357ebd65467 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 12 Jan 2012 17:57:35 -0800 Subject: [SCSI] libsas: let libata recover links that fail to transmit initial sig-fis libsas fails to discover all sata devices in the domain. If a device fails negotiation and does not transmit a signature fis the link needs recovery. libata already understands how to manage slow to come up links, so treat these conditions as ata device attach events for the purposes of creating an ata_port. This allows libata to manage retrying link bring up. Rediscovery is modified to be careful about checking changes in dev_type. It looks like libsas leaks old devices if the sas address changes, but that's a fix for another patch. Acked-by: Jack Wang Signed-off-by: Dan Williams Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_ata.c | 71 +++++++++++++-- drivers/scsi/libsas/sas_discover.c | 1 + drivers/scsi/libsas/sas_expander.c | 178 +++++++++++++++++++++---------------- drivers/scsi/libsas/sas_internal.h | 6 +- include/scsi/sas.h | 4 +- include/scsi/sas_ata.h | 8 +- 6 files changed, 179 insertions(+), 89 deletions(-) (limited to 'drivers/scsi/libsas/sas_expander.c') diff --git a/drivers/scsi/libsas/sas_ata.c b/drivers/scsi/libsas/sas_ata.c index ba1ebfe991d..25008a42412 100644 --- a/drivers/scsi/libsas/sas_ata.c +++ b/drivers/scsi/libsas/sas_ata.c @@ -278,26 +278,84 @@ static struct sas_internal *dev_to_sas_internal(struct domain_device *dev) return to_sas_internal(dev->port->ha->core.shost->transportt); } +static void sas_get_ata_command_set(struct domain_device *dev); + +int sas_get_ata_info(struct domain_device *dev, struct ex_phy *phy) +{ + if (phy->attached_tproto & SAS_PROTOCOL_STP) + dev->tproto = phy->attached_tproto; + if (phy->attached_sata_dev) + dev->tproto |= SATA_DEV; + + if (phy->attached_dev_type == SATA_PENDING) + dev->dev_type = SATA_PENDING; + else { + int res; + + dev->dev_type = SATA_DEV; + res = sas_get_report_phy_sata(dev->parent, phy->phy_id, + &dev->sata_dev.rps_resp); + if (res) { + SAS_DPRINTK("report phy sata to %016llx:0x%x returned " + "0x%x\n", SAS_ADDR(dev->parent->sas_addr), + phy->phy_id, res); + return res; + } + memcpy(dev->frame_rcvd, &dev->sata_dev.rps_resp.rps.fis, + sizeof(struct dev_to_host_fis)); + /* TODO switch to ata_dev_classify() */ + sas_get_ata_command_set(dev); + } + return 0; +} + +static int sas_ata_clear_pending(struct domain_device *dev, struct ex_phy *phy) +{ + int res; + + /* we weren't pending, so successfully end the reset sequence now */ + if (dev->dev_type != SATA_PENDING) + return 1; + + /* hmmm, if this succeeds do we need to repost the domain_device to the + * lldd so it can pick up new parameters? + */ + res = sas_get_ata_info(dev, phy); + if (res) + return 0; /* retry */ + else + return 1; +} + static int smp_ata_check_ready(struct ata_link *link) { int res; - u8 addr[8]; struct ata_port *ap = link->ap; struct domain_device *dev = ap->private_data; struct domain_device *ex_dev = dev->parent; struct sas_phy *phy = sas_get_local_phy(dev); + struct ex_phy *ex_phy = &ex_dev->ex_dev.ex_phy[phy->number]; - res = sas_get_phy_attached_sas_addr(ex_dev, phy->number, addr); + res = sas_ex_phy_discover(ex_dev, phy->number); sas_put_local_phy(phy); + /* break the wait early if the expander is unreachable, * otherwise keep polling */ if (res == -ECOMM) return res; - if (res != SMP_RESP_FUNC_ACC || SAS_ADDR(addr) == 0) + if (res != SMP_RESP_FUNC_ACC) return 0; - else - return 1; + + switch (ex_phy->attached_dev_type) { + case SATA_PENDING: + return 0; + case SAS_END_DEV: + if (ex_phy->attached_sata_dev) + return sas_ata_clear_pending(dev, ex_phy); + default: + return -ENODEV; + } } static int local_ata_check_ready(struct ata_link *link) @@ -584,6 +642,9 @@ static void sas_get_ata_command_set(struct domain_device *dev) struct dev_to_host_fis *fis = (struct dev_to_host_fis *) dev->frame_rcvd; + if (dev->dev_type == SATA_PENDING) + return; + if ((fis->sector_count == 1 && /* ATA */ fis->lbal == 1 && fis->lbam == 0 && diff --git a/drivers/scsi/libsas/sas_discover.c b/drivers/scsi/libsas/sas_discover.c index c1ac99d25f5..8bcfcaa7b2e 100644 --- a/drivers/scsi/libsas/sas_discover.c +++ b/drivers/scsi/libsas/sas_discover.c @@ -48,6 +48,7 @@ void sas_init_dev(struct domain_device *dev) case SATA_DEV: case SATA_PM: case SATA_PM_PORT: + case SATA_PENDING: INIT_LIST_HEAD(&dev->sata_dev.children); break; default: diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index 4b2ecd35dc5..7e2d3c4c617 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -183,13 +183,27 @@ static char sas_route_char(struct domain_device *dev, struct ex_phy *phy) } } -static void sas_set_ex_phy(struct domain_device *dev, int phy_id, - void *disc_resp) +static enum sas_dev_type to_dev_type(struct discover_resp *dr) { + /* This is detecting a failure to transmit initial dev to host + * FIS as described in section J.5 of sas-2 r16 + */ + if (dr->attached_dev_type == NO_DEVICE && dr->attached_sata_dev && + dr->linkrate >= SAS_LINK_RATE_1_5_GBPS) + return SATA_PENDING; + else + return dr->attached_dev_type; +} + +static void sas_set_ex_phy(struct domain_device *dev, int phy_id, void *rsp) +{ + enum sas_dev_type dev_type; + enum sas_linkrate linkrate; + u8 sas_addr[SAS_ADDR_SIZE]; + struct smp_resp *resp = rsp; + struct discover_resp *dr = &resp->disc; struct expander_device *ex = &dev->ex_dev; struct ex_phy *phy = &ex->ex_phy[phy_id]; - struct smp_resp *resp = disc_resp; - struct discover_resp *dr = &resp->disc; struct sas_rphy *rphy = dev->rphy; bool new_phy = !phy->phy; char *type; @@ -213,8 +227,13 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id, break; } + /* check if anything important changed to squelch debug */ + dev_type = phy->attached_dev_type; + linkrate = phy->linkrate; + memcpy(sas_addr, phy->attached_sas_addr, SAS_ADDR_SIZE); + + phy->attached_dev_type = to_dev_type(dr); phy->phy_id = phy_id; - phy->attached_dev_type = dr->attached_dev_type; phy->linkrate = dr->linkrate; phy->attached_sata_host = dr->attached_sata_host; phy->attached_sata_dev = dr->attached_sata_dev; @@ -229,7 +248,7 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id, phy->last_da_index = -1; phy->phy->identify.sas_address = SAS_ADDR(phy->attached_sas_addr); - phy->phy->identify.device_type = phy->attached_dev_type; + phy->phy->identify.device_type = dr->attached_dev_type; phy->phy->identify.initiator_port_protocols = phy->attached_iproto; phy->phy->identify.target_port_protocols = phy->attached_tproto; phy->phy->identify.phy_identifier = phy_id; @@ -246,6 +265,9 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id, } switch (phy->attached_dev_type) { + case SATA_PENDING: + type = "stp pending"; + break; case NO_DEVICE: type = "no device"; break; @@ -270,6 +292,16 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id, type = "unknown"; } + /* this routine is polled by libata error recovery so filter + * unimportant messages + */ + if (new_phy || phy->attached_dev_type != dev_type || + phy->linkrate != linkrate || + SAS_ADDR(phy->attached_sas_addr) != SAS_ADDR(sas_addr)) + /* pass */; + else + return; + SAS_DPRINTK("ex %016llx phy%02d:%c:%X attached: %016llx (%s)\n", SAS_ADDR(dev->sas_addr), phy->phy_id, sas_route_char(dev, phy), phy->linkrate, @@ -304,50 +336,25 @@ struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id) static int sas_ex_phy_discover_helper(struct domain_device *dev, u8 *disc_req, u8 *disc_resp, int single) { - struct domain_device *ata_dev = sas_ex_to_ata(dev, single); - int i, res; + struct discover_resp *dr; + int res; disc_req[9] = single; - for (i = 1 ; i < 3; i++) { - struct discover_resp *dr; - res = smp_execute_task(dev, disc_req, DISCOVER_REQ_SIZE, - disc_resp, DISCOVER_RESP_SIZE); - if (res) - return res; - dr = &((struct smp_resp *)disc_resp)->disc; - if (memcmp(dev->sas_addr, dr->attached_sas_addr, - SAS_ADDR_SIZE) == 0) { - sas_printk("Found loopback topology, just ignore it!\n"); - return 0; - } - - /* This is detecting a failure to transmit initial - * dev to host FIS as described in section J.5 of - * sas-2 r16 - */ - if (!(dr->attached_dev_type == 0 && - dr->attached_sata_dev)) - break; - - /* In order to generate the dev to host FIS, we send a - * link reset to the expander port. If a device was - * previously detected on this port we ask libata to - * manage the reset and link recovery. - */ - if (ata_dev) { - sas_ata_schedule_reset(ata_dev); - break; - } - sas_smp_phy_control(dev, single, PHY_FUNC_LINK_RESET, NULL); - /* Wait for the reset to trigger the negotiation */ - msleep(500); + res = smp_execute_task(dev, disc_req, DISCOVER_REQ_SIZE, + disc_resp, DISCOVER_RESP_SIZE); + if (res) + return res; + dr = &((struct smp_resp *)disc_resp)->disc; + if (memcmp(dev->sas_addr, dr->attached_sas_addr, SAS_ADDR_SIZE) == 0) { + sas_printk("Found loopback topology, just ignore it!\n"); + return 0; } sas_set_ex_phy(dev, single, disc_resp); return 0; } -static int sas_ex_phy_discover(struct domain_device *dev, int single) +int sas_ex_phy_discover(struct domain_device *dev, int single) { struct expander_device *ex = &dev->ex_dev; int res = 0; @@ -652,9 +659,8 @@ int sas_smp_get_phy_events(struct sas_phy *phy) #define RPS_REQ_SIZE 16 #define RPS_RESP_SIZE 60 -static int sas_get_report_phy_sata(struct domain_device *dev, - int phy_id, - struct smp_resp *rps_resp) +int sas_get_report_phy_sata(struct domain_device *dev, int phy_id, + struct smp_resp *rps_resp) { int res; u8 *rps_req = alloc_smp_req(RPS_REQ_SIZE); @@ -764,21 +770,9 @@ static struct domain_device *sas_ex_discover_end_dev( #ifdef CONFIG_SCSI_SAS_ATA if ((phy->attached_tproto & SAS_PROTOCOL_STP) || phy->attached_sata_dev) { - child->dev_type = SATA_DEV; - if (phy->attached_tproto & SAS_PROTOCOL_STP) - child->tproto = phy->attached_tproto; - if (phy->attached_sata_dev) - child->tproto |= SATA_DEV; - res = sas_get_report_phy_sata(parent, phy_id, - &child->sata_dev.rps_resp); - if (res) { - SAS_DPRINTK("report phy sata to %016llx:0x%x returned " - "0x%x\n", SAS_ADDR(parent->sas_addr), - phy_id, res); + res = sas_get_ata_info(child, phy); + if (res) goto out_free; - } - memcpy(child->frame_rcvd, &child->sata_dev.rps_resp.rps.fis, - sizeof(struct dev_to_host_fis)); rphy = sas_end_device_alloc(phy->port); if (unlikely(!rphy)) @@ -993,7 +987,8 @@ static int sas_ex_discover_dev(struct domain_device *dev, int phy_id) if (ex_phy->attached_dev_type != SAS_END_DEV && ex_phy->attached_dev_type != FANOUT_DEV && - ex_phy->attached_dev_type != EDGE_DEV) { + ex_phy->attached_dev_type != EDGE_DEV && + ex_phy->attached_dev_type != SATA_PENDING) { SAS_DPRINTK("unknown device type(0x%x) attached to ex %016llx " "phy 0x%x\n", ex_phy->attached_dev_type, SAS_ADDR(dev->sas_addr), @@ -1019,6 +1014,7 @@ static int sas_ex_discover_dev(struct domain_device *dev, int phy_id) switch (ex_phy->attached_dev_type) { case SAS_END_DEV: + case SATA_PENDING: child = sas_ex_discover_end_dev(dev, phy_id); break; case FANOUT_DEV: @@ -1688,8 +1684,8 @@ static int sas_get_phy_change_count(struct domain_device *dev, return res; } -int sas_get_phy_attached_sas_addr(struct domain_device *dev, int phy_id, - u8 *attached_sas_addr) +static int sas_get_phy_attached_dev(struct domain_device *dev, int phy_id, + u8 *sas_addr, enum sas_dev_type *type) { int res; struct smp_resp *disc_resp; @@ -1701,10 +1697,11 @@ int sas_get_phy_attached_sas_addr(struct domain_device *dev, int phy_id, dr = &disc_resp->disc; res = sas_get_phy_discover(dev, phy_id, disc_resp); - if (!res) { - memcpy(attached_sas_addr,disc_resp->disc.attached_sas_addr,8); - if (dr->attached_dev_type == 0) - memset(attached_sas_addr, 0, 8); + if (res == 0) { + memcpy(sas_addr, disc_resp->disc.attached_sas_addr, 8); + *type = to_dev_type(dr); + if (*type == 0) + memset(sas_addr, 0, 8); } kfree(disc_resp); return res; @@ -1953,39 +1950,62 @@ out: return res; } +static bool dev_type_flutter(enum sas_dev_type new, enum sas_dev_type old) +{ + if (old == new) + return true; + + /* treat device directed resets as flutter, if we went + * SAS_END_DEV to SATA_PENDING the link needs recovery + */ + if ((old == SATA_PENDING && new == SAS_END_DEV) || + (old == SAS_END_DEV && new == SATA_PENDING)) + return true; + + return false; +} + static int sas_rediscover_dev(struct domain_device *dev, int phy_id, bool last) { struct expander_device *ex = &dev->ex_dev; struct ex_phy *phy = &ex->ex_phy[phy_id]; - u8 attached_sas_addr[8]; + enum sas_dev_type type = NO_DEVICE; + u8 sas_addr[8]; int res; - res = sas_get_phy_attached_sas_addr(dev, phy_id, attached_sas_addr); + res = sas_get_phy_attached_dev(dev, phy_id, sas_addr, &type); switch (res) { case SMP_RESP_NO_PHY: phy->phy_state = PHY_NOT_PRESENT; sas_unregister_devs_sas_addr(dev, phy_id, last); - goto out; break; + return res; case SMP_RESP_PHY_VACANT: phy->phy_state = PHY_VACANT; sas_unregister_devs_sas_addr(dev, phy_id, last); - goto out; break; + return res; case SMP_RESP_FUNC_ACC: break; } - if (SAS_ADDR(attached_sas_addr) == 0) { + if (SAS_ADDR(sas_addr) == 0) { phy->phy_state = PHY_EMPTY; sas_unregister_devs_sas_addr(dev, phy_id, last); - } else if (SAS_ADDR(attached_sas_addr) == - SAS_ADDR(phy->attached_sas_addr)) { - SAS_DPRINTK("ex %016llx phy 0x%x broadcast flutter\n", - SAS_ADDR(dev->sas_addr), phy_id); + return res; + } else if (SAS_ADDR(sas_addr) == SAS_ADDR(phy->attached_sas_addr) && + dev_type_flutter(type, phy->attached_dev_type)) { + struct domain_device *ata_dev = sas_ex_to_ata(dev, phy_id); + char *action = ""; + sas_ex_phy_discover(dev, phy_id); - } else - res = sas_discover_new(dev, phy_id); -out: - return res; + + if (ata_dev && phy->attached_dev_type == SATA_PENDING) + action = ", needs recovery"; + SAS_DPRINTK("ex %016llx phy 0x%x broadcast flutter%s\n", + SAS_ADDR(dev->sas_addr), phy_id, action); + return res; + } + + return sas_discover_new(dev, phy_id); } /** diff --git a/drivers/scsi/libsas/sas_internal.h b/drivers/scsi/libsas/sas_internal.h index 7818c4673c3..e028d7a4420 100644 --- a/drivers/scsi/libsas/sas_internal.h +++ b/drivers/scsi/libsas/sas_internal.h @@ -91,8 +91,9 @@ int sas_smp_get_phy_events(struct sas_phy *phy); void sas_device_set_phy(struct domain_device *dev, struct sas_port *port); struct domain_device *sas_find_dev_by_rphy(struct sas_rphy *rphy); struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id); -int sas_get_phy_attached_sas_addr(struct domain_device *dev, int phy_id, - u8 *attached_sas_addr); +int sas_ex_phy_discover(struct domain_device *dev, int single); +int sas_get_report_phy_sata(struct domain_device *dev, int phy_id, + struct smp_resp *rps_resp); int sas_try_ata_reset(struct asd_sas_phy *phy); void sas_hae_reset(struct work_struct *work); @@ -122,6 +123,7 @@ static inline void sas_fill_in_rphy(struct domain_device *dev, case SATA_DEV: /* FIXME: need sata device type */ case SAS_END_DEV: + case SATA_PENDING: rphy->identify.device_type = SAS_END_DEVICE; break; case EDGE_DEV: diff --git a/include/scsi/sas.h b/include/scsi/sas.h index 3673d685e6a..a577a833603 100644 --- a/include/scsi/sas.h +++ b/include/scsi/sas.h @@ -89,8 +89,7 @@ enum sas_oob_mode { SAS_OOB_MODE }; -/* See sas_discover.c if you plan on changing these. - */ +/* See sas_discover.c if you plan on changing these */ enum sas_dev_type { NO_DEVICE = 0, /* protocol */ SAS_END_DEV = 1, /* protocol */ @@ -100,6 +99,7 @@ enum sas_dev_type { SATA_DEV = 5, SATA_PM = 7, SATA_PM_PORT= 8, + SATA_PENDING = 9, }; enum sas_protocol { diff --git a/include/scsi/sas_ata.h b/include/scsi/sas_ata.h index cb724fd010f..0ca2f8a6bc6 100644 --- a/include/scsi/sas_ata.h +++ b/include/scsi/sas_ata.h @@ -33,9 +33,10 @@ static inline int dev_is_sata(struct domain_device *dev) { return dev->dev_type == SATA_DEV || dev->dev_type == SATA_PM || - dev->dev_type == SATA_PM_PORT; + dev->dev_type == SATA_PM_PORT || dev->dev_type == SATA_PENDING; } +int sas_get_ata_info(struct domain_device *dev, struct ex_phy *phy); int sas_ata_init_host_and_port(struct domain_device *found_dev, struct scsi_target *starget); @@ -82,6 +83,11 @@ static inline void sas_ata_schedule_reset(struct domain_device *dev) static inline void sas_ata_wait_eh(struct domain_device *dev) { } + +static inline int sas_get_ata_info(struct domain_device *dev, struct ex_phy *phy) +{ + return 0; +} #endif #endif /* _SAS_ATA_H_ */ -- cgit v1.2.3-70-g09d2 From c666aae6919114d6cff789d79f80cfa85f3a7339 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 19 Jan 2012 18:43:08 -0800 Subject: [SCSI] libsas: delete device on sas address changed If the phy is attached to a new sas address unregister the first address before processing the new attachment. Signed-off-by: Dan Williams Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_expander.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers/scsi/libsas/sas_expander.c') diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index 7e2d3c4c617..d63f0fbcd10 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -2005,6 +2005,15 @@ static int sas_rediscover_dev(struct domain_device *dev, int phy_id, bool last) return res; } + /* delete the old link */ + if (SAS_ADDR(phy->attached_sas_addr) && + SAS_ADDR(sas_addr) != SAS_ADDR(phy->attached_sas_addr)) { + SAS_DPRINTK("ex %016llx phy 0x%x replace %016llx\n", + SAS_ADDR(dev->sas_addr), phy_id, + SAS_ADDR(phy->attached_sas_addr)); + sas_unregister_devs_sas_addr(dev, phy_id, last); + } + return sas_discover_new(dev, phy_id); } -- cgit v1.2.3-70-g09d2 From 92625f9bff3853951cc75f5bc084ee67c1317d2f Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 18 Jan 2012 20:14:01 -0800 Subject: [SCSI] libsas: restore scan order ata devices are always scanned after ssp. Prior to the ata error handling reworks libsas would tend to scan devices in ascending expander phy order. Restore this ordering by deferring ssp discovery to a DISCE_PROBE event, and keep the probe order consistent with the discovery order, not the placement of sata devices. Signed-off-by: Dan Williams Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_ata.c | 29 ------------------------- drivers/scsi/libsas/sas_discover.c | 44 +++++++++++++++++++++++++++----------- drivers/scsi/libsas/sas_expander.c | 4 +--- include/scsi/sas_ata.h | 5 ----- 4 files changed, 32 insertions(+), 50 deletions(-) (limited to 'drivers/scsi/libsas/sas_expander.c') diff --git a/drivers/scsi/libsas/sas_ata.c b/drivers/scsi/libsas/sas_ata.c index 25008a42412..a9ec1643ee9 100644 --- a/drivers/scsi/libsas/sas_ata.c +++ b/drivers/scsi/libsas/sas_ata.c @@ -683,35 +683,6 @@ static void sas_get_ata_command_set(struct domain_device *dev) dev->sata_dev.command_set = ATAPI_COMMAND_SET; } -void sas_probe_sata(struct work_struct *work) -{ - struct domain_device *dev, *n; - struct sas_discovery_event *ev = - container_of(work, struct sas_discovery_event, work); - struct asd_sas_port *port = ev->port; - - clear_bit(DISCE_PROBE, &port->disc.pending); - - list_for_each_entry_safe(dev, n, &port->disco_list, disco_list_node) { - int err; - - spin_lock_irq(&port->dev_list_lock); - list_add_tail(&dev->dev_list_node, &port->dev_list); - spin_unlock_irq(&port->dev_list_lock); - - err = sas_rphy_add(dev->rphy); - - if (err) { - SAS_DPRINTK("%s: for %s device %16llx returned %d\n", - __func__, dev->parent ? "exp-attached" : - "direct-attached", - SAS_ADDR(dev->sas_addr), err); - sas_unregister_dev(port, dev); - } else - list_del_init(&dev->disco_list_node); - } -} - /** * sas_discover_sata -- discover an STP/SATA domain device * @dev: pointer to struct domain_device of interest diff --git a/drivers/scsi/libsas/sas_discover.c b/drivers/scsi/libsas/sas_discover.c index 8bcfcaa7b2e..18fa364aa00 100644 --- a/drivers/scsi/libsas/sas_discover.c +++ b/drivers/scsi/libsas/sas_discover.c @@ -152,7 +152,7 @@ static int sas_get_port_device(struct asd_sas_port *port) dev->rphy = rphy; - if (dev_is_sata(dev)) + if (dev_is_sata(dev) || dev->dev_type == SAS_END_DEV) list_add_tail(&dev->disco_list_node, &port->disco_list); else { spin_lock_irq(&port->dev_list_lock); @@ -198,8 +198,34 @@ void sas_notify_lldd_dev_gone(struct domain_device *dev) } } -/* ---------- Common/dispatchers ---------- */ +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 asd_sas_port *port = ev->port; + clear_bit(DISCE_PROBE, &port->disc.pending); + + list_for_each_entry_safe(dev, n, &port->disco_list, disco_list_node) { + int err; + + spin_lock_irq(&port->dev_list_lock); + list_add_tail(&dev->dev_list_node, &port->dev_list); + spin_unlock_irq(&port->dev_list_lock); + + err = sas_rphy_add(dev->rphy); + + if (err) { + SAS_DPRINTK("%s: for %s device %16llx returned %d\n", + __func__, dev->parent ? "exp-attached" : + "direct-attached", + SAS_ADDR(dev->sas_addr), err); + sas_unregister_dev(port, dev); + } else + list_del_init(&dev->disco_list_node); + } +} /** * sas_discover_end_dev -- discover an end device (SSP, etc) @@ -213,18 +239,10 @@ 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 ---------- */ @@ -491,7 +509,7 @@ 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_sata, + [DISCE_PROBE] = sas_probe_devices, [DISCE_DESTRUCT] = sas_destruct_devices, }; diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index d63f0fbcd10..14e3244c1b2 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -806,9 +806,7 @@ static struct domain_device *sas_ex_discover_end_dev( child->rphy = rphy; sas_fill_in_rphy(child, rphy); - spin_lock_irq(&parent->port->dev_list_lock); - list_add_tail(&child->dev_list_node, &parent->port->dev_list); - spin_unlock_irq(&parent->port->dev_list_lock); + list_add_tail(&child->disco_list_node, &parent->port->disco_list); res = sas_discover_end_dev(child); if (res) { diff --git a/include/scsi/sas_ata.h b/include/scsi/sas_ata.h index 0ca2f8a6bc6..1556eff4cc4 100644 --- a/include/scsi/sas_ata.h +++ b/include/scsi/sas_ata.h @@ -44,7 +44,6 @@ void sas_ata_task_abort(struct sas_task *task); void sas_ata_strategy_handler(struct Scsi_Host *shost); void sas_ata_eh(struct Scsi_Host *shost, struct list_head *work_q, struct list_head *done_q); -void sas_probe_sata(struct work_struct *work); void sas_ata_schedule_reset(struct domain_device *dev); void sas_ata_wait_eh(struct domain_device *dev); #else @@ -72,10 +71,6 @@ static inline void sas_ata_eh(struct Scsi_Host *shost, struct list_head *work_q, { } -static inline void sas_probe_sata(struct work_struct *work) -{ -} - static inline void sas_ata_schedule_reset(struct domain_device *dev) { } -- cgit v1.2.3-70-g09d2 From 77c309f3cdf9e217032dfe330f5881d352bb0436 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 8 Feb 2012 23:20:41 -0800 Subject: [SCSI] libsas: fixup target_port_protocols for expanders that don't report sata If discovery returns 0 for target_port_protocols but shows an attached sata device, just report SAS_PROTOCOL_SATA in the identify data so userspace can reliably search for sata devices in the domain. Signed-off-by: Dan Williams Signed-off-by: James Bottomley --- drivers/scsi/libsas/sas_expander.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/scsi/libsas/sas_expander.c') diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index 14e3244c1b2..05acd9e35fc 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -251,6 +251,8 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id, void *rsp) phy->phy->identify.device_type = dr->attached_dev_type; phy->phy->identify.initiator_port_protocols = phy->attached_iproto; phy->phy->identify.target_port_protocols = phy->attached_tproto; + if (!phy->attached_tproto && dr->attached_sata_dev) + phy->phy->identify.target_port_protocols = SAS_PROTOCOL_SATA; phy->phy->identify.phy_identifier = phy_id; phy->phy->minimum_linkrate_hw = dr->hmin_linkrate; phy->phy->maximum_linkrate_hw = dr->hmax_linkrate; -- cgit v1.2.3-70-g09d2