参考以下资料:
linux 内核 - ioctl 函数详解
https://blog.csdn.net/qq_19923217/article/details/82698787
Linux设备驱动之Ioctl控制
https://www.cnblogs.com/geneil/archive/2011/12/04/2275372.html
1、ioctl 是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情境下,如果需要扩展新的功能,通常以增设 ioctl() 命令的方式实现。
2. 用户空间 ioctl
用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。
头文件:#include
函数原型:int ioctl(int filedes, int request, ...);
参数:
fd:文件描述符;
request:交互协议;
...:可变参数,依赖request指定长度及类型。
返回值:成功返回0;若出错则返回-1,并设置全局变量errorno;errorno取值如下:
EBADF d is not a valid descriptor.
EFAULT argp references an inaccessible memory area.
EINVAL Request or argp is not valid.
ENOTTY d is not associated with a character special device.
ENOTTY The specified request does not apply to the kind of object that the descriptor d references.
在实际应用中,ioctl最常见的errorno值为ENOTTY(error not a typewriter),即第一个参数fd指向的不是一个字符设备,不支持ioctl操作,这时候应该检查前面的open函数是否出错或者设备路径是否正确。
在用户空间使用 ioctl 时,可以做如下的出错判断以及处理:
int ret;
ret = ioctl(fd, MYCMD);
if (ret == -1) {
printf("ioctl: %s\n", strerror(errno));
}
3. 驱动程序 ioctl
在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情,因为设备都是特定的。关键在于怎么样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。
函数原型:
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
头文件:#include
1)unlocked_ioctl,顾名思义,应该在无大内核锁(BKL)的情况下调用;如果是64位的用户程序运行在64位的kernel上,调用的是unlocked_ioctl,如果是32位的用户程序运行在32位的kernel上,调用的也是unlocked_ioctl
2)compat_ioctl,,主要目的是为 64 位系统提供 32 位 ioctl 的兼容方法,也是在无大内核锁的情况下调用。当有32bit的userspace application call 64bit kernel的ioctl的时候,这个callback会被调用到。如果没有实现compat_ioctl,那么32位的用户程序在64位的kernel上执行ioctl时会返回错误:Not a typewriter
3)ioctl交互协议request与第三个参数的定义也有关系。要注意如果用户态是32位,内核态是64位系统。那么第三个参数的变量长度需要在两个系统中保持一样的长度。否则交互协议会出错。
4. ioctl 用户与驱动之间的协议
前文提到 ioctl 方法第二个参数 cmd 为用户与驱动的 “协议”,理论上可以为任意 int 型数据,可以为 0、1、2、3……,但是为了确保该 “协议” 的唯一性,ioctl 命令应该使用更科学严谨的方法赋值,在linux中,提供了一种 ioctl 命令的统一格式,将 32 位 int 型数据划分为四个位段,如下图所示:
| 设备类型 | 序列号 | 方向 | 数据尺寸 |
|-----------|--------|-------|--------- |
| 8 bit | 8 bit | 2 bit |8~14 bit|
|-----------|--------|-------|--------- |
在内核中,提供了宏接口以生成上述格式的 ioctl 命令:
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
1)dir(direction),ioctl 命令访问模式(数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;
2)type(device type),设备类型,占据 8 bit,在一些文献中翻译为 “幻数” 或者 “魔数”,可以为任意 char 型字符,例如
‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识;
3)nr(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增;
4)size,涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数;
通常而言,为了方便会使用宏 _IOC() 衍生的接口来直接定义 ioctl 命令:
// include/uapi/asm-generic/ioctl.h
/* used to create numbers */
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
_IO: 定义不带参数的 ioctl 命令
_IOW: 定义带写参数的 ioctl 命令(copy_from_user)
_IOR: 定义带读参数的ioctl命令(copy_to_user)
_IOWR: 定义带读写参数的 ioctl 命令
同时,内核还提供了反向解析 ioctl 命令的宏接口:
// include/uapi/asm-generic/ioctl.h
/* used to decode ioctl numbers */
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
1、在公共头文件中定义以下参数:
1)用户态和内核态之间传递的参数:
typedef struct gpio_opt
{
int gpio_num;
int gpio_status;
}gpio_opt;
2)定义ioctl交互命令:
/* 定义魔幻数 */
static const unsigned char GPIO_MAGIC = 'A';
/* 定义交互指令,这里以读写为例,还可以定义其他命令 */
/* 定义带读写参数的ioctl命令,这里是往内核文件中写数据 */
#define IOCTL_GPIO_GET _IOR(GPIO_MAGIC, 1, gpio_opt)
/* 定义带读写参数的ioctl命令,这里是从内核文件中读数据 */
#define IOCTL_GPIO_SET _IOW(GPIO_MAGIC, 2, gpio_opt)
2、在用户态调用ioctl函数
用户态引用头文件为:#include
int gpio_get(int fd, int *status)
{
/* 读 */
int ret = 0;
gpio_opt gpio;
memset(&gpio, 0, sizeof(gpio));
gpio.gpio_num = 111;
ret = ioctl(fd, IOCTL_GPIO_GET, &gpio);
if(ret < 0)
{
printf("[%s] %s %d : ioctl: %s \n", __FILE__, __func__, __LINE__, strerror(errno));
return -1;
}
*status = gpio.gpio_status;
printf("[%s] %s %d : get gpio_status = %d \n", __FILE__, __func__, __LINE__, gpio.gpio_status);
return 0;
}
int gpio_set(int fd)
{
/* 写 */
int ret = 0;
gpio_opt gpio;
memset(&gpio, 0, sizeof(gpio));
gpio.gpio_num = 111;
gpio.gpio_status = 1;
ret = ioctl(fd, IOCTL_GPIO_SET, &gpio);
if(ret < 0)
{
printf("[%s] %s %d : ioctl: %s \n", __FILE__, __func__, __LINE__, strerror(errno));
close(fd);
return -1;
}
return 0;
}
3、在内核态实现ioctl函数
用户态引用头文件为:#include
static int gpio_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
{
static gpio_opt gpio;
int n = 0;
if (copy_from_user(&gpio, (gpio_opt *)arg, sizeof(gpio_opt)))
return -EFAULT;
switch(cmd){
case IOCTL_GPIO_GET:
gpio.gpio_status = 2;
n = copy_to_user((gpio_opt *)arg, &gpio, sizeof(gpio_opt));
if (n == 0)
{
printk("[%s] %s %d : gpio.gpio_num = %d gpio.gpio_status = %d\n", __FILE__, __func__, __LINE__, gpio.gpio_num, gpio.gpio_status);
}
else
{
printk("[%s] %s %d : gpio_get failed!\n", __FILE__, __func__, __LINE__);
return -EFAULT;
}
break;
case IOCTL_GPIO_SET:
if((gpio.gpio_num == 111) && (gpio.gpio_status == 1))//匹配条件也可修改
{
//可添加set操作
printk("[%s] %s %d : gpio.gpio_num = %d gpio.gpio_status = %d\n", __FILE__, __func__, __LINE__, gpio.gpio_num, gpio.gpio_status);
}
else
{
printk("[%s] %s %d : gpio_set failed!\n", __FILE__, __func__, __LINE__);
}
break;
default:
return -ENOTTY; /*不能支持的命令*/
}
return 0;
}