Skip to content

Commit

Permalink
[SCSI] libsas: fix wide port hotplug issues
Browse files Browse the repository at this point in the history
Hotplug of phys which form wide ports simply does not work at the moment.  Fix
this by adding checks at the hotplug points to see if the attached sas address
of the phy already exists (in which case it's part of a wide port) and act
accordingly.

Signed-off-by: Tom Peng <tom_peng@usish.com>
Signed-off-by: Jack Wang <jack_wang@usish.com>
Signed-off-by: Lindar Liu <lindar_liu@usish.com>
Signed-off-by: Kevin Ao <aoqingyun@usish.com>
[jejb: tidied up coding, fixed an error case and made TRUE/FALSE lower
 case to fix a ppc64 compile error in linux-next]
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
  • Loading branch information
Tom Peng authored and James Bottomley committed Jul 30, 2009
1 parent a0cc1ec commit 19252de
Showing 1 changed file with 107 additions and 40 deletions.
147 changes: 107 additions & 40 deletions drivers/scsi/libsas/sas_expander.c
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,7 @@ static int sas_ex_join_wide_port(struct domain_device *parent, int phy_id)
if (!memcmp(phy->attached_sas_addr, ephy->attached_sas_addr,
SAS_ADDR_SIZE) && ephy->port) {
sas_port_add_phy(ephy->port, phy->phy);
phy->port = ephy->port;
phy->phy_state = PHY_DEVICE_DISCOVERED;
return 0;
}
Expand Down Expand Up @@ -945,11 +946,21 @@ static int sas_ex_discover_dev(struct domain_device *dev, int phy_id)
if (ex->ex_phy[i].phy_state == PHY_VACANT ||
ex->ex_phy[i].phy_state == PHY_NOT_PRESENT)
continue;

/*
* Due to races, the phy might not get added to the
* wide port, so we add the phy to the wide port here.
*/
if (SAS_ADDR(ex->ex_phy[i].attached_sas_addr) ==
SAS_ADDR(child->sas_addr))
SAS_ADDR(child->sas_addr)) {
ex->ex_phy[i].phy_state= PHY_DEVICE_DISCOVERED;
res = sas_ex_join_wide_port(dev, i);
if (!res)
SAS_DPRINTK("Attaching ex phy%d to wide port %016llx\n",
i, SAS_ADDR(ex->ex_phy[i].attached_sas_addr));

}
}
res = 0;
}

return res;
Expand Down Expand Up @@ -1598,7 +1609,7 @@ static int sas_get_phy_attached_sas_addr(struct domain_device *dev,
}

static int sas_find_bcast_phy(struct domain_device *dev, int *phy_id,
int from_phy)
int from_phy, bool update)
{
struct expander_device *ex = &dev->ex_dev;
int res = 0;
Expand All @@ -1611,7 +1622,9 @@ static int sas_find_bcast_phy(struct domain_device *dev, int *phy_id,
if (res)
goto out;
else if (phy_change_count != ex->ex_phy[i].phy_change_count) {
ex->ex_phy[i].phy_change_count = phy_change_count;
if (update)
ex->ex_phy[i].phy_change_count =
phy_change_count;
*phy_id = i;
return 0;
}
Expand Down Expand Up @@ -1653,31 +1666,52 @@ static int sas_get_ex_change_count(struct domain_device *dev, int *ecc)
kfree(rg_req);
return res;
}
/**
* sas_find_bcast_dev - find the device issue BROADCAST(CHANGE).
* @dev:domain device to be detect.
* @src_dev: the device which originated BROADCAST(CHANGE).
*
* Add self-configuration expander suport. Suppose two expander cascading,
* when the first level expander is self-configuring, hotplug the disks in
* second level expander, BROADCAST(CHANGE) will not only be originated
* in the second level expander, but also be originated in the first level
* expander (see SAS protocol SAS 2r-14, 7.11 for detail), it is to say,
* expander changed count in two level expanders will all increment at least
* once, but the phy which chang count has changed is the source device which
* we concerned.
*/

static int sas_find_bcast_dev(struct domain_device *dev,
struct domain_device **src_dev)
{
struct expander_device *ex = &dev->ex_dev;
int ex_change_count = -1;
int phy_id = -1;
int res;
struct domain_device *ch;

res = sas_get_ex_change_count(dev, &ex_change_count);
if (res)
goto out;
if (ex_change_count != -1 &&
ex_change_count != ex->ex_change_count) {
*src_dev = dev;
ex->ex_change_count = ex_change_count;
} else {
struct domain_device *ch;

list_for_each_entry(ch, &ex->children, siblings) {
if (ch->dev_type == EDGE_DEV ||
ch->dev_type == FANOUT_DEV) {
res = sas_find_bcast_dev(ch, src_dev);
if (src_dev)
return res;
}
if (ex_change_count != -1 && ex_change_count != ex->ex_change_count) {
/* Just detect if this expander phys phy change count changed,
* in order to determine if this expander originate BROADCAST,
* and do not update phy change count field in our structure.
*/
res = sas_find_bcast_phy(dev, &phy_id, 0, false);
if (phy_id != -1) {
*src_dev = dev;
ex->ex_change_count = ex_change_count;
SAS_DPRINTK("Expander phy change count has changed\n");
return res;
} else
SAS_DPRINTK("Expander phys DID NOT change\n");
}
list_for_each_entry(ch, &ex->children, siblings) {
if (ch->dev_type == EDGE_DEV || ch->dev_type == FANOUT_DEV) {
res = sas_find_bcast_dev(ch, src_dev);
if (src_dev)
return res;
}
}
out:
Expand All @@ -1700,24 +1734,26 @@ static void sas_unregister_ex_tree(struct domain_device *dev)
}

static void sas_unregister_devs_sas_addr(struct domain_device *parent,
int phy_id)
int phy_id, bool last)
{
struct expander_device *ex_dev = &parent->ex_dev;
struct ex_phy *phy = &ex_dev->ex_phy[phy_id];
struct domain_device *child, *n;

list_for_each_entry_safe(child, n, &ex_dev->children, siblings) {
if (SAS_ADDR(child->sas_addr) ==
SAS_ADDR(phy->attached_sas_addr)) {
if (child->dev_type == EDGE_DEV ||
child->dev_type == FANOUT_DEV)
sas_unregister_ex_tree(child);
else
sas_unregister_dev(child);
break;
if (last) {
list_for_each_entry_safe(child, n,
&ex_dev->children, siblings) {
if (SAS_ADDR(child->sas_addr) ==
SAS_ADDR(phy->attached_sas_addr)) {
if (child->dev_type == EDGE_DEV ||
child->dev_type == FANOUT_DEV)
sas_unregister_ex_tree(child);
else
sas_unregister_dev(child);
break;
}
}
sas_disable_routing(parent, phy->attached_sas_addr);
}
sas_disable_routing(parent, phy->attached_sas_addr);
memset(phy->attached_sas_addr, 0, SAS_ADDR_SIZE);
sas_port_delete_phy(phy->port, phy->phy);
if (phy->port->num_phys == 0)
Expand Down Expand Up @@ -1770,15 +1806,31 @@ static int sas_discover_new(struct domain_device *dev, int phy_id)
{
struct ex_phy *ex_phy = &dev->ex_dev.ex_phy[phy_id];
struct domain_device *child;
int res;
bool found = false;
int res, i;

SAS_DPRINTK("ex %016llx phy%d new device attached\n",
SAS_ADDR(dev->sas_addr), phy_id);
res = sas_ex_phy_discover(dev, phy_id);
if (res)
goto out;
/* to support the wide port inserted */
for (i = 0; i < dev->ex_dev.num_phys; i++) {
struct ex_phy *ex_phy_temp = &dev->ex_dev.ex_phy[i];
if (i == phy_id)
continue;
if (SAS_ADDR(ex_phy_temp->attached_sas_addr) ==
SAS_ADDR(ex_phy->attached_sas_addr)) {
found = true;
break;
}
}
if (found) {
sas_ex_join_wide_port(dev, phy_id);
return 0;
}
res = sas_ex_discover_devices(dev, phy_id);
if (res)
if (!res)
goto out;
list_for_each_entry(child, &dev->ex_dev.children, siblings) {
if (SAS_ADDR(child->sas_addr) ==
Expand All @@ -1793,7 +1845,7 @@ static int sas_discover_new(struct domain_device *dev, int phy_id)
return res;
}

static int sas_rediscover_dev(struct domain_device *dev, int phy_id)
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];
Expand All @@ -1804,19 +1856,19 @@ static int sas_rediscover_dev(struct domain_device *dev, int phy_id)
switch (res) {
case SMP_RESP_NO_PHY:
phy->phy_state = PHY_NOT_PRESENT;
sas_unregister_devs_sas_addr(dev, phy_id);
sas_unregister_devs_sas_addr(dev, phy_id, last);
goto out; break;
case SMP_RESP_PHY_VACANT:
phy->phy_state = PHY_VACANT;
sas_unregister_devs_sas_addr(dev, phy_id);
sas_unregister_devs_sas_addr(dev, phy_id, last);
goto out; break;
case SMP_RESP_FUNC_ACC:
break;
}

if (SAS_ADDR(attached_sas_addr) == 0) {
phy->phy_state = PHY_EMPTY;
sas_unregister_devs_sas_addr(dev, phy_id);
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",
Expand All @@ -1828,12 +1880,27 @@ static int sas_rediscover_dev(struct domain_device *dev, int phy_id)
return res;
}

/**
* sas_rediscover - revalidate the domain.
* @dev:domain device to be detect.
* @phy_id: the phy id will be detected.
*
* NOTE: this process _must_ quit (return) as soon as any connection
* errors are encountered. Connection recovery is done elsewhere.
* Discover process only interrogates devices in order to discover the
* domain.For plugging out, we un-register the device only when it is
* the last phy in the port, for other phys in this port, we just delete it
* from the port.For inserting, we do discovery when it is the
* first phy,for other phys in this port, we add it to the port to
* forming the wide-port.
*/
static int sas_rediscover(struct domain_device *dev, const int phy_id)
{
struct expander_device *ex = &dev->ex_dev;
struct ex_phy *changed_phy = &ex->ex_phy[phy_id];
int res = 0;
int i;
bool last = true; /* is this the last phy of the port */

SAS_DPRINTK("ex %016llx phy%d originated BROADCAST(CHANGE)\n",
SAS_ADDR(dev->sas_addr), phy_id);
Expand All @@ -1848,13 +1915,13 @@ static int sas_rediscover(struct domain_device *dev, const int phy_id)
SAS_ADDR(changed_phy->attached_sas_addr)) {
SAS_DPRINTK("phy%d part of wide port with "
"phy%d\n", phy_id, i);
goto out;
last = false;
break;
}
}
res = sas_rediscover_dev(dev, phy_id);
res = sas_rediscover_dev(dev, phy_id, last);
} else
res = sas_discover_new(dev, phy_id);
out:
return res;
}

Expand All @@ -1881,7 +1948,7 @@ int sas_ex_revalidate_domain(struct domain_device *port_dev)

do {
phy_id = -1;
res = sas_find_bcast_phy(dev, &phy_id, i);
res = sas_find_bcast_phy(dev, &phy_id, i, true);
if (phy_id == -1)
break;
res = sas_rediscover(dev, phy_id);
Expand Down

0 comments on commit 19252de

Please sign in to comment.