forked from Minki/linux
[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 <dan.j.williams@intel.com> Signed-off-by: James Bottomley <JBottomley@Parallels.com>
This commit is contained in:
parent
3a9c5560f6
commit
f41a0c441c
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 ---------- */
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 *);
|
||||
|
Loading…
Reference in New Issue
Block a user