[SCSI] aic94xx: plumb in I_T_nexus_reset task management function
authorJames Bottomley <James.Bottomley@HansenPartnership.com>
Sun, 24 Feb 2008 05:37:26 +0000 (23:37 -0600)
committerJames Bottomley <James.Bottomley@HansenPartnership.com>
Sun, 24 Feb 2008 05:52:46 +0000 (23:52 -0600)
Currently aic94xx has no exported I_T_nexus_reset function.  This is a
bit of a huge problem, since sas_ata relies on this function to
perform an ATA phy reset and also it means that if abort fails, we
really have no bigger hammer to hit everything with.

Plumb in the I_T_nexus_reset by quiescing the sequencer, sending the
correct phy reset (link for ATA and hard for SAS) and then carefully
resuming the sequencer again.

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
drivers/scsi/aic94xx/aic94xx.h
drivers/scsi/aic94xx/aic94xx_init.c
drivers/scsi/aic94xx/aic94xx_tmf.c

index 32f513b1b78ab314253e7ddceb4117aa5e40fcad..eb8efdcefe482e0bf8997b566a72db30d76027ba 100644 (file)
@@ -102,6 +102,7 @@ int  asd_abort_task_set(struct domain_device *, u8 *lun);
 int  asd_clear_aca(struct domain_device *, u8 *lun);
 int  asd_clear_task_set(struct domain_device *, u8 *lun);
 int  asd_lu_reset(struct domain_device *, u8 *lun);
+int  asd_I_T_nexus_reset(struct domain_device *dev);
 int  asd_query_task(struct sas_task *);
 
 /* ---------- Adapter and Port management ---------- */
index 5d761eb67442356fa50e360e4cde33af360c8612..88d1e731b65e15b04e5e1a71f281ba1d2035a8d7 100644 (file)
@@ -1003,7 +1003,7 @@ static struct sas_domain_function_template aic94xx_transport_functions = {
        .lldd_abort_task_set    = asd_abort_task_set,
        .lldd_clear_aca         = asd_clear_aca,
        .lldd_clear_task_set    = asd_clear_task_set,
-       .lldd_I_T_nexus_reset   = NULL,
+       .lldd_I_T_nexus_reset   = asd_I_T_nexus_reset,
        .lldd_lu_reset          = asd_lu_reset,
        .lldd_query_task        = asd_query_task,
 
index 144f5ad20453bbc56ec010d8e870c881c0ef0fd2..d684c7432e63af41074255d0bc645deb47cf61a6 100644 (file)
@@ -140,8 +140,14 @@ int asd_clear_nexus_port(struct asd_sas_port *port)
        CLEAR_NEXUS_POST;
 }
 
-#if 0
-static int asd_clear_nexus_I_T(struct domain_device *dev)
+enum clear_nexus_phase {
+       NEXUS_PHASE_PRE,
+       NEXUS_PHASE_POST,
+       NEXUS_PHASE_RESUME,
+};
+
+static int asd_clear_nexus_I_T(struct domain_device *dev,
+                              enum clear_nexus_phase phase)
 {
        struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha;
        struct asd_ascb *ascb;
@@ -150,12 +156,56 @@ static int asd_clear_nexus_I_T(struct domain_device *dev)
 
        CLEAR_NEXUS_PRE;
        scb->clear_nexus.nexus = NEXUS_I_T;
-       scb->clear_nexus.flags = SEND_Q | EXEC_Q | NOTINQ;
+       switch (phase) {
+       case NEXUS_PHASE_PRE:
+               scb->clear_nexus.flags = EXEC_Q | SUSPEND_TX;
+               break;
+       case NEXUS_PHASE_POST:
+               scb->clear_nexus.flags = SEND_Q | NOTINQ;
+               break;
+       case NEXUS_PHASE_RESUME:
+               scb->clear_nexus.flags = RESUME_TX;
+       }
        scb->clear_nexus.conn_handle = cpu_to_le16((u16)(unsigned long)
                                                   dev->lldd_dev);
        CLEAR_NEXUS_POST;
 }
-#endif
+
+int asd_I_T_nexus_reset(struct domain_device *dev)
+{
+       int res, tmp_res, i;
+       struct sas_phy *phy = sas_find_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 ||
+                         (dev->tproto & SAS_PROTOCOL_STP)) ? 0 : 1;
+
+       asd_clear_nexus_I_T(dev, NEXUS_PHASE_PRE);
+       /* send a hard reset */
+       ASD_DPRINTK("sending %s reset to %s\n",
+                   reset_type ? "hard" : "soft", phy->dev.bus_id);
+       res = sas_phy_reset(phy, reset_type);
+       if (res == TMF_RESP_FUNC_COMPLETE) {
+               /* wait for the maximum settle time */
+               msleep(500);
+               /* clear all outstanding commands (keep nexus suspended) */
+               asd_clear_nexus_I_T(dev, NEXUS_PHASE_POST);
+       }
+       for (i = 0 ; i < 3; i++) {
+               tmp_res = asd_clear_nexus_I_T(dev, NEXUS_PHASE_RESUME);
+               if (tmp_res == TC_RESUME)
+                       return res;
+               msleep(500);
+       }
+
+       /* This is a bit of a problem:  the sequencer is still suspended
+        * and is refusing to resume.  Hope it will resume on a bigger hammer
+        * or the disk is lost */
+       dev_printk(KERN_ERR, &phy->dev,
+                  "Failed to resume nexus after reset 0x%x\n", tmp_res);
+
+       return TMF_RESP_FUNC_FAILED;
+}
 
 static int asd_clear_nexus_I_T_L(struct domain_device *dev, u8 *lun)
 {