天津房地产集团网站建设,深圳百度seo整站,app网站公司名称,建工网校和建工社是一个吗文章目录一、配置连接说明二、更新设备树#xff08;1#xff09;将led灯引脚添加到pinctrl子系统#xff08;2#xff09;设备树中添加LDE灯的设备树节点#xff08;3#xff09;编译更新设备树三、驱动开发与测试#xff08;1#xff09;编写设备驱动代码#xff08…
文章目录一、配置连接说明二、更新设备树1将led灯引脚添加到pinctrl子系统2设备树中添加LDE灯的设备树节点3编译更新设备树三、驱动开发与测试1编写设备驱动代码2编写驱动测试代码3Makefile四、结果展示五、ioctl接口讲解前面我们介绍了Linux设备模型、平台设备驱动、设备树(device tree)、GPIO子系统以及pinctrl子系统等大家看这篇文章之前需要提前知道的基础都在这篇文章中
Linux设备模型、平台设备驱动、设备树(device tree)、GPIO子系统以及pinctrl子系统介绍
有部分函数没有涉及到的最后会讲解。 一、配置连接说明
我们做控制led灯的时候用的是下面三个管脚
控制LED灯连接实图 二、更新设备树
1将led灯引脚添加到pinctrl子系统
将我们的引脚添加到 igkboard.dts 下的 iomuxc 节点下
pinctrl_my_gpio_leds: my-gpio-leds {fsl,pins MX6UL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x17059 /* led run */MX6UL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x17059MX6UL_PAD_JTAG_MOD__GPIO1_IO10 0x17059;};引脚定义都是在文件:~/imx6ull/imx6ull/bsp/kernel/linux-imx/arch/arm/boot/dts下可以查看
wangdengtaowangdengtao-virtual-machine:~/imx6ull/imx6ull/bsp/kernel/linux-imx/arch/arm/boot/dts$ cat imx6ul-pinfunc.h
/* SPDX-License-Identifier: GPL-2.0-only */
/** Copyright 2014 - 2015 Freescale Semiconductor, Inc.*/#ifndef __DTS_IMX6UL_PINFUNC_H
#define __DTS_IMX6UL_PINFUNC_H/** The pin function ID is a tuple of* mux_reg conf_reg input_reg mux_mode input_val*/
#define MX6UL_PAD_BOOT_MODE0__GPIO5_IO10 0x0014 0x02a0 0x0000 5 0
#define MX6UL_PAD_BOOT_MODE1__GPIO5_IO11 0x0018 0x02a4 0x0000 5 0#define MX6UL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x0020 0x02ac 0x0000 5 0
#define MX6UL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x003c 0x02c8 0x0000 5 0
#define MX6UL_PAD_SNVS_TAMPER0__GPIO5_IO00 0x001c 0x02a8 0x0000 5 0
#define MX6UL_PAD_JTAG_MOD__GPIO1_IO10 0x0044 0x02d0 0x0000 5 0
2设备树中添加LDE灯的设备树节点
将我们的 my_leds 设备节点添加在 igkbosrd.dts 的根节点下 my_leds {compatible my-gpio-leds; /*设置“compatible”属性值与led的平台驱动做匹配*/pinctrl-names default; /*定义引脚状态*/pinctrl-0 pinctrl_my_gpio_leds; /*指定LED灯的引脚pinctrl信息*/status okay;led-gpios gpio5 8 GPIO_ACTIVE_HIGH,/*指定引脚使用的哪个GPIO 引脚名字 GPIO组 GPIO编号 有效电平*/gpio5 1 GPIO_ACTIVE_HIGH,gpio1 10 GPIO_ACTIVE_HIGH;default-state off;};
3编译更新设备树
添加完成之后我们需要去 linux-imx 文件夹下执行 make dtbs 编译一下我们的设备树然后将开发板上如下的文件路径下的 igkboard.dtb 以及 zImagelinux下的zImage文件再/bootl路径下 修改。
wangdengtaowangdengtao-virtual-machine:~/imx6ull/imx6ull/bsp/kernel/linux-imx$ make dtbsrootigkboard:~# find / -name zImage
/run/media/mmcblk1p1/zImage
rootigkboard:~# find / -name igkboard.dtb
/run/media/mmcblk1p1/igkboard.dtb替换之后执行 sudo reboot 即可。
使用新的设备树重新启动之后正常情况下会在开发板的 “/proc/device-tree” 目录下生成 “my_leds” 设备树节点。如下所示。
rootigkboard:~# cd /proc/device-tree/
rootigkboard:/proc/device-tree# ls
#address-cells 3p3v backlight-lcd clock-di0 compatible leds mqs panel pxp_v4l2 regulator0 soc w1
#size-cells __symbols__ chosen clock-di1 cpus memory80000000 my_leds pmu regulator-peri-3v3 reserved-memory sound-mqs1p8v aliases clock-cli clock-osc keys model name pwm-buzzer regulator-sd1-vmmc serial-number timer进入节点文件我们可以看到我们设置的gpio子系统的属性
rootigkboard:/proc/device-tree# cd my_leds/
rootigkboard:/proc/device-tree/my_leds# ls
compatible default-state led-gpios name pinctrl-0 pinctrl-names status三、驱动开发与测试
1编写设备驱动代码
代码中涉及到的字符设备驱动不了解的可以参考这篇文章Linux下字符设备驱动开发以及流程介绍
/************************************************************************* File Name: led_gpio.c Author: WangDengtao Mail: 1799055460qq.com Created Time: 2023年03月21日 星期二 13时55分02秒************************************************************************/
#include linux/module.h
#include linux/fs.h
#include linux/errno.h
#include linux/kernel.h
#include linux/major.h
#include linux/stat.h
#include linux/init.h
#include linux/device.h
#include linux/tty.h
#include linux/kmod.h
#include linux/gfp.h
#include linux/gpio.h
#include linux/gpio/consumer.h
#include linux/of_gpio.h
#include linux/platform_device.h/*如果没有定义DEV_MAJOR就设置设备号为0,采用动态申请如果有则使用宏定义的设备号*/
//#define DEV_MAJOR 88
#ifndef DEV_MAJOR
#define DEV_MAJOR 0
#endif#define PLATDRV_MAGIC 0x60 //魔术字
#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19)
#define DEV_NAME my_led /*宏定义设备的名字*/int dev_major DEV_MAJOR;/*led设备初始化*/
struct led_device { dev_t devid; /* 设备号 */struct cdev *cdev; /*cdev结构体*/struct class *class; /*定义一个class用于创建类 */struct device *device; /*设备 */struct device_node *node; /* led设备节点 */struct gpio_desc *led_gpio1,*led_gpio2,*led_gpio3; /*led灯GPIO描述符 */
}led_dev;/*字符设备操作函数集open函数*/
static int led_open(struct inode *inode, struct file *file)
{file-private_data led_dev; //设置私有数据printk(KERN_DEBUG /dev/led%d opened.\n, led_dev.devid);return 0;
}/*字符设备操作函数集close函数*/
static int led_release(struct inode *inode, struct file *file)
{printk(KERN_DEBUG /dev/led%d opened.\n, led_dev.devid);return 0;
}static void print_led_help(void)
{printk(Follow is the ioctl() command for LED driver:\n);printk(Turn LED on command : %u\n, LED_ON);printk(Turn LED off command : %u\n, LED_OFF);
}/*字符设备操作函数集ioctl函数*/
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{if(cmd LED_OFF)/* variable case */{if(arg 1){gpiod_set_value(led_dev.led_gpio1, 0);}else if(arg 2){gpiod_set_value(led_dev.led_gpio2, 0);}else if(arg 3){gpiod_set_value(led_dev.led_gpio3, 0);}else{printk(arg argument 1 2 3\n);return -EINVAL;}}else if(cmd LED_ON){if(arg 1){gpiod_set_value(led_dev.led_gpio1, 1);}else if(arg 2){gpiod_set_value(led_dev.led_gpio2, 1);}else if(arg 3){gpiod_set_value(led_dev.led_gpio3, 1);}else{printk(arg argument 1 2 3\n);return -EINVAL;}}else{printk(%s driver dont support ioctl command%d\n, DEV_NAME, cmd);print_led_help();return -EINVAL;}return 0;
}/*字符设备操作函数集*/
static struct file_operations led_fops {.owner THIS_MODULE,.open led_open,.release led_release,.unlocked_ioctl led_ioctl,
};
/*驱动安装函数*/
static int led_probe(struct platform_device * pdev)
{int result 0;/*获取led的设备树节点,该函数适用于只有一个gpioindex为0*///led_dev.led_gpio gpiod_get(pdev - dev, led, 0);led_dev.led_gpio1 gpiod_get_index(pdev - dev, led, 0, GPIOD_OUT_HIGH);led_dev.led_gpio2 gpiod_get_index(pdev - dev, led, 1, GPIOD_OUT_HIGH);led_dev.led_gpio3 gpiod_get_index(pdev - dev, led, 2, GPIOD_OUT_HIGH);if(IS_ERR(led_dev.led_gpio1)){printk(gpiod request failure\n);return -1;}/*设置GPIO的方向为输出状态默认为低电平*/result gpiod_direction_output(led_dev.led_gpio1, 0);gpiod_direction_output(led_dev.led_gpio2, 0);gpiod_direction_output(led_dev.led_gpio3, 0);if(0 ! result ){printk(gpiod direction output set failure\n);return result;}/*字符设备驱动注册的流程一分配主次设备号这里不仅支持静态指定也支持动态申请*//*静态申请主次设备号*/if(0 ! dev_major){led_dev.devid MKDEV(dev_major, 0);//将主设备号dev_major和从设备号0分配给devno变量result register_chrdev_region(led_dev.devid, 1, DEV_NAME);//请求分配一个设备号名字为DEV_NAME(chardev)设备号是88 0}/*动态申请*/else{result alloc_chrdev_region(led_dev.devid, 0, 1, DEV_NAME);//求分配一个名字为wangdengtao_dev的设备号从设备号为0,保存到devid变量中dev_major MAJOR(led_dev.devid);//获取设备号}/*失败后的处理结果总规上面只执行一次所以直接在外面判断就可*/if(result 0){printk(KERN_ERR %s chardev cant use major %d\n, DEV_NAME, dev_major);return -result;}printk(%s driver use major %d\n, DEV_NAME, dev_major);/*字符串设备驱动流程三分配cdev结构体使用动态申请的方式*//*内核在内部使用类型struct cdev的结构体来代表字符设备。在内核调用你的设备操作之前你必须分配一个这样的结构体并注册给linux内核在这个结构体里有对于这个设备进行操作的函数具体定义在file_operation结构体中。*/if(NULL (led_dev.cdev cdev_alloc())){printk(KERN_ERR %s driver cant alloc for the cdev\n, DEV_NAME);unregister_chrdev_region(led_dev.devid, 1);//释放掉设备号return -ENOMEM;}/*字符设备驱动流程三分配cdev结构体绑定主次设备号,fops到cdev结构体中并且注册到linux内核*/led_dev.cdev - owner THIS_MODULE; /*.owner这表示谁拥有这个驱动程序*/cdev_init(led_dev.cdev, led_fops);/*初始化设备*/result cdev_add(led_dev.cdev, led_dev.devid, 1); /*将字符设备注册进内核*/if(0 ! result){printk(KERN_INFO %s driver cant register cdev:result %d\n, DEV_NAME, result);goto ERROR;}printk(KERN_INFO %s driver can register cdev:result %d\n, DEV_NAME, result);/*自动创建设备类型、/dev设备节点*/led_dev.class class_create(THIS_MODULE, DEV_NAME); /*创建设备类型sys/class/chrdev*/if (IS_ERR(led_dev.class)) {printk(%s driver create class failure\n, DEV_NAME);result -ENOMEM;goto ERROR;}/*/dev/chrdev 注册这个设备节点*/led_dev.device device_create(led_dev.class, NULL, led_dev.devid, NULL, DEV_NAME); if(IS_ERR(led_dev.device)){result -ENOMEM;//返回错误码应用空间strerror查看goto ERROR;}return 0;ERROR:printk(KERN_ERR %s driver installed failure.\n, DEV_NAME);cdev_del(led_dev.cdev);unregister_chrdev_region(led_dev.devid, 1);return result;
}static int led_remove(struct platform_device *pdev)
{gpiod_set_value(led_dev.led_gpio1, 0); //低电平关闭灯gpiod_set_value(led_dev.led_gpio2, 0); //低电平关闭灯gpiod_set_value(led_dev.led_gpio3, 0); //低电平关闭灯gpiod_put(led_dev.led_gpio1); //释放gpiogpiod_put(led_dev.led_gpio2); //释放gpiogpiod_put(led_dev.led_gpio3); //释放gpiocdev_del(led_dev.cdev); //删除cdevunregister_chrdev_region(led_dev.devid, 1);//释放设备号device_destroy(led_dev.class, led_dev.devid);//注销设备class_destroy(led_dev.class); //注销类return 0;
}static const struct of_device_id leds_match_table[] {{.compatible my-gpio-leds},{/* sentinel */},
};
MODULE_DEVICE_TABLE(of, leds_match_table);/*内核中使用platform_driver结构体来描述平台驱动*/
static struct platform_driver gpio_led_driver
{.probe led_probe, //安装驱动的时候会执行的函数.remove led_remove, //驱动卸载的时候会执行的函数.driver { //描述驱动的属性.name my_led, //name域.owner THIS_MODULE, //使用者一般都是THIS_MODULE.of_match_table leds_match_table, //驱动能够兼容的设备类型 },
};/*入口函数*/
static int __init platdrv_led_init(void)
{int rv;/*当我们初始化了platform_driver之后通过platform_driver_register()函数来注册我们的平台驱动;成功注册了一个平台驱动后就会在/sys/bus/platform/driver目录下生成一个新的目录项.成功 0失败 负数*/rv platform_driver_register(gpio_led_driver);if(rv 0){printk(KERN_ERR %s:%d: Cant register platform driver %d \n, __FUNCTION__, __LINE__, rv);return rv;}printk(Regist LED Platform Driver successfully!\n );return 0;
}/*出口函数*/
static void __exit platdrv_led_exit(void)
{printk(%s: %d remove LED platform driver\n, __FUNCTION__, __LINE__);/*卸载的驱动模块时需要注销掉已注册的平台驱动*/platform_driver_unregister(gpio_led_driver);
}/*调用函数 module_init 来声明 xxx_init 为驱动入口函数当加载驱动的时候 xxx_init函数就会被调用.*/
module_init(platdrv_led_init);
/*调用函数module_exit来声明xxx_exit为驱动出口函数当卸载驱动的时候xxx_exit函数就会被调用.*/
module_exit(platdrv_led_exit);/*添加LICENSE和作者信息是来告诉内核该模块带有一个自由许可证没有这样的说明在加载模块的时内核会“抱怨”.*/
MODULE_LICENSE(Dual BSD/GPL);//许可 GPL、GPL v2、Dual MPL/GPL、Proprietary(专有)等没有内核会提示.
MODULE_AUTHOR(WangDengtao);//作者
MODULE_VERSION(V1.0);//版本
2编写驱动测试代码
/************************************************************************* File Name: led_gpio_test.c Author: WangDengtao Mail: 1799055460qq.com Created Time: 2023年03月23日 星期四 10时46分40秒************************************************************************/#include stdio.h
#include unistd.h
#include stdlib.h
#include string.h
#include fcntl.h
#include sys/ioctl.h
#include sys/types.h
#include sys/stat.h
#include sys/select.h#define LED_CNT 1
#define DEVNAME_LEN 30
#define PLATDRV_MAGIC 0x60#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19)static void msleep(unsigned long ms)
{struct timeval tv;tv.tv_sec ms/1000;tv.tv_usec (ms%1000)*1000;select(0, NULL, NULL, NULL, tv);
}int main(int argc, char **argv)
{int fd[LED_CNT];char dev_name[DEVNAME_LEN];memset(dev_name, 0, sizeof(dev_name));snprintf(dev_name, sizeof(dev_name), /dev/my_led);fd[LED_CNT] open(dev_name, O_RDWR, 0755);if(fd[LED_CNT] 0){printf(file %s open failure!\n, dev_name);goto err;}printf(open fd[%d] successfully.\n, fd[LED_CNT]);while(1){ioctl(fd[LED_CNT], LED_ON, 1);msleep(500);ioctl(fd[LED_CNT], LED_OFF, 1);ioctl(fd[LED_CNT], LED_ON, 2);msleep(500);ioctl(fd[LED_CNT], LED_OFF, 2);ioctl(fd[LED_CNT], LED_ON, 3);msleep(500);ioctl(fd[LED_CNT], LED_OFF, 3);msleep(500);}close(fd[LED_CNT]);return 0;
err:close(fd[LED_CNT]);return -1;
}
3Makefile
同时编译驱动文件以及测试文件编译运行之后我们可以看见可执行文件以及.ko文件。
KERNAL_DIR ? /home/wangdengtao/imx6ull/imx6ull/bsp/kernel/linux-imx
PWD :$(shell pwd)
obj-m : led_gpio.oCCarm-linux-gnueabihf-gcc
APP_NAMEled_gpio_testall:$(MAKE) -C $(KERNAL_DIR) M$(PWD) modules${CC} ${APP_NAME}.c -o ${APP_NAME}make clearclear:rm -f *.o *.cmd *.mod *.mod.crm -rf *~ core .depend .tmp_versions Module.symvers modules.order -frm -f .*ko.cmd .*.o.cmd .*.o.drm -f *.unsignedclean:rm -f *.korm -f ${APP_NAME}
wangdengtaowangdengtao-virtual-machine:~/wangdengtao/driver/arm$ make
make -C /home/wangdengtao/imx6ull/imx6ull/bsp/kernel/linux-imx M/home/wangdengtao/wangdengtao/driver/arm modules
make[1]: 进入目录“/home/wangdengtao/imx6ull/imx6ull/bsp/kernel/linux-imx”CC [M] /home/wangdengtao/wangdengtao/driver/arm/led_gpio.oMODPOST /home/wangdengtao/wangdengtao/driver/arm/Module.symversCC [M] /home/wangdengtao/wangdengtao/driver/arm/led_gpio.mod.oLD [M] /home/wangdengtao/wangdengtao/driver/arm/led_gpio.ko
make[1]: 离开目录“/home/wangdengtao/imx6ull/imx6ull/bsp/kernel/linux-imx”
make[1]: 进入目录“/home/wangdengtao/wangdengtao/driver/arm”
make[1]: 离开目录“/home/wangdengtao/wangdengtao/driver/arm”
wangdengtaowangdengtao-virtual-machine:~/wangdengtao/driver/arm$ ls
led_gpio.c led_gpio.ko led_gpio_test led_gpio_test.c Makefile将我们的可执行文件以及.ko文件上传到开发板
rootigkboard:~# tftp -gr led_gpio.ko 192.168.137.8
rootigkboard:~# tftp -gr led_gpio_test 192.168.137.8
rootigkboard:~# ls
led_gpio.ko led_gpio_test四、结果展示
安装我们的驱动可以看见在 /dev 路径下生成的设备树文件 my_led。
rootigkboard:~# insmod led_gpio.ko
rootigkboard:~# lsmod
Module Size Used by
led_gpio 16384 0
rtl8188fu 999424 0
imx_rngc 16384 0
rng_core 20480 1 imx_rngc
secvio 16384 0
error 20480 1 secvio
rootigkboard:~# ls -l /dev/my_led
crw------- 1 root root 243, 0 Mar 25 08:49 /dev/my_led执行我们的测试代码我们可以看见我们的led灯隔5毫秒闪烁了:
rootigkboard:~# ./led_gpio_test
open fd[3] successfully.最后卸载我们的驱动
rootigkboard:~# rmmod led_gpio
rootigkboard:~# lsmod
Module Size Used by
rtl8188fu 999424 0
imx_rngc 16384 0
rng_core 20480 1 imx_rngc
secvio 16384 0
error 20480 1 secvio五、ioctl接口讲解
大部分驱动需要除了读写设备的能力还需要有通过设备驱动进行各种硬件控制的能力。
ioctl 驱动函数
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);inode和 filp 指针是对应应用程序传递的文件描述符 fd 的值 和传递给 open 方法的相同参数。cmd参数从用户那里不改变地传下来并且可选的参数。arg参数以一个 unsigned long 的形式传递 不管它是否由用户给定为一个整数或一个指针。
为了保证 cmd 命令的唯一性类似于现实中的身份证。
wangdengtaowangdengtao-virtual-machine:~$ cat /opt/TuxamitoSoftToolchains/arm-arm1176jzfssf-linux-gnueabi/gcc-4.6.4/arm-arm1176jzfssf-linux-gnueabi/sysroot/usr/include/asm-generic/ioctl.h
#ifndef _ASM_GENERIC_IOCTL_H
#define _ASM_GENERIC_IOCTL_H/* ioctl command encoding: 32 bits total, command in lower 16 bits,* size of the parameter structure in the lower 14 bits of the* upper 16 bits.* Encoding the size of the parameter structure in the ioctl request* is useful for catching programs compiled with old versions* and to avoid overwriting user space outside the user buffer area.* The highest 2 bits are reserved for indicating the access mode.* NOTE: This limits the max parameter size to 16kB -1 !*//** The following is for compatibility across the various Linux* platforms. The generic ioctl numbering scheme doesnt really enforce* a type field. De facto, however, the top 8 bits of the lower 16* bits are indeed used as a type field, so we might just as well make* this explicit here. Please be sure to use the decoding macros* below from now on.*/
#define _IOC_NRBITS 8
#define _IOC_TYPEBITS 8/** Let any architecture override either of the following before* including this file.*/#ifndef _IOC_SIZEBITS
# define _IOC_SIZEBITS 14
#endif#ifndef _IOC_DIRBITS
# define _IOC_DIRBITS 2
#endif#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)
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT_IOC_TYPEBITS)
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT_IOC_SIZEBITS)/** Direction bits, which any architecture can choose to override* before including this file.*/#ifndef _IOC_NONE
# define _IOC_NONE 0U
#endif#ifndef _IOC_WRITE
# define _IOC_WRITE 1U
#endif#ifndef _IOC_READ
# define _IOC_READ 2U
#endif#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 */
#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)#endif /* _ASM_GENERIC_IOCTL_H */在驱动程序里 ioctl() 函数上传送的变量 cmd 是应用程序用于区别设备驱动程序请求处理内容的值。cmd除了可区别数字外还包含有助于处理的几种相应信息。 cmd的大小为 32位共分 4 个域
bit31~bit30 2位为 “区别读写” 区作用是区分是读取命令还是写入命令。
bit29~bit15 14位为 “数据大小” 区表示 ioctl() 中的 arg 变量传送的内存大小。
bit20~bit08 8位为 “魔数(也称为幻数)区这个值用以与其它设备驱动程序的 ioctl 命令进行区别。
bit07~bit00 8位为 “区别序号” 区是区分命令的命令顺序序号。
内核定义了 _IO() , _IOR() , IOW() 和 _IOWR() 这 4 个宏来辅助生成上面的 cmd 。下面分析 _IO() 的实现。
上面的代码中可以看见_IO的定义以及_IOC的定义
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)#define _IOC(dir,type,nr,size) \(((dir) _IOC_DIRSHIFT) | \((type) _IOC_TYPESHIFT) | \((nr) _IOC_NRSHIFT) | \((size) _IOC_SIZESHIFT))#ifndef _IOC_NONE
# define _IOC_NONE 0U
#endif#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_NRSHIFT 0
#define _IOC_NRBITS 8
#define _IOC_TYPEBITS 8(dir) _IOC_DIRSHIFT) dir 往左移 30 位即移到 bit31~bit30 两位上得到方向(读写)的属性
(size) _IOC_SIZESHIFT) 位左移 16 位得到“数据大小”区
(type) _IOC_TYPESHIFT) 左移 8位得到魔数区
(nr) _IOC_NRSHIFT) 左移 0 位( bit7~bit0) 前面代码中我们使用的宏定义解释
#define PLATDRV_MAGIC 0x60#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19)_IO (魔数 基数)
魔数 (magic number)
魔数范围为 0~255 。通常用英文字符 “A” ~ “Z” 或者 “a” ~ “z” 来表示。设备驱动程序从传递进来的命令获取魔数然后与自身处理的魔数想比较如果相同则处理不同则不处理。魔数是拒绝误使用的初步辅助状态。设备驱动 程序可以通过 _IOC_TYPE (cmd) 来获取魔数。不同的设备驱动程序最好设置不同的魔数但并不是要求绝对也是可以使用其他设备驱动程序已用过的魔数。
基(序列号)数
基数用于区别各种命令。通常从 0开始递增相同设备驱动程序上可以重复使用该值。例如读取和写入命令中使用了相同的基数设备驱动程序也能分辨出来原因在于设备驱动程序区分命令时 使用 switch 且直接使用命令变量 cmd值。创建命令的宏生成的值由多个域组合而成所以即使是相同的基数也会判断为不同的命令。设备驱动程序想要从命令中获取该基数就使用下面的宏_IOC_NR (cmd)