本节介绍一种执行同步 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) 中等待设备的其他线程继续进行下一个数据传输。
以下示例说明了一个同步中断例程。
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); }