hisi_sas: Add tmf methods
authorJohn Garry <john.garry@huawei.com>
Tue, 17 Nov 2015 16:50:56 +0000 (00:50 +0800)
committerMartin K. Petersen <martin.petersen@oracle.com>
Thu, 26 Nov 2015 03:13:07 +0000 (22:13 -0500)
Add function methods for tmf's.

Signed-off-by: John Garry <john.garry@huawei.com>
Reviewed-by: Arnd Bergmann <arnd@arndb.de>
Reviewed-by: Hannes Reinecke <hare@suse.de>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
drivers/scsi/hisi_sas/hisi_sas_main.c

index 63ebaf3a9263c21553021b1b159a43bc111f63b8..4ee90eb55a0f6acb7c1db7d8ed05c87c7c1bae65 100644 (file)
@@ -553,6 +553,309 @@ static int hisi_sas_queue_command(struct sas_task *task, gfp_t gfp_flags)
 }
 
 
+static void hisi_sas_task_done(struct sas_task *task)
+{
+       if (!del_timer(&task->slow_task->timer))
+               return;
+       complete(&task->slow_task->completion);
+}
+
+static void hisi_sas_tmf_timedout(unsigned long data)
+{
+       struct sas_task *task = (struct sas_task *)data;
+
+       task->task_state_flags |= SAS_TASK_STATE_ABORTED;
+       complete(&task->slow_task->completion);
+}
+
+#define TASK_TIMEOUT 20
+#define TASK_RETRY 3
+static int hisi_sas_exec_internal_tmf_task(struct domain_device *device,
+                                          void *parameter, u32 para_len,
+                                          struct hisi_sas_tmf_task *tmf)
+{
+       struct hisi_sas_device *sas_dev = device->lldd_dev;
+       struct hisi_hba *hisi_hba = sas_dev->hisi_hba;
+       struct device *dev = &hisi_hba->pdev->dev;
+       struct sas_task *task;
+       int res, retry;
+
+       for (retry = 0; retry < TASK_RETRY; retry++) {
+               task = sas_alloc_slow_task(GFP_KERNEL);
+               if (!task)
+                       return -ENOMEM;
+
+               task->dev = device;
+               task->task_proto = device->tproto;
+
+               memcpy(&task->ssp_task, parameter, para_len);
+               task->task_done = hisi_sas_task_done;
+
+               task->slow_task->timer.data = (unsigned long) task;
+               task->slow_task->timer.function = hisi_sas_tmf_timedout;
+               task->slow_task->timer.expires = jiffies + TASK_TIMEOUT*HZ;
+               add_timer(&task->slow_task->timer);
+
+               res = hisi_sas_task_exec(task, GFP_KERNEL, 1, tmf);
+
+               if (res) {
+                       del_timer(&task->slow_task->timer);
+                       dev_err(dev, "abort tmf: executing internal task failed: %d\n",
+                               res);
+                       goto ex_err;
+               }
+
+               wait_for_completion(&task->slow_task->completion);
+               res = TMF_RESP_FUNC_FAILED;
+               /* Even TMF timed out, return direct. */
+               if ((task->task_state_flags & SAS_TASK_STATE_ABORTED)) {
+                       if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) {
+                               dev_err(dev, "abort tmf: TMF task[%d] timeout\n",
+                                       tmf->tag_of_task_to_be_managed);
+                               if (task->lldd_task) {
+                                       struct hisi_sas_slot *slot =
+                                               task->lldd_task;
+
+                                       hisi_sas_slot_task_free(hisi_hba,
+                                                               task, slot);
+                               }
+
+                               goto ex_err;
+                       }
+               }
+
+               if (task->task_status.resp == SAS_TASK_COMPLETE &&
+                   task->task_status.stat == SAM_STAT_GOOD) {
+                       res = TMF_RESP_FUNC_COMPLETE;
+                       break;
+               }
+
+               if (task->task_status.resp == SAS_TASK_COMPLETE &&
+                     task->task_status.stat == SAS_DATA_UNDERRUN) {
+                       /* no error, but return the number of bytes of
+                        * underrun
+                        */
+                       dev_warn(dev, "abort tmf: task to dev %016llx "
+                                "resp: 0x%x sts 0x%x underrun\n",
+                                SAS_ADDR(device->sas_addr),
+                                task->task_status.resp,
+                                task->task_status.stat);
+                       res = task->task_status.residual;
+                       break;
+               }
+
+               if (task->task_status.resp == SAS_TASK_COMPLETE &&
+                       task->task_status.stat == SAS_DATA_OVERRUN) {
+                       dev_warn(dev, "abort tmf: blocked task error\n");
+                       res = -EMSGSIZE;
+                       break;
+               }
+
+               dev_warn(dev, "abort tmf: task to dev "
+                        "%016llx resp: 0x%x status 0x%x\n",
+                        SAS_ADDR(device->sas_addr), task->task_status.resp,
+                        task->task_status.stat);
+               sas_free_task(task);
+               task = NULL;
+       }
+ex_err:
+       WARN_ON(retry == TASK_RETRY);
+       sas_free_task(task);
+       return res;
+}
+
+static int hisi_sas_debug_issue_ssp_tmf(struct domain_device *device,
+                               u8 *lun, struct hisi_sas_tmf_task *tmf)
+{
+       struct sas_ssp_task ssp_task;
+
+       if (!(device->tproto & SAS_PROTOCOL_SSP))
+               return TMF_RESP_FUNC_ESUPP;
+
+       memcpy(ssp_task.LUN, lun, 8);
+
+       return hisi_sas_exec_internal_tmf_task(device, &ssp_task,
+                               sizeof(ssp_task), tmf);
+}
+
+static int hisi_sas_abort_task(struct sas_task *task)
+{
+       struct scsi_lun lun;
+       struct hisi_sas_tmf_task tmf_task;
+       struct domain_device *device = task->dev;
+       struct hisi_sas_device *sas_dev = device->lldd_dev;
+       struct hisi_hba *hisi_hba = dev_to_hisi_hba(task->dev);
+       struct device *dev = &hisi_hba->pdev->dev;
+       int rc = TMF_RESP_FUNC_FAILED;
+       unsigned long flags;
+
+       if (!sas_dev) {
+               dev_warn(dev, "Device has been removed\n");
+               return TMF_RESP_FUNC_FAILED;
+       }
+
+       spin_lock_irqsave(&task->task_state_lock, flags);
+       if (task->task_state_flags & SAS_TASK_STATE_DONE) {
+               spin_unlock_irqrestore(&task->task_state_lock, flags);
+               rc = TMF_RESP_FUNC_COMPLETE;
+               goto out;
+       }
+
+       spin_unlock_irqrestore(&task->task_state_lock, flags);
+       sas_dev->dev_status = HISI_SAS_DEV_EH;
+       if (task->lldd_task && task->task_proto & SAS_PROTOCOL_SSP) {
+               struct scsi_cmnd *cmnd = task->uldd_task;
+               struct hisi_sas_slot *slot = task->lldd_task;
+               u32 tag = slot->idx;
+
+               int_to_scsilun(cmnd->device->lun, &lun);
+               tmf_task.tmf = TMF_ABORT_TASK;
+               tmf_task.tag_of_task_to_be_managed = cpu_to_le16(tag);
+
+               rc = hisi_sas_debug_issue_ssp_tmf(task->dev, lun.scsi_lun,
+                                                 &tmf_task);
+
+               /* if successful, clear the task and callback forwards.*/
+               if (rc == TMF_RESP_FUNC_COMPLETE) {
+                       if (task->lldd_task) {
+                               struct hisi_sas_slot *slot;
+
+                               slot = &hisi_hba->slot_info
+                                       [tmf_task.tag_of_task_to_be_managed];
+                               spin_lock_irqsave(&hisi_hba->lock, flags);
+                               hisi_hba->hw->slot_complete(hisi_hba, slot, 1);
+                               spin_unlock_irqrestore(&hisi_hba->lock, flags);
+                       }
+               }
+
+       } else if (task->task_proto & SAS_PROTOCOL_SATA ||
+               task->task_proto & SAS_PROTOCOL_STP) {
+               if (task->dev->dev_type == SAS_SATA_DEV) {
+                       struct hisi_slot_info *slot = task->lldd_task;
+
+                       dev_notice(dev, "abort task: hba=%p task=%p slot=%p\n",
+                                  hisi_hba, task, slot);
+                       task->task_state_flags |= SAS_TASK_STATE_ABORTED;
+                       rc = TMF_RESP_FUNC_COMPLETE;
+                       goto out;
+               }
+
+       }
+
+out:
+       if (rc != TMF_RESP_FUNC_COMPLETE)
+               dev_notice(dev, "abort task: rc=%d\n", rc);
+       return rc;
+}
+
+static int hisi_sas_abort_task_set(struct domain_device *device, u8 *lun)
+{
+       struct hisi_sas_tmf_task tmf_task;
+       int rc = TMF_RESP_FUNC_FAILED;
+
+       tmf_task.tmf = TMF_ABORT_TASK_SET;
+       rc = hisi_sas_debug_issue_ssp_tmf(device, lun, &tmf_task);
+
+       return rc;
+}
+
+static int hisi_sas_clear_aca(struct domain_device *device, u8 *lun)
+{
+       int rc = TMF_RESP_FUNC_FAILED;
+       struct hisi_sas_tmf_task tmf_task;
+
+       tmf_task.tmf = TMF_CLEAR_ACA;
+       rc = hisi_sas_debug_issue_ssp_tmf(device, lun, &tmf_task);
+
+       return rc;
+}
+
+static int hisi_sas_debug_I_T_nexus_reset(struct domain_device *device)
+{
+       struct sas_phy *phy = sas_get_local_phy(device);
+       int rc, reset_type = (device->dev_type == SAS_SATA_DEV ||
+                       (device->tproto & SAS_PROTOCOL_STP)) ? 0 : 1;
+       rc = sas_phy_reset(phy, reset_type);
+       sas_put_local_phy(phy);
+       msleep(2000);
+       return rc;
+}
+
+static int hisi_sas_I_T_nexus_reset(struct domain_device *device)
+{
+       struct hisi_sas_device *sas_dev = device->lldd_dev;
+       struct hisi_hba *hisi_hba = dev_to_hisi_hba(device);
+       unsigned long flags;
+       int rc = TMF_RESP_FUNC_FAILED;
+
+       if (sas_dev->dev_status != HISI_SAS_DEV_EH)
+               return TMF_RESP_FUNC_FAILED;
+       sas_dev->dev_status = HISI_SAS_DEV_NORMAL;
+
+       rc = hisi_sas_debug_I_T_nexus_reset(device);
+
+       spin_lock_irqsave(&hisi_hba->lock, flags);
+       hisi_sas_release_task(hisi_hba, device);
+       spin_unlock_irqrestore(&hisi_hba->lock, flags);
+
+       return 0;
+}
+
+static int hisi_sas_lu_reset(struct domain_device *device, u8 *lun)
+{
+       struct hisi_sas_tmf_task tmf_task;
+       struct hisi_sas_device *sas_dev = device->lldd_dev;
+       struct hisi_hba *hisi_hba = dev_to_hisi_hba(device);
+       struct device *dev = &hisi_hba->pdev->dev;
+       unsigned long flags;
+       int rc = TMF_RESP_FUNC_FAILED;
+
+       tmf_task.tmf = TMF_LU_RESET;
+       sas_dev->dev_status = HISI_SAS_DEV_EH;
+       rc = hisi_sas_debug_issue_ssp_tmf(device, lun, &tmf_task);
+       if (rc == TMF_RESP_FUNC_COMPLETE) {
+               spin_lock_irqsave(&hisi_hba->lock, flags);
+               hisi_sas_release_task(hisi_hba, device);
+               spin_unlock_irqrestore(&hisi_hba->lock, flags);
+       }
+
+       /* If failed, fall-through I_T_Nexus reset */
+       dev_err(dev, "lu_reset: for device[%llx]:rc= %d\n",
+               sas_dev->device_id, rc);
+       return rc;
+}
+
+static int hisi_sas_query_task(struct sas_task *task)
+{
+       struct scsi_lun lun;
+       struct hisi_sas_tmf_task tmf_task;
+       int rc = TMF_RESP_FUNC_FAILED;
+
+       if (task->lldd_task && task->task_proto & SAS_PROTOCOL_SSP) {
+               struct scsi_cmnd *cmnd = task->uldd_task;
+               struct domain_device *device = task->dev;
+               struct hisi_sas_slot *slot = task->lldd_task;
+               u32 tag = slot->idx;
+
+               int_to_scsilun(cmnd->device->lun, &lun);
+               tmf_task.tmf = TMF_QUERY_TASK;
+               tmf_task.tag_of_task_to_be_managed = cpu_to_le16(tag);
+
+               rc = hisi_sas_debug_issue_ssp_tmf(device,
+                                                 lun.scsi_lun,
+                                                 &tmf_task);
+               switch (rc) {
+               /* The task is still in Lun, release it then */
+               case TMF_RESP_FUNC_SUCC:
+               /* The task is not in Lun or failed, reset the phy */
+               case TMF_RESP_FUNC_FAILED:
+               case TMF_RESP_FUNC_COMPLETE:
+                       break;
+               }
+       }
+       return rc;
+}
+
 static void hisi_sas_port_formed(struct asd_sas_phy *sas_phy)
 {
        hisi_sas_port_notify_formed(sas_phy);
@@ -629,6 +932,12 @@ static struct sas_domain_function_template hisi_sas_transport_ops = {
        .lldd_dev_found         = hisi_sas_dev_found,
        .lldd_dev_gone          = hisi_sas_dev_gone,
        .lldd_execute_task      = hisi_sas_queue_command,
+       .lldd_abort_task        = hisi_sas_abort_task,
+       .lldd_abort_task_set    = hisi_sas_abort_task_set,
+       .lldd_clear_aca         = hisi_sas_clear_aca,
+       .lldd_I_T_nexus_reset   = hisi_sas_I_T_nexus_reset,
+       .lldd_lu_reset          = hisi_sas_lu_reset,
+       .lldd_query_task        = hisi_sas_query_task,
        .lldd_port_formed       = hisi_sas_port_formed,
        .lldd_port_deformed     = hisi_sas_port_deformed,
 };