Merge remote-tracking branch 'scsi/for-next'
[deliverable/linux.git] / drivers / scsi / hisi_sas / hisi_sas_main.c
index 18dd5ea2c721e021be7214bd83952ce5cce9f4e9..85c73d311e4d75669841be26e2be1ffd9a233d7e 100644 (file)
 
 static int hisi_sas_debug_issue_ssp_tmf(struct domain_device *device,
                                u8 *lun, struct hisi_sas_tmf_task *tmf);
+static int
+hisi_sas_internal_task_abort(struct hisi_hba *hisi_hba,
+                            struct domain_device *device,
+                            int abort_flag, int tag);
 
 static struct hisi_hba *dev_to_hisi_hba(struct domain_device *device)
 {
@@ -116,6 +120,14 @@ static int hisi_sas_task_prep_ata(struct hisi_hba *hisi_hba,
        return hisi_hba->hw->prep_stp(hisi_hba, slot);
 }
 
+static int hisi_sas_task_prep_abort(struct hisi_hba *hisi_hba,
+               struct hisi_sas_slot *slot,
+               int device_id, int abort_flag, int tag_to_abort)
+{
+       return hisi_hba->hw->prep_abort(hisi_hba, slot,
+                       device_id, abort_flag, tag_to_abort);
+}
+
 /*
  * This function will issue an abort TMF regardless of whether the
  * task is in the sdev or not. Then it will do the task complete
@@ -192,7 +204,7 @@ static int hisi_sas_task_prep(struct sas_task *task, struct hisi_hba *hisi_hba,
                return rc;
        }
        port = device->port->lldd_port;
-       if (port && !port->port_attached && !tmf) {
+       if (port && !port->port_attached) {
                if (sas_protocol_ata(task->task_proto)) {
                        struct task_status_struct *ts = &task->task_status;
 
@@ -609,6 +621,9 @@ static void hisi_sas_dev_gone(struct domain_device *device)
        dev_info(dev, "found dev[%lld:%x] is gone\n",
                 sas_dev->device_id, sas_dev->dev_type);
 
+       hisi_sas_internal_task_abort(hisi_hba, device,
+                                    HISI_SAS_INT_ABT_DEV, 0);
+
        hisi_hba->hw->free_device(hisi_hba, sas_dev);
        device->lldd_dev = NULL;
        memset(sas_dev, 0, sizeof(*sas_dev));
@@ -728,6 +743,12 @@ static int hisi_sas_exec_internal_tmf_task(struct domain_device *device,
                        break;
                }
 
+               if (task->task_status.resp == SAS_TASK_COMPLETE &&
+                       task->task_status.stat == TMF_RESP_FUNC_SUCC) {
+                       res = TMF_RESP_FUNC_SUCC;
+                       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
@@ -826,18 +847,22 @@ static int hisi_sas_abort_task(struct sas_task *task)
                        }
                }
 
+               hisi_sas_internal_task_abort(hisi_hba, device,
+                                            HISI_SAS_INT_ABT_CMD, tag);
        } 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;
+                       hisi_sas_internal_task_abort(hisi_hba, device,
+                                                    HISI_SAS_INT_ABT_DEV, 0);
                        rc = TMF_RESP_FUNC_COMPLETE;
-                       goto out;
                }
+       } else if (task->task_proto & SAS_PROTOCOL_SMP) {
+               /* SMP */
+               struct hisi_sas_slot *slot = task->lldd_task;
+               u32 tag = slot->idx;
 
+               hisi_sas_internal_task_abort(hisi_hba, device,
+                                            HISI_SAS_INT_ABT_CMD, tag);
        }
 
 out:
@@ -954,6 +979,157 @@ static int hisi_sas_query_task(struct sas_task *task)
        return rc;
 }
 
+static int
+hisi_sas_internal_abort_task_exec(struct hisi_hba *hisi_hba, u64 device_id,
+                                 struct sas_task *task, int abort_flag,
+                                 int task_tag)
+{
+       struct domain_device *device = task->dev;
+       struct hisi_sas_device *sas_dev = device->lldd_dev;
+       struct device *dev = &hisi_hba->pdev->dev;
+       struct hisi_sas_port *port;
+       struct hisi_sas_slot *slot;
+       struct hisi_sas_cmd_hdr *cmd_hdr_base;
+       int dlvry_queue_slot, dlvry_queue, n_elem = 0, rc, slot_idx;
+
+       if (!device->port)
+               return -1;
+
+       port = device->port->lldd_port;
+
+       /* simply get a slot and send abort command */
+       rc = hisi_sas_slot_index_alloc(hisi_hba, &slot_idx);
+       if (rc)
+               goto err_out;
+       rc = hisi_hba->hw->get_free_slot(hisi_hba, &dlvry_queue,
+                                        &dlvry_queue_slot);
+       if (rc)
+               goto err_out_tag;
+
+       slot = &hisi_hba->slot_info[slot_idx];
+       memset(slot, 0, sizeof(struct hisi_sas_slot));
+
+       slot->idx = slot_idx;
+       slot->n_elem = n_elem;
+       slot->dlvry_queue = dlvry_queue;
+       slot->dlvry_queue_slot = dlvry_queue_slot;
+       cmd_hdr_base = hisi_hba->cmd_hdr[dlvry_queue];
+       slot->cmd_hdr = &cmd_hdr_base[dlvry_queue_slot];
+       slot->task = task;
+       slot->port = port;
+       task->lldd_task = slot;
+
+       memset(slot->cmd_hdr, 0, sizeof(struct hisi_sas_cmd_hdr));
+
+       rc = hisi_sas_task_prep_abort(hisi_hba, slot, device_id,
+                                     abort_flag, task_tag);
+       if (rc)
+               goto err_out_tag;
+
+       /* Port structure is static for the HBA, so
+       *  even if the port is deformed it is ok
+       *  to reference.
+       */
+       list_add_tail(&slot->entry, &port->list);
+       spin_lock(&task->task_state_lock);
+       task->task_state_flags |= SAS_TASK_AT_INITIATOR;
+       spin_unlock(&task->task_state_lock);
+
+       hisi_hba->slot_prep = slot;
+
+       sas_dev->running_req++;
+       /* send abort command to our chip */
+       hisi_hba->hw->start_delivery(hisi_hba);
+
+       return 0;
+
+err_out_tag:
+       hisi_sas_slot_index_free(hisi_hba, slot_idx);
+err_out:
+       dev_err(dev, "internal abort task prep: failed[%d]!\n", rc);
+
+       return rc;
+}
+
+/**
+ * hisi_sas_internal_task_abort -- execute an internal
+ * abort command for single IO command or a device
+ * @hisi_hba: host controller struct
+ * @device: domain device
+ * @abort_flag: mode of operation, device or single IO
+ * @tag: tag of IO to be aborted (only relevant to single
+ *       IO mode)
+ */
+static int
+hisi_sas_internal_task_abort(struct hisi_hba *hisi_hba,
+                            struct domain_device *device,
+                            int abort_flag, int tag)
+{
+       struct sas_task *task;
+       struct hisi_sas_device *sas_dev = device->lldd_dev;
+       struct device *dev = &hisi_hba->pdev->dev;
+       int res;
+       unsigned long flags;
+
+       if (!hisi_hba->hw->prep_abort)
+               return -EOPNOTSUPP;
+
+       task = sas_alloc_slow_task(GFP_KERNEL);
+       if (!task)
+               return -ENOMEM;
+
+       task->dev = device;
+       task->task_proto = device->tproto;
+       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 + 20*HZ;
+       add_timer(&task->slow_task->timer);
+
+       /* Lock as we are alloc'ing a slot, which cannot be interrupted */
+       spin_lock_irqsave(&hisi_hba->lock, flags);
+       res = hisi_sas_internal_abort_task_exec(hisi_hba, sas_dev->device_id,
+                                               task, abort_flag, tag);
+       spin_unlock_irqrestore(&hisi_hba->lock, flags);
+       if (res) {
+               del_timer(&task->slow_task->timer);
+               dev_err(dev, "internal task abort: executing internal task failed: %d\n",
+                       res);
+               goto exit;
+       }
+       wait_for_completion(&task->slow_task->completion);
+       res = TMF_RESP_FUNC_FAILED;
+
+       if (task->task_status.resp == SAS_TASK_COMPLETE &&
+               task->task_status.stat == TMF_RESP_FUNC_COMPLETE) {
+               res = TMF_RESP_FUNC_COMPLETE;
+               goto exit;
+       }
+
+       /* 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, "internal task abort: timeout.\n");
+                       if (task->lldd_task) {
+                               struct hisi_sas_slot *slot = task->lldd_task;
+
+                               hisi_sas_slot_task_free(hisi_hba, task, slot);
+                       }
+               }
+       }
+
+exit:
+       dev_info(dev, "internal task abort: task to dev %016llx task=%p "
+               "resp: 0x%x sts 0x%x\n",
+               SAS_ADDR(device->sas_addr),
+               task,
+               task->task_status.resp, /* 0 is complete, -1 is undelivered */
+               task->task_status.stat);
+       sas_free_task(task);
+
+       return res;
+}
+
 static void hisi_sas_port_formed(struct asd_sas_phy *sas_phy)
 {
        hisi_sas_port_notify_formed(sas_phy);
This page took 0.026258 seconds and 5 git commands to generate.