第 1 部分针对 Oracle Solaris 平台设计设备驱动程序
9. 直接内存访问 (Direct Memory Access, DMA)
本节介绍一种执行同步 I/O 传输的简单方法。此方法假设硬件是使用 DMA 一次只能传输一个数据缓冲区的简单磁盘设备。另一个假设是磁盘可以通过软件命令启动和停止。设备驱动程序的 strategy(9E) 例程等待当前请求完成,然后再接受新的请求。当传输完成时,设备中断。如果发生错误,设备也中断。
执行块驱动程序的同步数据传输的步骤如下:
检查是否有无效的 buf(9S) 请求。
检查传递到 strategy(9E) 的 buf(9S) 结构是否有效。所有驱动程序应检查以下条件:
请求起始于有效的块。驱动程序将 b_blkno 字段转换为正确的设备偏移,然后确定该偏移对设备而言是否有效。
请求不能超出设备上的最后一个块。
满足特定于设备的要求。
如果遇到错误,驱动程序应使用 bioerror(9F) 指示相应的错误。然后,驱动程序通过调用 biodone(9F) 来完成请求。biodone() 会通知 strategy(9E) 的调用方传输已完成。在本例中,传输因错误而停止。
检查此设备是否忙。
同步数据传输允许对设备进行单线程访问。设备驱动程序通过两种方式执行这种访问:
驱动程序保持由互斥锁保护的忙标志。
当设备忙时,驱动程序等待 cv_wait(9F) 的条件变量。
如果设备忙,线程会一直等待,直到中断处理程序指示设备不再忙。cv_broadcast(9F) 或 cv_signal(9F) 函数可以指示可用的状态。有关条件变量的详细信息,请参见第 3 章。
当设备不再忙时,strategy(9E) 例程将设备标记为可用。然后,strategy() 为传输准备缓冲区和设备。
为 DMA 设置缓冲区。
通过使用 ddi_dma_alloc_handle(9F) 为 DMA 传送准备数据缓冲区,以便分配 DMA 句柄。使用 ddi_dma_buf_bind_handle(9F) 将数据缓冲区绑定到该句柄。有关设置 DMA 资源及相关数据结构的信息,请参见第 9 章。
开始传输。
此时,指向 buf(9S) 结构的指针将保存在设备的状态结构中。然后中断例程通过调用 biodone(9F) 来完成传输。
设备驱动程序随后访问设备寄存器以启动数据传输。在大多数情况下,驱动程序应通过使用互斥锁来保护设备寄存器免受其他线程干扰。在本例中,由于 strategy(9E) 是单线程的,因此没有必要保护设备寄存器。有关数据锁定的详细信息,请参见第 3 章。
当执行线程已经启动设备的 DMA 引擎时,驱动程序可以将执行控制权返回到正在调用的例程,如下所示:
static int
xxstrategy(struct buf *bp)
{
struct xxstate *xsp;
struct device_reg *regp;
minor_t instance;
ddi_dma_cookie_t cookie;
instance = getminor(bp->b_edev);
xsp = ddi_get_soft_state(statep, instance);
if (xsp == NULL) {
bioerror(bp, ENXIO);
biodone(bp);
return (0);
}
/* validate the transfer request */
if ((bp->b_blkno >= xsp->Nblocks) || (bp->b_blkno < 0)) {
bioerror(bp, EINVAL);
biodone(bp);
return (0);
}
/*
* Hold off all threads until the device is not busy.
*/
mutex_enter(&xsp->mu);
while (xsp->busy) {
cv_wait(&xsp->cv, &xsp->mu);
}
xsp->busy = 1;
mutex_exit(&xsp->mu);
/*
* If the device has power manageable components,
* mark the device busy with pm_busy_components(9F),
* and then ensure that the device
* is powered up by calling pm_raise_power(9F).
*
* Set up DMA resources with ddi_dma_alloc_handle(9F) and
* ddi_dma_buf_bind_handle(9F).
*/
xsp->bp = bp;
regp = xsp->regp;
ddi_put32(xsp->data_access_handle, ®p->dma_addr,
cookie.dmac_address);
ddi_put32(xsp->data_access_handle, ®p->dma_size,
(uint32_t)cookie.dmac_size);
ddi_put8(xsp->data_access_handle, ®p->csr,
ENABLE_INTERRUPTS | START_TRANSFER);
return (0);
}处理中断的设备。
当设备完成数据传输时,设备会生成中断,最终导致驱动程序的中断例程被调用。注册中断时,大多数驱动程序将设备的状态结构指定为中断例程的参数。请参见 ddi_add_intr(9F) 手册页和注册中断。随后,中断例程可以访问正在被传输的 buf(9S) 结构,以及状态结构提供的任何其他信息。
中断处理程序会检查设备的状态寄存器,来确定是否在未发生任何错误的情况下完成传输。如果发生错误,处理程序应该使用 bioerror(9F) 指示相应的错误。处理程序还应该清除设备的挂起中断,然后通过调用 biodone(9F) 来完成传输。
最后一项任务是处理程序清除忙标志。处理程序随后对条件变量调用 cv_signal(9F) 或 cv_broadcast(9F),发出设备不再忙的信号。此通知会使在 strategy(9E) 中等待设备的其他线程继续进行下一个数据传输。
以下示例说明了一个同步中断例程。
示例 16-4 块驱动程序的同步中断例程
static u_int
xxintr(caddr_t arg)
{
struct xxstate *xsp = (struct xxstate *)arg;
struct buf *bp;
uint8_t status;
mutex_enter(&xsp->mu);
status = ddi_get8(xsp->data_access_handle, &xsp->regp->csr);
if (!(status & INTERRUPTING)) {
mutex_exit(&xsp->mu);
return (DDI_INTR_UNCLAIMED);
}
/* Get the buf responsible for this interrupt */
bp = xsp->bp;
xsp->bp = NULL;
/*
* This example is for a simple device which either
* succeeds or fails the data transfer, indicated in the
* command/status register.
*/
if (status & DEVICE_ERROR) {
/* failure */
bp->b_resid = bp->b_bcount;
bioerror(bp, EIO);
} else {
/* success */
bp->b_resid = 0;
}
ddi_put8(xsp->data_access_handle, &xsp->regp->csr,
CLEAR_INTERRUPT);
/* The transfer has finished, successfully or not */
biodone(bp);
/*
* If the device has power manageable components that were
* marked busy in strategy(9F), mark them idle now with
* pm_idle_component(9F)
* Release any resources used in the transfer, such as DMA
* resources ddi_dma_unbind_handle(9F) and
* ddi_dma_free_handle(9F).
*
* Let the next I/O thread have access to the device.
*/
xsp->busy = 0;
cv_signal(&xsp->cv);
mutex_exit(&xsp->mu);
return (DDI_INTR_CLAIMED);
}