可以使用程控 I/O 或 DMA 传输数据。同步或异步入口点都可以使用这些数据传输方法,具体视设备的功能而定。
程控 I/O 设备依赖 CPU 来执行数据传输。程控 I/O 数据传输与设备寄存器的其他读写操作相同。可使用各种数据访问例程,从设备内存读取值或向设备内存中存储值。
可以使用 uiomove(9F) 将数据传输到一些程控 I/O 设备。uiomove(9F) 在 uio(9S) 结构所定义的用户空间与内核之间传输数据。uiomove() 可以处理缺页,因此不必锁定要向其传输数据的内存。uiomove() 还会更新 uio(9S) 结构中的 uio_resid 字段。以下示例说明了编写 ramdisk read(9E) 例程的一种方法。它使用同步 I/O,并依赖 ramdisk 状态结构中下列字段的存在:
caddr_t ram; /* base address of ramdisk */ int ramsize; /* size of the ramdisk */
static int rd_read(dev_t dev, struct uio *uiop, cred_t *credp) { rd_devstate_t *rsp; rsp = ddi_get_soft_state(rd_statep, getminor(dev)); if (rsp == NULL) return (ENXIO); if (uiop->uio_offset >= rsp->ramsize) return (EINVAL); /* * uiomove takes the offset into the kernel buffer, * the data transfer count (minimum of the requested and * the remaining data), the UIO_READ flag, and a pointer * to the uio structure. */ return (uiomove(rsp->ram + uiop->uio_offset, min(uiop->uio_resid, rsp->ramsize - uiop->uio_offset), UIO_READ, uiop)); }
另一个程控 I/O 示例是每次直接向设备内存中写入一字节数据的驱动程序。每一字节都是使用 uwritec(9F) 从 uio(9S) 结构中检索到的。随后该字节被发送到设备中。read(9E) 可以使用 ureadc(9F) 将字节从设备传输到由 uio(9S) 结构描述的区域中。
static int xxwrite(dev_t dev, struct uio *uiop, cred_t *credp) { int value; struct xxstate *xsp; xsp = ddi_get_soft_state(statep, getminor(dev)); if (xsp == NULL) return (ENXIO); /* if the device implements a power manageable component, do this: */ pm_busy_component(xsp->dip, 0); if (xsp->pm_suspended) pm_raise_power(xsp->dip, normal power); while (uiop->uio_resid > 0) { /* * do the programmed I/O access */ value = uwritec(uiop); if (value == -1) return (EFAULT); ddi_put8(xsp->data_access_handle, &xsp->regp->data, (uint8_t)value); ddi_put8(xsp->data_access_handle, &xsp->regp->csr, START_TRANSFER); /* * this device requires a ten microsecond delay * between writes */ drv_usecwait(10); } pm_idle_component(xsp->dip, 0); return (0); }
字符驱动程序通常在 read(9E) 和 write(9E) 中使用 physio(9F) 来设置 DMA 传输,如示例 15–5 中所示。
int physio(int (*strat)(struct buf *), struct buf *bp, dev_t dev, int rw, void (*mincnt)(struct buf *), struct uio *uio);
physio(9F) 要求驱动程序提供 strategy(9E) 例程的地址。physio(9F) 可确保内存空间处于锁定状态,即在数据传输期间内存不能页出。由于 DMA 传输不能处理缺页,因此这种锁定对 DMA 传输来说是十分必要的。physio(9F) 还提供了一种将较大的传输分解为一系列更小的、更易于管理的传输的自动方法。有关更多信息,请参见minphys() 入口点。
static int xxread(dev_t dev, struct uio *uiop, cred_t *credp) { struct xxstate *xsp; int ret; xsp = ddi_get_soft_state(statep, getminor(dev)); if (xsp == NULL) return (ENXIO); ret = physio(xxstrategy, NULL, dev, B_READ, xxminphys, uiop); return (ret); } static int xxwrite(dev_t dev, struct uio *uiop, cred_t *credp) { struct xxstate *xsp; int ret; xsp = ddi_get_soft_state(statep, getminor(dev)); if (xsp == NULL) return (ENXIO); ret = physio(xxstrategy, NULL, dev, B_WRITE, xxminphys, uiop); return (ret); }
在对 physio(9F) 的调用中,xxstrategy 是指向驱动程序 strategy() 例程的指针。如果将 NULL 作为 buf(9S) 结构指针传递,则指示 physio(9F) 分配 buf(9S) 结构。如果驱动程序必须向 physio(9F) 提供 buf(9S) 结构,应使用 getrbuf(9F) 来分配该结构。如果传输成功完成,physio(9F) 返回零;如果传输失败,则返回错误号。调用 strategy(9E) 后,physio(9F) 会调用 biowait(9F) 以进入阻塞状态,直到传输完成或失败。physio(9F) 的返回值由 buf(9S) 结构中 bioerror(9F) 设置的错误字段确定。
支持 aread(9E) 和 awrite(9E) 的字符驱动程序使用 aphysio(9F),而非 physio(9F)。
int aphysio(int (*strat)(struct buf *), int (*cancel)(struct buf *), dev_t dev, int rw, void (*mincnt)(struct buf *), struct aio_req *aio_reqp);
anocancel(9F) 的地址是唯一一个当前可作为第二个参数传递到 aphysio(9F) 的值。
aphysio(9F) 要求驱动程序传递 strategy(9E) 例程的地址。aphysio(9F) 可确保内存空间处于锁定状态,即在数据传输期间内存不能页出。由于 DMA 传输不能处理缺页,因此这种锁定对 DMA 传输来说是十分必要的。aphysio(9F) 还提供了一种将较大的传输分解为一系列更小的、更易于管理的传输的自动方法。有关更多信息,请参见minphys() 入口点。
示例 15–5 和示例 15–6 说明了 aread(9E) 和 awrite(9E) 入口点与 read(9E) 和 write(9E) 入口点之间的轻微差异。这种差异主要在于,前两者使用 aphysio(9F),而非 physio(9F)。
static int xxaread(dev_t dev, struct aio_req *aiop, cred_t *cred_p) { struct xxstate *xsp; xsp = ddi_get_soft_state(statep, getminor(dev)); if (xsp == NULL) return (ENXIO); return (aphysio(xxstrategy, anocancel, dev, B_READ, xxminphys, aiop)); } static int xxawrite(dev_t dev, struct aio_req *aiop, cred_t *cred_p) { struct xxstate *xsp; xsp = ddi_get_soft_state(statep, getminor(dev)); if (xsp == NULL) return (ENXIO); return (aphysio(xxstrategy, anocancel, dev, B_WRITE, xxminphys,aiop)); }
在对 aphysio(9F) 的调用中,xxstrategy() 是指向驱动程序策略例程的指针。aiop 是指向 aio_req(9S) 结构的指针。aiop 将被传递到 aread(9E) 和 awrite(9E)。aio_req(9S) 描述了数据在用户空间中的存储位置。如果成功调度了 I/O 请求,aphysio(9F) 返回零;如果调度失败,则返回错误号。调用 strategy(9E) 后,aphysio(9F) 会返回而不等待 I/O 完成或失败。
minphys() 入口点是指向要由 physio(9F) 或 aphysio(9F) 调用的函数的指针。xxminphys 的用途在于确保所请求的传输大小不会超过驱动程序强加的限制。如果用户请求较大的传输,则会重复调用 strategy(9E),这就要求每次都不能超过强加的限制。因为 DMA 资源有限,所以该方法非常重要。对于慢速设备(例如打印机)的驱动程序,应避免其长时间占用资源。
通常,驱动程序会传递内核函数 minphys(9F) 的地址,但驱动程序也可以定义自己的 xxminphys() 例程。xxminphys() 的作用是使 buf(9S) 结构的 b_bcount 字段保持在驱动程序限制内。驱动程序还应遵循其他系统限制。例如,驱动程序的 xxminphys() 例程应该在设置 b_bcount 字段之后而在返回之前调用系统 minphys(9F) 例程。
#define XXMINVAL (512 << 10) /* 512 KB */ static void xxminphys(struct buf *bp) { if (bp->b_bcount > XXMINVAL) bp->b_bcount = XXMINVAL minphys(bp); }
strategy(9E) 例程源于块驱动程序。策略函数因实现用于对块设备的 I/O 请求的有效排队的策略而得名。面向字符设备的驱动程序也可以使用 strategy(9E) 例程。在这里提供的字符 I/O 模型中,strategy(9E) 并不维护请求队列,只是一次为一个请求提供服务。
在以下示例中,用于面向字符的 DMA 设备的 strategy(9E) 例程为同步数据传输分配 DMA 资源。strategy() 通过对设备寄存器进行编程来启动此命令。有关详细说明,请参见第 9 章。
strategy(9E) 不会以参数形式接收设备编号 (dev_t)。设备编号是从传递给 strategy(9E) 的 buf(9S) 结构中的 b_edev 字段检索到的。
static int xxstrategy(struct buf *bp) { minor_t instance; struct xxstate *xsp; ddi_dma_cookie_t cookie; instance = getminor(bp->b_edev); xsp = ddi_get_soft_state(statep, instance); /* ... */ * 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; /* remember bp */ /* Program DMA engine and start command */ return (0); }
虽然声明了 strategy() 返回 int,但 strategy() 必须总是返回零。
在完成 DMA 传输时,设备会产生中断,从而导致对中断例程的调用。在以下示例中,xxintr() 接收指向可能产生中断的设备的状态结构的指针。
static u_int xxintr(caddr_t arg) { struct xxstate *xsp = (struct xxstate *)arg; if ( /* device did not interrupt */ ) { return (DDI_INTR_UNCLAIMED); } if ( /* error */ ) { /* error handling */ } /* Release any resources used in the transfer, such as DMA resources. * ddi_dma_unbind_handle(9F) and ddi_dma_free_handle(9F) * Notify threads that the transfer is complete. */ biodone(xsp->bp); return (DDI_INTR_CLAIMED); }
驱动程序通过调用 bioerror(9F) 来指示错误。当传输完成或者使用 biodone(9F) 指示错误后,驱动程序必须调用 bioerror(9F)。