每天总是想学一点,然后积累多了,自己也就有那么点的满足感,不得不承认自己还是不行,但是,一直相信,只要肯努力,肯坚持,那就算是一种成功!!
学习 Linux
那更得这样!!
不过做玩这点东西,五一我也打算休息的。
(四)、嵌入式Linux内核驱动进阶
(7)、字符设备驱动(ioctl
控制)—①
大部分驱动除了需要具备读写设备的能力外,还学要具备对硬件控制的能力。
例如,要求设备报告错误信息,改变波特率,这些操作常常通过 ioctl
方法来实现!
1、ioctl
使用方法
1)用户使用 ioctl
的方法
在用户下,使用 ioctl() 系统调用来控制设备,原型如下:
int ioctl(int fd,unsigned long
cmd,...)
原型中的点表示这是一个可选的参数,存在与否依赖于控制命令(第二个参数)是否涉及到与设备的数据交互。
2)驱动 ioctl
方法
在内核中,ioctl 驱动方法和用户空间版本不同的原型:
int (*ioctl) (struct innod *innod,struct
file *filp,unsigned int cmd,unsigned long arg)
cmd 参数是从用户空间传下来的,可选的参数 arg 以一个 unsigned long
的形式传递,不管它是一个整数或者一个指针。
如果 cmd 命令不涉及数据传输,则第三个参数 arg 的值没有任何意义!
注意:
在 2.6.36 以及以后的内核版本中,file_operation 结构中的 ioctl
成员已经被取消,使用心得内核编写驱动时,可以用下面两个操作替换:
// 不适用 BLK(大内核锁),将使用这种函数指针代替
ioctl
long (*unlocked_ioctl) (struct file
*,unsigned int,unsigned long);
// 兼容 64 位系统,使用此函数指针代替
long (*compat_ioctl) (struct file
*,unsigned int,unsigned long);
2、定义命令
在编写 ioctl 代码之前,首先需要定义命令。
为了防止对错误的设备使用正确的命令,命令号应该在系统范围内唯一!ioctl
命令编码被划分为几个位段,include/asm/ioctl.h 中定义了这些字段:
类型(幻数),序号,传送方向,参数的大小。
其中 Documentation/ioctl-number.txt 文件中罗列了在内核中已经使用了的幻数。
1)位段
定义 ioctl 命令的正确方法是使用 4 个位段,这个列表中介绍的符号定义在
中:
字段(bit) 说明
type(8) 幻数(类型),表明哪个设备的命令,在参考了
ioctl-number.txt 之后选出
8位宽
number(8) 序号,表明设备的第几个,8位宽
direction(2) 传送数据的方向,可能的值是
_IOC_NONE(没有数据传输),_IOC_READ
、_IOC_WRITE。数据传送的是从应用程序的观点来看待的,_IOC_READ 意思是从设备读
size(8~14) 用户数据的大小。(8~14 位宽,视处理器而定)
2)帮助定义命令的宏以及解开各个字段的宏!
①、帮助定义命令的宏:
_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)
3)定义命令(范例)
#define MEM_IOC_MAGIC
'm' // 定义幻数
#define MEM_IOCSET
_IOW(MEM_IOC_MAGIC,0,int)
#define MEM_IOCGQSET
_IOR(MEM_IOC_MAGIC,1,int)
3、ioctl
函数实现
定义好了命令,下一步就是实现 ioctl 函数了,ioctl 函数饿实现包括三个技术环节:
▲:返回值
▲:参数使用
▲:命令操作
1)返回值
ioctl 函数的实现通常是根据命令执行的一个 switch 语句。但是,当命令号不能匹配任何一个设备所支持的命令时,通常返回
-EINVAL(非法参数)!
2)参数
如果是一个整数,可以直接使用。
如果是指针,我们必须确保这个用户地址是有效的,因此使用前需进行正确的检查!
①不需要进行参数检查的函数:
▲:copy_from_user
▲:copy_to_user
▲:get_user
▲:put_user
②、需要检查的函数:
▲:__get_user
▲:__put_user
③、检查函数:
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。
④、参数检查例程:
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(VERFIY_READ,(void __user *)arg,_IOC_SIZE(cmd));
4、命令操作范例:
switch(cmd)
{
case
MEM_IOCSQUANTUM: // Set: arg points to the value
retval = _get_user(scull_quantum,(int *)arg);
break;
case
MEM_IOCGQUANTUM: // Get: arg is pointer to result
retval = _put_user(scull_quantum,(int *)arg);
break;
default:
return -EINVAL;
}