ioctl应用详解

前言

参考以下资料:

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)


ioctl 实例分析

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;
}

    
    
    
    
    
    
    
    
    
    
    
    
    
    

你可能感兴趣的:(linux)