驱动ioctl(cmd域)

一、基础

ioctl是linux中一种除read和write之外的数据传递机制, 通信双方是应用层和内核层。

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
// cmd:控制命令
// ...:可选参数:插入*argp,具体内容依赖于cmd。变参并不意味着它可以传任意多个参数进去,它的意思是第三个参数可传可不传。


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

unlocked_ioctl 函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应。
compat_ioctl 函数与unlocked_ioctl函数功能一样,区别在于在 64 位系统上,32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl。

二、cmd域

ioctl中cmd可以自定义,可能导致不同设备驱动拥有相同的命令号,如设备A、B都支持0x0,0x1,0x2,会造成命令码污染(操作不同设备命令码相同的话,不小心操作错了设备,可能出现严重后果)。因此linux推荐采用一套统一的ioctl()命令生成方式。

cmd有4个域组成:

名称 bit 说明
方向(dir) 30-31(2bit) 区分读写,_IOC_NONE, _IOC_READ,_IOC_WRITE, _IOC_READ
大小(size) 15-29(14bit) 区分数据大小,arg变量褚松的内存大小
设备类型(魔数,type) 8-15(8bit) 设备区分
序列号(nr) 0-7(8bit) 命令区分,命令序列号

定义在uapi/asm-generic/ioctl.h中。定义命令宏,需要传入幻数(type)、序号(nr)和大小(size)。传输的方向是以应用层的角度来描述的。

_IO(type,nr) //没有参数的命令
_IOR(type,nr,size) //该命令是从驱动读取数据
_IOW(type,nr,size) //该命令是从驱动写入数据
_IOWR(type,nr,size) //双向数据传输

拆分命令宏:

_IOC_DIR(cmd) //从命令中提取方向
_IOC_TYPE(cmd) //从命令中提取幻数
_IOC_NR(cmd) //从命令中提取序数
_IOC_SIZE(cmd) //从命令中提取数据大小

魔数 (magic number)
魔数范围为 0~255 ,内核中ioctl-number.txt给出了一些推荐的和已经使用的“魔数”。通常,用英文字符 "A" ~ "Z" 或者 "a" ~ "z" 来表示。设备驱动程序从传递进来的命令获取魔数,然后与自身处理的魔数想比较,如果相同则处理,不同则不处理。魔数是拒绝误使用的初步辅助状态。设备驱动程序可以通过 _IOC_TYPE (cmd) 来获取魔数。不同的设备驱动程序最好设置不同的魔数,但并不是要求绝对,也是可以使用其他设备驱动程序已用过的魔数。

三、args

应用层的ioctl的第三个参数是"...",这个跟printf的"..."不一样,printf中是意味这你可以传任意个数的参数,而ioctl最多也只能传一个,"..."的意思是让内核不要检查这个参数的类型。也就是说,从用户层可以传入任何参数,只要你传入的个数是1。

一般会有两种的传参方法:

  • 整数,使用方便。
  • 指针,可以任意类型参数。

驱动程序必须和应用程序中arg参数类型相同。

// 应用层应用程序 test.c
ioctl(fd, CMD1, chard); 
ioctl(fd, CMD2, intd);

----------------------
// >>> ioctl.h
#define CMD_MAGIC  'x' //定义幻数
#define CMD_MAX_NR  2  // 定义最大序数

char chard = 'x'; // 要传递的数据
int intc = 1000;

#define CMD1 _IO(CMD_MAGIC, 1)    // _IOR(CMD_MAGIC, 1, chard)
#define CMD2 _IO(CMD_MAGIC, 2)    // _IOR(CMD_MAGIC, 2, intc)

---------------------
// >>> 内核层驱动程序 test_dev.c
long dev_ioctl(struct file *file, unsigned int cmd, unsigned long ptr)
{
	switch (cmd){
	case CMD1:
	      printk("received a char : %c\n", (char)ptr);
	      break;
        case CMD2:
              printk("received a int : %d\n", (int)ptr);
              break;
	}
}

四、测试

在宋宝华 led driver基础上修改测试。

#define LIGHT_MAJOR     200
#define CMD_MAGIC       'x'
#define CMD_NR_MAX      7
#define CMD_LIGHT_ON    _IO(CMD_MAGIC, 0)
#define CMD_LIGHT_OFF   _IO(CMD_MAGIC, 1)
#define CMD_WR_CHAR     _IO(CMD_MAGIC, 2)
#define CMD_RD_CHAR     _IO(CMD_MAGIC, 3)
#define CMD_WR_INT      _IO(CMD_MAGIC, 4)
#define CMD_RD_INT      _IO(CMD_MAGIC, 5)
#define CMD_WR_PTR      _IO(CMD_MAGIC, 6)
#define CMD_RD_PTR      _IO(CMD_MAGIC, 7)

long light_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct light_dev *dev = filp->private_data;
    int i = 0, ret = 0;
    static int intd = 0xF0F0;
    static char chrd = 0xF0;
    static unsigned char id[10] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,0x8, 0x9};

    if(_IOC_TYPE(cmd) != CMD_MAGIC){
        return -EINVAL;
    }
    if(_IOC_NR(cmd) > CMD_NR_MAX){
        return -EINVAL;
    }

    switch(cmd){
        case CMD_LIGHT_OFF:
            dev->value = 0;
            printk(KERN_INFO "light off\n");
            break;
        case CMD_LIGHT_ON:
            dev->value = 1;
            printk(KERN_INFO "light on\n");
            break;
        case CMD_RD_CHAR:
            ret = put_user(chrd, (char *)arg);
            break;
        case CMD_WR_CHAR:
            printk(KERN_INFO "app write: 0x%x\n", (char)arg);
            chrd = (char)arg;
            break;
        case CMD_RD_INT:
            ret = put_user(intd, (int *)arg);
            break;
        case CMD_WR_INT:
            printk(KERN_INFO "app write: 0x%x\n", (int)arg);
            intd = (int)arg;
            break;
        case CMD_RD_PTR:
            ret = copy_to_user((char*)arg, id, sizeof(id));
            break;
        case CMD_WR_PTR:
            ret = copy_from_user(id, (char *)arg, sizeof(id));
            printk(KERN_INFO "app write: ");
            for(i = 0; i < sizeof(id); i++){
                printk(KERN_INFO "0x%x ", id[i]);
            }
            printk(KERN_INFO "\n");
            break;

        default:
            return -ENOTTY;
    }

    return ret;
}

app测试

#include <sys/ioctl.h>

#define CMD_MAGIC       'x'
#define CMD_NR_MAX      7
#define CMD_LIGHT_ON    _IO(CMD_MAGIC, 0)
#define CMD_LIGHT_OFF   _IO(CMD_MAGIC, 1)
#define CMD_WR_CHAR     _IO(CMD_MAGIC, 2)
#define CMD_RD_CHAR     _IO(CMD_MAGIC, 3)
#define CMD_WR_INT      _IO(CMD_MAGIC, 4)
#define CMD_RD_INT      _IO(CMD_MAGIC, 5)
#define CMD_WR_PTR      _IO(CMD_MAGIC, 6)
#define CMD_RD_PTR      _IO(CMD_MAGIC, 7)

	char ch = 0;
	int intd = 0;
	char id[10];
	int i = 0;

	ioctl(fd, CMD_RD_CHAR, &ch);
	printf("read char 0x%x\n", ch & 0xFF);
	ch = 0xF1;
	ioctl(fd, CMD_WR_CHAR, ch);
	ioctl(fd, CMD_RD_CHAR, &ch);
	printf("read char 0x%x\n", ch & 0xFF);
	
	ioctl(fd, CMD_RD_INT, &intd);
	printf("read int 0x%x\n", intd);
	intd = 9000;
	ioctl(fd, CMD_WR_INT, intd);
	ioctl(fd, CMD_RD_INT, &intd);
	printf("read int 0x%x\n", intd);
	
	ioctl(fd, CMD_RD_PTR, id);
	printf("read id: ");
	for(i = 0; i < (int)sizeof(id); i++){
		printf("0x%x ", id[i]&0xFF);
	}
	printf("\n");
	memset(id, 0x51, sizeof(id));
	ioctl(fd, CMD_WR_PTR, id);
	ioctl(fd, CMD_RD_PTR, id);
	printf("read id: ");
	for(i = 0; i < (int)sizeof(id); i++){
		printf("0x%x ", id[i]&0xFF);
	}
	printf("\n");

测试结果:

read char 0xf0
read char 0xf1
read int 0xf0f0
read int 0x2328
read id: 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 
read id: 0x51 0x51 0x51 0x51 0x51 0x51 0x51 0x51 0x51 0x51 

// dmesg
[25181.505585] light on
[25181.505662] app write: 0xfffffff1
[25181.505668] app write: 0x2328
[25181.505675] app write: 
[25181.505676] 0x51 
[25181.505677] 0x51 
[25181.505678] 0x51 
[25181.505678] 0x51 
[25181.505679] 0x51 
[25181.505679] 0x51 
[25181.505680] 0x51 
[25181.505681] 0x51 
[25181.505681] 0x51 
[25181.505682] 0x51 

五、参考

  1. Linux内核中ioctl的cmd域修改_IO,_IOR,_IOW,_IOWR宏的用法与解析

  2. ldd4中6.3.5 ioctl函数 宋宝华

  3. ioctl命令及参数传递

  4. Linux设备驱动之Ioctl控制

posted @ 2015-09-22 22:58  yuxi_o  阅读(601)  评论(0编辑  收藏  举报