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

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

[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: default avatarDan Williams <dan.j.williams@intel.com>
Signed-off-by: default avatarJames Bottomley <JBottomley@Parallels.com>
parent 3a9c5560
Loading
Loading
Loading
Loading
+6 −3
Original line number Diff line number Diff line
@@ -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)
+5 −4
Original line number Diff line number Diff line
@@ -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;
}

+5 −2
Original line number Diff line number Diff line
@@ -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)
+24 −0
Original line number Diff line number Diff line
@@ -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 ---------- */

/**
+4 −1
Original line number Diff line number Diff line
@@ -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;
Loading