在命令传输过程中,HBA 驱动程序将执行以下步骤:
调用 SCSI HBA 驱动程序的 tran_start(9E) 入口点可将 SCSI 命令传输到指定地址的目标。在目标驱动程序通过 HBA 驱动程序的 tran_init_pkt(9E) 入口点分配的 scsi_pkt(9S) 结构中,对 SCSI 命令进行了完整描述。如果该命令涉及数据传送,则还必须为 scsi_pkt(9S) 结构分配 DMA 资源。
目标驱动程序调用 scsi_transport(9F) 时,将会调用 tran_start() 入口点。
tran_start() 应执行基本错误检查以及命令要求的任何初始化操作。scsi_pkt(9S) 结构的 pkt_flags 字段中的 FLAG_NOINTR 标志会影响 tran_start() 的行为。如果未设置 FLAG_NOINTR ,则 tran_start() 必须将命令排队以在硬件上执行并立即返回。完成命令后,HBA 驱动程序应调用 pkt 完成例程。
如果设置了 FLAG_NOINTR,则 HBA 驱动程序不会调用 pkt 完成例程。
以下示例说明如何处理 tran_start(9E) 入口点。ISP 硬件按目标设备提供了队列。对于只能管理一个活动的未完成命令的设备,驱动程序通常需要管理按目标的队列。然后,驱动程序会在完成当前命令后以循环方式启动新命令。
static int isp_scsi_start( struct scsi_address *ap, struct scsi_pkt *pkt) { struct isp_cmd *sp; struct isp *isp; struct isp_request *req; u_long cur_lbolt; int xfercount; int rval = TRAN_ACCEPT; int i; sp = (struct isp_cmd *)pkt->pkt_ha_private; isp = (struct isp *)ap->a_hba_tran->tran_hba_private; sp->cmd_flags = (sp->cmd_flags & ~CFLAG_TRANFLAG) | CFLAG_IN_TRANSPORT; pkt->pkt_reason = CMD_CMPLT; /* * set up request in cmd_isp_request area so it is ready to * go once we have the request mutex */ req = &sp->cmd_isp_request; req->req_header.cq_entry_type = CQ_TYPE_REQUEST; req->req_header.cq_entry_count = 1; req->req_header.cq_flags = 0; req->req_header.cq_seqno = 0; req->req_reserved = 0; req->req_token = (opaque_t)sp; req->req_target = TGT(sp); req->req_lun_trn = LUN(sp); req->req_time = pkt->pkt_time; ISP_SET_PKT_FLAGS(pkt->pkt_flags, req->req_flags); /* * Set up data segments for dma transfers. */ if (sp->cmd_flags & CFLAG_DMAVALID) { if (sp->cmd_flags & CFLAG_CMDIOPB) { (void) ddi_dma_sync(sp->cmd_dmahandle, sp->cmd_dma_offset, sp->cmd_dma_len, DDI_DMA_SYNC_FORDEV); } ASSERT(sp->cmd_cookiecnt > 0 && sp->cmd_cookiecnt <= ISP_NDATASEGS); xfercount = 0; req->req_seg_count = sp->cmd_cookiecnt; for (i = 0; i < sp->cmd_cookiecnt; i++) { req->req_dataseg[i].d_count = sp->cmd_dmacookies[i].dmac_size; req->req_dataseg[i].d_base = sp->cmd_dmacookies[i].dmac_address; xfercount += sp->cmd_dmacookies[i].dmac_size; } for (; i < ISP_NDATASEGS; i++) { req->req_dataseg[i].d_count = 0; req->req_dataseg[i].d_base = 0; } pkt->pkt_resid = xfercount; if (sp->cmd_flags & CFLAG_DMASEND) { req->req_flags |= ISP_REQ_FLAG_DATA_WRITE; } else { req->req_flags |= ISP_REQ_FLAG_DATA_READ; } } else { req->req_seg_count = 0; req->req_dataseg[0].d_count = 0; } /* * Set up cdb in the request */ req->req_cdblen = sp->cmd_cdblen; bcopy((caddr_t)pkt->pkt_cdbp, (caddr_t)req->req_cdb, sp->cmd_cdblen); /* * Start the cmd. If NO_INTR, must poll for cmd completion. */ if ((pkt->pkt_flags & FLAG_NOINTR) == 0) { mutex_enter(ISP_REQ_MUTEX(isp)); rval = isp_i_start_cmd(isp, sp); mutex_exit(ISP_REQ_MUTEX(isp)); } else { rval = isp_i_polled_cmd_start(isp, sp); } return (rval); }
中断处理程序必须检查设备状态,以确保设备正在生成相关中断。另外,中断处理程序还必须检查出现的全部错误,并传送设备生成的所有中断。
如果传送了数据,则应检查硬件以确定实际传送的数据量。scsi_pkt(9S) 结构中的 pkt_resid 字段应该设置为剩余未传送的数据量。
通过 tran_init_pkt(9E) 分配 DMA 资源时,使用 PKT_CONSISTENT 标志标记的命令需要特殊处理。HBA 驱动程序必须确保在执行目标驱动程序的命令完成回调之前,正确同步针对该命令的数据传送。
完成命令后,需要按照以下两个要求执行操作:
如果已将新命令排入队列,请尽快在硬件上启动该命令。
调用命令完成回调。目标驱动程序已在 scsi_pkt(9S) 结构中设置了回调,用于通知目标驱动程序完成命令的时间。
如有可能,在调用 PKT_COMP 命令完成回调之前,请在硬件上启动新命令。该命令完成处理可能需要大量时间。通常,目标驱动程序会调用函数(如 biodone(9F) 和可能会调用的 scsi_transport(9F))来启动新命令。
如果此中断是由该驱动程序请求的,则中断处理程序必须返回 DDI_INTR_CLAIMED。否则,处理程序会返回 DDI_INTR_UNCLAIMED。
以下示例说明了 SCSI HBA isp 驱动程序的中断处理程序。如果 attach(9E) 中添加了中断处理程序,则应设置 caddr_t 参数。此参数通常是一个指向按实例分配的状态结构的指针。
static u_int isp_intr(caddr_t arg) { struct isp_cmd *sp; struct isp_cmd *head, *tail; u_short response_in; struct isp_response *resp; struct isp *isp = (struct isp *)arg; struct isp_slot *isp_slot; int n; if (ISP_INT_PENDING(isp) == 0) { return (DDI_INTR_UNCLAIMED); } do { again: /* * head list collects completed packets for callback later */ head = tail = NULL; /* * Assume no mailbox events (e.g., mailbox cmds, asynch * events, and isp dma errors) as common case. */ if (ISP_CHECK_SEMAPHORE_LOCK(isp) == 0) { mutex_enter(ISP_RESP_MUTEX(isp)); /* * Loop through completion response queue and post * completed pkts. Check response queue again * afterwards in case there are more. */ isp->isp_response_in = response_in = ISP_GET_RESPONSE_IN(isp); /* * Calculate the number of requests in the queue */ n = response_in - isp->isp_response_out; if (n < 0) { n = ISP_MAX_REQUESTS - isp->isp_response_out + response_in; } while (n-- > 0) { ISP_GET_NEXT_RESPONSE_OUT(isp, resp); sp = (struct isp_cmd *)resp->resp_token; /* * Copy over response packet in sp */ isp_i_get_response(isp, resp, sp); } if (head) { tail->cmd_forw = sp; tail = sp; tail->cmd_forw = NULL; } else { tail = head = sp; sp->cmd_forw = NULL; } ISP_SET_RESPONSE_OUT(isp); ISP_CLEAR_RISC_INT(isp); mutex_exit(ISP_RESP_MUTEX(isp)); if (head) { isp_i_call_pkt_comp(isp, head); } } else { if (isp_i_handle_mbox_cmd(isp) != ISP_AEN_SUCCESS) { return (DDI_INTR_CLAIMED); } /* * if there was a reset then check the response * queue again */ goto again; } } while (ISP_INT_PENDING(isp)); return (DDI_INTR_CLAIMED); } static void isp_i_call_pkt_comp( struct isp *isp, struct isp_cmd *head) { struct isp *isp; struct isp_cmd *sp; struct scsi_pkt *pkt; struct isp_response *resp; u_char status; while (head) { sp = head; pkt = sp->cmd_pkt; head = sp->cmd_forw; ASSERT(sp->cmd_flags & CFLAG_FINISHED); resp = &sp->cmd_isp_response; pkt->pkt_scbp[0] = (u_char)resp->resp_scb; pkt->pkt_state = ISP_GET_PKT_STATE(resp->resp_state); pkt->pkt_statistics = (u_long) ISP_GET_PKT_STATS(resp->resp_status_flags); pkt->pkt_resid = (long)resp->resp_resid; /* * If data was xferred and this is a consistent pkt, * do a dma sync */ if ((sp->cmd_flags & CFLAG_CMDIOPB) && (pkt->pkt_state & STATE_XFERRED_DATA)) { (void) ddi_dma_sync(sp->cmd_dmahandle, sp->cmd_dma_offset, sp->cmd_dma_len, DDI_DMA_SYNC_FORCPU); } sp->cmd_flags = (sp->cmd_flags & ~CFLAG_IN_TRANSPORT) | CFLAG_COMPLETED; /* * Call packet completion routine if FLAG_NOINTR is not set. */ if (((pkt->pkt_flags & FLAG_NOINTR) == 0) && pkt->pkt_comp) { (*pkt->pkt_comp)(pkt); } } }
HBA 驱动程序负责强制执行超时设置。除非在 scsi_pkt(9S) 结构中指定了零超时,否则必须在指定时间内完成命令。
如果命令超时,则 HBA 驱动程序应将 scsi_pkt(9S) 标记为 pkt_reason=CMD_TIMEOUT,而且将 pkt_statistics 的值设置为与 STAT_TIMEOUT 进行或运算所得的值。另外,HBA 驱动程序还应尝试恢复目标和总线。如果恢复能够成功执行,则驱动程序应使用 pkt_statistics 与 STAT_BUS_RESET 或 STAT_DEV_RESET 进行或运算所得的值标记 scsi_pkt(9S)。
完成恢复尝试后,HBA 驱动程序应调用命令完成回调。
如果恢复不成功或未尝试恢复,则目标驱动程序可能会通过调用 scsi_reset(9F) 尝试从超时中恢复。
ISP 硬件直接管理命令超时,并会返回超时命令的必需状态。isp 样例驱动程序的超时处理程序每 60 秒才检查一次活动命令的超时状态。
isp 样例驱动程序使用 timeout(9F) 功能来安排内核每 60 秒调用一次超时处理程序。caddr_t 参数是在执行 attach(9E) 期间初始化超时值时设置的参数。在这种情况下,caddr_t 参数是一个指向按驱动程序实例分配的状态结构的指针。
如果 ISP 硬件未将超时命令作为超时项返回,则表明出现了问题。该硬件将无法正常工作并需要重置。