HBA 驱动程序可以通过 SCSA 接口与目标驱动程序协同工作。SCSA 接口要求 HBA 驱动程序提供许多可通过 scsi_hba_tran(9S) 结构调用的入口点。
这些入口点分为以下五个功能组:
目标驱动程序实例初始化
资源分配和取消资源分配
命令传输
功能管理
中止和重置处理
动态重新配置
|
功能组 |
组内入口点 |
说明 |
|---|---|---|
|
目标驱动程序实例初始化 |
执行按目标的初始化(可选) |
|
|
|
探测 SCSI 总线是否存在目标(可选) |
|
|
|
执行按目标的取消分配(可选) |
|
|
资源分配 |
分配 SCSI 包和 DMA 资源 |
|
|
|
释放 SCSI 包和 DMA 资源 |
|
|
|
执行 DMA 前后同步内存 |
|
|
|
释放 DMA 资源 |
|
|
命令传输 |
传输 SCSI 命令 |
|
|
功能管理 |
查询功能值 |
|
|
|
设置功能值 |
|
|
中止和重置 |
中止未完成的 SCSI 命令 |
|
|
|
重置目标设备或 SCSI 总线 |
|
|
|
重置 SCSI 总线 |
|
|
|
请求向目标发出总线重置通知(可选) |
|
|
动态重新配置 |
停止总线上的活动 |
|
|
|
恢复总线上的活动 |
以下各节介绍了目标入口点。
使用 tran_tgt_init(9E) 入口点,HBA 可以分配和初始化按目标的任何资源。此外,tran_tgt_init() 还允许 HBA 将设备地址限定为该特定 HBA 的有效且可支持的地址。如果返回 DDI_FAILURE,则不会探测或附加该设备的目标驱动程序实例。
无需使用 tran_tgt_init()。如果未提供 tran_tgt_init(),则框架会尝试探测和附加相应目标驱动程序的所有可能实例。
static int
isp_tran_tgt_init(
dev_info_t *hba_dip,
dev_info_t *tgt_dip,
scsi_hba_tran_t *tran,
struct scsi_device *sd)
{
return ((sd->sd_address.a_target < N_ISP_TARGETS_WIDE &&
sd->sd_address.a_lun < 8) ? DDI_SUCCESS : DDI_FAILURE);
}
使用 tran_tgt_probe(9E) 入口点,HBA 可以自定义 scsi_probe(9F) 的操作(如有必要)。仅当目标驱动程序调用 scsi_probe() 时,才会调用此入口点。
HBA 驱动程序可以通过调用 scsi_hba_probe(9F) 并返回其返回值来保留 scsi_probe () 的正常操作。
无需使用此入口点。如果不需要此入口点,则 HBA 驱动程序应将 scsi_hba_tran(9S) 结构中的 tran_tgt_probe 向量设置为指向 scsi_hba_probe()。
scsi_probe() 可用于分配 scsi_inquiry(9S) 结构,并将 scsi_device(9S) 结构的 sd_inq 字段设置为指向 scsi_inquiry 中的数据。scsi_hba_probe() 可自动处理此任务。scsi_unprobe(9F) 随后将释放 scsi_inquiry 数据。
除分配 scsi_inquiry 数据以外,tran_tgt_probe() 必须是无状态的,因为同一 SCSI 设备可能会多次调用 tran_tgt_probe()。通常,scsi_inquiry 数据的分配通过 scsi_hba_probe() 来处理。
scsi_inquiry(9S) 结构的分配通过 scsi_hba_probe() 自动处理。此信息仅在需要自定义 scsi_probe() 处理时才有意义。
static int
isp_tran_tgt_probe(
struct scsi_device *sd,
int (*callback)())
{
/*
* Perform any special probe customization needed.
* Normal probe handling.
*/
return (scsi_hba_probe(sd, callback));
}
使用 tran_tgt_free(9E) 入口点,HBA 可以执行目标实例的所有取消分配或清理过程。此入口点是可选的。
static void
isp_tran_tgt_free(
dev_info_t *hba_dip,
dev_info_t *tgt_dip,
scsi_hba_tran_t *hba_tran,
struct scsi_device *sd)
{
/*
* Undo any special per-target initialization done
* earlier in tran_tgt_init(9F) and tran_tgt_probe(9F)
*/
}
以下各节讨论了资源分配。
tran_init_pkt(9E) 入口点可为目标驱动程序请求分配和初始化 scsi_pkt(9S) 结构和 DMA 资源。
目标驱动程序调用 SCSA 函数 scsi_init_pkt(9F) 时,将会调用 tran_init_pkt(9E) 入口点。
每次调用 tran_init_pkt(9E) 入口点时,都会请求执行以下三种可能服务中的一种或多种:
分配和初始化 scsi_pkt(9S) 结构
分配用于数据传送的 DMA 资源
重新分配用于下一个数据传送部分的 DMA 资源
如果 pkt 为 NULL,则 tran_init_pkt(9E) 入口点必须通过 scsi_hba_pkt_alloc(9F) 分配 scsi_pkt(9S) 结构。
scsi_hba_pkt_alloc(9F) 可为以下各项分配空间:
长度为 cmdlen 的 SCSI CDB
长度为 statuslen 的 SCSI 状态的完成区
长度为 tgtlen 的按包的目标驱动程序专用数据区
长度为 hbalen 的按包的 HBA 驱动程序专用数据区
除以下成员外,必须将 scsi_pkt(9S) 结构成员(包括 pkt)初始化为零:
pkt_scbp-状态完成
pkt_cdbp-CDB
pkt_ha_private-HBA 驱动程序专用数据
pkt_private-目标驱动程序专用数据
这些成员是指向存储字段值的内存空间的指针,如下图所示。有关更多信息,请参阅scsi_pkt 结构 (HBA)。

以下示例说明了 scsi_pkt 结构的分配和初始化。
static struct scsi_pkt *
isp_scsi_init_pkt(
struct scsi_address *ap,
struct scsi_pkt *pkt,
struct buf *bp,
int cmdlen,
int statuslen,
int tgtlen,
int flags,
int (*callback)(),
caddr_t arg)
{
struct isp_cmd *sp;
struct isp *isp;
struct scsi_pkt *new_pkt;
ASSERT(callback == NULL_FUNC || callback == SLEEP_FUNC);
isp = (struct isp *)ap->a_hba_tran->tran_hba_private;
/*
* First step of isp_scsi_init_pkt: pkt allocation
*/
if (pkt == NULL) {
pkt = scsi_hba_pkt_alloc(isp->isp_dip, ap, cmdlen,
statuslen, tgtlen, sizeof (struct isp_cmd),
callback, arg);
if (pkt == NULL) {
return (NULL);
}
sp = (struct isp_cmd *)pkt->pkt_ha_private;
/*
* Initialize the new pkt
*/
sp->cmd_pkt = pkt;
sp->cmd_flags = 0;
sp->cmd_scblen = statuslen;
sp->cmd_cdblen = cmdlen;
sp->cmd_dmahandle = NULL;
sp->cmd_ncookies = 0;
sp->cmd_cookie = 0;
sp->cmd_cookiecnt = 0;
sp->cmd_nwin = 0;
pkt->pkt_address = *ap;
pkt->pkt_comp = (void (*)())NULL;
pkt->pkt_flags = 0;
pkt->pkt_time = 0;
pkt->pkt_resid = 0;
pkt->pkt_statistics = 0;
pkt->pkt_reason = 0;
new_pkt = pkt;
} else {
sp = (struct isp_cmd *)pkt->pkt_ha_private;
new_pkt = NULL;
}
/*
* Second step of isp_scsi_init_pkt: dma allocation/move
*/
if (bp && bp->b_bcount != 0) {
if (sp->cmd_dmahandle == NULL) {
if (isp_i_dma_alloc(isp, pkt, bp,
flags, callback) == 0) {
if (new_pkt) {
scsi_hba_pkt_free(ap, new_pkt);
}
return ((struct scsi_pkt *)NULL);
}
} else {
ASSERT(new_pkt == NULL);
if (isp_i_dma_move(isp, pkt, bp) == 0) {
return ((struct scsi_pkt *)NULL);
}
}
}
return (pkt);
}
如果符合以下条件,则 tran_init_pkt(9E) 入口点必须分配用于数据传送的 DMA 资源:
bp 不为 null。
bp->b_bcount 不为零。
尚未为此 scsi_pkt(9S) 分配 DMA 资源。
HBA 驱动程序需要跟踪如何为特定命令分配 DMA 资源。按包的 HBA 驱动程序专用数据的标志位或 DMA 句柄可能会进行此分配。
使用 pkt 中的 PKT_DMA_PARTIAL 标志,目标驱动程序可以将数据传送按多个 SCSI 命令分类以适应整个请求。如果 HBA 硬件的分散/集中功能或系统 DMA 资源无法完成单个 SCSI 命令的请求,则此方法会非常有用。
使用 PKT_DMA_PARTIAL 标志,HBA 驱动程序可以设置 DDI_DMA_PARTIAL 标志。DDI_DMA_PARTIAL 标志有助于分配此 SCSI 命令的 DMA 资源。例如,ddi_dma_buf_bind_handle(9F) 命令可用于分配 DMA 资源。分配 DMA 资源时使用的 DMA 属性应准确说明针对 HBA 硬件执行 DMA 的能力设定的约束。如果系统只能为部分请求分配 DMA 资源,则 ddi_dma_buf_bind_handle(9F) 将返回 DDI_DMA_PARTIAL_MAP。
tran_init_pkt(9E) 入口点必须在字段 pkt_resid 中返回未为此传送分配的 DMA 资源量。
目标驱动程序可以请求 tran_init_pkt(9E) 同时为该 pkt 分配 scsi_pkt(9S) 结构和 DMA 资源。在这种情况下,如果 HBA 驱动程序无法分配 DMA 资源,则该驱动程序必须在返回前释放已分配的 scsi_pkt(9S)。scsi_pkt(9S) 必须通过调用 scsi_hba_pkt_free(9F) 进行释放。
目标驱动程序可能会首先分配 scsi_pkt(9S),随后再为此 pkt 分配 DMA 资源。在这种情况下,如果 HBA 驱动程序无法分配 DMA 资源,则该驱动程序决不能释放 pkt。在这种情况下,目标驱动程序负责释放 pkt。
static int
isp_i_dma_alloc(
struct isp *isp,
struct scsi_pkt *pkt,
struct buf *bp,
int flags,
int (*callback)())
{
struct isp_cmd *sp = (struct isp_cmd *)pkt->pkt_ha_private;
int dma_flags;
ddi_dma_attr_t tmp_dma_attr;
int (*cb)(caddr_t);
int i;
ASSERT(callback == NULL_FUNC || callback == SLEEP_FUNC);
if (bp->b_flags & B_READ) {
sp->cmd_flags &= ~CFLAG_DMASEND;
dma_flags = DDI_DMA_READ;
} else {
sp->cmd_flags |= CFLAG_DMASEND;
dma_flags = DDI_DMA_WRITE;
}
if (flags & PKT_CONSISTENT) {
sp->cmd_flags |= CFLAG_CMDIOPB;
dma_flags |= DDI_DMA_CONSISTENT;
}
if (flags & PKT_DMA_PARTIAL) {
dma_flags |= DDI_DMA_PARTIAL;
}
tmp_dma_attr = isp_dma_attr;
tmp_dma_attr.dma_attr_burstsizes = isp->isp_burst_size;
cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT :
DDI_DMA_SLEEP;
if ((i = ddi_dma_alloc_handle(isp->isp_dip, &tmp_dma_attr,
cb, 0, &sp->cmd_dmahandle)) != DDI_SUCCESS) {
switch (i) {
case DDI_DMA_BADATTR:
bioerror(bp, EFAULT);
return (0);
case DDI_DMA_NORESOURCES:
bioerror(bp, 0);
return (0);
}
}
i = ddi_dma_buf_bind_handle(sp->cmd_dmahandle, bp, dma_flags,
cb, 0, &sp->cmd_dmacookies[0], &sp->cmd_ncookies);
switch (i) {
case DDI_DMA_PARTIAL_MAP:
if (ddi_dma_numwin(sp->cmd_dmahandle, &sp->cmd_nwin) ==
DDI_FAILURE) {
cmn_err(CE_PANIC, "ddi_dma_numwin() failed\n");
}
if (ddi_dma_getwin(sp->cmd_dmahandle, sp->cmd_curwin,
&sp->cmd_dma_offset, &sp->cmd_dma_len,
&sp->cmd_dmacookies[0], &sp->cmd_ncookies) ==
DDI_FAILURE) {
cmn_err(CE_PANIC, "ddi_dma_getwin() failed\n");
}
goto get_dma_cookies;
case DDI_DMA_MAPPED:
sp->cmd_nwin = 1;
sp->cmd_dma_len = 0;
sp->cmd_dma_offset = 0;
get_dma_cookies:
i = 0;
sp->cmd_dmacount = 0;
for (;;) {
sp->cmd_dmacount += sp->cmd_dmacookies[i++].dmac_size;
if (i == ISP_NDATASEGS || i == sp->cmd_ncookies)
break;
ddi_dma_nextcookie(sp->cmd_dmahandle,
&sp->cmd_dmacookies[i]);
}
sp->cmd_cookie = i;
sp->cmd_cookiecnt = i;
sp->cmd_flags |= CFLAG_DMAVALID;
pkt->pkt_resid = bp->b_bcount - sp->cmd_dmacount;
return (1);
case DDI_DMA_NORESOURCES:
bioerror(bp, 0);
break;
case DDI_DMA_NOMAPPING:
bioerror(bp, EFAULT);
break;
case DDI_DMA_TOOBIG:
bioerror(bp, EINVAL);
break;
case DDI_DMA_INUSE:
cmn_err(CE_PANIC, "ddi_dma_buf_bind_handle:"
" DDI_DMA_INUSE impossible\n");
default:
cmn_err(CE_PANIC, "ddi_dma_buf_bind_handle:"
" 0x%x impossible\n", i);
}
ddi_dma_free_handle(&sp->cmd_dmahandle);
sp->cmd_dmahandle = NULL;
sp->cmd_flags &= ~CFLAG_DMAVALID;
return (0);
}
对于先前分配的包含待传送数据的包,tran_init_pkt(9E) 入口点在满足以下条件时必须重新分配 DMA 资源:
已分配部分 DMA 资源。
先前调用 tran_init_pkt(9E) 时返回了非零的 pkt_resid。
bp 不为 null。
bp->b_bcount 不为零。
重新分配下一个传送部分的 DMA 资源时,tran_init_pkt(9E) 必须在字段 pkt_resid 中返回未为此传送分配的 DMA 资源量。
如果在尝试移动 DMA 资源时出现错误,则 tran_init_pkt(9E) 决不能释放 scsi_pkt(9S)。在这种情况下,目标驱动程序负责释放包。
如果回调参数为 NULL_FUNC,则 tran_init_pkt(9E) 入口点决不能休眠或调用可能会休眠的任何函数。如果回调参数为 SLEEP_FUNC 并且资源不会立即可用,则 tran_init_pkt(9E) 入口点会休眠。除非无法满足请求,否则 tran_init_pkt() 将休眠,直到资源可用为止。
static int
isp_i_dma_move(
struct isp *isp,
struct scsi_pkt *pkt,
struct buf *bp)
{
struct isp_cmd *sp = (struct isp_cmd *)pkt->pkt_ha_private;
int i;
ASSERT(sp->cmd_flags & CFLAG_COMPLETED);
sp->cmd_flags &= ~CFLAG_COMPLETED;
/*
* If there are no more cookies remaining in this window,
* must move to the next window first.
*/
if (sp->cmd_cookie == sp->cmd_ncookies) {
/*
* For small pkts, leave things where they are
*/
if (sp->cmd_curwin == sp->cmd_nwin && sp->cmd_nwin == 1)
return (1);
/*
* At last window, cannot move
*/
if (++sp->cmd_curwin >= sp->cmd_nwin)
return (0);
if (ddi_dma_getwin(sp->cmd_dmahandle, sp->cmd_curwin,
&sp->cmd_dma_offset, &sp->cmd_dma_len,
&sp->cmd_dmacookies[0], &sp->cmd_ncookies) ==
DDI_FAILURE)
return (0);
sp->cmd_cookie = 0;
} else {
/*
* Still more cookies in this window - get the next one
*/
ddi_dma_nextcookie(sp->cmd_dmahandle,
&sp->cmd_dmacookies[0]);
}
/*
* Get remaining cookies in this window, up to our maximum
*/
i = 0;
for (;;) {
sp->cmd_dmacount += sp->cmd_dmacookies[i++].dmac_size;
sp->cmd_cookie++;
if (i == ISP_NDATASEGS ||
sp->cmd_cookie == sp->cmd_ncookies)
break;
ddi_dma_nextcookie(sp->cmd_dmahandle,
&sp->cmd_dmacookies[i]);
}
sp->cmd_cookiecnt = i;
pkt->pkt_resid = bp->b_bcount - sp->cmd_dmacount;
return (1);
}
tran_destroy_pkt(9E) 入口点是用于取消分配 scsi_pkt(9S) 结构的 HBA 驱动程序函数。目标驱动程序调用 scsi_destroy_pkt(9F) 时,将会调用 tran_destroy_pkt() 入口点。
tran_destroy_pkt() 入口点必须释放已为包分配的所有 DMA 资源。如果释放了 DMA 资源并且所有高速缓存的数据在完成传送后仍然保留,则会进行隐式 DMA 同步。tran_destroy_pkt() 入口点通过调用 scsi_hba_pkt_free(9F) 释放 SCSI 包。
static void
isp_scsi_destroy_pkt(
struct scsi_address *ap,
struct scsi_pkt *pkt)
{
struct isp_cmd *sp = (struct isp_cmd *)pkt->pkt_ha_private;
/*
* Free the DMA, if any
*/
if (sp->cmd_flags & CFLAG_DMAVALID) {
sp->cmd_flags &= ~CFLAG_DMAVALID;
(void) ddi_dma_unbind_handle(sp->cmd_dmahandle);
ddi_dma_free_handle(&sp->cmd_dmahandle);
sp->cmd_dmahandle = NULL;
}
/*
* Free the pkt
*/
scsi_hba_pkt_free(ap, pkt);
}
tran_sync_pkt(9E) 入口点可在 DMA 传送前后同步为 scsi_pkt(9S) 结构分配的 DMA 对象。目标驱动程序调用 scsi_sync_pkt(9F) 时,将会调用 tran_sync_pkt() 入口点。
如果数据传送方向是从设备到内存的 DMA 读取,则 tran_sync_pkt() 必须同步 CPU 的数据视图。如果数据传送方向是从内存到设备的 DMA 写入,则 tran_sync_pkt() 必须同步设备的数据视图。
static void
isp_scsi_sync_pkt(
struct scsi_address *ap,
struct scsi_pkt *pkt)
{
struct isp_cmd *sp = (struct isp_cmd *)pkt->pkt_ha_private;
if (sp->cmd_flags & CFLAG_DMAVALID) {
(void)ddi_dma_sync(sp->cmd_dmahandle, sp->cmd_dma_offset,
sp->cmd_dma_len,
(sp->cmd_flags & CFLAG_DMASEND) ?
DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU);
}
}
tran_dmafree(9E) 入口点可取消分配已为 scsi_pkt(9S) 结构分配的 DMA 资源。目标驱动程序调用 scsi_dmafree(9F) 时,将会调用 tran_dmafree() 入口点。
tran_dmafree() 必须仅释放为 scsi_pkt(9S) 结构分配的 DMA 资源,而不释放 scsi_pkt(9S) 本身。释放 DMA 资源时,将隐式执行 DMA 同步。
scsi_pkt(9S) 在单独请求 tran_destroy_pkt(9E) 时释放。由于 tran_destroy_pkt() 还必须释放 DMA 资源,因此 HBA 驱动程序必须准确记录 scsi_pkt() 结构是否分配了 DMA 资源。
static void
isp_scsi_dmafree(
struct scsi_address *ap,
struct scsi_pkt *pkt)
{
struct isp_cmd *sp = (struct isp_cmd *)pkt->pkt_ha_private;
if (sp->cmd_flags & CFLAG_DMAVALID) {
sp->cmd_flags &= ~CFLAG_DMAVALID;
(void)ddi_dma_unbind_handle(sp->cmd_dmahandle);
ddi_dma_free_handle(&sp->cmd_dmahandle);
sp->cmd_dmahandle = NULL;
}
}
在命令传输过程中,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 硬件未将超时命令作为超时项返回,则表明出现了问题。该硬件将无法正常工作并需要重置。
以下各节讨论了功能管理。
SCSI HBA 驱动程序的 tran_getcap(9E) 入口点通过 scsi_ifgetcap(9F) 进行调用。目标驱动程序调用 scsi_ifgetcap() 可确定 SCSA 定义的一组功能中其中一个的当前值。
目标驱动程序可以通过将 whom 参数设置为非零值来请求特定目标的功能的当前设置。whom 值为零表明请求 SCSI 总线或适配器硬件的一般功能的当前设置。
对于未定义的功能或所请求的功能的当前值,tran_getcap() 入口点应返回 -1。
HBA 驱动程序可以使用函数 scsi_hba_lookup_capstr(9F) 来比较功能字符串和已定义功能的标准集。
static int
isp_scsi_getcap(
struct scsi_address *ap,
char *cap,
int whom)
{
struct isp *isp;
int rval = 0;
u_char tgt = ap->a_target;
/*
* We don't allow getting capabilities for other targets
*/
if (cap == NULL || whom == 0) {
return (-1);
}
isp = (struct isp *)ap->a_hba_tran->tran_hba_private;
ISP_MUTEX_ENTER(isp);
switch (scsi_hba_lookup_capstr(cap)) {
case SCSI_CAP_DMA_MAX:
rval = 1 << 24; /* Limit to 16MB max transfer */
break;
case SCSI_CAP_MSG_OUT:
rval = 1;
break;
case SCSI_CAP_DISCONNECT:
if ((isp->isp_target_scsi_options[tgt] &
SCSI_OPTIONS_DR) == 0) {
break;
} else if (
(isp->isp_cap[tgt] & ISP_CAP_DISCONNECT) == 0) {
break;
}
rval = 1;
break;
case SCSI_CAP_SYNCHRONOUS:
if ((isp->isp_target_scsi_options[tgt] &
SCSI_OPTIONS_SYNC) == 0) {
break;
} else if (
(isp->isp_cap[tgt] & ISP_CAP_SYNC) == 0) {
break;
}
rval = 1;
break;
case SCSI_CAP_WIDE_XFER:
if ((isp->isp_target_scsi_options[tgt] &
SCSI_OPTIONS_WIDE) == 0) {
break;
} else if (
(isp->isp_cap[tgt] & ISP_CAP_WIDE) == 0) {
break;
}
rval = 1;
break;
case SCSI_CAP_TAGGED_QING:
if ((isp->isp_target_scsi_options[tgt] &
SCSI_OPTIONS_DR) == 0 ||
(isp->isp_target_scsi_options[tgt] &
SCSI_OPTIONS_TAG) == 0) {
break;
} else if (
(isp->isp_cap[tgt] & ISP_CAP_TAG) == 0) {
break;
}
rval = 1;
break;
case SCSI_CAP_UNTAGGED_QING:
rval = 1;
break;
case SCSI_CAP_PARITY:
if (isp->isp_target_scsi_options[tgt] &
SCSI_OPTIONS_PARITY) {
rval = 1;
}
break;
case SCSI_CAP_INITIATOR_ID:
rval = isp->isp_initiator_id;
break;
case SCSI_CAP_ARQ:
if (isp->isp_cap[tgt] & ISP_CAP_AUTOSENSE) {
rval = 1;
}
break;
case SCSI_CAP_LINKED_CMDS:
break;
case SCSI_CAP_RESET_NOTIFICATION:
rval = 1;
break;
case SCSI_CAP_GEOMETRY:
rval = (64 << 16) | 32;
break;
default:
rval = -1;
break;
}
ISP_MUTEX_EXIT(isp);
return (rval);
}
SCSI HBA 驱动程序的 tran_setcap(9E) 入口点通过 scsi_ifsetcap(9F) 进行调用。目标驱动程序调用 scsi_ifsetcap() 可更改 SCSA 定义的一组功能中其中一个的当前值。
目标驱动程序可能会通过将 whom 参数设置为非零值来请求为特定目标设置新值。whom 值为零通常表明请求为 SCSI 总线或适配器硬件设置新值。
tran_setcap() 应相应地返回以下值:
-1,如果功能未定义
0,如果 HBA 驱动程序无法将功能设置为请求值
1,如果 HBA 驱动程序可以将功能设置为请求值
HBA 驱动程序可以使用函数 scsi_hba_lookup_capstr(9F) 来比较功能字符串和已定义功能的标准集。
static int
isp_scsi_setcap(
struct scsi_address *ap,
char *cap,
int value,
int whom)
{
struct isp *isp;
int rval = 0;
u_char tgt = ap->a_target;
int update_isp = 0;
/*
* We don't allow setting capabilities for other targets
*/
if (cap == NULL || whom == 0) {
return (-1);
}
isp = (struct isp *)ap->a_hba_tran->tran_hba_private;
ISP_MUTEX_ENTER(isp);
switch (scsi_hba_lookup_capstr(cap)) {
case SCSI_CAP_DMA_MAX:
case SCSI_CAP_MSG_OUT:
case SCSI_CAP_PARITY:
case SCSI_CAP_UNTAGGED_QING:
case SCSI_CAP_LINKED_CMDS:
case SCSI_CAP_RESET_NOTIFICATION:
/*
* None of these are settable through
* the capability interface.
*/
break;
case SCSI_CAP_DISCONNECT:
if ((isp->isp_target_scsi_options[tgt] &
SCSI_OPTIONS_DR) == 0) {
break;
} else {
if (value) {
isp->isp_cap[tgt] |= ISP_CAP_DISCONNECT;
} else {
isp->isp_cap[tgt] &= ~ISP_CAP_DISCONNECT;
}
}
rval = 1;
break;
case SCSI_CAP_SYNCHRONOUS:
if ((isp->isp_target_scsi_options[tgt] &
SCSI_OPTIONS_SYNC) == 0) {
break;
} else {
if (value) {
isp->isp_cap[tgt] |= ISP_CAP_SYNC;
} else {
isp->isp_cap[tgt] &= ~ISP_CAP_SYNC;
}
}
rval = 1;
break;
case SCSI_CAP_TAGGED_QING:
if ((isp->isp_target_scsi_options[tgt] &
SCSI_OPTIONS_DR) == 0 ||
(isp->isp_target_scsi_options[tgt] &
SCSI_OPTIONS_TAG) == 0) {
break;
} else {
if (value) {
isp->isp_cap[tgt] |= ISP_CAP_TAG;
} else {
isp->isp_cap[tgt] &= ~ISP_CAP_TAG;
}
}
rval = 1;
break;
case SCSI_CAP_WIDE_XFER:
if ((isp->isp_target_scsi_options[tgt] &
SCSI_OPTIONS_WIDE) == 0) {
break;
} else {
if (value) {
isp->isp_cap[tgt] |= ISP_CAP_WIDE;
} else {
isp->isp_cap[tgt] &= ~ISP_CAP_WIDE;
}
}
rval = 1;
break;
case SCSI_CAP_INITIATOR_ID:
if (value < N_ISP_TARGETS_WIDE) {
struct isp_mbox_cmd mbox_cmd;
isp->isp_initiator_id = (u_short) value;
/*
* set Initiator SCSI ID
*/
isp_i_mbox_cmd_init(isp, &mbox_cmd, 2, 2,
ISP_MBOX_CMD_SET_SCSI_ID,
isp->isp_initiator_id,
0, 0, 0, 0);
if (isp_i_mbox_cmd_start(isp, &mbox_cmd) == 0) {
rval = 1;
}
}
break;
case SCSI_CAP_ARQ:
if (value) {
isp->isp_cap[tgt] |= ISP_CAP_AUTOSENSE;
} else {
isp->isp_cap[tgt] &= ~ISP_CAP_AUTOSENSE;
}
rval = 1;
break;
default:
rval = -1;
break;
}
ISP_MUTEX_EXIT(isp);
return (rval);
}
以下各节讨论了 SCSI HBA 的中止入口点和重置入口点。
调用 SCSI HBA 驱动程序的 tran_abort(9E) 入口点可中止当前正在传输给特定目标的所有命令。目标驱动程序调用 scsi_abort(9F) 时,将会调用此入口点。
tran_abort() 入口点会尝试中止 pkt 参数表示的命令。如果 pkt 参数为 NULL,则 tran_abort() 会尝试中止传输层中针对特定目标或逻辑单元的所有未完成命令。
每个已成功中止的命令都必须标记为 pkt_reason CMD_ABORTED 以及 pkt_statistics 与 STAT_ABORTED 进行或运算所得的值。
调用 SCSI HBA 驱动程序的 tran_reset(9E) 入口点可重置 SCSI 总线或特定的 SCSI 目标设备。目标驱动程序调用 scsi_reset(9F) 时,将会调用此入口点。
如果级别为 RESET_ALL(),则 tran_reset 入口点必须重置 SCSI 总线。如果级别为 RESET_TARGET,则仅有特定目标或逻辑单元必须重置。
受重置影响的活动命令必须带有 pkt_reason CMD_RESET 标记。重置类型可确定应使用 STAT_BUS_RESET 还是使用 STAT_DEV_RESET 与 pkt_statistics 进行或运算。
在目标上尚未处于活动状态的传输层中的命令必须标记为 pkt_reason CMD_RESET 以及 pkt_statistics 与 STAT_ABORTED 进行或运算所得的值。
tran_bus_reset(9E) 必须重置 SCSI 总线而不重置目标。
#include <sys/scsi/scsi.h> int tran_bus_reset(dev_info_t *hba-dip, int level);
其中:
与 SCSI HBA 关联的指针
必须设置为 RESET_BUS,以便仅重置 SCSI 总线而不重置目标。
执行 HBA 驱动程序的 attach(9E) 的过程中,应初始化 scsi_hba_tran(9S) 结构中的 tran_bus_reset() 向量。该向量应指向用户启动总线重置时将调用的 HBA 入口点。
实现特定于硬件。如果 HBA 驱动程序无法在不影响目标的情况下重置 SCSI 总线,则驱动程序将无法执行 RESET_BUS 或不会初始化此向量。
重置 SCSI 总线时,请使用 tran_reset_notify(9E) 入口点。此函数将请求 SCSI HBA 驱动程序通过回调来通知目标驱动程序。
isp_scsi_reset_notify(
struct scsi_address *ap,
int flag,
void (*callback)(caddr_t),
caddr_t arg)
{
struct isp *isp;
struct isp_reset_notify_entry *p, *beforep;
int rval = DDI_FAILURE;
isp = (struct isp *)ap->a_hba_tran->tran_hba_private;
mutex_enter(ISP_REQ_MUTEX(isp));
/*
* Try to find an existing entry for this target
*/
p = isp->isp_reset_notify_listf;
beforep = NULL;
while (p) {
if (p->ap == ap)
break;
beforep = p;
p = p->next;
}
if ((flag & SCSI_RESET_CANCEL) && (p != NULL)) {
if (beforep == NULL) {
isp->isp_reset_notify_listf = p->next;
} else {
beforep->next = p->next;
}
kmem_free((caddr_t)p, sizeof (struct
isp_reset_notify_entry));
rval = DDI_SUCCESS;
} else if ((flag & SCSI_RESET_NOTIFY) && (p == NULL)) {
p = kmem_zalloc(sizeof (struct isp_reset_notify_entry),
KM_SLEEP);
p->ap = ap;
p->callback = callback;
p->arg = arg;
p->next = isp->isp_reset_notify_listf;
isp->isp_reset_notify_listf = p;
rval = DDI_SUCCESS;
}
mutex_exit(ISP_REQ_MUTEX(isp));
return (rval);
}
要支持最少的一组热插拔操作,驱动程序可能需要实现对总线静止、总线取消静止和总线重置的支持。scsi_hba_tran(9S) 结构支持这些操作。如果硬件不要求静止、取消静止或重置等操作,则无需对驱动程序进行任何更改。
scsi_hba_tran 结构包含以下字段:
int (*tran_quiesce)(dev_info_t *hba-dip); int (*tran_unquiesce)(dev_info_t *hba-dip); int (*tran_bus_reset)(dev_info_t *hba-dip, int level);
这些接口将使 SCSI 总线处于静止状态和取消其静止状态。
#include <sys/scsi/scsi.h> int prefixtran_quiesce(dev_info_t *hba-dip); int prefixtran_unquiesce(dev_info_t *hba-dip);
tran_quiesce(9E) 和 tran_unquiesce(9E) 用于不适于热插拔的 SCSI 设备。HBA 驱动程序必须实现这些函数才能支持动态重新配置 (dynamic reconfiguration, DR)。
在执行 attach(9E) 的过程中,应将 scsi_hba_tran(9S) 结构中的 tran_quiesce() 和 tran_unquiesce() 向量初始化为指向 HBA 入口点。用户启动静止和取消静止操作时,将会调用这些函数。
tran_quiesce() 入口点用于在重新配置连接到 SCSI 总线的设备之前和期间停止 SCSI 总线上的所有活动。完成重新配置操作后,SCSA 框架可调用 tran_unquiesce() 入口点来恢复 SCSI 总线上的活动。
返回成功信息之前,HBA 驱动程序需要处理 tran_quiesce(),方法是等待所有未完成的命令完成。 驱动程序使总线处于静止状态后,必须将任何新的 I/O 请求排入队列,直到 SCSA 框架调用对应的 tran_unquiesce() 入口点为止。
HBA 驱动程序通过启动队列中的所有目标驱动程序 I/O 请求来处理对 tran_unquiesce() 的调用。