diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/scsi/aic94xx/aic94xx_tmf.c | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | drivers/scsi/aic94xx/aic94xx_tmf.c | 686 |
1 files changed, 686 insertions, 0 deletions
diff --git a/drivers/scsi/aic94xx/aic94xx_tmf.c b/drivers/scsi/aic94xx/aic94xx_tmf.c new file mode 100644 index 000000000..27d32b8c2 --- /dev/null +++ b/drivers/scsi/aic94xx/aic94xx_tmf.c @@ -0,0 +1,686 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Aic94xx Task Management Functions + * + * Copyright (C) 2005 Adaptec, Inc. All rights reserved. + * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com> + */ + +#include <linux/spinlock.h> +#include <linux/gfp.h> +#include "aic94xx.h" +#include "aic94xx_sas.h" +#include "aic94xx_hwi.h" + +/* ---------- Internal enqueue ---------- */ + +static int asd_enqueue_internal(struct asd_ascb *ascb, + void (*tasklet_complete)(struct asd_ascb *, + struct done_list_struct *), + void (*timed_out)(struct timer_list *t)) +{ + int res; + + ascb->tasklet_complete = tasklet_complete; + ascb->uldd_timer = 1; + + ascb->timer.function = timed_out; + ascb->timer.expires = jiffies + AIC94XX_SCB_TIMEOUT; + + add_timer(&ascb->timer); + + res = asd_post_ascb_list(ascb->ha, ascb, 1); + if (unlikely(res)) + del_timer(&ascb->timer); + return res; +} + +/* ---------- CLEAR NEXUS ---------- */ + +struct tasklet_completion_status { + int dl_opcode; + int tmf_state; + u8 tag_valid:1; + __be16 tag; +}; + +#define DECLARE_TCS(tcs) \ + struct tasklet_completion_status tcs = { \ + .dl_opcode = 0, \ + .tmf_state = 0, \ + .tag_valid = 0, \ + .tag = 0, \ + } + + +static void asd_clear_nexus_tasklet_complete(struct asd_ascb *ascb, + struct done_list_struct *dl) +{ + struct tasklet_completion_status *tcs = ascb->uldd_task; + ASD_DPRINTK("%s: here\n", __func__); + if (!del_timer(&ascb->timer)) { + ASD_DPRINTK("%s: couldn't delete timer\n", __func__); + return; + } + ASD_DPRINTK("%s: opcode: 0x%x\n", __func__, dl->opcode); + tcs->dl_opcode = dl->opcode; + complete(ascb->completion); + asd_ascb_free(ascb); +} + +static void asd_clear_nexus_timedout(struct timer_list *t) +{ + struct asd_ascb *ascb = from_timer(ascb, t, timer); + struct tasklet_completion_status *tcs = ascb->uldd_task; + + ASD_DPRINTK("%s: here\n", __func__); + tcs->dl_opcode = TMF_RESP_FUNC_FAILED; + complete(ascb->completion); +} + +#define CLEAR_NEXUS_PRE \ + struct asd_ascb *ascb; \ + struct scb *scb; \ + int res; \ + DECLARE_COMPLETION_ONSTACK(completion); \ + DECLARE_TCS(tcs); \ + \ + ASD_DPRINTK("%s: PRE\n", __func__); \ + res = 1; \ + ascb = asd_ascb_alloc_list(asd_ha, &res, GFP_KERNEL); \ + if (!ascb) \ + return -ENOMEM; \ + \ + ascb->completion = &completion; \ + ascb->uldd_task = &tcs; \ + scb = ascb->scb; \ + scb->header.opcode = CLEAR_NEXUS + +#define CLEAR_NEXUS_POST \ + ASD_DPRINTK("%s: POST\n", __func__); \ + res = asd_enqueue_internal(ascb, asd_clear_nexus_tasklet_complete, \ + asd_clear_nexus_timedout); \ + if (res) \ + goto out_err; \ + ASD_DPRINTK("%s: clear nexus posted, waiting...\n", __func__); \ + wait_for_completion(&completion); \ + res = tcs.dl_opcode; \ + if (res == TC_NO_ERROR) \ + res = TMF_RESP_FUNC_COMPLETE; \ + return res; \ +out_err: \ + asd_ascb_free(ascb); \ + return res + +int asd_clear_nexus_ha(struct sas_ha_struct *sas_ha) +{ + struct asd_ha_struct *asd_ha = sas_ha->lldd_ha; + + CLEAR_NEXUS_PRE; + scb->clear_nexus.nexus = NEXUS_ADAPTER; + CLEAR_NEXUS_POST; +} + +int asd_clear_nexus_port(struct asd_sas_port *port) +{ + struct asd_ha_struct *asd_ha = port->ha->lldd_ha; + + CLEAR_NEXUS_PRE; + scb->clear_nexus.nexus = NEXUS_PORT; + scb->clear_nexus.conn_mask = port->phy_mask; + CLEAR_NEXUS_POST; +} + +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; + + CLEAR_NEXUS_PRE; + scb->clear_nexus.nexus = NEXUS_I_T; + 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; +} + +int asd_I_T_nexus_reset(struct domain_device *dev) +{ + int res, tmp_res, i; + struct sas_phy *phy = sas_get_local_phy(dev); + /* Standard mandates link reset for ATA (type 0) and + * hard reset for SSP (type 1) */ + int reset_type = (dev->dev_type == SAS_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", dev_name(&phy->dev)); + res = sas_phy_reset(phy, reset_type); + if (res == TMF_RESP_FUNC_COMPLETE || res == -ENODEV) { + /* 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) + goto out; + 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); + + res = TMF_RESP_FUNC_FAILED; + out: + sas_put_local_phy(phy); + return res; +} + +static int asd_clear_nexus_I_T_L(struct domain_device *dev, u8 *lun) +{ + struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; + + CLEAR_NEXUS_PRE; + scb->clear_nexus.nexus = NEXUS_I_T_L; + scb->clear_nexus.flags = SEND_Q | EXEC_Q | NOTINQ; + memcpy(scb->clear_nexus.ssp_task.lun, lun, 8); + scb->clear_nexus.conn_handle = cpu_to_le16((u16)(unsigned long) + dev->lldd_dev); + CLEAR_NEXUS_POST; +} + +static int asd_clear_nexus_tag(struct sas_task *task) +{ + struct asd_ha_struct *asd_ha = task->dev->port->ha->lldd_ha; + struct asd_ascb *tascb = task->lldd_task; + + CLEAR_NEXUS_PRE; + scb->clear_nexus.nexus = NEXUS_TAG; + memcpy(scb->clear_nexus.ssp_task.lun, task->ssp_task.LUN, 8); + scb->clear_nexus.ssp_task.tag = tascb->tag; + if (task->dev->tproto) + scb->clear_nexus.conn_handle = cpu_to_le16((u16)(unsigned long) + task->dev->lldd_dev); + CLEAR_NEXUS_POST; +} + +static int asd_clear_nexus_index(struct sas_task *task) +{ + struct asd_ha_struct *asd_ha = task->dev->port->ha->lldd_ha; + struct asd_ascb *tascb = task->lldd_task; + + CLEAR_NEXUS_PRE; + scb->clear_nexus.nexus = NEXUS_TRANS_CX; + if (task->dev->tproto) + scb->clear_nexus.conn_handle = cpu_to_le16((u16)(unsigned long) + task->dev->lldd_dev); + scb->clear_nexus.index = cpu_to_le16(tascb->tc_index); + CLEAR_NEXUS_POST; +} + +/* ---------- TMFs ---------- */ + +static void asd_tmf_timedout(struct timer_list *t) +{ + struct asd_ascb *ascb = from_timer(ascb, t, timer); + struct tasklet_completion_status *tcs = ascb->uldd_task; + + ASD_DPRINTK("tmf timed out\n"); + tcs->tmf_state = TMF_RESP_FUNC_FAILED; + complete(ascb->completion); +} + +static int asd_get_tmf_resp_tasklet(struct asd_ascb *ascb, + struct done_list_struct *dl) +{ + struct asd_ha_struct *asd_ha = ascb->ha; + unsigned long flags; + struct tc_resp_sb_struct { + __le16 index_escb; + u8 len_lsb; + u8 flags; + } __attribute__ ((packed)) *resp_sb = (void *) dl->status_block; + + int edb_id = ((resp_sb->flags & 0x70) >> 4)-1; + struct asd_ascb *escb; + struct asd_dma_tok *edb; + struct ssp_frame_hdr *fh; + struct ssp_response_iu *ru; + int res = TMF_RESP_FUNC_FAILED; + + ASD_DPRINTK("tmf resp tasklet\n"); + + spin_lock_irqsave(&asd_ha->seq.tc_index_lock, flags); + escb = asd_tc_index_find(&asd_ha->seq, + (int)le16_to_cpu(resp_sb->index_escb)); + spin_unlock_irqrestore(&asd_ha->seq.tc_index_lock, flags); + + if (!escb) { + ASD_DPRINTK("Uh-oh! No escb for this dl?!\n"); + return res; + } + + edb = asd_ha->seq.edb_arr[edb_id + escb->edb_index]; + ascb->tag = *(__be16 *)(edb->vaddr+4); + fh = edb->vaddr + 16; + ru = edb->vaddr + 16 + sizeof(*fh); + res = ru->status; + if (ru->datapres == SAS_DATAPRES_RESPONSE_DATA) + res = ru->resp_data[3]; +#if 0 + ascb->tag = fh->tag; +#endif + ascb->tag_valid = 1; + + asd_invalidate_edb(escb, edb_id); + return res; +} + +static void asd_tmf_tasklet_complete(struct asd_ascb *ascb, + struct done_list_struct *dl) +{ + struct tasklet_completion_status *tcs; + + if (!del_timer(&ascb->timer)) + return; + + tcs = ascb->uldd_task; + ASD_DPRINTK("tmf tasklet complete\n"); + + tcs->dl_opcode = dl->opcode; + + if (dl->opcode == TC_SSP_RESP) { + tcs->tmf_state = asd_get_tmf_resp_tasklet(ascb, dl); + tcs->tag_valid = ascb->tag_valid; + tcs->tag = ascb->tag; + } + + complete(ascb->completion); + asd_ascb_free(ascb); +} + +static int asd_clear_nexus(struct sas_task *task) +{ + int res = TMF_RESP_FUNC_FAILED; + int leftover; + struct asd_ascb *tascb = task->lldd_task; + DECLARE_COMPLETION_ONSTACK(completion); + unsigned long flags; + + tascb->completion = &completion; + + ASD_DPRINTK("task not done, clearing nexus\n"); + if (tascb->tag_valid) + res = asd_clear_nexus_tag(task); + else + res = asd_clear_nexus_index(task); + leftover = wait_for_completion_timeout(&completion, + AIC94XX_SCB_TIMEOUT); + tascb->completion = NULL; + ASD_DPRINTK("came back from clear nexus\n"); + spin_lock_irqsave(&task->task_state_lock, flags); + if (leftover < 1) + res = TMF_RESP_FUNC_FAILED; + if (task->task_state_flags & SAS_TASK_STATE_DONE) + res = TMF_RESP_FUNC_COMPLETE; + spin_unlock_irqrestore(&task->task_state_lock, flags); + + return res; +} + +/** + * asd_abort_task -- ABORT TASK TMF + * @task: the task to be aborted + * + * Before calling ABORT TASK the task state flags should be ORed with + * SAS_TASK_STATE_ABORTED (unless SAS_TASK_STATE_DONE is set) under + * the task_state_lock IRQ spinlock, then ABORT TASK *must* be called. + * + * Implements the ABORT TASK TMF, I_T_L_Q nexus. + * Returns: SAS TMF responses (see sas_task.h), + * -ENOMEM, + * -SAS_QUEUE_FULL. + * + * When ABORT TASK returns, the caller of ABORT TASK checks first the + * task->task_state_flags, and then the return value of ABORT TASK. + * + * If the task has task state bit SAS_TASK_STATE_DONE set, then the + * task was completed successfully prior to it being aborted. The + * caller of ABORT TASK has responsibility to call task->task_done() + * xor free the task, depending on their framework. The return code + * is TMF_RESP_FUNC_FAILED in this case. + * + * Else the SAS_TASK_STATE_DONE bit is not set, + * If the return code is TMF_RESP_FUNC_COMPLETE, then + * the task was aborted successfully. The caller of + * ABORT TASK has responsibility to call task->task_done() + * to finish the task, xor free the task depending on their + * framework. + * else + * the ABORT TASK returned some kind of error. The task + * was _not_ cancelled. Nothing can be assumed. + * The caller of ABORT TASK may wish to retry. + */ +int asd_abort_task(struct sas_task *task) +{ + struct asd_ascb *tascb = task->lldd_task; + struct asd_ha_struct *asd_ha = tascb->ha; + int res = 1; + unsigned long flags; + struct asd_ascb *ascb = NULL; + struct scb *scb; + int leftover; + DECLARE_TCS(tcs); + DECLARE_COMPLETION_ONSTACK(completion); + DECLARE_COMPLETION_ONSTACK(tascb_completion); + + tascb->completion = &tascb_completion; + + 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); + res = TMF_RESP_FUNC_COMPLETE; + ASD_DPRINTK("%s: task 0x%p done\n", __func__, task); + goto out_done; + } + spin_unlock_irqrestore(&task->task_state_lock, flags); + + ascb = asd_ascb_alloc_list(asd_ha, &res, GFP_KERNEL); + if (!ascb) + return -ENOMEM; + + ascb->uldd_task = &tcs; + ascb->completion = &completion; + scb = ascb->scb; + scb->header.opcode = SCB_ABORT_TASK; + + switch (task->task_proto) { + case SAS_PROTOCOL_SATA: + case SAS_PROTOCOL_STP: + scb->abort_task.proto_conn_rate = (1 << 5); /* STP */ + break; + case SAS_PROTOCOL_SSP: + scb->abort_task.proto_conn_rate = (1 << 4); /* SSP */ + scb->abort_task.proto_conn_rate |= task->dev->linkrate; + break; + case SAS_PROTOCOL_SMP: + break; + default: + break; + } + + if (task->task_proto == SAS_PROTOCOL_SSP) { + scb->abort_task.ssp_frame.frame_type = SSP_TASK; + memcpy(scb->abort_task.ssp_frame.hashed_dest_addr, + task->dev->hashed_sas_addr, HASHED_SAS_ADDR_SIZE); + memcpy(scb->abort_task.ssp_frame.hashed_src_addr, + task->dev->port->ha->hashed_sas_addr, + HASHED_SAS_ADDR_SIZE); + scb->abort_task.ssp_frame.tptt = cpu_to_be16(0xFFFF); + + memcpy(scb->abort_task.ssp_task.lun, task->ssp_task.LUN, 8); + scb->abort_task.ssp_task.tmf = TMF_ABORT_TASK; + scb->abort_task.ssp_task.tag = cpu_to_be16(0xFFFF); + } + + scb->abort_task.sister_scb = cpu_to_le16(0xFFFF); + scb->abort_task.conn_handle = cpu_to_le16( + (u16)(unsigned long)task->dev->lldd_dev); + scb->abort_task.retry_count = 1; + scb->abort_task.index = cpu_to_le16((u16)tascb->tc_index); + scb->abort_task.itnl_to = cpu_to_le16(ITNL_TIMEOUT_CONST); + + res = asd_enqueue_internal(ascb, asd_tmf_tasklet_complete, + asd_tmf_timedout); + if (res) + goto out_free; + wait_for_completion(&completion); + ASD_DPRINTK("tmf came back\n"); + + tascb->tag = tcs.tag; + tascb->tag_valid = tcs.tag_valid; + + 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); + res = TMF_RESP_FUNC_COMPLETE; + ASD_DPRINTK("%s: task 0x%p done\n", __func__, task); + goto out_done; + } + spin_unlock_irqrestore(&task->task_state_lock, flags); + + if (tcs.dl_opcode == TC_SSP_RESP) { + /* The task to be aborted has been sent to the device. + * We got a Response IU for the ABORT TASK TMF. */ + if (tcs.tmf_state == TMF_RESP_FUNC_COMPLETE) + res = asd_clear_nexus(task); + else + res = tcs.tmf_state; + } else if (tcs.dl_opcode == TC_NO_ERROR && + tcs.tmf_state == TMF_RESP_FUNC_FAILED) { + /* timeout */ + res = TMF_RESP_FUNC_FAILED; + } else { + /* In the following we assume that the managing layer + * will _never_ make a mistake, when issuing ABORT + * TASK. + */ + switch (tcs.dl_opcode) { + default: + res = asd_clear_nexus(task); + fallthrough; + case TC_NO_ERROR: + break; + /* The task hasn't been sent to the device xor + * we never got a (sane) Response IU for the + * ABORT TASK TMF. + */ + case TF_NAK_RECV: + res = TMF_RESP_INVALID_FRAME; + break; + case TF_TMF_TASK_DONE: /* done but not reported yet */ + res = TMF_RESP_FUNC_FAILED; + leftover = + wait_for_completion_timeout(&tascb_completion, + AIC94XX_SCB_TIMEOUT); + spin_lock_irqsave(&task->task_state_lock, flags); + if (leftover < 1) + res = TMF_RESP_FUNC_FAILED; + if (task->task_state_flags & SAS_TASK_STATE_DONE) + res = TMF_RESP_FUNC_COMPLETE; + spin_unlock_irqrestore(&task->task_state_lock, flags); + break; + case TF_TMF_NO_TAG: + case TF_TMF_TAG_FREE: /* the tag is in the free list */ + case TF_TMF_NO_CONN_HANDLE: /* no such device */ + res = TMF_RESP_FUNC_COMPLETE; + break; + case TF_TMF_NO_CTX: /* not in seq, or proto != SSP */ + res = TMF_RESP_FUNC_ESUPP; + break; + } + } + out_done: + tascb->completion = NULL; + if (res == TMF_RESP_FUNC_COMPLETE) { + task->lldd_task = NULL; + mb(); + asd_ascb_free(tascb); + } + ASD_DPRINTK("task 0x%p aborted, res: 0x%x\n", task, res); + return res; + + out_free: + asd_ascb_free(ascb); + ASD_DPRINTK("task 0x%p aborted, res: 0x%x\n", task, res); + return res; +} + +/** + * asd_initiate_ssp_tmf -- send a TMF to an I_T_L or I_T_L_Q nexus + * @dev: pointer to struct domain_device of interest + * @lun: pointer to u8[8] which is the LUN + * @tmf: the TMF to be performed (see sas_task.h or the SAS spec) + * @index: the transaction context of the task to be queried if QT TMF + * + * This function is used to send ABORT TASK SET, CLEAR ACA, + * CLEAR TASK SET, LU RESET and QUERY TASK TMFs. + * + * No SCBs should be queued to the I_T_L nexus when this SCB is + * pending. + * + * Returns: TMF response code (see sas_task.h or the SAS spec) + */ +static int asd_initiate_ssp_tmf(struct domain_device *dev, u8 *lun, + int tmf, int index) +{ + struct asd_ha_struct *asd_ha = dev->port->ha->lldd_ha; + struct asd_ascb *ascb; + int res = 1; + struct scb *scb; + DECLARE_COMPLETION_ONSTACK(completion); + DECLARE_TCS(tcs); + + if (!(dev->tproto & SAS_PROTOCOL_SSP)) + return TMF_RESP_FUNC_ESUPP; + + ascb = asd_ascb_alloc_list(asd_ha, &res, GFP_KERNEL); + if (!ascb) + return -ENOMEM; + + ascb->completion = &completion; + ascb->uldd_task = &tcs; + scb = ascb->scb; + + if (tmf == TMF_QUERY_TASK) + scb->header.opcode = QUERY_SSP_TASK; + else + scb->header.opcode = INITIATE_SSP_TMF; + + scb->ssp_tmf.proto_conn_rate = (1 << 4); /* SSP */ + scb->ssp_tmf.proto_conn_rate |= dev->linkrate; + /* SSP frame header */ + scb->ssp_tmf.ssp_frame.frame_type = SSP_TASK; + memcpy(scb->ssp_tmf.ssp_frame.hashed_dest_addr, + dev->hashed_sas_addr, HASHED_SAS_ADDR_SIZE); + memcpy(scb->ssp_tmf.ssp_frame.hashed_src_addr, + dev->port->ha->hashed_sas_addr, HASHED_SAS_ADDR_SIZE); + scb->ssp_tmf.ssp_frame.tptt = cpu_to_be16(0xFFFF); + /* SSP Task IU */ + memcpy(scb->ssp_tmf.ssp_task.lun, lun, 8); + scb->ssp_tmf.ssp_task.tmf = tmf; + + scb->ssp_tmf.sister_scb = cpu_to_le16(0xFFFF); + scb->ssp_tmf.conn_handle= cpu_to_le16((u16)(unsigned long) + dev->lldd_dev); + scb->ssp_tmf.retry_count = 1; + scb->ssp_tmf.itnl_to = cpu_to_le16(ITNL_TIMEOUT_CONST); + if (tmf == TMF_QUERY_TASK) + scb->ssp_tmf.index = cpu_to_le16(index); + + res = asd_enqueue_internal(ascb, asd_tmf_tasklet_complete, + asd_tmf_timedout); + if (res) + goto out_err; + wait_for_completion(&completion); + + switch (tcs.dl_opcode) { + case TC_NO_ERROR: + res = TMF_RESP_FUNC_COMPLETE; + break; + case TF_NAK_RECV: + res = TMF_RESP_INVALID_FRAME; + break; + case TF_TMF_TASK_DONE: + res = TMF_RESP_FUNC_FAILED; + break; + case TF_TMF_NO_TAG: + case TF_TMF_TAG_FREE: /* the tag is in the free list */ + case TF_TMF_NO_CONN_HANDLE: /* no such device */ + res = TMF_RESP_FUNC_COMPLETE; + break; + case TF_TMF_NO_CTX: /* not in seq, or proto != SSP */ + res = TMF_RESP_FUNC_ESUPP; + break; + default: + /* Allow TMF response codes to propagate upwards */ + res = tcs.dl_opcode; + break; + } + return res; +out_err: + asd_ascb_free(ascb); + return res; +} + +int asd_abort_task_set(struct domain_device *dev, u8 *lun) +{ + int res = asd_initiate_ssp_tmf(dev, lun, TMF_ABORT_TASK_SET, 0); + + if (res == TMF_RESP_FUNC_COMPLETE) + asd_clear_nexus_I_T_L(dev, lun); + return res; +} + +int asd_clear_task_set(struct domain_device *dev, u8 *lun) +{ + int res = asd_initiate_ssp_tmf(dev, lun, TMF_CLEAR_TASK_SET, 0); + + if (res == TMF_RESP_FUNC_COMPLETE) + asd_clear_nexus_I_T_L(dev, lun); + return res; +} + +int asd_lu_reset(struct domain_device *dev, u8 *lun) +{ + int res = asd_initiate_ssp_tmf(dev, lun, TMF_LU_RESET, 0); + + if (res == TMF_RESP_FUNC_COMPLETE) + asd_clear_nexus_I_T_L(dev, lun); + return res; +} + +/** + * asd_query_task -- send a QUERY TASK TMF to an I_T_L_Q nexus + * @task: pointer to sas_task struct of interest + * + * Returns: TMF_RESP_FUNC_COMPLETE if the task is not in the task set, + * or TMF_RESP_FUNC_SUCC if the task is in the task set. + * + * Normally the management layer sets the task to aborted state, + * and then calls query task and then abort task. + */ +int asd_query_task(struct sas_task *task) +{ + struct asd_ascb *ascb = task->lldd_task; + int index; + + if (ascb) { + index = ascb->tc_index; + return asd_initiate_ssp_tmf(task->dev, task->ssp_task.LUN, + TMF_QUERY_TASK, index); + } + return TMF_RESP_FUNC_COMPLETE; +} |