第 1 部分针对 Oracle Solaris 平台设计设备驱动程序
9. 直接内存访问 (Direct Memory Access, DMA)
14. 分层驱动程序接口 (Layered Driver Interface, LDI)
当用户线程对与设备相关联的文件描述符发出 ioctl(9E) 系统调用时,就会调用 ioctl(2) 例程。I/O 控制机制是获取和设置设备特定参数的统称。该机制经常用于设置设备特定模式(通过设置内部驱动程序软件标志或将命令写入设备)。也可以使用该控制机制向用户返回有关当前设备状态的信息。简而言之,控制机制可以做应用程序和驱动程序需要完成的任何事情。
int xxioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp);
cmd 参数表明应该执行哪个 ioctl(9E) 命令。根据约定,命令的 8-15 位指示与 I/O 控制命令有关的驱动程序。通常,字符的 ASCII 代码代表该驱动程序。驱动程序特定命令位于 0-7 位。以下示例说明了一些 I/O 命令的创建:
#define XXIOC ('x' << 8) /* 'x' is a character that represents device xx */ #define XX_GET_STATUS (XXIOC | 1) /* get status register */ #define XX_SET_CMD (XXIOC | 2) /* send command */
对 arg 的解释视命令而定。在驱动程序文档或手册页中应该介绍了这些 I/O 控制命令。在公共头文件中还会定义命令,以便应用程序能够确定命令的名称、命令执行的操作以及命令以 arg 的形式接受或返回的内容。进出驱动程序的任何 arg 数据传输都必须由驱动程序执行。
特定种类的设备(如帧缓存器或磁盘)必须支持 I/O 控制请求的标准集。这些标准 I/O 控制接口在 Solaris 8 Reference Manual Collection 中进行了介绍。例如,fbio(7I) 介绍了帧缓存器必须支持的 I/O 控制,而 dkio(7I) 则介绍了标准的磁盘 I/O 控制。有关 I/O 控制的更多信息,请参见其他 I/O 控制。
驱动程序必须使用 ddi_copyin(9F) 从用户级别的应用程序向内核级别的应用程序传输 arg 数据。驱动程序必须使用 ddi_copyout(9F) 从内核级别向用户级别传输数据。在这两种情况下,如果使用 ddi_copyin(9F) 或 ddi_copyout(9F) 失败,则会导致系统出现紧急情况。如果体系结构将内核地址空间和用户地址空间分开,或者用户地址空间被换出,系统都会出现紧急情况。
对于每个支持的 ioctl(9E) 请求,ioctl(9E) 通常是 switch 语句。
示例 15-12 ioctl(9E) 例程
static int xxioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { uint8_t csr; struct xxstate *xsp; xsp = ddi_get_soft_state(statep, getminor(dev)); if (xsp == NULL) { return (ENXIO); } switch (cmd) { case XX_GET_STATUS: csr = ddi_get8(xsp->data_access_handle, &xsp->regp->csr); if (ddi_copyout(&csr, (void *)arg, sizeof (uint8_t), mode) != 0) { return (EFAULT); } break; case XX_SET_CMD: if (ddi_copyin((void *)arg, &csr, sizeof (uint8_t), mode) != 0) { return (EFAULT); } ddi_put8(xsp->data_access_handle, &xsp->regp->csr, csr); break; default: /* generic "ioctl unknown" error */ return (ENOTTY); } return (0); }
cmd 变量识别特定的设备控制操作。如果 arg 包含用户虚拟地址,则会出现问题。ioctl(9E) 必须调用 ddi_copyin(9F) 或 ddi_copyout(9F),以便在 arg 指向的应用程序中的数据结构与驱动程序之间传输数据。在示例 15-12 中,对于 XX_GET_STATUS 请求,xsp->regp->csr 的内容会被复制到 arg 中的地址。ioctl(9E) 可以在 *rvalp 中存储任何作为成功发出请求的 ioctl(2) 系统调用的返回值的整数值。应当避免返回负值,例如 -1。许多应用程序假定负值表示失败。
以下示例说明了使用上一段落中所讨论的 I/O 控制的应用程序。
示例 15-13 使用 ioctl(9E)
#include <sys/types.h> #include "xxio.h" /* contains device's ioctl cmds and args */ int main(void) { uint8_t status; /* ... */ /* * read the device status */ if (ioctl(fd, XX_GET_STATUS, &status) == -1) { /* error handling */ } printf("device status %x\n", status); exit(0); }
Oracle Solaris 内核在适当的硬件上以 64 位模式运行,既支持 32 位应用程序,也支持 64 位应用程序。要求 64 位设备驱动程序同时支持来自这两种处理能力的程序的 I/O 控制命令。32 位程序与 64 位程序的差异在于 C 语言类型模型。32 位程序是 ILP32,而 64 位程序是 LP64。有关 C 数据类型模型的信息,请参见附录 C。
如果程序和内核之间传输的数据具有不同的格式,则驱动程序必须能够处理这种模型不匹配。处理模型不匹配需要对数据进行适当的调整。
要确定是否存在模型不匹配,ioctl(9E) 模式参数会将数据模型位传递到驱动程序。如示例 15-14 中所示,该模式参数随后会被传递到 ddi_model_convert_from(9F),以确定是否有必要进行模型转换。
模式参数的标志子字段用于将数据模型传递到 ioctl(9E) 例程中。可以将此标志设置为以下值之一:
DATAMODEL_ILP32
DATAMODEL_LP64
可以有条件地定义 FNATIVE,以匹配内核实现的数据模型。应使用 FMODELS 掩码来提取 mode 参数的标志。驱动程序随后会对数据模型进行明确检查,以确定如何复制应用程序的数据结构。
DDI 函数 ddi_model_convert_from(9F) 是一个公用例程,可帮助一些驱动程序完成它们的 ioctl() 调用。该函数将用户应用程序的数据类型模型用作参数,并返回下列值之一:
DDI_MODEL_ILP32-从 ILP32 应用程序进行转换
DDI_MODEL_NONE-无需转换
如果不必进行数据转换,则会返回 DDI_MODEL_NONE。当应用程序和驱动程序具有相同的数据模型时,便会发生这种情况。DDI_MODEL_ILP32 将返回到被编译为 LP64 模式而且能够与 32 位应用程序进行通信的驱动程序。
在以下示例中,驱动程序复制了包含用户地址的数据结构。数据结构的处理能力从 ILP32 更改为 LP64。相应地,此 64 位驱动程序在与 32 位应用程序进行通信时使用 32 位版本的结构。
示例 15-14 用于支持 32 位应用程序和 64 位应用程序的 ioctl(9E) 例程
struct args32 { uint32_t addr; /* 32-bit address in LP64 */ int len; } struct args { caddr_t addr; /* 64-bit address in LP64 */ int len; } static int xxioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { struct xxstate *xsp; struct args a; xsp = ddi_get_soft_state(statep, getminor(dev)); if (xsp == NULL) { return (ENXIO); } switch (cmd) { case XX_COPYIN_DATA: switch(ddi_model_convert_from(mode)) { case DDI_MODEL_ILP32: { struct args32 a32; /* copy 32-bit args data shape */ if (ddi_copyin((void *)arg, &a32, sizeof (struct args32), mode) != 0) { return (EFAULT); } /* convert 32-bit to 64-bit args data shape */ a.addr = a32.addr; a.len = a32.len; break; } case DDI_MODEL_NONE: /* application and driver have same data model. */ if (ddi_copyin((void *)arg, &a, sizeof (struct args), mode) != 0) { return (EFAULT); } } /* continue using data shape in native driver data model. */ break; case XX_COPYOUT_DATA: /* copyout handling */ break; default: /* generic "ioctl unknown" error */ return (ENOTTY); } return (0); }
驱动程序有时需要将不再适于 32 位大小结构的本机数值复制出来。在这种情况下,驱动程序应向调用方返回 EOVERFLOW。EOVERFLOW 用于表明接口中的数据类型太小,无法保存要返回的值,如以下示例中所示。
示例 15-15 处理 copyout (9F) 溢出
int xxioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cr, int *rval_p) { struct resdata res; /* body of driver */ switch (ddi_model_convert_from(mode & FMODELS)) { case DDI_MODEL_ILP32: { struct resdata32 res32; if (res.size > UINT_MAX) return (EOVERFLOW); res32.size = (size32_t)res.size; res32.flag = res.flag; if (ddi_copyout(&res32, (void *)arg, sizeof (res32), mode)) return (EFAULT); } break; case DDI_MODEL_NONE: if (ddi_copyout(&res, (void *)arg, sizeof (res), mode)) return (EFAULT); break; } return (0); }