hisi_sas: Add tmf methods

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>
This commit is contained in:
John Garry 2015-11-18 00:50:56 +08:00 committed by Martin K. Petersen
parent 701f75ecd9
commit 0efff300c7

View 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,
};