要点:学习ioctl()驱动编写,如何传入命令来控制硬件。
1)ioctl知识
用户空间的ioctl()调用如下:
int ioctl(int fd, unsigned long cmd, …);
2)驱动中ioctl:
Int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg );
主要工作:传入设备对应的inode,根据命令cmd和arg来修改filp。
3)实现
分为两步:
n 定义命令(一般在头文件中, scull.h);
n 实现命令, switch。
i. 定义命令
编写ioctl之前先需要定义命令,命令号在系统范围内必须是唯一的。命令cmd被划分为4个位段:类型type(幻数:确保唯一)、序号(对应该设备驱动的命令的序号)、传送方向、参数大小。
type:幻数(类型):表明哪个设备的命令,在参考了ioctl-number.txt之后选出,8位宽
number:序号,表明设备命令中的第几个,8位宽
direction:数据传送的方向,可能的值是_IOC_NONE(没有数据传输),_IOC_READ,_IOC_WRITE。数据传送是从应用程序的观点来看的,_IOC_READ意思是从设备读
size:用户数据的大小。(13/14位宽,视处理器而定)
内核提供了下列宏来帮助定义命令:
_IO(type, nr):没有参数传递的命令。(那么direction的值为_IOC_NONE,size的值为0)
_IOR(type, nr, datatype):从驱动中读数据(4个值已经确定)
_IOW(type, nr, datatype):写数据到驱动
_IOWR(type, nr, datatype):双向传送,type和number成员作为参数被传递
定义命令(范例)
#define MEM_IOC_MAGIC 'm' //定义幻数,一个字母刚好是8位,必须唯一
#define MEM_IOCSET _IOW(MEM_IOC_MAGIC, 0, int)
#define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC, 1, int)
ii. 实现
1) 返回值:switch;
2) 传入参数:如果是整数,则可直接使用;如果是指针,则需要先检验该指针是否有效(用access_ok()检验该指针是否是用户空间合法的)。
注:
不需要检测的函数:copy_to_user(), copy_from_user(), get_user(), put_user();
需要用access_ok检验的:__get_user()内核从用户空间读数据, __put_user()内核向用户空间写数据.(此处要注意access_ok() 与_IOC_READ的反向问题)即如下语句:
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
_IOC_READ:表示用户读取内核设备的数据(主体是用户程序);
access_ok()的write是验证内核可以向用户空间的指针写数据(主体是内核)。
4)int access_ok(int type, const void* addr, unsigned long size)
第一个参数是VERIFY_READ或者VERIFY_WRITE,用来表明是读用户内存还是写用户内存。Addr参数是要操作的用户内存地址,size是操作的长度。如果ioctl需要从用户空间读一个整数,那么size参数等于sizeof(int)。access_ok返回一个布尔值:1是成功(存取没问题)和0是失败(存取有问题),如果该函数返回失败,则ioctl应当返回-EFAULT.
5)ioctl代码:先检验
int scull_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { 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; 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; switch(cmd) { case SCULL_IOCRESET: scull_quantum = SCULL_QUANTUM; scull_qset = SCULL_QSET; break; case SCULL_IOCSQUANTUM: /* Set: arg points to the value */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; retval = __get_user(scull_quantum, (int __user *)arg); break; case SCULL_IOCTQUANTUM: /* Tell: arg is the value */ if (! capable (CAP_SYS_ADMIN)) return -EPERM; scull_quantum = arg; break; case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */ retval = __put_user(scull_quantum, (int __user *)arg); break; default: /* redundant, as cmd was checked against MAXNR */ return -ENOTTY; } return retval; }