第 1 部分针对 Oracle Solaris 平台设计设备驱动程序
9. 直接内存访问 (Direct Memory Access, DMA)
本节介绍一种执行异步 I/O 传输的方法。驱动程序将对 I/O 请求进行排队,然后将控制权返回到调用方。还是假设硬件是一次可以传输一个缓冲区的简单磁盘设备。当数据传输完成时,设备中断。如果发生错误,也会产生中断。执行异步数据传输的基本步骤如下:
检查是否有无效的 buf(9S) 请求。
对请求进行排队。
开始第一个传输。
处理中断的设备。
正如同步传输示例中所示,设备驱动程序会检查传递到 strategy(9E) 的 buf(9S) 结构是否有效。有关更多详细信息,请参见同步数据传输(块驱动程序)。
与同步数据传输不同,驱动程序不等待异步请求完成,而是向队列中添加请求。队列的开头可以是当前传输,也可以是保留活动请求的状态结构中的独立字段,如示例 16-5 中所示。
如果队列开始是空的,那么硬件不忙,并且 strategy(9E) 在返回之前开始传输。否则,如果在队列非空的情况下完成一个传输,则中断例程会开始一个新的传输。为方便起见,示例 16-5 仅在一个单独的例程中决定是否开始新的传输。
驱动程序可以使用 buf(9S) 结构中的 av_forw 和 av_back 成员来管理传输请求列表。可以使用单个指针管理单链接表,也可以同时使用两个指针建立双链接表。设备硬件规格指定哪种类型的列表管理(如插入策略)用于优化设备的性能。传输列表是按设备提供的列表,因此列表的头和尾都存储在状态结构中。
以下示例提供了对驱动程序共享数据(如传输列表)有访问权限的多个线程。您必须标识共享数据,并且必须用互斥锁保护这些数据。有关互斥锁的更多详细信息,请参见第 3 章。
示例 16-5 对块驱动程序的数据传输请求进行排队
static int xxstrategy(struct buf *bp) { struct xxstate *xsp; minor_t instance; instance = getminor(bp->b_edev); xsp = ddi_get_soft_state(statep, instance); /* ... */ /* validate transfer request */ /* ... */ /* * Add the request to the end of the queue. Depending on the device, a sorting * algorithm, such as disksort(9F) can be used if it improves the * performance of the device. */ mutex_enter(&xsp->mu); bp->av_forw = NULL; if (xsp->list_head) { /* Non-empty transfer list */ xsp->list_tail->av_forw = bp; xsp->list_tail = bp; } else { /* Empty Transfer list */ xsp->list_head = bp; xsp->list_tail = bp; } mutex_exit(&xsp->mu); /* Start the transfer if possible */ (void) xxstart((caddr_t)xsp); return (0); }
可实现排队的设备驱动程序通常具有 start() 例程。start() 可将下一个请求从队列中删除,并开始出入设备的数据传输。在以下示例中,不管设备状态是忙还是空闲,start() 将处理所有请求。
注 - 必须写入 start() 以便从任何上下文都可以对其进行调用。内核上下文中的策略例程与中断上下文中的中断例程都可以调用 start()。
每次 strategy() 对请求排队时,将由 strategy(9E) 调用 start(),以便可以启动空闲设备。如果设备忙,则 start() 立即返回。
在处理程序从声明的中断返回之前,也可以由中断处理程序调用 start(),以便可以为非空队列提供服务。如果队列是空的,则 start() 立即返回。
由于 start() 是一个专用驱动程序例程,因此 start() 可以采用任何参数并返回任何类型。将写入以下代码样例用作 DMA 回调,尽管没有显示那一部分。相应地,此示例必须采用 caddr_t 作为参数并返回 int。有关 DMA 回调例程的更多信息,请参见处理资源分配故障。
示例 16-6 开始块驱动程序的第一个数据请求
static int xxstart(caddr_t arg) { struct xxstate *xsp = (struct xxstate *)arg; struct buf *bp; mutex_enter(&xsp->mu); /* * If there is nothing more to do, or the device is * busy, return. */ if (xsp->list_head == NULL || xsp->busy) { mutex_exit(&xsp->mu); return (0); } xsp->busy = 1; /* Get the first buffer off the transfer list */ bp = xsp->list_head; /* Update the head and tail pointer */ xsp->list_head = xsp->list_head->av_forw; if (xsp->list_head == NULL) xsp->list_tail = NULL; bp->av_forw = NULL; 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; ddi_put32(xsp->data_access_handle, &xsp->regp->dma_addr, cookie.dmac_address); ddi_put32(xsp->data_access_handle, &xsp->regp->dma_size, (uint32_t)cookie.dmac_size); ddi_put8(xsp->data_access_handle, &xsp->regp->csr, ENABLE_INTERRUPTS | START_TRANSFER); return (0); }
中断例程与异步版本类似,只是增加了对 start() 的调用并删除了对 cv_signal(9F) 的调用。
示例 16-7 异步中断的块驱动程序例程
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; mutex_exit(&xsp->mu); (void) xxstart((caddr_t)xsp); return (DDI_INTR_CLAIMED); }