目录
简介:
在驱动程序里,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);
}
}