[SCSI] libsas: let libata recover links that fail to transmit initial sig-fis
authorDan Williams <dan.j.williams@intel.com>
Fri, 13 Jan 2012 01:57:35 +0000 (17:57 -0800)
committerJames Bottomley <JBottomley@Parallels.com>
Wed, 29 Feb 2012 21:33:02 +0000 (15:33 -0600)
libsas fails to discover all sata devices in the domain.  If a device fails
negotiation and does not transmit a signature fis the link needs recovery.
libata already understands how to manage slow to come up links, so treat these
conditions as ata device attach events for the purposes of creating an
ata_port.  This allows libata to manage retrying link bring up.

Rediscovery is modified to be careful about checking changes in dev_type.  It
looks like libsas leaks old devices if the sas address changes, but that's a
fix for another patch.

Acked-by: Jack Wang <jack_wang@usish.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Signed-off-by: James Bottomley <JBottomley@Parallels.com>
drivers/scsi/libsas/sas_ata.c
drivers/scsi/libsas/sas_discover.c
drivers/scsi/libsas/sas_expander.c
drivers/scsi/libsas/sas_internal.h
include/scsi/sas.h
include/scsi/sas_ata.h

index ba1ebfe991d7d0232362ac4ba29739b83cccd458..25008a42412ff142cd183b565ba0b8a85378f9a3 100644 (file)
@@ -278,26 +278,84 @@ static struct sas_internal *dev_to_sas_internal(struct domain_device *dev)
        return to_sas_internal(dev->port->ha->core.shost->transportt);
 }
 
+static void sas_get_ata_command_set(struct domain_device *dev);
+
+int sas_get_ata_info(struct domain_device *dev, struct ex_phy *phy)
+{
+       if (phy->attached_tproto & SAS_PROTOCOL_STP)
+               dev->tproto = phy->attached_tproto;
+       if (phy->attached_sata_dev)
+               dev->tproto |= SATA_DEV;
+
+       if (phy->attached_dev_type == SATA_PENDING)
+               dev->dev_type = SATA_PENDING;
+       else {
+               int res;
+
+               dev->dev_type = SATA_DEV;
+               res = sas_get_report_phy_sata(dev->parent, phy->phy_id,
+                                             &dev->sata_dev.rps_resp);
+               if (res) {
+                       SAS_DPRINTK("report phy sata to %016llx:0x%x returned "
+                                   "0x%x\n", SAS_ADDR(dev->parent->sas_addr),
+                                   phy->phy_id, res);
+                       return res;
+               }
+               memcpy(dev->frame_rcvd, &dev->sata_dev.rps_resp.rps.fis,
+                      sizeof(struct dev_to_host_fis));
+               /* TODO switch to ata_dev_classify() */
+               sas_get_ata_command_set(dev);
+       }
+       return 0;
+}
+
+static int sas_ata_clear_pending(struct domain_device *dev, struct ex_phy *phy)
+{
+       int res;
+
+       /* we weren't pending, so successfully end the reset sequence now */
+       if (dev->dev_type != SATA_PENDING)
+               return 1;
+
+       /* hmmm, if this succeeds do we need to repost the domain_device to the
+        * lldd so it can pick up new parameters?
+        */
+       res = sas_get_ata_info(dev, phy);
+       if (res)
+               return 0; /* retry */
+       else
+               return 1;
+}
+
 static int smp_ata_check_ready(struct ata_link *link)
 {
        int res;
-       u8 addr[8];
        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_get_local_phy(dev);
+       struct ex_phy *ex_phy = &ex_dev->ex_dev.ex_phy[phy->number];
 
-       res = sas_get_phy_attached_sas_addr(ex_dev, phy->number, addr);
+       res = sas_ex_phy_discover(ex_dev, phy->number);
        sas_put_local_phy(phy);
+
        /* break the wait early if the expander is unreachable,
         * otherwise keep polling
         */
        if (res == -ECOMM)
                return res;
-       if (res != SMP_RESP_FUNC_ACC || SAS_ADDR(addr) == 0)
+       if (res != SMP_RESP_FUNC_ACC)
                return 0;
-       else
-               return 1;
+
+       switch (ex_phy->attached_dev_type) {
+       case SATA_PENDING:
+               return 0;
+       case SAS_END_DEV:
+               if (ex_phy->attached_sata_dev)
+                       return sas_ata_clear_pending(dev, ex_phy);
+       default:
+               return -ENODEV;
+       }
 }
 
 static int local_ata_check_ready(struct ata_link *link)
@@ -584,6 +642,9 @@ static void sas_get_ata_command_set(struct domain_device *dev)
        struct dev_to_host_fis *fis =
                (struct dev_to_host_fis *) dev->frame_rcvd;
 
+       if (dev->dev_type == SATA_PENDING)
+               return;
+
        if ((fis->sector_count == 1 && /* ATA */
             fis->lbal         == 1 &&
             fis->lbam         == 0 &&
index c1ac99d25f5ecaf84616cee50d5248b6c2130e6a..8bcfcaa7b2e1394b63f962a204157459ca3b9a6e 100644 (file)
@@ -48,6 +48,7 @@ void sas_init_dev(struct domain_device *dev)
        case SATA_DEV:
        case SATA_PM:
        case SATA_PM_PORT:
+       case SATA_PENDING:
                INIT_LIST_HEAD(&dev->sata_dev.children);
                break;
        default:
index 4b2ecd35dc5ada1172200eca57e24b260a7c5f33..7e2d3c4c61713180cd7d4f3ba99b1edc92b1ba32 100644 (file)
@@ -183,13 +183,27 @@ static char sas_route_char(struct domain_device *dev, struct ex_phy *phy)
        }
 }
 
-static void sas_set_ex_phy(struct domain_device *dev, int phy_id,
-                          void *disc_resp)
+static enum sas_dev_type to_dev_type(struct discover_resp *dr)
 {
+       /* This is detecting a failure to transmit initial dev to host
+        * FIS as described in section J.5 of sas-2 r16
+        */
+       if (dr->attached_dev_type == NO_DEVICE && dr->attached_sata_dev &&
+           dr->linkrate >= SAS_LINK_RATE_1_5_GBPS)
+               return SATA_PENDING;
+       else
+               return dr->attached_dev_type;
+}
+
+static void sas_set_ex_phy(struct domain_device *dev, int phy_id, void *rsp)
+{
+       enum sas_dev_type dev_type;
+       enum sas_linkrate linkrate;
+       u8 sas_addr[SAS_ADDR_SIZE];
+       struct smp_resp *resp = rsp;
+       struct discover_resp *dr = &resp->disc;
        struct expander_device *ex = &dev->ex_dev;
        struct ex_phy *phy = &ex->ex_phy[phy_id];
-       struct smp_resp *resp = disc_resp;
-       struct discover_resp *dr = &resp->disc;
        struct sas_rphy *rphy = dev->rphy;
        bool new_phy = !phy->phy;
        char *type;
@@ -213,8 +227,13 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id,
                break;
        }
 
+       /* check if anything important changed to squelch debug */
+       dev_type = phy->attached_dev_type;
+       linkrate  = phy->linkrate;
+       memcpy(sas_addr, phy->attached_sas_addr, SAS_ADDR_SIZE);
+
+       phy->attached_dev_type = to_dev_type(dr);
        phy->phy_id = phy_id;
-       phy->attached_dev_type = dr->attached_dev_type;
        phy->linkrate = dr->linkrate;
        phy->attached_sata_host = dr->attached_sata_host;
        phy->attached_sata_dev  = dr->attached_sata_dev;
@@ -229,7 +248,7 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id,
        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.device_type = dr->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;
@@ -246,6 +265,9 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id,
                }
 
        switch (phy->attached_dev_type) {
+       case SATA_PENDING:
+               type = "stp pending";
+               break;
        case NO_DEVICE:
                type = "no device";
                break;
@@ -270,6 +292,16 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id,
                type = "unknown";
        }
 
+       /* this routine is polled by libata error recovery so filter
+        * unimportant messages
+        */
+       if (new_phy || phy->attached_dev_type != dev_type ||
+           phy->linkrate != linkrate ||
+           SAS_ADDR(phy->attached_sas_addr) != SAS_ADDR(sas_addr))
+               /* pass */;
+       else
+               return;
+
        SAS_DPRINTK("ex %016llx phy%02d:%c:%X attached: %016llx (%s)\n",
                    SAS_ADDR(dev->sas_addr), phy->phy_id,
                    sas_route_char(dev, phy), phy->linkrate,
@@ -304,50 +336,25 @@ struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id)
 static int sas_ex_phy_discover_helper(struct domain_device *dev, u8 *disc_req,
                                      u8 *disc_resp, int single)
 {
-       struct domain_device *ata_dev = sas_ex_to_ata(dev, single);
-       int i, res;
+       struct discover_resp *dr;
+       int res;
 
        disc_req[9] = single;
-       for (i = 1 ; i < 3; i++) {
-               struct discover_resp *dr;
 
-               res = smp_execute_task(dev, disc_req, DISCOVER_REQ_SIZE,
-                                      disc_resp, DISCOVER_RESP_SIZE);
-               if (res)
-                       return res;
-               dr = &((struct smp_resp *)disc_resp)->disc;
-               if (memcmp(dev->sas_addr, dr->attached_sas_addr,
-                         SAS_ADDR_SIZE) == 0) {
-                       sas_printk("Found loopback topology, just ignore it!\n");
-                       return 0;
-               }
-
-               /* This is detecting a failure to transmit initial
-                * dev to host FIS as described in section J.5 of
-                * sas-2 r16
-                */
-               if (!(dr->attached_dev_type == 0 &&
-                     dr->attached_sata_dev))
-                       break;
-
-               /* In order to generate the dev to host FIS, we send a
-                * link reset to the expander port.  If a device was
-                * previously detected on this port we ask libata to
-                * manage the reset and link recovery.
-                */
-               if (ata_dev) {
-                       sas_ata_schedule_reset(ata_dev);
-                       break;
-               }
-               sas_smp_phy_control(dev, single, PHY_FUNC_LINK_RESET, NULL);
-               /* Wait for the reset to trigger the negotiation */
-               msleep(500);
+       res = smp_execute_task(dev, disc_req, DISCOVER_REQ_SIZE,
+                              disc_resp, DISCOVER_RESP_SIZE);
+       if (res)
+               return res;
+       dr = &((struct smp_resp *)disc_resp)->disc;
+       if (memcmp(dev->sas_addr, dr->attached_sas_addr, SAS_ADDR_SIZE) == 0) {
+               sas_printk("Found loopback topology, just ignore it!\n");
+               return 0;
        }
        sas_set_ex_phy(dev, single, disc_resp);
        return 0;
 }
 
-static int sas_ex_phy_discover(struct domain_device *dev, int single)
+int sas_ex_phy_discover(struct domain_device *dev, int single)
 {
        struct expander_device *ex = &dev->ex_dev;
        int  res = 0;
@@ -652,9 +659,8 @@ int sas_smp_get_phy_events(struct sas_phy *phy)
 #define RPS_REQ_SIZE  16
 #define RPS_RESP_SIZE 60
 
-static int sas_get_report_phy_sata(struct domain_device *dev,
-                                         int phy_id,
-                                         struct smp_resp *rps_resp)
+int sas_get_report_phy_sata(struct domain_device *dev, int phy_id,
+                           struct smp_resp *rps_resp)
 {
        int res;
        u8 *rps_req = alloc_smp_req(RPS_REQ_SIZE);
@@ -764,21 +770,9 @@ static struct domain_device *sas_ex_discover_end_dev(
 
 #ifdef CONFIG_SCSI_SAS_ATA
        if ((phy->attached_tproto & SAS_PROTOCOL_STP) || phy->attached_sata_dev) {
-               child->dev_type = SATA_DEV;
-               if (phy->attached_tproto & SAS_PROTOCOL_STP)
-                       child->tproto = phy->attached_tproto;
-               if (phy->attached_sata_dev)
-                       child->tproto |= SATA_DEV;
-               res = sas_get_report_phy_sata(parent, phy_id,
-                                             &child->sata_dev.rps_resp);
-               if (res) {
-                       SAS_DPRINTK("report phy sata to %016llx:0x%x returned "
-                                   "0x%x\n", SAS_ADDR(parent->sas_addr),
-                                   phy_id, res);
+               res = sas_get_ata_info(child, phy);
+               if (res)
                        goto out_free;
-               }
-               memcpy(child->frame_rcvd, &child->sata_dev.rps_resp.rps.fis,
-                      sizeof(struct dev_to_host_fis));
 
                rphy = sas_end_device_alloc(phy->port);
                if (unlikely(!rphy))
@@ -993,7 +987,8 @@ static int sas_ex_discover_dev(struct domain_device *dev, int phy_id)
 
        if (ex_phy->attached_dev_type != SAS_END_DEV &&
            ex_phy->attached_dev_type != FANOUT_DEV &&
-           ex_phy->attached_dev_type != EDGE_DEV) {
+           ex_phy->attached_dev_type != EDGE_DEV &&
+           ex_phy->attached_dev_type != SATA_PENDING) {
                SAS_DPRINTK("unknown device type(0x%x) attached to ex %016llx "
                            "phy 0x%x\n", ex_phy->attached_dev_type,
                            SAS_ADDR(dev->sas_addr),
@@ -1019,6 +1014,7 @@ static int sas_ex_discover_dev(struct domain_device *dev, int phy_id)
 
        switch (ex_phy->attached_dev_type) {
        case SAS_END_DEV:
+       case SATA_PENDING:
                child = sas_ex_discover_end_dev(dev, phy_id);
                break;
        case FANOUT_DEV:
@@ -1688,8 +1684,8 @@ static int sas_get_phy_change_count(struct domain_device *dev,
        return res;
 }
 
-int sas_get_phy_attached_sas_addr(struct domain_device *dev, int phy_id,
-                                 u8 *attached_sas_addr)
+static int sas_get_phy_attached_dev(struct domain_device *dev, int phy_id,
+                                   u8 *sas_addr, enum sas_dev_type *type)
 {
        int res;
        struct smp_resp *disc_resp;
@@ -1701,10 +1697,11 @@ int sas_get_phy_attached_sas_addr(struct domain_device *dev, int phy_id,
        dr = &disc_resp->disc;
 
        res = sas_get_phy_discover(dev, phy_id, disc_resp);
-       if (!res) {
-               memcpy(attached_sas_addr,disc_resp->disc.attached_sas_addr,8);
-               if (dr->attached_dev_type == 0)
-                       memset(attached_sas_addr, 0, 8);
+       if (res == 0) {
+               memcpy(sas_addr, disc_resp->disc.attached_sas_addr, 8);
+               *type = to_dev_type(dr);
+               if (*type == 0)
+                       memset(sas_addr, 0, 8);
        }
        kfree(disc_resp);
        return res;
@@ -1953,39 +1950,62 @@ out:
        return res;
 }
 
+static bool dev_type_flutter(enum sas_dev_type new, enum sas_dev_type old)
+{
+       if (old == new)
+               return true;
+
+       /* treat device directed resets as flutter, if we went
+        * SAS_END_DEV to SATA_PENDING the link needs recovery
+        */
+       if ((old == SATA_PENDING && new == SAS_END_DEV) ||
+           (old == SAS_END_DEV && new == SATA_PENDING))
+               return true;
+
+       return false;
+}
+
 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];
-       u8 attached_sas_addr[8];
+       enum sas_dev_type type = NO_DEVICE;
+       u8 sas_addr[8];
        int res;
 
-       res = sas_get_phy_attached_sas_addr(dev, phy_id, attached_sas_addr);
+       res = sas_get_phy_attached_dev(dev, phy_id, sas_addr, &type);
        switch (res) {
        case SMP_RESP_NO_PHY:
                phy->phy_state = PHY_NOT_PRESENT;
                sas_unregister_devs_sas_addr(dev, phy_id, last);
-               goto out; break;
+               return res;
        case SMP_RESP_PHY_VACANT:
                phy->phy_state = PHY_VACANT;
                sas_unregister_devs_sas_addr(dev, phy_id, last);
-               goto out; break;
+               return res;
        case SMP_RESP_FUNC_ACC:
                break;
        }
 
-       if (SAS_ADDR(attached_sas_addr) == 0) {
+       if (SAS_ADDR(sas_addr) == 0) {
                phy->phy_state = PHY_EMPTY;
                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",
-                           SAS_ADDR(dev->sas_addr), phy_id);
+               return res;
+       } else if (SAS_ADDR(sas_addr) == SAS_ADDR(phy->attached_sas_addr) &&
+                  dev_type_flutter(type, phy->attached_dev_type)) {
+               struct domain_device *ata_dev = sas_ex_to_ata(dev, phy_id);
+               char *action = "";
+
                sas_ex_phy_discover(dev, phy_id);
-       } else
-               res = sas_discover_new(dev, phy_id);
-out:
-       return res;
+
+               if (ata_dev && phy->attached_dev_type == SATA_PENDING)
+                       action = ", needs recovery";
+               SAS_DPRINTK("ex %016llx phy 0x%x broadcast flutter%s\n",
+                           SAS_ADDR(dev->sas_addr), phy_id, action);
+               return res;
+       }
+
+       return sas_discover_new(dev, phy_id);
 }
 
 /**
index 7818c4673c3a3889d95067f78e89d2aa2210ab57..e028d7a442025bf8ca2b4289ef78b3f8f026b7aa 100644 (file)
@@ -91,8 +91,9 @@ 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,
-                                 u8 *attached_sas_addr);
+int sas_ex_phy_discover(struct domain_device *dev, int single);
+int sas_get_report_phy_sata(struct domain_device *dev, int phy_id,
+                           struct smp_resp *rps_resp);
 int sas_try_ata_reset(struct asd_sas_phy *phy);
 void sas_hae_reset(struct work_struct *work);
 
@@ -122,6 +123,7 @@ static inline void sas_fill_in_rphy(struct domain_device *dev,
        case SATA_DEV:
                /* FIXME: need sata device type */
        case SAS_END_DEV:
+       case SATA_PENDING:
                rphy->identify.device_type = SAS_END_DEVICE;
                break;
        case EDGE_DEV:
index 3673d685e6ad6fd0323f95d039b72230cd603055..a577a833603db88adbeb11e5b91964a2fb881483 100644 (file)
@@ -89,8 +89,7 @@ enum sas_oob_mode {
        SAS_OOB_MODE
 };
 
-/* See sas_discover.c if you plan on changing these.
- */
+/* See sas_discover.c if you plan on changing these */
 enum sas_dev_type {
        NO_DEVICE   = 0,          /* protocol */
        SAS_END_DEV = 1,          /* protocol */
@@ -100,6 +99,7 @@ enum sas_dev_type {
        SATA_DEV    = 5,
        SATA_PM     = 7,
        SATA_PM_PORT= 8,
+       SATA_PENDING  = 9,
 };
 
 enum sas_protocol {
index cb724fd010f6c47fb38040a6796167d4f19304de..0ca2f8a6bc602958395e1ead03c982530ee958cf 100644 (file)
 static inline int dev_is_sata(struct domain_device *dev)
 {
        return dev->dev_type == SATA_DEV || dev->dev_type == SATA_PM ||
-              dev->dev_type == SATA_PM_PORT;
+              dev->dev_type == SATA_PM_PORT || dev->dev_type == SATA_PENDING;
 }
 
+int sas_get_ata_info(struct domain_device *dev, struct ex_phy *phy);
 int sas_ata_init_host_and_port(struct domain_device *found_dev,
                               struct scsi_target *starget);
 
@@ -82,6 +83,11 @@ static inline void sas_ata_schedule_reset(struct domain_device *dev)
 static inline void sas_ata_wait_eh(struct domain_device *dev)
 {
 }
+
+static inline int sas_get_ata_info(struct domain_device *dev, struct ex_phy *phy)
+{
+       return 0;
+}
 #endif
 
 #endif /* _SAS_ATA_H_ */