scsi: libsas: fix memory leak in sas_smp_get_phy_events()
[pandora-kernel.git] / drivers / scsi / libsas / sas_expander.c
index 16ad97d..c984cfd 100644 (file)
@@ -192,13 +192,22 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id,
        phy->attached_sata_ps   = dr->attached_sata_ps;
        phy->attached_iproto = dr->iproto << 1;
        phy->attached_tproto = dr->tproto << 1;
-       memcpy(phy->attached_sas_addr, dr->attached_sas_addr, SAS_ADDR_SIZE);
+       /* help some expanders that fail to zero sas_address in the 'no
+        * device' case
+        */
+       if (phy->attached_dev_type == NO_DEVICE ||
+           phy->linkrate < SAS_LINK_RATE_1_5_GBPS)
+               memset(phy->attached_sas_addr, 0, SAS_ADDR_SIZE);
+       else
+               memcpy(phy->attached_sas_addr, dr->attached_sas_addr, SAS_ADDR_SIZE);
        phy->attached_phy_id = dr->attached_phy_id;
        phy->phy_change_count = dr->change_count;
        phy->routing_attr = dr->routing_attr;
        phy->virtual = dr->virtual;
        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.initiator_port_protocols = phy->attached_iproto;
        phy->phy->identify.target_port_protocols = phy->attached_tproto;
        phy->phy->identify.phy_identifier = phy_id;
@@ -329,6 +338,7 @@ static void ex_assign_report_general(struct domain_device *dev,
        dev->ex_dev.ex_change_count = be16_to_cpu(rg->change_count);
        dev->ex_dev.max_route_indexes = be16_to_cpu(rg->route_indexes);
        dev->ex_dev.num_phys = min(rg->num_phys, (u8)MAX_EXPANDER_PHYS);
+       dev->ex_dev.t2t_supp = rg->t2t_supp;
        dev->ex_dev.conf_route_table = rg->conf_route_table;
        dev->ex_dev.configuring = rg->configuring;
        memcpy(dev->ex_dev.enclosure_logical_id, rg->enclosure_logical_id, 8);
@@ -556,6 +566,7 @@ int sas_smp_get_phy_events(struct sas_phy *phy)
        phy->phy_reset_problem_count = scsi_to_u32(&resp[24]);
 
  out:
+       kfree(req);
        kfree(resp);
        return res;
 
@@ -751,7 +762,10 @@ static struct domain_device *sas_ex_discover_end_dev(
  out_list_del:
        sas_rphy_free(child->rphy);
        child->rphy = NULL;
+
+       spin_lock_irq(&parent->port->dev_list_lock);
        list_del(&child->dev_list_node);
+       spin_unlock_irq(&parent->port->dev_list_lock);
  out_free:
        sas_port_delete(phy->port);
  out_err:
@@ -761,7 +775,7 @@ static struct domain_device *sas_ex_discover_end_dev(
 }
 
 /* See if this phy is part of a wide port */
-static int sas_ex_join_wide_port(struct domain_device *parent, int phy_id)
+static bool sas_ex_join_wide_port(struct domain_device *parent, int phy_id)
 {
        struct ex_phy *phy = &parent->ex_dev.ex_phy[phy_id];
        int i;
@@ -777,11 +791,11 @@ static int sas_ex_join_wide_port(struct domain_device *parent, int phy_id)
                        sas_port_add_phy(ephy->port, phy->phy);
                        phy->port = ephy->port;
                        phy->phy_state = PHY_DEVICE_DISCOVERED;
-                       return 0;
+                       return true;
                }
        }
 
-       return -ENODEV;
+       return false;
 }
 
 static struct domain_device *sas_ex_discover_expander(
@@ -919,8 +933,7 @@ static int sas_ex_discover_dev(struct domain_device *dev, int phy_id)
                return res;
        }
 
-       res = sas_ex_join_wide_port(dev, phy_id);
-       if (!res) {
+       if (sas_ex_join_wide_port(dev, phy_id)) {
                SAS_DPRINTK("Attaching ex phy%d to wide port %016llx\n",
                            phy_id, SAS_ADDR(ex_phy->attached_sas_addr));
                return res;
@@ -965,8 +978,7 @@ static int sas_ex_discover_dev(struct domain_device *dev, int phy_id)
                        if (SAS_ADDR(ex->ex_phy[i].attached_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)
+                               if (sas_ex_join_wide_port(dev, i))
                                        SAS_DPRINTK("Attaching ex phy%d to wide port %016llx\n",
                                                    i, SAS_ADDR(ex->ex_phy[i].attached_sas_addr));
 
@@ -1133,15 +1145,17 @@ static void sas_print_parent_topology_bug(struct domain_device *child,
        };
        struct domain_device *parent = child->parent;
 
-       sas_printk("%s ex %016llx phy 0x%x <--> %s ex %016llx phy 0x%x "
-                  "has %c:%c routing link!\n",
+       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",
 
                   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],
@@ -1238,10 +1252,15 @@ static int sas_check_parent_topology(struct domain_device *child)
                                        sas_print_parent_topology_bug(child, parent_phy, child_phy);
                                        res = -ENODEV;
                                }
-                       } else if (parent_phy->routing_attr == TABLE_ROUTING &&
-                                  child_phy->routing_attr != SUBTRACTIVE_ROUTING) {
-                               sas_print_parent_topology_bug(child, parent_phy, child_phy);
-                               res = -ENODEV;
+                       } else if (parent_phy->routing_attr == TABLE_ROUTING) {
+                               if (child_phy->routing_attr == SUBTRACTIVE_ROUTING ||
+                                   (child_phy->routing_attr == TABLE_ROUTING &&
+                                    child_ex->t2t_supp && parent_ex->t2t_supp)) {
+                                       /* All good */;
+                               } else {
+                                       sas_print_parent_topology_bug(child, parent_phy, child_phy);
+                                       res = -ENODEV;
+                               }
                        }
                        break;
                case FANOUT_DEV:
@@ -1630,9 +1649,17 @@ static int sas_find_bcast_phy(struct domain_device *dev, int *phy_id,
                int phy_change_count = 0;
 
                res = sas_get_phy_change_count(dev, i, &phy_change_count);
-               if (res)
-                       goto out;
-               else if (phy_change_count != ex->ex_phy[i].phy_change_count) {
+               switch (res) {
+               case SMP_RESP_PHY_VACANT:
+               case SMP_RESP_NO_PHY:
+                       continue;
+               case SMP_RESP_FUNC_ACC:
+                       break;
+               default:
+                       return res;
+               }
+
+               if (phy_change_count != ex->ex_phy[i].phy_change_count) {
                        if (update)
                                ex->ex_phy[i].phy_change_count =
                                        phy_change_count;
@@ -1640,8 +1667,7 @@ static int sas_find_bcast_phy(struct domain_device *dev, int *phy_id,
                        return 0;
                }
        }
-out:
-       return res;
+       return 0;
 }
 
 static int sas_get_ex_change_count(struct domain_device *dev, int *ecc)
@@ -1729,7 +1755,7 @@ out:
        return res;
 }
 
-static void sas_unregister_ex_tree(struct domain_device *dev)
+static void sas_unregister_ex_tree(struct asd_sas_port *port, struct domain_device *dev)
 {
        struct expander_device *ex = &dev->ex_dev;
        struct domain_device *child, *n;
@@ -1738,11 +1764,11 @@ static void sas_unregister_ex_tree(struct domain_device *dev)
                child->gone = 1;
                if (child->dev_type == EDGE_DEV ||
                    child->dev_type == FANOUT_DEV)
-                       sas_unregister_ex_tree(child);
+                       sas_unregister_ex_tree(port, child);
                else
-                       sas_unregister_dev(child);
+                       sas_unregister_dev(port, child);
        }
-       sas_unregister_dev(dev);
+       sas_unregister_dev(port, dev);
 }
 
 static void sas_unregister_devs_sas_addr(struct domain_device *parent,
@@ -1759,9 +1785,9 @@ static void sas_unregister_devs_sas_addr(struct domain_device *parent,
                                child->gone = 1;
                                if (child->dev_type == EDGE_DEV ||
                                    child->dev_type == FANOUT_DEV)
-                                       sas_unregister_ex_tree(child);
+                                       sas_unregister_ex_tree(parent->port, child);
                                else
-                                       sas_unregister_dev(child);
+                                       sas_unregister_dev(parent->port, child);
                                break;
                        }
                }
@@ -1822,32 +1848,20 @@ 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;
-       bool found = false;
-       int res, i;
+       int res;
 
        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 res;
+
+       if (sas_ex_join_wide_port(dev, phy_id))
                return 0;
-       }
+
        res = sas_ex_discover_devices(dev, phy_id);
-       if (!res)
-               goto out;
+       if (res)
+               return res;
        list_for_each_entry(child, &dev->ex_dev.children, siblings) {
                if (SAS_ADDR(child->sas_addr) ==
                    SAS_ADDR(ex_phy->attached_sas_addr)) {
@@ -1857,7 +1871,6 @@ static int sas_discover_new(struct domain_device *dev, int phy_id)
                        break;
                }
        }
-out:
        return res;
 }
 
@@ -1956,9 +1969,7 @@ int sas_ex_revalidate_domain(struct domain_device *port_dev)
        struct domain_device *dev = NULL;
 
        res = sas_find_bcast_dev(port_dev, &dev);
-       if (res)
-               goto out;
-       if (dev) {
+       while (res == 0 && dev) {
                struct expander_device *ex = &dev->ex_dev;
                int i = 0, phy_id;
 
@@ -1970,8 +1981,10 @@ int sas_ex_revalidate_domain(struct domain_device *port_dev)
                        res = sas_rediscover(dev, phy_id);
                        i = phy_id + 1;
                } while (i < ex->num_phys);
+
+               dev = NULL;
+               res = sas_find_bcast_dev(port_dev, &dev);
        }
-out:
        return res;
 }