当前位置: 首页 > news >正文

Linux下LED设备驱动开发(LED灯实现闪烁)

文章目录

    • 一、配置连接说明
    • 二、更新设备树
      • (1)将led灯引脚添加到pinctrl子系统
      • (2)设备树中添加LDE灯的设备树节点
      • (3)编译更新设备树
    • 三、驱动开发与测试
      • (1)编写设备驱动代码
      • (2)编写驱动测试代码
      • (3)Makefile
    • 四、结果展示
    • 五、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下可以查看:

wangdengtao@wangdengtao-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 以及 zImage(linux下的zImage文件再/bootl路径下) 修改。

wangdengtao@wangdengtao-virtual-machine:~/imx6ull/imx6ull/bsp/kernel/linux-imx$ make dtbs
root@igkboard:~# find / -name zImage
/run/media/mmcblk1p1/zImage
root@igkboard:~# find / -name igkboard.dtb
/run/media/mmcblk1p1/igkboard.dtb

替换之后执行 sudo reboot 即可。

使用新的设备树重新启动之后正常情况下会在开发板的 “/proc/device-tree” 目录下生成 “my_leds” 设备树节点。如下所示。

root@igkboard:~# cd /proc/device-tree/
root@igkboard:/proc/device-tree# ls
'#address-cells'   3p3v          backlight-lcd   clock-di0   compatible   leds              mqs       panel        pxp_v4l2             regulator@0       soc         w1
'#size-cells'      __symbols__   chosen          clock-di1   cpus         memory@80000000   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子系统的属性:

root@igkboard:/proc/device-tree# cd my_leds/
root@igkboard:/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: 1799055460@qq.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 don't 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的设备树节点,该函数适用于只有一个gpio,index为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 can't 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 can't 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 can't 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: Can't 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: 1799055460@qq.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;
}

(3)Makefile

同时编译驱动文件以及测试文件,编译运行之后我们可以看见可执行文件以及.ko文件。

KERNAL_DIR ?= /home/wangdengtao/imx6ull/imx6ull/bsp/kernel/linux-imx
PWD :=$(shell pwd)
obj-m := led_gpio.oCC=arm-linux-gnueabihf-gcc
APP_NAME=led_gpio_testall:$(MAKE) -C $(KERNAL_DIR) M=$(PWD) modules@${CC} ${APP_NAME}.c -o ${APP_NAME}@make clearclear:@rm -f *.o *.cmd *.mod *.mod.c@rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f@rm -f .*ko.cmd .*.o.cmd .*.o.d@rm -f *.unsignedclean:@rm -f *.ko@rm -f ${APP_NAME}
wangdengtao@wangdengtao-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”
wangdengtao@wangdengtao-virtual-machine:~/wangdengtao/driver/arm$ ls
led_gpio.c  led_gpio.ko  led_gpio_test  led_gpio_test.c  Makefile

将我们的可执行文件以及.ko文件上传到开发板:

root@igkboard:~# tftp -gr led_gpio.ko 192.168.137.8
root@igkboard:~# tftp -gr led_gpio_test 192.168.137.8
root@igkboard:~# ls
led_gpio.ko  led_gpio_test

四、结果展示

安装我们的驱动,可以看见在 /dev 路径下生成的设备树文件 my_led

root@igkboard:~# insmod led_gpio.ko 
root@igkboard:~# 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
root@igkboard:~# ls -l /dev/my_led 
crw------- 1 root root 243, 0 Mar 25 08:49 /dev/my_led

执行我们的测试代码,我们可以看见我们的led灯隔5毫秒闪烁了:

root@igkboard:~# ./led_gpio_test 
open fd[3] successfully.

最后卸载我们的驱动:

root@igkboard:~# rmmod led_gpio
root@igkboard:~# 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 命令的唯一性(类似于现实中的身份证)。

wangdengtao@wangdengtao-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 doesn't 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)


相关文章:

Linux下LED设备驱动开发(LED灯实现闪烁)

文章目录一、配置连接说明二、更新设备树&#xff08;1&#xff09;将led灯引脚添加到pinctrl子系统&#xff08;2&#xff09;设备树中添加LDE灯的设备树节点&#xff08;3&#xff09;编译更新设备树三、驱动开发与测试&#xff08;1&#xff09;编写设备驱动代码&#xff08…...

JavaEE-多线程中wait和notify都有哪些区别?

更多内容请点击了解 本篇文章将详细讲述wait和notify的区别&#xff0c;请往下看 目录 更多内容请点击了解 文章目录 一、wait和notify概念 二、wait()方法详解 三、notify()方法详解 代码如下&#xff1a; 3.1notifyAll()详解 四、wait和sleep的对比 一、wait和notif…...

JavaScript实现列表分页(小白版)

组件用惯了&#xff0c;突然叫你用纯cssJavaScript写一个分页&#xff0c;顿时就慌了。久久没有接触js了&#xff0c;不知道咋写了。本文章也是借与参考做的一个demo案例&#xff0c;小白看了都会的那种。咱们就以ul列表为例进行分页&#xff1a; 首先模拟的数据列表是这样的&a…...

Python调用GPT3.5接口的最新方法

GPT3.5接口调用方法主要包括openai安装、api_requestor.py替换、接口调用、示例程序说明四个部分。 1 openai安装 Python openai库可直接通过pip install openai安装。如果已经安装openai&#xff0c;但是后续提示找不到ChatCompletion&#xff0c;那么请使用命令“pip instal…...

Java开发 - 拦截器初体验

目录 前言 拦截器 什么是拦截器 拦截器和过滤器 Spring MVC的拦截器 Mybatis的拦截器...

【数据仓库-7】-- 使用维度建模的一些缘由

维度建模是一种用于设计数据仓库和商业智能系统的方法。以下是选择维度建模的两类理由。 1.传统方法,有背书且可靠 易于理解和使用:维度建模使用直观的图形和术语,使得非技术人员也能够理解和使用数据仓库和商业智能系统。 快速开发和部署:维度建模是一种迭代开发方法,能…...

【开发实践】在线考试系统(一) 生成错题知识点的思维导图

一、需求分析设计 笔者开发了一个在线考试系统&#xff0c;导师提出一个需求&#xff1a;添加对考试错题相关知识点的总结。 在question表中关联知识点的编号&#xff0c;题目可能关联多个知识点。这里笔者的设计是&#xff0c;只关联一个知识点&#xff0c;便于维护。 下面是知…...

Java Web 实战 17 - 计算机网络之传输层协议(2)

大家好 , 这篇文章继续给大家讲解 TCP 协议当中的一些操作 , 比如 : 滑动窗口、流量控制、拥塞控制、延时应答、捎带应答、面向字节流这几个提升 TCP 效率的操作 . 我们还会给大家分析 TCP 连接出现异常的时候 , 该如何处理 . 最后会将 TCP 和 UDP 进行比较 上一篇文章的链接也…...

MyBatis<3>:动态SQL的使用<if><trim><where><set><foreach>

动态SQL是MyBatis的强大特性之一&#xff0c;能够完成不同条件下不同的sql拼接。参考官方文档&#xff1a;https://mybatis.org/mybatis-3/zh/dynamic-sql.html<if>标签看这个场景&#xff0c;有必填字段 和 非必填字段 &#xff0c;当字段不确定是否传入的时候&#xff…...

【超好懂的比赛题解】暨南大学2023东软教育杯ACM校赛个人题解

title : 暨南大学2023东软教育杯ACM校赛 题解 tags : ACM,练习记录 date : 2023-3-26 author : Linno 文章目录暨南大学2023东软教育杯ACM校赛 题解A-小王的魔法B-苏神的遗憾C-神父的碟D-基站建设E-小王的数字F-Uziの真身G-电子围棋H-二分大法I-丁真的小马朋友们J-单车运营K-超…...

go-zero学习及使用中遇到的问题

go-zero学习及使用中遇到的问题1 go-zero入门--单体服务demo1.1 单体服务【官方示例】1.1.1 创建greet服务1.1.2 目录结构1.1.3 编写逻辑1.1.4 启动并访问服务1.2 修改GET入参1.2.1 去除options限制的入参值1.2.2 重启并访问服务1.3 添加post请求【新增方法】1.3.1 修改 greet/…...

CCF-CSP认证 202303 500分题解

202303-1 田地丈量&#xff08;矩阵面积交&#xff09; 矩阵面积交x轴线段交长度*y轴线段交长度 线段交长度&#xff0c;相交的时候是min右端点-max左端点&#xff0c;不相交的时候是0 #include<bits/stdc.h> using namespace std; int n,a,b,ans,x,y,x2,y2; int f(in…...

板内盘中孔设计狂飙,细密间距线路中招

一博高速先生成员&#xff1a;王辉东大风起兮云飞扬&#xff0c;投板兮人心舒畅。赵理工打了哈欠&#xff0c;伸了个懒腰&#xff0c;看了看窗外&#xff0c;对林如烟说道&#xff1a;“春天虽美&#xff0c;但是容易让人沉醉。如烟&#xff0c;快女神节了&#xff0c;要不今晚…...

面试热点题:回溯算法 递增子序列与全排列 II

前言&#xff1a; 如果你一点也不了解什么叫做回溯算法&#xff0c;那么推荐你看看这一篇回溯入门&#xff0c;让你快速了解回溯算法的基本原理及框架 递增子序列 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两…...

怎么找回回收站删除的文件

我们都知道&#xff0c;电脑文件都是放在桌面上的&#xff0c;单独存放或者一起存放在文件夹里。但总会有已用完或者是没用的文件&#xff0c;这让我们不得不对其进行清理。而清空回收站也是不可避免的。如果出现了清空文件中还有我们需要的文件&#xff0c;怎么找回回收站删除…...

dp-打家劫舍

你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&#xff0c;系统会自动报警。给定一个代表每个房屋存放金额的非…...

C++预处理连接

目录定义常量字符串前缀定义枚举类型Boost C库中常常使用预处理连接来定义宏和模板类Google开源的C单元测试框架gtest&#xff0c;使用预处理连接技术创建测试用例和测试方法C预处理连接&#xff08;Preprocessor Concatenation&#xff09;是一种宏定义技巧&#xff0c;用于将…...

3、DRF实战总结:基于类的视图APIView, GenericAPIView和GenericViewSet视图集(附源码)

前面介绍了什么是符合RESTful规范的API接口&#xff0c;以及使用了基于函数的视图(FBV)编写了对文章进行增删查改的API。在本篇文章将使用基于类的视图(Class-based View, CBV)重写之前的接口。 参考&#xff1a; 1、Django开发总结&#xff1a;Django MVT与MVC设计模式&…...

AutoSAR PduR -AutoSAR PDU常用的使用方式【发送,接收,网关】

总目录链接==>> AutoSAR入门和实战系列总目录 @学前问答: AutoSAR PDU在哪里全局定义的? AutoSAR PDU涉及到哪些模块? AutoSAR PDU网关怎么使用? 文章目录 1 AutoSAR PDU发送2 AutoSAR PDU接收3 AutoSAR PDU网关转发4 答疑解析AutoSAR PDU 怎么样通过PduR 实现与其…...

瑟瑟发抖吧~OpenAI刚刚推出王炸——引入ChatGPT插件,开启AI新生态

5分钟学会使用ChatGPT 插件&#xff08;ChatGPT plugins&#xff09;——ChatGPT生态建设的开端ChatGPT插件是什么OpenAI最新官方blog资料表示&#xff0c;已经在ChatGPT中实现了对插件的初步支持。插件是专门为以安全为核心原则的语言模型设计的工具&#xff0c;可帮助ChatGPT…...

脉诊(切脉、诊脉、按脉、持脉)之法——入门篇

认识脉诊何谓脉诊&#xff1f;脉诊的渊源脉诊重要吗&#xff1f;脉诊确有其事&#xff0c;还是故弄玄虚&#xff1f;中医科学吗&#xff1f;如何脉诊&#xff1f;寸口脉诊法何谓脉诊&#xff1f; 所谓脉诊&#xff0c;就是通过把脉来诊断身体健康状况的一种必要手段。 …...

【十二天学java】day09常用api介绍

1.API 1.1API概述 什么是API API (Application Programming Interface) &#xff1a;应用程序编程接口 java中的API 指的就是 JDK 中提供的各种功能的 Java类&#xff0c;这些类将底层的实现封装了起来&#xff0c;我们不需要关心这些类是如何实现的&#xff0c;只需要学习这…...

软件测试 - 测试用例常见面试题

1.测试用例的要素测试用例是为了实施测试而向被测试的系统提供的一组集合, 这组集合包含 : 测试环境, 操作步骤, 测试数据, 预期结果等要素.例如 : 在 B 站输入框输入一个空格, 检查结果测试用例标题 : 输入框输入空格测试环境 : Windows 系统, 谷歌浏览器-版本 111.0.5563.65&…...

几种常见的API接口分页方案

文章目录1 概述2 分页方案2.1 基于偏移量2.2 基于游标3 重复数据处理3.1 基于时间3.2 基于热度3.3 基于推荐1 概述 列表是互联网产品中很常见的一种内容排列形式&#xff0c;而且列表的数据集往往成千上万&#xff0c;一次性返回全量数据集的场景几乎不存在&#xff0c;所以出…...

【Object 类的方法】

在 Java 中&#xff0c;所有类都继承了 Object 类&#xff0c;因此 Object 类中的方法可以在所有 Java 对象中使用。下面是 Object 类中的一些常用方法介绍&#xff1a; equals(Object obj): 用于判断两个对象是否相等。默认情况下&#xff0c;该方法比较的是两个对象的地址是…...

留用户、补内容,在线音乐暗战不停

在线音乐在人们的日常生活中扮演着愈发重要的角色&#xff0c;尤其是在面临巨大压力时&#xff0c;人们往往更倾向于通过倾听一段音乐来缓解内心的紧张与焦虑。而随着在线音乐用户数量的增长以及付费意愿的增强&#xff0c;在线音乐行业也实现了稳步发展。 经过多年的发展&…...

python--exec

在Python中&#xff0c;eval和exec都是用来执行动态代码的内置函数&#xff0c;但它们的作用和使用方式有所不同。 eval(): 将字符串作为Python表达式进行求值&#xff0c;并返回结果。 exec(): 将字符串作为Python语句进行执行&#xff0c;没有返回值。 eval()的使用范围通常限…...

干货分享!这6个高效率办公软件,总有一个值得你收藏!

分享6款高效办公软件&#xff0c;可以解决你很多需求&#xff0c;职场人一定要知道。每一款都是精挑细的&#xff0c;可能有的已经很大众了&#xff0c;但肯定还有小伙伴不知道&#xff0c;废话不多说&#xff0c;直接看&#xff01;&#xff01; 1、Flomo笔记&#xff1a;记录…...

代码随想录刷题-链表总结篇

文章目录链表理论基础单链表双链表循环链表其余知识点链表理论基础单链表双链表循环链表其余知识点移除链表元素习题我的解法虚拟头结点解法设计链表习题我的解法代码随想录代码反转链表习题双指针递归两两交换链表中的节点习题我的解法代码随想录解法删除链表的倒数第N个节点习…...

C++:指针:什么是野指针

野指针目录1&#xff1a;定义2&#xff1a;野指针常见情形2.1 &#xff1a;未初始化的野指针2.2 所指的对象已经消亡2.3 指针释放之后未置空3&#xff1a;避免野指针1&#xff1a;定义 指向非法的内存地址的指针叫做野指针&#xff08;Wild Pointer&#xff09;&#xff0c;也…...

安卓android下载安装/seo网站外包公司

题目大意&#xff1a;三种操作&#xff1a; $Name\;Socore&#xff1a;$上传最新得分记录&#xff0c;把以前的记录删除。$?Name&#xff1a;$ 查询玩家排名。如果两个玩家的得分相同&#xff0c;则先得到该得分的玩家排在前面。$?Index&#xff1a;$ 返回自第$Index$名开始的…...

网站建设电话话术/惠州seo关键词排名

UVA_10806这个题目我们可以把边的容量设为1&#xff0c;费用设为权值&#xff0c;然后再引入一条边N-N1&#xff0c;容量设为2&#xff0c;费用设为0&#xff0c;然后去求1到N1的最小费用最大流&#xff0c;如果到N1的流量为2&#xff0c;则输出最小费用&#xff0c;否则就是无…...

怎么用div布局做网站/怎么做电商创业

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼#include#include#include#include#define B breakvoid de(int timee){unsigned long int start,stop;struct timeval str;struct timeval stp;gettimeofday(&str,NULL);startstr.tv_usec;while(1){ gettimeofday(&stp,NUL…...

南京营销型网站/百度指数分析平台

解决org.apache.commons.lang.xwork.StringUtils异常今夜&#xff0c;晴&#xff0c;时间&#xff0c;凌晨两点本码农在敲代码时遇到一个问题&#xff0c;就是页面用Ajax传输json数据到后台时&#xff0c;Struts框架使用json-default&#xff0c;在调用模型后返回到页面时&…...

美工宝盒网站/清博舆情系统

通过access日志记录&#xff0c;可以分析出并发访问量的大小&#xff0c;也可以分析访问的基本信息&#xff0c;比如请求ip地址&#xff0c;请求客户端信息以及访问的具体地址等。 1. 配置logstash的配置文件 input {file {start_position > beginning path > "E:…...

web网站开发学习/外贸推广渠道有哪些

操作系统概述 1.操作系统做了什么 我们编写这样一条C语言代码 #include<stdio.h>int main() {puts("hello world");return 0; }1.用户告诉操作系统执行此程序2.操作系统接收到指令,先去磁盘上找到此程序的相关信息,然后检查其类型是否为可执行文件;紧接着通…...