Merge branch 'topic/pcm-estrpipe-in-pm' into for-linus
[pandora-kernel.git] / drivers / scsi / libsas / sas_expander.c
index 54fa1e4..b338195 100644 (file)
@@ -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;
                }
@@ -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;
@@ -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;
@@ -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;
                }
@@ -1653,31 +1666,52 @@ out:
        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:
@@ -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)
@@ -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) ==
@@ -1793,7 +1845,7 @@ out:
        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];
@@ -1804,11 +1856,11 @@ 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;
@@ -1816,7 +1868,7 @@ static int sas_rediscover_dev(struct domain_device *dev, int phy_id)
 
        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",
@@ -1828,12 +1880,27 @@ out:
        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);
@@ -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;
 }
 
@@ -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);