第 1 部分针对 Oracle Solaris 平台设计设备驱动程序
9. 直接内存访问 (Direct Memory Access, DMA)
14. 分层驱动程序接口 (Layered Driver Interface, LDI)
本节介绍如何使用 segmap(9E) 和 devmap(9E) 入口点。
segmap(9E) 入口点负责设置 mmap(2) 系统调用所请求的内存映射。许多内存映射设备的驱动程序使用 ddi_devmap_segmap(9F) 作为入口点,而不是定义自己的 segmap(9E) 例程。通过提供 segmap() 入口点,驱动程序能够在创建映射之前或之后管理常规任务。例如,驱动程序可以检查映射权限并分配专用映射资源。驱动程序还可以调整映射,以适应非按页对齐的设备缓冲区。segmap() 入口点在返回之前必须调用 ddi_devmap_segmap(9F) 函数。ddi_devmap_segmap() 函数调用驱动程序的 devmap(9E) 入口点以执行实际映射。
segmap() 函数的语法如下:
int segmap(dev_t dev, off_t off, struct as *asp, caddr_t *addrp, off_t len, unsigned int prot, unsigned int maxprot, unsigned int flags, cred_t *credp);
其中:
要映射其内存的设备。
设备内存中的偏移,映射将从此位置开始。
指向设备内存将要映射到的地址空间的指针。
请注意,此参数可以是 struct as *(如示例 10-1 中所示),也可以是 ddi_as_handle_t(如示例 10-2 中所示)。这是因为 ddidevmap.h 包括以下声明:
typedef struct as *ddi_as_handle_t
指向设备内存将要映射到的地址空间中的地址的指针。
所映射的内存的长度(以字节为单位)。
指定保护的位字段。可能的设置有 PROT_READ、PROT_WRITE、PROT_EXEC、PROT_USER 和 PROT_ALL。有关详细信息,请参见手册页。
尝试的映射可用的最大保护标志。如果用户打开只读的特殊文件,则 PROT_WRITE 位可能会被屏蔽。
指示映射类型的标志。可能的值包括 MAP_SHARED 和 MAP_PRIVATE。
指向用户凭证结构的指针。
在以下示例中,驱动程序控制允许只写映射的帧缓存器。如果应用程序尝试进行读取访问,并随后调用 ddi_devmap_segmap(9F) 以设置用户映射,驱动程序将返回 EINVAL。
示例 10-1 segmap(9E) 例程
static int xxsegmap(dev_t dev, off_t off, struct as *asp, caddr_t *addrp, off_t len, unsigned int prot, unsigned int maxprot, unsigned int flags, cred_t *credp) { if (prot & PROT_READ) return (EINVAL); return (ddi_devmap_segmap(dev, off, as, addrp, len, prot, maxprot, flags, cred)); }
以下示例说明如何处理其缓冲区未在寄存器空间中按页对齐的设备。本示例将映射从偏移 0x800 开始的缓冲区,因此 mmap(2) 会返回与该缓冲区起始位置对应的地址。devmap_devmem_setup(9F) 函数映射整页,要求映射按页对齐,并返回页起始位置的地址。如果此地址是通过 segmap(9E) 传递的,或者未定义 segmap() 入口点,则 mmap() 将返回对应于页起始位置的地址,而不是对应于缓冲区起始位置的地址。在本示例中,缓冲区偏移将与 devmap_devmem_setup 返回的按页对齐地址相加,因此,得到的返回地址就是所需的缓冲区起始地址。
示例 10-2 使用 segmap() 函数更改 mmap() 调用返回的地址
#define BUFFER_OFFSET 0x800 int xx_segmap(dev_t dev, off_t off, ddi_as_handle_t as, caddr_t *addrp, off_t len, uint_t prot, uint_t maxprot, uint_t flags, cred_t *credp) { int rval; unsigned long pagemask = ptob(1L) - 1L; if ((rval = ddi_devmap_segmap(dev, off, as, addrp, len, prot, maxprot, flags, credp)) == DDI_SUCCESS) { /* * The address returned by ddi_devmap_segmap is the start of the page * that contains the buffer. Add the offset of the buffer to get the * final address. */ *addrp += BUFFER_OFFSET & pagemask); } return (rval); }
devmap(9E) 入口点通过 segmap(9E) 入口点内部的 ddi_devmap_segmap(9F) 函数调用。
devmap(9E) 入口点是作为 mmap(2) 系统调用的结果调用的。调用 devmap(9E) 函数可将设备内存或内核内存导出到用户应用程序。devmap() 函数用于进行以下操作:
验证用户到设备内存或内核内存的映射
将应用程序映射中的逻辑偏移转换为设备或内核内存中的对应偏移
将映射信息传递给系统以设置映射
devmap() 函数的语法如下所示:
int devmap(dev_t dev, devmap_cookie_t handle, offset_t off, size_t len, size_t *maplen, uint_t model);
其中:
要映射其内存的设备。
系统创建的设备映射句柄,用来描述到设备或内核中的连续内存的映射。
应用程序映射中的逻辑偏移,必须通过驱动程序将其转换为设备或内核内存中的对应偏移。
所映射的内存的长度(以字节为单位)。
使驱动程序可将不同的内核内存区域或多个物理上不连续的内存区域与一个连续的用户应用程序映射相关联。
当前线程的数据模型类型。
系统在一次 mmap(2) 系统调用中将创建多个映射句柄。例如,映射可能包含多个物理上不连续的内存区域。
devmap(9E) 的首次调用使用参数 off 和 len 的初始值。应用程序将这些参数传递给 mmap(2)。devmap(9E) 将 *maplen 设置为从 off 到连续内存区域末尾之间的长度。*maplen 值必须向上进位为页面大小的倍数。 *maplen 值可被设置为小于原始映射长度 len。如果这样,系统将使用调整了 off 和 len 参数的新映射句柄反复调用 devmap(9E),直到达到初始映射长度为止。
如果一个驱动程序支持多个应用程序数据模型,则必须将 model 传递给 ddi_model_convert_from(9F)。ddi_model_convert_from() 函数可以确定当前线程与设备驱动程序之间是否存在数据模型不匹配的情况。设备驱动程序可能必须调整数据结构的形状,然后才能将结构导出到支持不同数据模型的用户线程。有关更多详细信息,请参见附录 C。
如果逻辑偏移 off 超出了驱动程序导出的内存范围,则 devmap(9E) 入口点必将返回 -1。