1、unlocked _ioctl接口作用
write:往设备写中写数据,单独这个接口并不能满足现实设备的全部控制需求。
一个lcd控制器:主要作用就是驱动lcd屏,要显示就是通过write接口把显示数据发给lcd控制器指定的显存。而参数设置类通过write接口设置就可能会和普通的显示数据弄混乱了。为了解决这个问题,内核提供了ioctl接口专门对设备控制(参数设置,参数查询等功能)。
ioctl主要用于实现对硬件设备控制类操作,使用write和read不太好实现的功能
2、ioctl系统调用
#include <sys/ioctl.h>
原型:int ioctl(int fd, int request, ...);
功能:通过命令形式来控制硬件设备,相当linux系统给我们提供扩展系统功能的一个接口,可以由用户自定义命令来让硬件执行不同的代码。
参数:
fd: 文件描述符
request: 发给设备的命令,这个命令可以是系统预定义的,也可是用户自定义
…:表示变参,可选择的,相当于printf参数一样,可以有多个,也可以没有。是否需要传送和request参数有关。
示例:有4个命令:1)0x10表示开全部灯; 2)0x20 表示开第N个灯;3)0x30表示关第N个灯;4)0x40表示关全部灯;
使用上面的ioctl函数来控制:
1)表示开全部灯;
ioctl(fd,0x10) //全部开
2)0x20 表示开第N个灯;
ioctl(fd,0x20,2) //开第二灯
3)0x30表示关第N个灯;
ioctl(fd,0x20,1) //关第一灯
4)0x40表示关全部灯;
ioctl(fd,0x40) //全部关
返回:
>=0 : 执行成功,>0时候值是什么含义由驱动程序中决定。
-1 : 执行失败
3、unlocked_ioctl接口驱动模板
在文件操作方法结构体当中定义如下:
struct file_operations {
……
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
……
}
原型:long xxx_unlocked_ioctl(struct file *pfile, unsigned int cmd, unsigned long args);
功能:对应于应用程序编程API(系统调用)接口的ioctl函数。
参数:pfile:文件结构指针,间接对应于系统调用的fd参数(文件描述符)
cmd:对应于系统调用的request ; args:args系统调用的…参数(可选参数)
返回值:>=0 : cmd命令执行成功,>0时候值是什么含义由驱动程序中决定。
< 0 : cmd命令执行失败,返回失败错误码,比如args参数非法,则返回-EFAULT,所返回的错误码会被系统存储在全局变量errno中,应用程序可以这个变量。
错误码:EFAULT args非法;EINVAL 参数无效。
4、unlocked_ioctl接口测试体验
4.1 unlocked_ioctl接口
设计思路:由于应用程序的ioctl函数中request参数对应于驱动的cmd参数,而应用程序通过传递不同的值来告诉驱动程序做不同事情,所以,驱动中的unlocked_ioctl接口代码内部一定是要判断cmd值来决定执行不同的代码,所以就是一个if语句或switch语句完成,对cmd值的判断。
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <asm/io.h> #include <asm/uaccess.h> #include <linux/miscdevice.h> #define DEVICE_NAME "ioctl-demo" static long chrdev_unlocked_ioctl (struct file *pfile, unsigned int cmd, unsigned long arg) { switch ( cmd ) { case 0 : printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg); break; case 1 : printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg); break; case 2 : printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg); break; case 3 : printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg); break; case 4 : printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg); break; default: printk("line:%d,cmd:%d,arg:%ld", __LINE__, cmd, arg); return -EINVAL; } return arg; } //文件操作方法: static const struct file_operations xxx_fops = { .unlocked_ioctl = chrdev_unlocked_ioctl, }; static struct miscdevice xxx_dev = { .fops = &xxx_fops, .minor = 255, .name = DEVICE_NAME, }; //模块初始化函数 static int __init xxx_dev_init( void ) { int ret; ret = misc_register (&xxx_dev ); if(ret<0){ printk ("module install fail !\n" ); return ret; } printk ("module install success !\n" ); return 0; } //模块卸载函数 static void __exit xxx_dev_exit ( void ) { misc_deregister ( &xxx_dev); printk ("module uninstall success !\n" ); } module_init (xxx_dev_init ); module_exit (xxx_dev_exit ); MODULE_LICENSE ( "GPL" ); MODULE_AUTHOR ( "Chenzhifa" ); MODULE_DESCRIPTION ( "STUDY_MODULE" );
测试结果:
[ 911.013119] module install success !
[root@edu118 home]# ./app
[ 913.403718] line:14,cmd:0,arg:10
ret = 10
line:17,cmd:1,arg:11
ret = 11
ret = -1
line:23,cmd:3,arg:13
ret = 13
line:26,cmd:4,arg:14
ret = 14
line:29,cmd:5,arg:15
ret = -1
[root@edu118 home]#
上面的测试得到出几点结论:
1. 知道用户空间中应用程序ioctl函数和驱动程序 .unlocked_ioctl 接口的参数对应关系。
2. 某一些cmd在自己的驱动中不能使用。
3. 可以使用这接口来实现对Led灯控制。--- 很容易实现。
5、标准unlocked_ioctl接口的命令合成
5.1 接口命令规则
1. 系统自定义命令:执行优先级会高于用户自定义命令
2. 用户自定义命令
ioctl执行不直接就执行驱动程序中.unlocked_ioctl接口,而是先根据cmd情况判断是否是属于预定义命令,如果是则去执行完成后返回了。返回后可能是执行用户自定义命令,也可能直接返回,不执行用户命令。
避免命令冲突:内核为解决这个问题,定义一个规则,命令值是特定格式组成 的。
Ioctl-decoding.txt linux-3.5\documentation\Ioctl
解释了cmd值的组成格式:
bits meaning
31-30 00 – 用户程序和驱动没有数据传递 uses _IO macro
10 – 用户程序从驱动读取数据: _IOR
01 – 用户程序向驱动写数据: _IOW
11 - 先用户程序写数据到驱动,再从驱动中读取数据(先写数据然后读取数据回来): _IOWR
29-16 当用户程序和驱动程序有数据传递时候才有效,表示要传递的数据大小。
示例:A,B两个驱动的CMD值它们的0~7可以相同,但是8~15最好不相同。A驱动中的所有CMD命令值的8~15位都应该是相同,B驱动所有CMD值8~15位值也是相同
led cmd示例:
#define IOC_CMD_LED_ON_ALL 0<<30 | 0<<16 | 'L' | 0 //全部开 #define IOC_CMD_LED_OFF_ALL 1<<30 | 4<<16 | 'L' | 1 //指定灯开 #define IOC_CMD_ON_X 1<<30 | 4<<16 | 'L' | 2 //指定灯关 #define IOC_CMD_OFF_X 0<<30 | 0<<16 | 'L' | 3 //全部关
这样是不会冲突的,内核提供了相应的命令合成和分解宏,定义在asm-generic\ioctl.h 文件中,如下:
_IO(type,nr) :定义没有数据传递的命令
_IOR(type,nr,size) :定义从驱动中读取数据的命令
_IOW(type,nr,size) :定义向驱动写入数据的命令
_IOWR(type,nr,size) :定义数据交换类型的命令,先写入数据,再读取数据这类命令。
type:表示命令组成的魔数,也就是8~15位;
nr:表示命令组成的编号,也就是0~7位
size:表示命令组成的参数传递大小,但是这里不传递数字,而是数据类型,如要传递4字节,可以写int,如要传递一个结构体数据给驱动,则把结构类型做size参数。
使用内核定义宏来改造前面定义的4条命令:
//定义命令 #define IOC_CMD_LED_MAGIC 'L' #define IOC_CMD_LED_ON_ALL _IO(IOC_CMD_LED_MAGIC,0) //全部开 #define IOC_CMD_LED_OFF_ALL _IO(IOC_CMD_LED_MAGIC,1) //全部关 #define IOC_CMD_ON_X _IOW(IOC_CMD_LED_MAGIC,2,int) //指定灯开 #define IOC_CMD_OFF_X _IOW(IOC_CMD_LED_MAGIC,3,int) //指定灯关 #define IOC_CMD_LED_MIN_NR 0 //最小命令序号 #define IOC_CMD_LED_MAX_NR 3 //最大命令序号
内核也提供也分解命令各部分的宏:
/* used to decode ioctl numbers.. */
_IOC_DIR(nr) 分解出命令的方向,也就是上面说30~31位的值
_IOC_TYPE(nr) 分解出命令的魔数,也就是上面说8~15位的值
_IOC_NR(nr) 分解出命令的编号,也就是上面说0~8位
_IOC_SIZE(nr) 分解出命令的复制数据大小,也就是上面说的16~29位
nr : 要分解的cmd值
示例:
_IOC_TYPE(LED_ON_X) 结果是 'L'
_IOC_NR(LED_ON_X) 结果是 1
Ioctl-number.txt linux-3.5\documentation\Ioctl
这个文件中列出当前内核哪些魔数和对应命令编号已经使用了,你不应该再去使用这些命令,否则就可能冲突。这个表只供参数,不表示一定是准确的。因为它是针对X86内核,并且是统计到2.6.31版本。但是,只要你按规则定义命令,冲突的可能性很小。
一个魔数如果已经使用了,你也想使用,则要避开上面描述命令号。例如:上面的魔数为0,对应的命令编号0~1F已经被使用,但是可以使用魔数为0,命令编号0x20开始的组合值。
- 标准unlocked_ioctl接口代码实现
- unlocked_ioctl接口控制led
在原来的LED驱动代码基础上修改:
1、定义命令:
单独定义在一个头文件中:ioctl_cmd.h
#ifndef __IOCTL_CMD__ #define __IOCTL_CMD__ //定义命令 #define IOC_CMD_LED_MAGIC 'L' #define IOC_CMD_LED_ON_ALL _IO(IOC_CMD_LED_MAGIC,0) //全部开 #define IOC_CMD_LED_OFF_ALL _IO(IOC_CMD_LED_MAGIC,1) //全部关 #define IOC_CMD_ON_X _IOW(IOC_CMD_LED_MAGIC,2,int) //指定灯开 #define IOC_CMD_OFF_X _IOW(IOC_CMD_LED_MAGIC,3,int) //指定灯关 #define IOC_CMD_LED_MIN_NR 0 //最小命令序号 #define IOC_CMD_LED_MAX_NR 3 //最大命令序号 #endif
2、驱动中包含自定义命令头文件
#include”ioctl_cmd.h“
3、修改unlocked_ioctl接口
//unlocked_ioctl 接口 static long xxx_unlocked_ioctl (struct file *pfile, unsigned int cmd, unsigned long args) { int nr = 0,ret; //printk("1 --- GPIO_SWPORTA_DR:0x%x\r\n", readl(GPIO_SWPORTA_DR)); if(_IOC_TYPE(cmd) != IOC_CMD_LED_MAGIC) return -EINVAL; if((_IOC_NR(cmd) < IOC_CMD_LED_MIN_NR) || (_IOC_NR(cmd) <IOC_CMD_LED_MIN_NR)) return -EINVAL; if(_IOC_DIR(cmd) == _IOC_WRITE){ //复制用户空间传递下来的灯编号 ret = copy_from_user(&nr, (void*)args, _IOC_SIZE(cmd)); if(ret) { return -EFAULT; } //检测用户空间传递下来的led编号是否合法 if(nr >= LED_NUM) { return -EINVAL; } } switch(cmd) { case IOC_CMD_ON_X: case IOC_CMD_OFF_X: //控制 led 亮灭 if(cmd == IOC_CMD_ON_X) { xyd_rk3399_pwrled_on_off_ctrl( 1); } else { xyd_rk3399_pwrled_on_off_ctrl( 0); } break; //开发板只有一个灯,所以代码和上面的相同,后面硬件扩展了再修改代码 case IOC_CMD_LED_ON_ALL: case IOC_CMD_LED_OFF_ALL: //控制 led 亮灭 if(cmd == IOC_CMD_LED_ON_ALL) { xyd_rk3399_pwrled_on_off_ctrl( 1); } else { xyd_rk3399_pwrled_on_off_ctrl( 0); } default: printk("ERROR:Illegal parameters\r\n"); return -EINVAL; } //printk("2 --- GPIO_SWPORTA_DR:0x%x\r\n", readl(GPIO_SWPORTA_DR)); return 0; }
5.2 ioctl系统调用控制led
#include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> //lseek #include <sys/ioctl.h> //ioctl #include "ioctl_cmd.h" #define DEFAULT_DEV_LED "/dev/xyd-leds" //默认打开 的设备名 int main(int argc, char**argv) { char *dev; int ret, i, nr = 0, fd; if(argc == 1) { dev = DEFAULT_DEV_LED; } else if(argc == 2) { dev = argv[1]; } else { printf("Usage:%s [/dev/devname]\r\n", argv[0]); return 0; } fd = open(dev, O_RDWR); if(fd < 0) { perror("open"); return -1; } while(1) { ioctl(fd, IOC_CMD_ON_X, &nr); //亮 sleep(1); ioctl(fd, IOC_CMD_OFF_X, &nr); //灭 sleep(1); } //关闭文件 close(fd); return 0; }