第 1 部分针对 Oracle Solaris 平台设计设备驱动程序
9. 直接内存访问 (Direct Memory Access, DMA)
14. 分层驱动程序接口 (Layered Driver Interface, LDI)
一些设备驱动程序可能需要分配可供用户程序通过 mmap(2) 进行访问的内核内存。一个示例是为两个应用程序间的通信设置共享内存。另一个示例是在驱动程序和应用程序之间共享内存。
将内核内存导出到用户应用程序时,请执行以下步骤:
使用 ddi_umem_alloc(9F) 分配内核内存。
使用 devmap_umem_setup(9F) 导出内存。
不再需要内存时,使用 ddi_umem_free(9F) 释放内存。
使用 ddi_umem_alloc(9F) 可以分配导出到应用程序的内核内存。 ddi_umem_alloc() 的语法如下所示:
void *ddi_umem_alloc(size_t size, int flag, ddi_umem_cookie_t *cookiep);
其中:
要分配的字节数。
用于确定休眠条件和内存类型。
指向内核内存 cookie 的指针。
ddi_umem_alloc(9F) 分配按页对齐的内核内存。ddi_umem_alloc () 返回一个指向所分配内存的指针。最初,内存被零填充。分配的字节数是系统页面大小的倍数,该页面大小是通过 size 参数向上舍入得到的。分配的内存可在内核中使用。此内存也可导出到应用程序。cookiep 是指向用来描述所分配的内核内存的内核内存 cookie 的指针。驱动程序将内核内存导出到用户应用程序时,devmap_umem_setup(9F) 中会使用 cookiep
flag 参数用于指示 ddi_umem_alloc(9F) 是立即阻塞还是返回,以及分配的内核内存是否可换页。flag 参数的值如下所示:
驱动程序无需等待内存成为可用。如果内存不可用,则返回 NULL。
驱动程序可以无限等待,直到内存可用为止。
驱动程序允许内存页被换出。如果未设置,则锁定内存。
ddi_umem_lock() 函数可以执行设备锁定内存检查。此函数针对 project.max-locked-memory 中指定的限制值进行检查。如果当前项目的锁定内存使用量低于限制,则会增加项目的锁定内存字节计数。进行限制检查后,内存将会锁定。ddi_umem_unlock() 函数可以解除锁定内存,从而减少项目的锁定内存字节计数。
其中所用的记帐方法是不严密的 "full price"(足价)模式。例如,对于同一项目中 umem_lockmemory() 的具有重叠内存区域的两个调用方会被计数两次。
有关 project.max-locked-memory 和 zone.max-locked_memory 对安装了区域的 Oracle Solaris 系统的资源控制的信息,请参见《Resource Management and Oracle Solaris Zones Developer’s Guide》和 resource_controls(5)。
以下示例说明如何为应用程序访问分配内核内存。驱动程序会导出一页内核内存,它将被多个应用程序用作共享存储区。应用程序第一次映射共享页时,会在 segmap(9E) 中分配内存。如果驱动程序必须支持多个应用程序数据模型,则会再分配一页。例如,64 位驱动程序可能同时将内存导出到 64 位应用程序和 32 位应用程序。64 位应用程序共享第一页,32 位应用程序共享第二页。
示例 10-4 使用 ddi_umem_alloc() 例程
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) { int error; minor_t instance = getminor(dev); struct xxstate *xsp = ddi_get_soft_state(statep, instance); size_t mem_size; /* 64-bit driver supports 64-bit and 32-bit applications */ switch (ddi_mmap_get_model()) { case DDI_MODEL_LP64: mem_size = ptob(2); break; case DDI_MODEL_ILP32: mem_size = ptob(1); break; } mutex_enter(&xsp->mu); if (xsp->umem == NULL) { /* allocate the shared area as kernel pageable memory */ xsp->umem = ddi_umem_alloc(mem_size, DDI_UMEM_SLEEP | DDI_UMEM_PAGEABLE, &xsp->ucookie); } mutex_exit(&xsp->mu); /* Set up the user mapping */ error = devmap_setup(dev, (offset_t)off, asp, addrp, len, prot, maxprot, flags, credp); return (error); }
使用 devmap_umem_setup(9F) 可将内核内存导出到用户应用程序。devmap_umem_setup() 必须通过驱动程序的 devmap(9E) 入口点进行调用 。devmap_umem_setup() 的语法如下所示:
int devmap_umem_setup(devmap_cookie_t handle, dev_info_t *dip, struct devmap_callback_ctl *callbackops, ddi_umem_cookie_t cookie, offset_t koff, size_t len, uint_t maxprot, uint_t flags, ddi_device_acc_attr_t *accattrp);
其中:
用于描述映射的不透明结构。
指向设备的 dev_info 结构的指针。
指向 devmap_callback_ctl(9S) 结构的指针。
ddi_umem_alloc(9F) 返回的内核内存 cookie。
cookie 指定的内核内存中的偏移。
导出的长度(以字节为单位)。
用于为导出的映射指定可能的最大保护。
必须设置为 DEVMAP_DEFAULTS。
指向 ddi_device_acc_attr(9S) 结构的指针。
handle 是系统用来标识映射的设备映射句柄。handle 通过 devmap(9E) 入口点传入。dip 是指向设备的 dev_info 结构的指针。callbackops 允许向驱动程序通知有关映射的用户事件。导出内核内存时,大多数驱动程序都会将 callbackops 设置为 NULL。
koff 和 len 用于在 ddi_umem_alloc(9F) 分配的内核内存中指定一个范围。如果用户的应用程序映射位于通过 devmap(9E) 入口点传入的偏移上,则可对此范围进行访问。通常,驱动程序将 devmap(9E) 偏移直接传递给 devmap_umem_setup(9F)。然后,mmap(2) 的返回地址将映射到 ddi_umem_alloc(9F) 返回的内核地址。koff 和 len 必须按页对齐。
通过 maxprot,驱动程序可为导出的内核内存中的不同区域指定不同的保护。例如,通过仅设置 PROT_READ 和 PROT_USER,一个区域可能不允许写访问。
以下示例说明如何将内核内存导出到应用程序。驱动程序首先检查请求的映射是否位于分配的内核内存区域之内。如果 64 位驱动程序收到来自 32 位应用程序的映射请求,则会将该请求重定向到内核存储区的第二页。此重定向可确保仅有编译到相同数据模型的应用程序才能共享相同的页。
示例 10-5 devmap_umem_setup(9F) 例程
static int xxdevmap(dev_t dev, devmap_cookie_t handle, offset_t off, size_t len, size_t *maplen, uint_t model) { struct xxstate *xsp; int error; /* round up len to a multiple of a page size */ len = ptob(btopr(len)); /* check if the requested range is ok */ if (off + len > ptob(1)) return (ENXIO); xsp = ddi_get_soft_state(statep, getminor(dev)); if (xsp == NULL) return (ENXIO); if (ddi_model_convert_from(model) == DDI_MODEL_ILP32) /* request from 32-bit application. Skip first page */ off += ptob(1); /* export the memory to the application */ error = devmap_umem_setup(handle, xsp->dip, NULL, xsp->ucookie, off, len, PROT_ALL, DEVMAP_DEFAULTS, NULL); *maplen = len; return (error); }
卸载驱动程序时,必须通过调用 ddi_umem_alloc(9F) 释放 ddi_umem_free(9F) 分配的内存。
void ddi_umem_free(ddi_umem_cookie_t cookie);
cookie 是 ddi_umem_alloc(9F) 返回的内核内存 cookie。