Linux设备驱动程序
——高级字符驱动程序操作(ioctl)
一、ioctl
除了读取和写入设备之外,大部分驱动程序还需要另外一种能力,即通过设备驱动程序执行各种类型的硬件控制。这些操作通常通过ioctl方法支持。
在用户空间,ioctl系统调用具有如下原型:
int ioctl(int d, int request, ...);
驱动程序的ioctl方法原型:
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
实现ioctl的方法:①定义命令,②实现命令
二、选择ioctl命令
在编写ioctl代码之前,需要选择对应不同命令的编号,为了防止对错误的设备使用正确的命令,命令号应该在系统范围内唯一,为方便程序员创建唯一的ioctl命令号,每一个命令号被分为许多个位字段。定义号码的新方法使用4个位字段,其符号定义在>中。
type -- 幻数,表明哪个设备的命令,8位宽
number -- 序数,表明设备命令中的第几个,8位宽
direction -- 数据传送的方向,可能的值是_IOC_NONE,_IOC_READ, _IOC_WRITE。数据传送是从应用程序的观点来看待的,_IOC_READ 意思是从设备读。
size -- 用户数据的大小
内核提供了一些构造命令编号的宏,在中:
_IO(type,nr) -- 构造无参数的命令编号
_IOR(type,nr,datatype) -- 构造从驱动程序中读取数据的命令编号
_IOW(type,nr,datatype) -- 用于写入数据的命令
_IOWR(type,nr,datatype) -- 用于双向传输
内核提供了解开以上位字段的宏
_IOC_DIR(nr)
_IOC_TYPE(nr)
_IOC_NR(nr)
_IOC_SIZE(nr)
一些命令示例:
-
/*
-
* Ioctl definitions
-
*/
-
-
/* Use 'k' as magic number */
-
#define SCULL_IOC_MAGIC 'k'
-
/* Please use a different 8-bit number in your code */
-
-
#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)
-
-
/*
-
* S means "Set" through a ptr,
-
* T means "Tell" directly with the argument value
-
* G means "Get": reply by setting through a pointer
-
* Q means "Query": response is on the return value
-
* X means "eXchange": switch G and S atomically
-
* H means "sHift": switch T and Q atomically
-
*/
-
#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)
-
#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)
-
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3)
-
#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4)
-
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int)
-
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)
-
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7)
-
#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8)
-
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)
-
#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int)
-
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11)
-
#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12)
-
-
/*
-
* The other entities only have "Tell" and "Query", because they're
-
* not printed in the book, and there's no need to have all six.
-
* (The previous stuff was only there to show different ways to do it.
-
*/
-
#define SCULL_P_IOCTSIZE _IO(SCULL_IOC_MAGIC, 13)
-
#define SCULL_P_IOCQSIZE _IO(SCULL_IOC_MAGIC, 14)
-
/* ... more to come */
-
-
#define SCULL_IOC_MAXNR 14
三、ioctl的返回值
如果使用了不合适的 ioctl 命令号,应当返回-ENOTTY 。这个错误码被 C 库解释为"不合适的设备 ioctl。然而,它返回-EINVAL仍是相当普遍的。
四、使用ioctl参数(附加参数)
如果附加参数为整数,直接使用即可。如果是指针,必须确保这个用户地址是有效的,因此使用前需进行正确的检查。通过函数access_ok验证地址(而不传输数据),在中声明:
int access_ok(int type,const void *addr,unsigned long size);
type -- 应该是VERIFY_READ或者VERIFY_WRITE,取决于要执行的动作时读取还是写入用户空间内存区。
addr -- 用户空间地址
size -- 字节数
-
int err = 0, tmp;
-
int retval = 0;
-
-
/*
-
* extract the type and number bitfields, and don't decode
-
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
-
*/
-
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
-
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
-
-
/*
-
* the direction is a bitmask, and VERIFY_WRITE catches R/W
-
* transfers. `Type' is user-oriented, while
-
* access_ok is kernel-oriented, so the concept of "read" and
-
* "write" is reversed
-
*/
-
if (_IOC_DIR(cmd) & _IOC_READ)
-
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
-
else if (_IOC_DIR(cmd) & _IOC_WRITE)
-
err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
-
if (err) return -EFAULT;
在调用access_ok之后,驱动程序就可以安全地进行实际的数据传输了。除了copy_from_user和copy_to_user函数外,还可以使用如下已经为常用的数据大小优化过的一组函数:
① 写数据到用户空间
put_user(datum,ptr)
__put_user(datum,ptr)
它们相对比较快,当要传递单个数据时,应该用这些宏,而不是copy_to_user。put_user进行检查以确保进程可以写入指定的内存地址。__put_user做的检查少些,应该在已经使用access_ok校验过再使用。
② 从用户空间接收数据
get_user(local, ptr)
__get_user(local, ptr)
__get_user应该在操作地址已被access_ok校验过再使用。
五、ioctl示例
该例子来自国嵌,ioctl.rar
阅读(2438) | 评论(0) | 转发(1) |