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

这篇具有很好参考价值的文章主要介绍了Linux下LED设备驱动开发(LED灯实现闪烁)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


前面我们介绍了Linux设备模型、平台设备驱动、设备树(device tree)、GPIO子系统以及pinctrl子系统等,大家看这篇文章之前需要提前知道的基础都在这篇文章中:

Linux设备模型、平台设备驱动、设备树(device tree)、GPIO子系统以及pinctrl子系统介绍

有部分函数没有涉及到的最后会讲解。


一、配置连接说明

我们做控制led灯的时候用的是下面三个管脚:
linux led驱动,# IGKBoard(imx6ull)驱动开发,驱动开发,linux,嵌入式硬件,物联网,c语言

控制LED灯连接实图:
linux led驱动,# IGKBoard(imx6ull)驱动开发,驱动开发,linux,嵌入式硬件,物联网,c语言


二、更新设备树

(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 0x17059
                              MX6UL_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-mqs
 1p8v              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); //释放gpio
	gpiod_put(led_dev.led_gpio2); //释放gpio
	gpiod_put(led_dev.led_gpio3); //释放gpio
	cdev_del(led_dev.cdev); //删除cdev
	unregister_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.o

CC=arm-linux-gnueabihf-gcc
APP_NAME=led_gpio_test

all:
	$(MAKE) -C $(KERNAL_DIR) M=$(PWD) modules
	@${CC} ${APP_NAME}.c -o ${APP_NAME}

	@make clear


clear:
	@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 *.unsigned

clean:
	@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.o
  MODPOST /home/wangdengtao/wangdengtao/driver/arm/Module.symvers
  CC [M]  /home/wangdengtao/wangdengtao/driver/arm/led_gpio.mod.o
  LD [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

linux led驱动,# IGKBoard(imx6ull)驱动开发,驱动开发,linux,嵌入式硬件,物联网,c语言


五、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) 文章来源地址https://www.toymoban.com/news/detail-582399.html


到了这里,关于Linux下LED设备驱动开发(LED灯实现闪烁)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 嵌入式Linux系统中的设备驱动开发:从设备树到驱动实现

    大家好,今天给大家介绍 嵌入式Linux系统中的设备驱动开发:从设备树到驱动实现 ,文章末尾附有分享大家一个资料包,差不多150多G。里面学习内容、面经、项目都比较新也比较全! 可进群免费领取。 在嵌入式Linux系统中,设备驱动是连接硬件设备和操作系统之间的桥梁。

    2024年02月19日
    浏览(62)
  • 驱动开发 day8 (设备树驱动,按键中断实现led亮灭)

    //编译驱动  (注意Makefile的编译到移植到开发板的内核)         make arch=arm //清除编译生成文件         make clean ****************************************** //安装驱动         insmod mycdev.ko //卸载驱动         rmmod mycdev   需要在内核路径/arch/arm/boot/dts/  修改 stm32mp157a-fsmp1a-dts 文件 *

    2024年02月14日
    浏览(41)
  • Linux驱动开发笔记(四):设备驱动介绍、熟悉杂项设备驱动和ubuntu开发杂项设备Demo

    若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/134533533 红胖子网络科技博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中… 上一篇:《Linux驱动开发笔记(三

    2024年02月05日
    浏览(50)
  • 4、Linux驱动开发:设备-设备号&设备号注册

    🍅点击这里查看所有博文   随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有

    2024年02月15日
    浏览(54)
  • Linux驱动开发实战(一)——设备驱动模型

    在早期的Linux内核中并没有为设备驱动提供统一的设备模型。随着内核的不断扩大及系统更加复杂,编写一个驱动程序越来越困难,所以在Linux2.6内核中添加了一个统一的设备模型。这样,写设备驱动程序就稍微容易一些了。本章将对设备模型进行详细的介绍。 设备驱动模型

    2024年02月16日
    浏览(46)
  • 正点原子嵌入式linux驱动开发——Linux 网络设备驱动

    网络驱动是linux里面驱动三巨头之一 ,linux下的网络功能非常强大,嵌入式linux中也常常用到网络功能。前面已经讲过了字符设备驱动和块设备驱动,本章就来学习一下linux里面的 网络设备驱动 。 本次笔记中讨论的都是有线网络! 提起网络,一般想到的硬件就是“网卡”。在

    2024年01月17日
    浏览(67)
  • 深入探讨Linux驱动开发:Linux设备树

    设备树(Device Tree,简称 DT)是一种在嵌入式系统中描述硬件设备的一种数据结构和编程语言。它用于将硬件设备的配置信息以树形结构的方式进行描述,以便操作系统(如 Linux)可以根据这些信息正确地识别、配置和管理硬件设备。 设备树最初被引入到 Linux 内核中,用于解

    2023年04月27日
    浏览(45)
  • Linux设备驱动开发 - 虚拟时钟Clock驱动示例

    By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 很多设备里面系统时钟架构极其复杂,让学习Clock驱动的盆友头大。这里我参考S3C2440的clock驱动写了一个virtual clock,即虚拟时钟驱动,分别包含clock的provider和

    2023年04月21日
    浏览(37)
  • 【Linux驱动开发】设备树详解(三)设备树Kernel解析

    ​ ​ 活动地址:CSDN21天学习挑战赛 【Linux驱动开发】设备树详解(一)设备树基础介绍 【Linux驱动开发】设备树详解(二)设备树语法详解 【Linux驱动开发】设备树详解(三)设备树Kernel解析   个人主页:董哥聊技术 我是董哥,嵌入式领域新星创作者 创作理念:专注分享

    2023年04月24日
    浏览(48)
  • 嵌入式Linux驱动开发 04:基于设备树的驱动开发

    前面文章 《嵌入式Linux驱动开发 03:平台(platform)总线驱动模型》 引入了资源和驱动分离的概念,这篇文章将在前面基础上更进一步,引入设备树的概念。 在平台总线驱动模型中资源和驱动已经从逻辑上和代码组织上进行了分离,但每次调整资源还是会涉及到内核,所以现

    2024年02月16日
    浏览(65)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包