• 【Linux】内核驱动篇七--设备树




    一、起源

    • 减少垃圾代码
    • 减轻驱动开发工作量
    • 驱动代码和设备信息分离
    • 参考Open Fireware设计
    • 用来记录硬件平台中各种硬件设备的属性信息

    二、基本组成

    两种源文件:

    1. xxxxx.dts, dts是device tree source的缩写
    2. xxxxx.dtsi, dtsi是device tree source include的缩写,意味着这样源文件用于被dts文件包含用

    实际使用时,需要把dts文件编译成对应的二进制文件(.dtb文件,dtb是device tree binary的缩写 )便于运行时存放在内存加快读取信息的速度


    三、基本语法

    • dts文件主体内容由多个节点组成
    • 每个节点可以包含0或多个子节点,形成树状关系
    • 每个dts文件都有一个根节点,其它节点都是它的子孙
    • 根节点一般来描述整个开发板硬件平台,其它节点用来表示具体设备、总线的属性信息
    • 各个节点可以有多个属性,每个属性用key-value键值对来表示

    节点语法:

    [label:] node-name[@unit-address] {    
    	[properties definitions];    
    	[child nodes];
    };
    
    label: 可选项,节点别名,为了缩短节点访问路径,后续节点中可以使用  &label 来表示引用指定节点
    node-name: 节点名
    unit-address: 设备地址,一般填写该设备寄存器组或内存块的首地址
    properties definitions:属性定义
    child nodes:子节点
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    属性语法:

    [label:] property-name = value;
    [label:] property-name;
    
    属性可以无值
    有值的属性,可以有三种取值:
    1. arrays of cells(1个或多个32位数据, 64位数据使用232位数据表示,空格分隔),用尖括号表示(< >)
    2. string(字符串), 用双引号表示(" ")
    3. bytestring(1个或多个字节,空格分隔),用方括号表示([])
    4.,分隔的多值
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    四、特殊节点

    4.1 根节点

    根节点表示整块开发板的信息

    #address-cells  // 在子节点的reg属性中, 使用多少个u32整数来描述地址(address)
    #size-cells     // 在子节点的reg属性中, 使用多少个u32整数来描述大小(size)
    compatible      // 定义一系列的字符串, 用来指定内核中哪个machine_desc可以支持本设备,即描述其兼容哪些平台                         
    model           // 比如有2款板子配置基本一致, 它们的compatible是一样的,那么就通过model来分辨这2款板子
    
    • 1
    • 2
    • 3
    • 4

    4.2 /memory

    所有设备树文件的必需节点,它定义了系统物理内存的 layout

    device_type = "memory";
    reg             //用来指定内存的地址、大小
    
    • 1
    • 2

    4.3 /chosen

    传递内核启动时使用的参数parameter

    bootargs  //字符串,内核启动参数, 跟u-boot中设置的bootargs作用一样
    
    • 1

    4.4 /cpus 多核CPU支持

    /cpus节点下有1个或多个cpu子节点, cpu子节点中用reg属性用来标明自己是哪一个cpu

    所以 /cpus 中有以下2个属性:

    #address-cells   // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
    #size-cells      // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size) 必须设置为0
    
    • 1
    • 2

    五、常用属性

    5.1 phandle

    ​ 数字形式的节点标识,在后续节点中属性值性质表示某节点时,可以引用对应节点

    ​ 如:

    pic@10000000 {    
    	phandle = <1>;    
    	interrupt-controller;
    };
    another-device-node {    
    	interrupt-parent = <1>;   // 使用phandle值为1来引用上述节点
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5.2 地址--------------- 重要

    reg属性:表示内存区域region,语法:

    reg = <address1 length1 [address2 length2] [address3 length3]>;
    
    • 1

    #address-cells:reg属性中, 使用多少个u32整数来描述地址(address),语法:

    #address-cells = <数字>;
    
    • 1

    #size-cells:reg属性中, 使用多少个u32整数来描述大小(size),语法:

    #size-cells = <数字>;
    
    • 1

    5.3 compatible --------------- 重要

    驱动和设备(设备节点)的匹配依据,compatible(兼容性)的值可以有不止一个字符串以满足不同的需求,语法:

    compatible = "字符串1","字符串2",...;
    
    • 1

    5.4 中断 --------------- 重要

    a. 中断控制器节点用的属性:

    interrupt-controller 一个无值空属性用来声明这个node接收中断信号,表示该节点是一个中断控制器

    #interrupt-cells 这是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符

    b. 中断源设备节点用的属性:

    interrupt-parent:标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的,语法:

    interrupt-parent = <引用某中断控制器节点>
    
    • 1

    interrupts 一个中断标识符列表,表示每一个中断输出信号,语法:

    interrupts = <中断号 触发方式>
    
    1 low-to-high 上升沿触发
    2 high-to-low 下降沿触发
    4 high level  高电平触发
    8 low level   低电平触发
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5.5 gpio --------------- 重要

    gpio也是最常见的IO口,常用的属性有:

    a. 对于GPIO控制器:

    gpio-controller,无值空属性,用来说明该节点描述的是一个gpio控制器

    #gpio-cells,用来表示要用几个cell描述一个 GPIO引脚

    b. 对于GPIO使用者节点:

    gpio使用节点的属性

    xxx-gpio = <&引用GPIO控制器 GPIO标号 工作模式>
    工作模式:
    1 低电平有效 GPIO_ACTIVE_HIGH
    0 高电平有效 GPIO_ACTIVE_LOW
    
    • 1
    • 2
    • 3
    • 4

    5.6 属性设置套路

    一般来说,每一种设备的节点属性设置都会有一些套路,比如可以设置哪些属性?属性值怎么设置?那怎么知道这些套路呢,有两种思路:

    1. 抄类似的dts,比如我们自己项目的平台是4412,那么就可以抄exynos4412-tiny4412.dts、exynos4412-smdk4412.dts这类相近的dts
    2. 查询内核中的文档,比如Documentation/devicetree/bindings/i2c/i2c-imx.txt就描述了imx平台的i2c属性设置方法;Documentation/devicetree/bindings/fb就描述了lcd、lvds这类属性设置方法

    六、常用接口

    struct device_node 对应设备树中的一个节点
    struct property 对应节点中一个属性

    6.1 of_find_node_by_path

    /**
    include/of.h
    of_find_node_by_path - 通过路径查找指定节点
    @path - 结点在设备树中的路径,包含节点名也可以是节点的别名
    成功:得到节点的首地址;失败:NULL
    */
    struct device_node * of_find_node_by_path(const char *path);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6.2 of_find_property

    /*
    include/of.h
    of_find_property - 提取指定属性的值
    @np - 设备节点指针
    @name - 属性名称
    @lenp - 属性值的字节数
    成功:属性值的首地址;失败:NULL
    */
    struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    6.3 of_get_named_gpio

    /**
     * include/of_gpio.h
     * of_get_named_gpio - 从设备树中提取gpio口
     * @np - 设备节点指针
     * @propname - 属性名
     * @index - gpio口引脚标号 
     * 成功:得到GPIO口编号;失败:负数,绝对值是错误码
     */
    int of_get_named_gpio(struct device_node *np, const char *propname, int index);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    6.4 irq_of_parse_and_map

    /*
    	功能:获得设备树中的中断号并进行映射
    	参数:node:设备节点
    		 index:序号
    	返回值:成功:中断号	失败:错误码
    */
    unsigned int irq_of_parse_and_map(struct device_node *node, int index)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6.5 读属性值

    of_property_read_string

    /*
    of_property_read_string - 提取字符串(属性值)
    @np - 设备节点指针
    @propname - 属性名称
    @out_string - 输出参数,指向字符串(属性值)
    成功:0;失败:负数,绝对值是错误码
    */
    int of_property_read_string(struct device_node *np, const char *propname, const char **out_string);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    读数值

    int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value)
    
    int of_property_read_u16(const struct device_node *np,const char *propname,u16 *out_value)
    
    int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    判断属性是否存在

    int of_property_read_bool(const struct device_node *np,const char *propname)
    
    • 1

    读数组

    int of_property_read_u32_array(const struct device_node *np,const char *propname,u32 *out_value,size_t sz)
    
    • 1

    七、GPIO接口

    7.1 向内核申请GPIO

    int gpio_request(unsigned gpio,const char *label)

    功能:其实就是让内核检查一下该GPIO引脚是否被其它设备占用,如果没有占用则返回0并用label做一下标记,表示被本设备占用,否则返回负数

    gpio:得到的设备号
    label:设备结点属性名称

    void gpio_free(unsigned gpio)

    功能:去除本设备对该GPIO的占用标记,表示本设备向内核归还对该GPIO引脚的使用权,此后其它设备可占用该GPIO引脚


    7.2 设置GPIO方向

    int gpio_direction_input(unsigned gpio)

    int gpio_direction_output(unsigned gpio,int value)

    value:0-低电平,1-高电平


    7.3 读写GPIO数据

    int gpio_get_value(unsigned gpio)

    int gpio_set_value(unsigned gpio,int value)


    八、led驱动设备树版

    1. 在设备树源文件的根节点下添加本设备的节点(该节点中包含本设备用到的资源信息)
      ..../linux3.14/arch/arm/boot/dts/exynos4412-fs4412.dts
    fs4412-leds {
    	compatible = "fs4412,led2-5";
    	led2-gpio = <&gpx2 7 0>;
    	led3-gpio = <&gpx1 0 0>;
    	led4-gpio = <&gpf3 4 0>;
    	led5-gpio = <&gpf3 5 0>;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 在linux内核源码的顶层目录下执行:make dtbs (生成对应的dtb文件)
    2. cp ???.dtb /tftpboot
    3. 编写驱动代码:
      a. 通过本设备在设备树中的路径找到对应节点(struct device_node类型的地址值)
      b. 调用 of_get_named_gpio 函数得到某个GPIO的编号
      c. struct leddev结构体记录所有用到的GPIO编号
      d. 使用某个GPIO引脚前需先通过gpio_request函数向内核申请占用该引脚不用该引脚时可通过gpio_free归还给内核
      e. 通过gpio_direction_inputgpio_direction_output函数来设置某个GPIO的作用
      f. 通过gpio_get_value函数可以获取某个GPIO引脚的当前电平
      g. 通过gpio_set_value函数可以改变某个GPIO引脚的电平

    例子程序:
    dev_led2:

    /*************************************************************************
      > File Name: led2.c
      > Author: xiuchengzhen
      > CSDN: xiuchengzhen.blog.csdn.net
      > Created Time: Tue 10 May 2022 07:29:52 PM PDT
     ************************************************************************/
    
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/mm.h>
    #include <linux/io.h>
    #include <linux/gpio.h>
    #include <linux/of_gpio.h>
    #include <linux/slab.h>
    
    #include "dev_led2.h"
    
    int major = 11;   //主设备号
    int minor = 0;    //次设备号
    int dev_num = 1;  //设备数量
    
    struct pdev  //设备结构体
    {
    	struct cdev leddev;  //led结构体
    	int led2_num;   //GPIO口编号
    	int led3_num;
    	int led4_num;
    	int led5_num;
    };
    
    struct pdev *pleddev = NULL;
    
    int led_open(struct inode *pnode, struct file *pfile)	//打开设备
    {
    	/* 申请GPIO引脚占用 */
    	gpio_request(pleddev->led2_num, "led2");
    	gpio_request(pleddev->led3_num, "led3");
    	gpio_request(pleddev->led4_num, "led4");
    	gpio_request(pleddev->led5_num, "led5");
    
    	return 0;
    }
    
    int led_release(struct inode *pnode, struct file *pfile)	//关闭设备
    {
    	/* 申请归还GPIO引脚 */
    	gpio_free(pleddev->led2_num);
    	gpio_free(pleddev->led3_num);
    	gpio_free(pleddev->led4_num);
    	gpio_free(pleddev->led5_num);
    	
    	return 0;
    }
    
    long leddev_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
    {
    	switch(cmd)
    	{
    		case LED2_ON:
    			gpio_direction_output(pleddev->led2_num,1);
    			break;
    		case LED2_OFF:
    			gpio_direction_output(pleddev->led2_num,0);
    			break;
    		case LED3_ON:
    			gpio_direction_output(pleddev->led3_num,1);
    			break;
    		case LED3_OFF:
    			gpio_direction_output(pleddev->led3_num,0);
    			break;
    		case LED4_ON:
    			gpio_direction_output(pleddev->led4_num,1);
    			break;
    		case LED4_OFF:
    			gpio_direction_output(pleddev->led4_num,0);
    			break;
    		case LED5_ON:
    			gpio_direction_output(pleddev->led5_num,1);
    			break;
    		case LED5_OFF:
    			gpio_direction_output(pleddev->led5_num,0);
    			break;
    		default: 
    			printk("cmd error!\n");
    			return -1;
    	}
    	return 0;
    }
    
    struct file_operations fops={
    	.owner = THIS_MODULE,
    	.open = led_open,
    	.release = led_release,
    	.unlocked_ioctl = leddev_ioctl,
    };
    
    int __init led2_init(void)
    {
    	dev_t devno = MKDEV(major, minor);
    	int ret = -1;
    
    	if((ret = register_chrdev_region(devno, dev_num, "dev_led")) < 0)
    	{
    		ret = alloc_chrdev_region(&devno, minor, dev_num, "dev_led");
    		if(ret < 0)
    		{
    			printk("alloc_chrdev_region fail!\n");
    			return -1;
    		}
    		major = MAJOR(devno);
    	}
    
    	/* 设备分配内存空间 */
    	pleddev = (struct pdev *)kmalloc(sizeof(struct pdev), GFP_KERNEL);
    	if(pleddev == NULL)
    	{
    		unregister_chrdev_region(devno, dev_num);
    		printk("pmydev malloc fail!\n");
    		return -1;
    	}
    
    	/* 设备初始化 */
    	cdev_init(&pleddev->leddev, &fops);
    	pleddev->leddev.owner = THIS_MODULE;
    
    	/* 设备添加 */
    	cdev_add(&pleddev->leddev, devno, dev_num);
    
    	/* 设备号获取 */
    	struct device_node *led_node = of_find_node_by_path("/fs4412-leds"); 
    	if(led_node == NULL)
    	{
    		printk("device_node get fail!\n");
    		return -1;
    	}
    	pleddev->led2_num = of_get_named_gpio(led_node, "led2-gpio", 0);
    	pleddev->led3_num = of_get_named_gpio(led_node, "led3-gpio", 0);
    	pleddev->led4_num = of_get_named_gpio(led_node, "led4-gpio", 0);
    	pleddev->led5_num = of_get_named_gpio(led_node, "led5-gpio", 0);
    
    
    	return 0;
    }
    
    void __exit led2_exit(void)
    {
    	dev_t devno = MKDEV(major, minor);
    
    	/* 注销设备 */
    	cdev_del(&pleddev->leddev);
    
    	/* 注销设备号 */
    	unregister_chrdev_region(devno, dev_num);
    
    	/* 释放空间 */
    	kfree(pleddev);
    
    }
    
    MODULE_LICENSE("GPL");
    module_init(led2_init);
    module_exit(led2_exit);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164

    dev_led2.h:

    /*************************************************************************
    	> File Name: mytest.h
    	> Author: xiuchengzhen
    	> CSDN: xiuchengzhen.blog.csdn.net
    	> Created Time: Mon 16 May 2022 05:39:30 AM PDT
     ************************************************************************/
    
    #ifndef DEVLED_H
    #define DEVLED_H
    
    #include <asm/ioctl.h>
    
    
    
    #define IOCTL_TYPE 'k'
    #define LED2_ON  _IOR(IOCTL_TYPE, 1,int *)
    #define LED2_OFF _IOR(IOCTL_TYPE, 2,int *)
    #define LED3_ON  _IOR(IOCTL_TYPE, 3,int *)
    #define LED3_OFF _IOR(IOCTL_TYPE, 4,int *)
    #define LED4_ON  _IOR(IOCTL_TYPE, 5,int *)
    #define LED4_OFF _IOR(IOCTL_TYPE, 6,int *)
    #define LED5_ON  _IOR(IOCTL_TYPE, 7,int *)
    #define LED5_OFF _IOR(IOCTL_TYPE, 8,int *)
    
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    dev_app.c:

    /*************************************************************************
    	> File Name: led_app.c
    	> Author: xiuchengzhen
    	> CSDN: xiuchengzhen.blog.csdn.net
    	> Created Time: Fri 20 May 2022 01:26:49 AM PDT
     ************************************************************************/
    
    #include <stdio.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/ioctl.h>
    #include <sys/types.h>
    #include <time.h>
    
    #include "dev_led.h"
    	
    int main(int argc, const char *argv[])
    {
    	int ret;
    
    	if(argc < 2)
    	{
    		printf("Run without file!\n");
    		return -1;
    	}
    
    	/* 打开设备文件 */
    	int fd = open("/dev/dev_led", O_RDWR);
    	if(fd < 0)
    	{
    		perror("open");
    		return -1;
    	}
    
    	while(1)
    	{
    		ioctl(fd, LED2_ON, 0);
    		sleep(1);
    		ioctl(fd, LED2_OFF, 0);
    		sleep(1);
    		ioctl(fd, LED3_ON, 0);
    		sleep(1);
    		ioctl(fd, LED3_OFF, 0);
    		sleep(1);
    		ioctl(fd, LED4_ON, 0);
    		sleep(1);
    		ioctl(fd, LED4_OFF, 0);
    		sleep(1);
    		ioctl(fd, LED5_ON, 0);
    		sleep(1);
    		ioctl(fd, LED5_OFF, 0);
    		sleep(1);
    	}
    
    	/* 关闭设备 */
    	close(fd);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    到这里就结束啦!
    在这里插入图片描述

  • 相关阅读:
    Win11如何设置用户权限?Win11设置用户权限的方法
    互联网Java工程师面试题·Java 面试篇·第一弹
    手工测试转自动化测试后,薪资可以达到多少?
    内核将驱动编译成模块报函数或者变量undefined的错误
    2022-08-26 第二小组 张明旭 前端学习记录
    C++前缀和算法应用:矩形区域不超过 K 的最大数值和
    多线程学习笔记
    Windows OpenGL ES 图像反色
    Spark 3.0 - 10.Ml 常用 Sample 采样方法
    spring-boot @Async注解 解决异步多线程入库的问题
  • 原文地址:https://blog.csdn.net/qq_51447215/article/details/124899340