当用户线程对与设备相关的文件描述符发出 ioctl(2) 系统调用时,就会调用 ioctl(9E) 例程。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 representing */
/* 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 语句。
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 控制的应用程序。
#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);
}
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 位版本的结构。
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 用于表明接口中的数据类型太小,无法保存要返回的值,如以下示例中所示。
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);
}