Linux驱动编程 - _IO, _IOR, _IOW, _IOWR 宏的用法与解析

目录

简介:

一、源码分析

1、格式说明

1.1 魔数 (magic number)

1.2 基(序列号)数

1.3 变量类型

2、Linux源码分析

二、实例


简介:


        在驱动程序里,ioctl() 函数上传送的变量 cmd 是应用程序用于区别设备驱动程序请求处理内容的值。Linux 提供 _IO, _IOR, _IOW, _IOWR 宏来处理此功能。

一、源码分析


分析源码前先看下 _IO, _IOR, _IOW, _IOWR 各个参数的含义

1、格式说明

_IO (魔数,基数);

_IOR (魔数,基数,变量类型)

_IOW (魔数,基数,变量类型)

_IOWR (魔数,基数,变量类型 )

1.1 魔数 (magic number)

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

1.2 基(序列号)数

基数用于区别各种命令。通常,从0开始递增,相同设备驱动程序上可以重复使用该值。例如,读取和写入命令中使用了相同的基数,设备驱动程序也能分辨出来,原因在于设备驱动程序区分命令时使用 switch ,且直接使用命令变量 cmd值。创建命令的宏生成的值由多个域组合而成,所以即使是相同的基数,也会判断为不同的命令。程序可以通过 _IOC_NR (cmd) 获取基数。

1.3 变量类型

代入变量或者是变量的类型,变量类型会被转换成数据大小。

2、Linux源码分析


源码路径:include/uapi/asm-generic/ioctl.h

#define _IOC_NRBITS	8
#define _IOC_TYPEBITS	8
# define _IOC_SIZEBITS	14
# define _IOC_DIRBITS	2

#define _IOC_NRMASK	((1 << _IOC_NRBITS)-1)
#define _IOC_TYPEMASK	((1 << _IOC_TYPEBITS)-1)
#define _IOC_SIZEMASK	((1 << _IOC_SIZEBITS)-1)
#define _IOC_DIRMASK	((1 << _IOC_DIRBITS)-1)

#define _IOC_NRSHIFT	0
#define _IOC_TYPESHIFT	(_IOC_NRSHIFT+_IOC_NRBITS)		//8
#define _IOC_SIZESHIFT	(_IOC_TYPESHIFT+_IOC_TYPEBITS)	//16
#define _IOC_DIRSHIFT	(_IOC_SIZESHIFT+_IOC_SIZEBITS)	//30

# define _IOC_NONE	0U
# define _IOC_WRITE	1U
# define _IOC_READ	2U

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

#define _IOC_TYPECHECK(t) (sizeof(t))
/*
 * Used to create numbers.
 *
 * NOTE: _IOW means userland is writing and kernel is reading. _IOR
 * means userland is reading and kernel is writing.
 */
#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)))
#define _IOR_BAD(type,nr,size)	_IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size)	_IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(type,nr,size)	_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

/* 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)

/* ...and for the drivers/sound files... */

#define IOC_IN		(_IOC_WRITE << _IOC_DIRSHIFT)
#define IOC_OUT		(_IOC_READ << _IOC_DIRSHIFT)
#define IOC_INOUT	((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)
#define IOCSIZE_MASK	(_IOC_SIZEMASK << _IOC_SIZESHIFT)
#define IOCSIZE_SHIFT	(_IOC_SIZESHIFT)

ioctl() 函数cmd 是应用程序用于区别请求处理内容。cmd的大小为 32位,共分 4 个域:

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

下面以_IO()、_IOR()、IOW()、 _IOWR() 中的_IO() 为例分析:

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

_IOC() 由 4 个参数移位组合而成,移位的4个宏如下:

#define _IOC_NRSHIFT 0
#define _IOC_TYPESHIFT 8
#define _IOC_SIZESHIFT 16
#define _IOC_DIRSHIFT 30

_IOC 展开后

#define _IOC(dir,type,nr,size) \
    (((dir)  << 30) | \
     ((type) << 8) | \
     ((nr)   << 0) | \
     ((size) << 16))

由此可知:

  • dir左移 30 位,即移到 bit31~bit30 两位,得到方向(读写)属性
  • size 左移16位,得到"数据大小"
  • type 左移8位,得到"魔数"
  • nr 左移0位,得到"区别序号"

_IOR()、IOW()、_IOWR() 同理,不再赘述。

二、实例


1、_IO 宏
该宏函数没有可传送的变量,只是用于传送命令。例如如下约定:

#define TEST_DRV_RESET _IO('Q', 0)

此时,省略由应用程序传送的 arg 变量或者代入 0 。在应用程序中使用该宏时,比如:

ioctl(dev, TEST_DEV_RESET, 0) 或者 ioctl(dev, TEST_DRV_RESET) 


这是因为变量的有效因素是可变因素。只作为命令使用时,没有必要判断出设备上数据的输出或输入。因此,设备驱动程序没有必要执行设备文件大开选项的相关处理。

2、_IOR 宏
该函数用于创建从设备读取数据的命令,例如可如下约定:

#define TEST_DEV_READ _IRQ('Q', 1, int)

应用程序从设备读取数据的大小为 int 。下面宏用于判断传送到设备驱动程序的 cmd 命令的读写状态:

_IOC_DIR (cmd)


运行该宏时,返回值的类型 如下:
 

  • _IOC_NONE : 无属性_IOC_READ : 可读属性
  • _IOC_WRITE : 可写属性
  • _IOC_READ | _IOC_WRITE : 可读,可写属性

使用该命令时,应用程序的 ioctl() 的 arg 变量值指定设备驱动程序上读取数据时的缓存(结构体)地址。

3、_IOW 宏
用于创建设 备上写入数据的命令,其余内容与 _IOR 相同。通常,使用该命令时,ioctl() 的 arg 变量值指定设备驱动程序上写入数据时的缓存(结构体)地址。

4、_IOWR 宏
用于创建设备上读写数据的命令。其余内 容与 _IOR 相同。通常,使用该命令时,ioctl() 的 arg 变量值指定设备驱动程序上写入或读取数据时的缓存 (结构体) 地址。

驱动示例:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
 
#define IOC_MAGIC                 'e'

#define GPIO_IOCTRL_SET		_IOW(IOC_MAGIC, 0, int *)
#define GPIO_IOCTRL_GET		_IOW(IOC_MAGIC, 1, int *)

struct gpiodev_dev{
	dev_t devid;			/* 设备号	*/
	struct cdev cdev;		/* cdev		*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备		*/
	int major;				/* 主设备号	*/
    int minor;				/* 次设备号	*/		
};

struct gpiodev_dev g_stCharDev; 	/* 字符设备 */

#define CDEV_CNT		1		  		/* 设备号个数 */
#define CDEV_NAME		"cdev_test"		/* 名字 */
 
static int chardev_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &g_stCharDev;  /* 设置私有数据 */
	printk("open...\n");
	return 0;
}
 
static ssize_t chardev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	struct gpioled_dev *dev = filp->private_data;
	
	printk("read...\n");
	return 0;
}
 
static ssize_t chardev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	struct gpioled_dev *dev = filp->private_data;

	printk("write...\n");
    return cnt;
}

static long chardev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	int ret = 0;
	struct gpioled_dev *dev = filp->private_data;
	
	printk("ioctl: cmd = %d\n", cmd);
	
	switch(cmd) {
		case GPIO_IOCTRL_SET:
			break;
		case GPIO_IOCTRL_GET:
			break;
		default:
			break;
	}

	return ret;
}
 
static int chardev_release(struct inode *inode, struct file *filp)
{
	printk("release...\n");
	return 0;
}
 
/* 设备操作函数 */
static struct file_operations chardev_fops = {
	.owner = THIS_MODULE,
	.open = chardev_open,
	.read = chardev_read,
	.write = chardev_write,
	.unlocked_ioctl = chardev_ioctl,
	.release = 	chardev_release,
};
 
/* 入口函数 */
static int __init chardev_init(void)
{
/* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (g_stCharDev.major) { /* 定义了设备号 */
        g_stCharDev.devid = MKDEV(g_stCharDev.major, 0);
        register_chrdev_region(g_stCharDev.devid, CDEV_CNT, CDEV_NAME);
    } else { /* 没有定义设备号 */
        alloc_chrdev_region(&g_stCharDev.devid, 0, CDEV_CNT, CDEV_NAME); /* 申请设备号 */
        g_stCharDev.major = MAJOR(g_stCharDev.devid); /* 获取分配号的主设备号 */
        g_stCharDev.minor = MINOR(g_stCharDev.devid); /* 获取分配号的次设备号 */
    }
    printk("major=%d, minor=%d\r\n", major, minor);
 
    /* 2、初始化 cdev */
    g_stCharDev.cdev.owner = THIS_MODULE;
    cdev_init(&g_stCharDev.cdev, &chardev_fops);				//file_operations
 
    /* 3、添加一个 cdev */
    cdev_add(&g_stCharDev.cdev, g_stCharDev.devid, CDEV_CNT);
 
    /* 4、创建类 */
    g_stCharDev.class = class_create(THIS_MODULE, CDEV_NAME);	///sys/class/目录下会创建一个新的文件夹
    if (IS_ERR(g_stCharDev.class)) {
        return PTR_ERR(g_stCharDev.class);
    }
 
    /* 5、创建设备 */
    g_stCharDev.device = device_create(g_stCharDev.class, NULL, g_stCharDev.devid, NULL, CDEV_NAME);//dev目录下创建相应的设备节点
    if (IS_ERR(g_stCharDev.device)) {
        return PTR_ERR(g_stCharDev.device);
    }
    return 0;
}
 
/* 出口函数 */
static void __exit chardev_exit(void)
{
    /* 注销字符设备驱动 */
    cdev_del(&g_stCharDev.cdev); /* 删除 cdev */
    unregister_chrdev_region(g_stCharDev.devid, CDEV_CNT); /* 注销 */
 
    device_destroy(g_stCharDev.class, g_stCharDev.devid);
    class_destroy(g_stCharDev.class);
}
 
module_init(chardev_init);
module_exit(chardev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dongao");

应用示例: 

#define GPIO_IOCTRL_SET _IOW(IOC_MAGIC, 0, int *)

int main(int argc, char *argv[])
{
	int ret;
    int fd = -1;
    int tz = 0;

	fd = open("/dev/cdev_test", O_RDWR);

	ret = ioctl(fd, GPIO_IOCTRL_SET, &tz);
	if (ret) {
		printf("GPIO_IOCTRL_SET failed, errno=%d\n", ret);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值