以下各节讨论了资源分配。
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; } }