aic94xx: aic94xx_task.c Implements the Execute Task SCSI RPC
PATCH 19/20 aic94xx: aic94xx_task.c Implements the Execute Task SCSI RPC
Signed-off-by Luben Tuikov <[email protected]>
drivers/scsi/aic94xx/aic94xx_task.c +645 -0
@@ -0,0 +1,645 @@ +/* +* Aic94xx SAS/SATA Tasks +* +* Copyright (C) 2005 Adaptec, Inc. All rights reserved. +* Copyright (C) 2005 Luben Tuikov <[email protected]> +* +* This file is licensed under GPLv2. +* +* This file is part of the aic94xx driver. +* +* The aic94xx driver is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; version 2 of the +* License. +* +* The aic94xx driver is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with the aic94xx driver; if not, write to the Free Software +* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +* +* $Id: //depot/aic94xx/aic94xx_task.c#50 $ +*/ + +#include <linux/spinlock.h> +#include <scsi/sas/sas_task.h> +#include <scsi/sas/sas_frames.h> +#include "aic94xx.h" +#include "aic94xx_sas.h" +#include "aic94xx_hwi.h" + +static void asd_unbuild_ata_ascb (struct asd_ascb *a); +static void asd_unbuild_smp_ascb (struct asd_ascb *a); +static void asd_unbuild_ssp_ascb (struct asd_ascb *a); + +static inline void asd_can_dequeue (struct asd_ha_struct *asd_ha,int num) +{ +unsigned long flags; + +spin_lock_irqsave (&asd_ha->seq.pend_q_lock, flags); + asd_ha->seq.can_queue += num; +spin_unlock_irqrestore (&asd_ha->seq.pend_q_lock, flags); +} + +/* PCI_DMA_... to our direction translation. +*/ +static const u8 data_dir_flags[] = { + [PCI_DMA_BIDIRECTIONAL ] =DATA_DIR_BYRECIPIENT ,/* UNSPECIFIED */ + [PCI_DMA_TODEVICE ] =DATA_DIR_OUT ,/* OUTBOUND */ + [PCI_DMA_FROMDEVICE ] =DATA_DIR_IN ,/* INBOUND */ + [PCI_DMA_NONE ] =DATA_DIR_NONE ,/* NO TRANSFER */ +}; + +static inline int asd_map_scatterlist (struct sas_task *task, +struct sg_el *sg_arr, +unsigned long gfp_flags) +{ +struct asd_ascb *ascb = task->lldd_task; +struct asd_ha_struct *asd_ha = ascb->ha; +struct scatterlist *sc; +int num_sg, res; + +if (task->data_dir ==PCI_DMA_NONE ) +return 0 ; + +if (task->num_scatter ==0 ) { +void *p = task->scatter; +dma_addr_t dma =pci_map_single (asd_ha->pcidev, p, + task->total_xfer_len, + task->data_dir); + sg_arr[0 ].bus_addr =cpu_to_le64 ((u64 )dma); + sg_arr[0 ].size =cpu_to_le32 (task->total_xfer_len); + sg_arr[0 ].flags |=ASD_SG_EL_LIST_EOL ; +return 0 ; + } + + num_sg =pci_map_sg (asd_ha->pcidev, task->scatter, task->num_scatter, + task->data_dir); +if (num_sg ==0 ) +return -ENOMEM ; + +if (num_sg >3 ) { +int i; + + ascb->sg_arr =asd_alloc_coherent (asd_ha, + num_sg*sizeof (struct sg_el ), + gfp_flags); +if (!ascb->sg_arr) { + res = -ENOMEM ; + goto err_unmap; + } +for (sc = task->scatter, i =0 ; i < num_sg; i++, sc++) { +struct sg_el *sg = + &((struct sg_el *)ascb->sg_arr->vaddr)[i]; + sg->bus_addr =cpu_to_le64 ((u64 )sg_dma_address (sc)); + sg->size =cpu_to_le32 ((u32 )sg_dma_len (sc)); +if (i == num_sg-1 ) + sg->flags |=ASD_SG_EL_LIST_EOL ; + } + +for (sc = task->scatter, i =0 ; i <2 ; i++, sc++) { + sg_arr[i].bus_addr = +cpu_to_le64 ((u64 )sg_dma_address (sc)); + sg_arr[i].size =cpu_to_le32 ((u32 )sg_dma_len (sc)); + } + sg_arr[1 ].next_sg_offs =2 *sizeof (*sg_arr); + sg_arr[1 ].flags |=ASD_SG_EL_LIST_EOS ; + +memset (&sg_arr[2 ],0 ,sizeof (*sg_arr)); + sg_arr[2 ].bus_addr=cpu_to_le64 ((u64 )ascb->sg_arr->dma_handle); + }else { +int i; +for (sc = task->scatter, i =0 ; i < num_sg; i++, sc++) { + sg_arr[i].bus_addr = +cpu_to_le64 ((u64 )sg_dma_address (sc)); + sg_arr[i].size =cpu_to_le32 ((u32 )sg_dma_len (sc)); + } + sg_arr[i-1 ].flags |=ASD_SG_EL_LIST_EOL ; + } + +return 0 ; +err_unmap: +pci_unmap_sg (asd_ha->pcidev, task->scatter, task->num_scatter, + task->data_dir); +return res; +} + +static inline void asd_unmap_scatterlist (struct asd_ascb *ascb) +{ +struct asd_ha_struct *asd_ha = ascb->ha; +struct sas_task *task = ascb->uldd_task; + +if (task->data_dir ==PCI_DMA_NONE ) +return ; + +if (task->num_scatter ==0 ) { +dma_addr_t dma = (dma_addr_t ) +le64_to_cpu (ascb->scb->ssp_task.sg_element[0 ].bus_addr); +pci_unmap_single (ascb->ha->pcidev, dma, task->total_xfer_len, + task->data_dir); +return ; + } + +asd_free_coherent (asd_ha, ascb->sg_arr); +pci_unmap_sg (asd_ha->pcidev, task->scatter, task->num_scatter, + task->data_dir); +} + +/* ---------- Task complete tasklet ---------- */ + +static void asd_get_response_tasklet (struct asd_ascb *ascb, +struct done_list_struct *dl) +{ +struct asd_ha_struct *asd_ha = ascb->ha; +struct sas_task *task = ascb->uldd_task; +struct task_status_struct *ts = &task->task_status; +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 size = ((resp_sb->flags & 7) << 8) | resp_sb->len_lsb; */ +int edb_id = ((resp_sb->flags &0x70 ) >>4 )-1 ; +struct asd_ascb *escb; +struct asd_dma_tok *edb; +void *r; + +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 ; + } + + ts->buf_valid_size =0 ; + edb = asd_ha->seq.edb_arr[edb_id + escb->edb_index]; + r = edb->vaddr; +if (task->task_proto ==SAS_PROTO_SSP ) { +struct ssp_response_iu *iu = + r +16 +sizeof (struct ssp_frame_hdr ); + + ts->residual =le32_to_cpu (*(__le32 *)r); + ts->resp =SAS_TASK_COMPLETE ; +if (iu->datapres ==0 ) + ts->stat = iu->status; +else if (iu->datapres ==1 ) + ts->stat = iu->resp_data[3 ]; +else if (iu->datapres ==2 ) { + ts->stat =SAM_CHECK_COND ; + ts->buf_valid_size =min ((u32 )SAS_STATUS_BUF_SIZE , +be32_to_cpu (iu->sense_data_len)); +memcpy (ts->buf, iu->sense_data, ts->buf_valid_size); +if (iu->status !=SAM_CHECK_COND ) { +ASD_DPRINTK ("device %llx sent sense data, but " +"stat(0x%x) is not CHECK_CONDITION" +"\n" , +SAS_ADDR (task->dev->sas_addr), + ts->stat); + } + } + }else { +struct ata_task_resp *resp = (void *) &ts->buf[0 ]; + + ts->residual =le32_to_cpu (*(__le32 *)r); + +if (SAS_STATUS_BUF_SIZE >=sizeof (*resp)) { + resp->frame_len =le16_to_cpu (*(__le16 *)(r+6 )); +memcpy (&resp->ending_fis[0 ], r+16 ,24 ); + ts->buf_valid_size =sizeof (*resp); + } + } + +asd_invalidate_edb (escb, edb_id); +} + +static void asd_task_tasklet_complete (struct asd_ascb *ascb, +struct done_list_struct *dl) +{ +struct sas_task *task = ascb->uldd_task; +struct task_status_struct *ts = &task->task_status; +unsigned long flags; +u8 opcode = dl->opcode; + +asd_can_dequeue (ascb->ha,1 ); + +Again: +switch (opcode) { +case TC_NO_ERROR : + ts->resp =SAS_TASK_COMPLETE ; + ts->stat =SAM_GOOD ; +break ; +case TC_UNDERRUN : + ts->resp =SAS_TASK_COMPLETE ; + ts->stat =SAS_DATA_UNDERRUN ; + ts->residual =le32_to_cpu (*(__le32 *)dl->status_block); +break ; +case TC_OVERRUN : + ts->resp =SAS_TASK_COMPLETE ; + ts->stat =SAS_DATA_OVERRUN ; + ts->residual =0 ; +break ; +case TC_SSP_RESP : +case TC_ATA_RESP : + ts->resp =SAS_TASK_COMPLETE ; + ts->stat =SAS_PROTO_RESPONSE ; +asd_get_response_tasklet (ascb, dl); +break ; +case TF_OPEN_REJECT : + ts->resp =SAS_TASK_UNDELIVERED ; + ts->stat =SAS_OPEN_REJECT ; +if (dl->status_block[1 ] &2 ) + ts->open_rej_reason =1 + dl->status_block[2 ]; +else if (dl->status_block[1 ] &1 ) + ts->open_rej_reason = (dl->status_block[2 ] >>4 )+10 ; +else + ts->open_rej_reason =SAS_OREJ_UNKNOWN ; +break ; +case TF_OPEN_TO : + ts->resp =SAS_TASK_UNDELIVERED ; + ts->stat =SAS_OPEN_TO ; +break ; +case TF_PHY_DOWN : +case TU_PHY_DOWN : + ts->resp =SAS_TASK_UNDELIVERED ; + ts->stat =SAS_PHY_DOWN ; +break ; +case TI_PHY_DOWN : + ts->resp =SAS_TASK_COMPLETE ; + ts->stat =SAS_PHY_DOWN ; +break ; +case TI_BREAK : +case TI_PROTO_ERR : +case TI_NAK : +case TI_ACK_NAK_TO : +case TF_SMP_XMIT_RCV_ERR : +case TC_ATA_R_ERR_RECV : + ts->resp =SAS_TASK_COMPLETE ; + ts->stat =SAS_INTERRUPTED ; +break ; +case TF_BREAK : +case TU_BREAK : +case TU_ACK_NAK_TO : +case TF_SMPRSP_TO : + ts->resp =SAS_TASK_UNDELIVERED ; + ts->stat =SAS_DEV_NO_RESPONSE ; +break ; +case TF_NAK_RECV : + ts->resp =SAS_TASK_COMPLETE ; + ts->stat =SAS_NAK_R_ERR ; +break ; +case TA_I_T_NEXUS_LOSS : + opcode = dl->status_block[0 ]; + goto Again; +break ; +case TF_INV_CONN_HANDLE : + ts->resp =SAS_TASK_UNDELIVERED ; + ts->stat =SAS_DEVICE_UNKNOWN ; +break ; +case TF_REQUESTED_N_PENDING : + ts->resp =SAS_TASK_UNDELIVERED ; + ts->stat =SAS_PENDING ; +break ; +case TC_TASK_CLEARED : +case TA_ON_REQ : + ts->resp =SAS_TASK_COMPLETE ; + ts->stat =SAS_ABORTED_TASK ; +break ; + +case TF_NO_SMP_CONN : +case TF_TMF_NO_CTX : +case TF_TMF_NO_TAG : +case TF_TMF_TAG_FREE : +case TF_TMF_TASK_DONE : +case TF_TMF_NO_CONN_HANDLE : +case TF_IRTT_TO : +case TF_IU_SHORT : +case TF_DATA_OFFS_ERR : + ts->resp =SAS_TASK_UNDELIVERED ; + ts->stat =SAS_DEV_NO_RESPONSE ; +break ; + +case TC_LINK_ADM_RESP : +case TC_CONTROL_PHY : +case TC_RESUME : +case TC_PARTIAL_SG_LIST : +default : +ASD_DPRINTK ("%s: dl opcode: 0x%x?\n" , __FUNCTION__, opcode); +break ; + } + +switch (task->task_proto) { +case SATA_PROTO : +case SAS_PROTO_STP : +asd_unbuild_ata_ascb (ascb); +break ; +case SAS_PROTO_SMP : +asd_unbuild_smp_ascb (ascb); +break ; +case SAS_PROTO_SSP : +asd_unbuild_ssp_ascb (ascb); +default : +break ; + } + +spin_lock_irqsave (&task->task_state_lock, flags); + task->task_state_flags &= ~SAS_TASK_STATE_PENDING ; + task->task_state_flags |=SAS_TASK_STATE_DONE ; +if (unlikely ((task->task_state_flags &SAS_TASK_STATE_ABORTED ))) { +spin_unlock_irqrestore (&task->task_state_lock, flags); +ASD_DPRINTK ("task 0x%p done with opcode 0x%x resp 0x%x " +"stat 0x%x but aborted by upper layer!\n" , + task, opcode, ts->resp, ts->stat); +complete (&ascb->completion); + }else { +spin_unlock_irqrestore (&task->task_state_lock, flags); + task->lldd_task =NULL ; +asd_ascb_free (ascb); +mb (); + task->task_done (task); + } +} + +/* ---------- ATA ---------- */ + +static int asd_build_ata_ascb (struct asd_ascb *ascb,struct sas_task *task, +unsigned long gfp_flags) +{ +struct domain_device *dev = task->dev; +struct scb *scb; +u8 flags; +int res =0 ; + + scb = ascb->scb; + +if (unlikely (task->ata_task.device_control_reg_update)) + scb->header.opcode =CONTROL_ATA_DEV ; +else if (dev->sata_dev.command_set ==ATA_COMMAND_SET ) + scb->header.opcode =INITIATE_ATA_TASK ; +else + scb->header.opcode =INITIATE_ATAPI_TASK ; + + scb->ata_task.proto_conn_rate = (1 <<5 );/* STP */ +if (dev->port->oob_mode ==SAS_OOB_MODE ) + scb->ata_task.proto_conn_rate |= dev->linkrate; + + scb->ata_task.total_xfer_len =cpu_to_le32 (task->total_xfer_len); + scb->ata_task.fis = task->ata_task.fis; + scb->ata_task.fis.fis_type =0x27 ; +if (likely (!task->ata_task.device_control_reg_update)) + scb->ata_task.fis.flags |=0x80 ;/* C=1: update ATA cmd reg */ + scb->ata_task.fis.flags &=0xF0 ;/* PM_PORT field shall be 0 */ +if (dev->sata_dev.command_set ==ATAPI_COMMAND_SET ) +memcpy (scb->ata_task.atapi_packet, task->ata_task.atapi_packet, +16 ); + scb->ata_task.sister_scb =cpu_to_le16 (0xFFFF ); + scb->ata_task.conn_handle =cpu_to_le16 ( + (u16 )(unsigned long )dev->lldd_dev); + +if (likely (!task->ata_task.device_control_reg_update)) { + flags =0 ; +if (task->ata_task.dma_xfer) + flags |=DATA_XFER_MODE_DMA ; +if (task->ata_task.use_ncq && + dev->sata_dev.command_set !=ATAPI_COMMAND_SET ) + flags |=ATA_Q_TYPE_NCQ ; + flags |= data_dir_flags[task->data_dir]; + scb->ata_task.ata_flags = flags; + + scb->ata_task.retry_count = task->ata_task.retry_count; + + flags =0 ; +if (task->ata_task.set_affil_pol) + flags |=SET_AFFIL_POLICY ; +if (task->ata_task.stp_affil_pol) + flags |=STP_AFFIL_POLICY ; + scb->ata_task.flags = flags; + } + ascb->tasklet_complete = asd_task_tasklet_complete; + +if (likely (!task->ata_task.device_control_reg_update)) + res =asd_map_scatterlist (task, scb->ata_task.sg_element, + gfp_flags); + +return res; +} + +static void asd_unbuild_ata_ascb (struct asd_ascb *a) +{ +asd_unmap_scatterlist (a); +} + +/* ---------- SMP ---------- */ + +static int asd_build_smp_ascb (struct asd_ascb *ascb,struct sas_task *task, +unsigned long gfp_flags) +{ +struct asd_ha_struct *asd_ha = ascb->ha; +struct domain_device *dev = task->dev; +struct scb *scb; + +pci_map_sg (asd_ha->pcidev, &task->smp_task.smp_req,1 , +PCI_DMA_FROMDEVICE ); +pci_map_sg (asd_ha->pcidev, &task->smp_task.smp_resp,1 , +PCI_DMA_FROMDEVICE ); + + scb = ascb->scb; + + scb->header.opcode =INITIATE_SMP_TASK ; + + scb->smp_task.proto_conn_rate = dev->linkrate; + + scb->smp_task.smp_req.bus_addr = +cpu_to_le64 ((u64 )sg_dma_address (&task->smp_task.smp_req)); + scb->smp_task.smp_req.size = +cpu_to_le32 ((u32 )sg_dma_len (&task->smp_task.smp_req)-4 ); + + scb->smp_task.smp_resp.bus_addr = +cpu_to_le64 ((u64 )sg_dma_address (&task->smp_task.smp_resp)); + scb->smp_task.smp_resp.size = +cpu_to_le32 ((u32 )sg_dma_len (&task->smp_task.smp_resp)-4 ); + + scb->smp_task.sister_scb =cpu_to_le16 (0xFFFF ); + scb->smp_task.conn_handle =cpu_to_le16 ((u16 ) + (unsigned long )dev->lldd_dev); + + ascb->tasklet_complete = asd_task_tasklet_complete; + +return 0 ; +} + +static void asd_unbuild_smp_ascb (struct asd_ascb *a) +{ +struct sas_task *task = a->uldd_task; + +BUG_ON (!task); +pci_unmap_sg (a->ha->pcidev, &task->smp_task.smp_req,1 , +PCI_DMA_FROMDEVICE ); +pci_unmap_sg (a->ha->pcidev, &task->smp_task.smp_resp,1 , +PCI_DMA_FROMDEVICE ); +} + +/* ---------- SSP ---------- */ + +static int asd_build_ssp_ascb (struct asd_ascb *ascb,struct sas_task *task, +unsigned long gfp_flags) +{ +struct domain_device *dev = task->dev; +struct scb *scb; +int res =0 ; + + scb = ascb->scb; + + scb->header.opcode =INITIATE_SSP_TASK ; + + scb->ssp_task.proto_conn_rate = (1 <<4 );/* SSP */ + scb->ssp_task.proto_conn_rate |= dev->linkrate; + scb->ssp_task.total_xfer_len =cpu_to_le32 (task->total_xfer_len); + scb->ssp_task.ssp_frame.frame_type =SSP_DATA ; +memcpy (scb->ssp_task.ssp_frame.hashed_dest_addr, dev->hashed_sas_addr, +HASHED_SAS_ADDR_SIZE ); +memcpy (scb->ssp_task.ssp_frame.hashed_src_addr, + dev->port->ha->hashed_sas_addr,HASHED_SAS_ADDR_SIZE ); + scb->ssp_task.ssp_frame.tptt =cpu_to_be16 (0xFFFF ); + +memcpy (scb->ssp_task.ssp_cmd.lun, task->ssp_task.LUN,8 ); +if (task->ssp_task.enable_first_burst) + scb->ssp_task.ssp_cmd.efb_prio_attr |=EFB_MASK ; + scb->ssp_task.ssp_cmd.efb_prio_attr |= (task->ssp_task.task_prio <<3 ); + scb->ssp_task.ssp_cmd.efb_prio_attr |= (task->ssp_task.task_attr &7 ); +memcpy (scb->ssp_task.ssp_cmd.cdb, task->ssp_task.cdb,16 ); + + scb->ssp_task.sister_scb =cpu_to_le16 (0xFFFF ); + scb->ssp_task.conn_handle =cpu_to_le16 ( + (u16 )(unsigned long )dev->lldd_dev); + scb->ssp_task.data_dir = data_dir_flags[task->data_dir]; + scb->ssp_task.retry_count = scb->ssp_task.retry_count; + + ascb->tasklet_complete = asd_task_tasklet_complete; + + res =asd_map_scatterlist (task, scb->ssp_task.sg_element, gfp_flags); + +return res; +} + +static void asd_unbuild_ssp_ascb (struct asd_ascb *a) +{ +asd_unmap_scatterlist (a); +} + +/* ---------- Execute Task ---------- */ + +static inline int asd_can_queue (struct asd_ha_struct *asd_ha,int num) +{ +int res =0 ; +unsigned long flags; + +spin_lock_irqsave (&asd_ha->seq.pend_q_lock, flags); +if ((asd_ha->seq.can_queue - num) <0 ) + res = -SAS_QUEUE_FULL ; +else + asd_ha->seq.can_queue -= num; +spin_unlock_irqrestore (&asd_ha->seq.pend_q_lock, flags); + +return res; +} + +int asd_execute_task (struct sas_task *task,const int num, +unsigned long gfp_flags) +{ +int res =0 ; +LIST_HEAD (alist); +struct sas_task *t = task; +struct asd_ascb *ascb =NULL , *a; +struct asd_ha_struct *asd_ha = task->dev->port->ha->lldd_ha; + + res =asd_can_queue (asd_ha, num); +if (res) +return res; + + res = num; + ascb =asd_ascb_alloc_list (asd_ha, &res, gfp_flags); +if (res) { + res = -ENOMEM ; + goto out_err; + } + +__list_add (&alist, ascb->list.prev, &ascb->list); +list_for_each_entry (a, &alist, list) { + a->uldd_task = t; + t->lldd_task = a; + t = list_entry(t->list.next, struct sas_task, list); + } +list_for_each_entry (a, &alist, list) { + t = a->uldd_task; + a->uldd_timer =1 ; +if (t->task_proto &SAS_PROTO_STP ) + t->task_proto =SAS_PROTO_STP ; +switch (t->task_proto) { +case SATA_PROTO : +case SAS_PROTO_STP : + res =asd_build_ata_ascb (a, t, gfp_flags); +break ; +case SAS_PROTO_SMP : + res =asd_build_smp_ascb (a, t, gfp_flags); +break ; +case SAS_PROTO_SSP : + res =asd_build_ssp_ascb (a, t, gfp_flags); +break ; +default : +asd_printk ("unknown sas_task proto: 0x%x\n" , + t->task_proto); + res = -ENOMEM ; +break ; + } +if (res) + goto out_err_unmap; + } +list_del_init (&alist); + + res =asd_post_ascb_list (asd_ha, ascb, num); +if (unlikely (res)) { + a =NULL ; +__list_add (&alist, ascb->list.prev, &ascb->list); + goto out_err_unmap; + } + +return 0 ; +out_err_unmap: + { +struct asd_ascb *b = a; +list_for_each_entry (a, &alist, list) { +if (a == b) +break ; + t = a->uldd_task; +switch (t->task_proto) { +case SATA_PROTO : +case SAS_PROTO_STP : +asd_unbuild_ata_ascb (a); +break ; +case SAS_PROTO_SMP : +asd_unbuild_smp_ascb (a); +break ; +case SAS_PROTO_SSP : +asd_unbuild_ssp_ascb (a); +default : +break ; + } + t->lldd_task =NULL ; + } + } +list_del_init (&alist); +out_err: +if (ascb) +asd_ascb_free_list (ascb); +asd_can_dequeue (asd_ha, num); +return res; +}