目录
1、unlocked _ioctl接口作用
2、ioctl系统调用
3、unlocked_ioctl接口驱动模板
4、unlocked_ioctl接口测试体验
4.1 unlocked_ioctl接口
5、标准unlocked_ioctl接口的命令合成
5.1 接口命令规则
5.2 ioctl系统调用控制led
write:往设备写中写数据,单独这个接口并不能满足现实设备的全部控制需求。
一个lcd控制器:主要作用就是驱动lcd屏,要显示就是通过write接口把显示数据发给lcd控制器指定的显存。而参数设置类通过write接口设置就可能会和普通的显示数据弄混乱了。为了解决这个问题,内核提供了ioctl接口专门对设备控制(参数设置,参数查询等功能)。
ioctl主要用于实现对硬件设备控制类操作,使用write和read不太好实现的功能
#include
原型: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 : 执行失败
在文件操作方法结构体当中定义如下:
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 参数无效。
设计思路:由于应用程序的ioctl函数中request参数对应于驱动的cmd参数,而应用程序通过传递不同的值来告诉驱动程序做不同事情,所以,驱动中的unlocked_ioctl接口代码内部一定是要判断cmd值来决定执行不同的代码,所以就是一个if语句或switch语句完成,对cmd值的判断。
#include
#include
#include
#include
#include
#include
#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灯控制。--- 很容易实现。
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开始的组合值。
在原来的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) = 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;
}
#include
#include
#include
#include
#include
#include //lseek
#include //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;
}