linux驱动---ioctl函数解析

转自(39条消息) linux驱动---ioctl函数解析_那可真是太开心了的博客-CSDN博客_linux驱动ioctl

参考:ioctl,unlocked_ioctl 处理方法-阿里云开发者社区 (aliyun.com)

一个字符设备驱动会实现常规的打开、关闭、读、写等功能,但是在一些细分的情景下,如果需要扩展新功能,通常以增设ioctl()命令的方式实现,其作用类似于“拾遗补漏”。在文件I/O中,ioctl扮演着重要角色,本文将以驱动开发为侧重点,从用户空间到内核空间纵向分析ioctl函数。

1. 用户空间的ioctl()

#include 
int ioctl(int fd, int cmd, ...) ;

在man手册中描述ioctl函数的作用是:操作特殊文件的底层设备参数。

2. 驱动中的ioctl()

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

3.ioctl命令,用户与驱动之间的协议

在驱动程序中,ioctl()函数上传送的变量cmd是应用程序用于区别设备驱动程序请求处理内容的值,cmd除了可区别数字外,还包含有助于处理的几种相应信息。cmd的大小为32位,共分为4个域

  • bit31 ~ bit30 2位为“区别读写”区,作用是区分是读取命令还是写入命令
  • bit29 ~ bit15 14位为 “数据大小” 区,表示 ioctl() 中的 arg 变量传送的内存大小。
  • bit20 ~ bit08 8位为 “魔数"(也称为"幻数")区,这个值用以与其它设备驱动程序的 ioctl 命令进行区别。
  • bit07 ~ bit00 8位为 “区别序号” 区,是区分命令的命令顺序序号。

在编写ioctl代码之前,需要选择对应不同命令的编号。为了防止对错误的设备使用正确的命令,命令号应该在系统范围内为唯一,这种错误匹配并不是不会发生,程序可能发现自己正在试图对FIFO和audio等这类非串行设备输入流修改波特率,如果每一个ioctl命令都是唯一的,应用程序进行这种操作时就会得到一个EINVAL错误,而不是无意间成功完成了意想不到的操作。

要按Linux内核的约定方法为驱动程序选择ioctl编号,应该首先看看include/asm/ioctl.h和Doucumention/ioctl-number.txt这两个文件。头文件定义了要使用的位字段:类型(幻数)、序数、传送方向以及参数大小等。ioctl-number.txt文件中罗列了内核所使用的幻数,选择自己的幻数要避免和内核冲突

在asm-generic/ioctl.h里有如下定义

#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)

再看__IOC()的定义:

	#define _IOC(dir,type,nr,size) \
	(((dir)  << _IOC_DIRSHIFT) | \
	 ((type) << _IOC_TYPESHIFT) | \
	 ((nr)   << _IOC_NRSHIFT) | \
	 ((size) << _IOC_SIZESHIFT))

可见,__IO()的最后结果由__IOC()中的4个参数移位组合而成。

#define _IOC_DIRSHIFT	(_IOC_SIZESHIFT+_IOC_SIZEBITS)
	
#define _IOC_SIZESHIFT	(_IOC_TYPESHIFT+_IOC_TYPEBITS)
	
#define _IOC_SIZEBITS	14
	
#define _IOC_TYPESHIFT	(_IOC_NRSHIFT+_IOC_NRBITS)
	
#define _IOC_TYPEBITS	8
	
#define _IOC_NRSHIFT	0
	
#define _IOC_NRBITS	8
所以可以得到
_IOC_DIRSHIFT = _IOC_SIZESHIFT + 14 = (_IOC_TYPESHIFT+_IOC_TYPEBITS) + 14
		      = (_IOC_NRSHIFT+_IOC_NRBITS + 8) +14
			  = 0 + 8 + 8 +14
			  = 30
			  
_IOC_TYPESHIFT = (_IOC_NRSHIFT+_IOC_NRBITS) = 0 + 8 = 8

_IOC_NRSHIFT = 0

_IOC_SIZESHIFT = (_IOC_TYPESHIFT+_IOC_TYPEBITS) = (_IOC_NRSHIFT+_IOC_NRBITS) + 8
			   = 0 + 8 + 8
			   = 16

所以,(dir) << _IOC_DIRSHIFT表示dir左移30位,得到bit31 ~ bit30两位上,得到方向(读写)的属性
(size) << _IOC_SIZESHIFT) 位左移 16 位得到“数据大小”区;
(type) << _IOC_TYPESHIFT) 左移 8位得到"魔数区" ;
(nr)   << _IOC_NRSHIFT)   左移 0 位( bit7~bit0)

在asm-generic/ioctl.h中还有如下宏定义
//构造无参数的命令编号
#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)))

1、dir(direction),ioctl命令访问模式(数据传输方向),占据2bit,可以为_IOC_NONE、_IOC_READ、_IOC_WRITE _IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据

2、type(device type),设备类型,占据8bit,在一些文献中翻译为“幻数”或者“魔数”,可以为任意char型字符,例如‘a’、‘b’、‘c’等等,其主要作用是使ioctl命令有唯一的设备标识;

tips:Documentions/ioctl-number.txt记录了在内核中已经使用的“魔数”字符,为避免冲突,在自定义ioctl命令之前应该先查阅该文档

3、nr(number),命令编号/序数,占据8bit,可以为任意unsigned char型数据,取值范围0~255,如果定义了多个ioctl命令,通常从0开始编号递增;

4、size,涉及到ioctl第三个参数arg,占据13bit或者14bit(体系相关,arm架构一般为14位),指定了arg的数据类型及长度,如果在驱动的ioctl实现中不检查,通常可以忽略该参数。

4.举例分析

内核空间

#define CLOSE_CMD       (_IO(0XEF,0X1))     /*关闭定时器*/
#define OPEN_CMD        (_IO(0XEF,0X2))     /*打开定时器*/
#define SETPERIOD_CMD   (_IO(0XEF,0X3))     /*设置定时器周期命令*/

......

static long timer_unlocked_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
{
    struct timer_dev *dev = (struct timer_dev *)filp->private_data;
    int timerperiod;
    unsigned long flags;
    switch (cmd) 
    {
        case CLOSE_CMD: /* 关闭定时器 */
            del_timer_sync(&dev->timer);
            break;
        case OPEN_CMD: /* 打开定时器 */
            spin_lock_irqsave(&dev->lock, flags);
            timerperiod = dev->timeperiod;
            spin_unlock_irqrestore(&dev->lock, flags);
            mod_timer(&dev->timer, jiffies +
            msecs_to_jiffies(timerperiod));
            break;
        case SETPERIOD_CMD: /* 设置定时器周期 */
            spin_lock_irqsave(&dev->lock, flags);
            dev->timeperiod = arg;
            spin_unlock_irqrestore(&dev->lock, flags);
            mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));
            break;
        default:
            break;
    }
    return 0;
}

//设备操作函数
static struct file_operations timer_fops = {
    .owner = THIS_MODULE,
    .open = timer_open,
    .unlocked_ioctl = timer_unlocked_ioctl,
};

用户空间

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"

/* 命令值 */
#define CLOSE_CMD (_IO(0XEF, 0x1)) /* 关闭定时器 */
#define OPEN_CMD (_IO(0XEF, 0x2)) /* 打开定时器 */
#define SETPERIOD_CMD (_IO(0XEF, 0x3)) /* 设置定时器周期命令 */

int main(int argc,char *argv[])
{
    int fd,ret;
    char *filename;
    unsigned int cmd;
    unsigned int arg;
    unsigned char str[100];

    if(argc != 2) 
    {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    fd = open(filename,O_RDWR);
    if(fd < 0)
    {
        printf("cant opem file %s\r\n",filename);
        return -1;
    }

    while (1) 
    {
        printf("Input CMD:");
        ret = scanf("%d", &cmd);
        if (ret != 1) /* 参数输入错误 */
        { 
            gets(str); /* 防止卡死 */
        }
        if(cmd == 1) /* 关闭 LED 灯 */
            cmd = CLOSE_CMD;
        else if(cmd == 2) /* 打开 LED 灯 */
            cmd = OPEN_CMD;
        else if(cmd == 3) 
        {
            cmd = SETPERIOD_CMD; /* 设置周期值 */
            printf("Input Timer Period:");
            ret = scanf("%d", &arg);
            if (ret != 1) /* 参数输入错误 */
            { 
                gets(str); /* 防止卡死 */
            }
        }
        ioctl(fd, cmd, arg); /* 控制定时器的打开和关闭 */
    }
    close(fd);
}

你可能感兴趣的:(linux,驱动开发,运维)