Linux设备树


1. 什么是设备树

在这里插入图片描述
    设备树就是“设备”和“树”,描述设备树的文件叫做DTS,DTS文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU数量、内存基地址、IIC接口设备、SPI接口设备等等。
    树的主干就是系统总线,IIC控制器、SPI控制器等等就是主线的分支,各种设备就是树上分支的节点。
    在没有设备树的时候,Linux内核源码中有大量的arch/arm/mach-xxx和arch/arm/plat-xxx文件夹,这些文件夹里面的文件就是对应平台下的板级信息。为了防止Linux内核虚胖、于是引入了设备树将板级硬件信息的内容都从Linux内核中分离开来,用一个专属的文件格式来描述,这个专属的文件就叫做设备树,文件扩展名为.dts。而将一个SOC做出的不同板子的的共同信息提取出来,变成一个通用的文件,就是.dtsi文件,类似于头文件。

2. DTS、DTB和DTC

    我们在移植Linux时使用的是.dtb文件,DTB文件就是DTS编译以后得到的二进制文件,而编译需要的工具就是DTC工具。
    进入Linux源码目录,输入以下命令即可编译DTS文件:

make all//编译所有东西,包括zImage、.ko、设备树
make dtbs

3. DTS语法

3.1 .dtsi头文件

    设备树的头文件扩展名为.dtsi。

 #include <dt-bindings/input/input.h>
 #include "imx6ull.dtsi"

    .dtsi文件用于描述SOC的内部外设信息,比如CPU架构、主频、外设寄存器地址范围,比如UART、IIC等等。

10 #include <dt-bindings/clock/imx6ul-clock.h>
11 #include <dt-bindings/gpio/gpio.h>
12 #include <dt-bindings/interrupt-controller/arm-gic.h>
13 #include "imx6ull-pinfunc.h"
14 #include "imx6ull-pinfunc-snvs.h"
15 #include "skeleton.dtsi"
16 
17 / {
18 aliases {
19 can0 = &flexcan1;
......
48 };
49 
50 cpus {
51 #address-cells = <1>;
52 #size-cells = <0>;
53 
54 cpu0: cpu@0 {
55 compatible = "arm,cortex-a7";
56 device_type = "cpu";
......
89 };
90 };
91 
92 intc: interrupt-controller@00a01000 {
93 compatible = "arm,cortex-a7-gic";
94 #interrupt-cells = <3>;
95 interrupt-controller;
96 reg = <0x00a01000 0x1000>,
97 <0x00a02000 0x100>;
98 };
99 
100 clocks {
101 #address-cells = <1>;
102 #size-cells = <0>;
103 
104 ckil: clock@0 {
105 compatible = "fixed-clock";
106 reg = <0>;
107 #clock-cells = <0>;
108 clock-frequency = <32768>;
109 clock-output-names = "ckil";
110 };
......
135 };
136 
137 soc {
138 #address-cells = <1>;
139 #size-cells = <1>;
140 compatible = "simple-bus";
141 interrupt-parent = <&gpc>;
142 ranges;
143 
144 busfreq {
145 compatible = "fsl,imx_busfreq";
......
162 };
197 
198 gpmi: gpmi-nand@01806000{
199 compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpminand";
200 #address-cells = <1>;
201 #size-cells = <1>;
202 reg = <0x01806000 0x2000>, <0x01808000 0x4000>;
......
216 };
......
1177 };
1178 };

    第54-89行就是cpu0这个设备节点信息,描述了I.MX6ULL这颗SOC所使用的CPU信息,比如架构是cortex-A7,支持的频率等等。文件中还将所有外设都描述出来,比如ecspi1-4、uart1-8等等。

3.2 设备节点

    每个节点代表一个设备,通过一些属性来描述节点信息,属性就是键值对。

1 / {                                    //根节点
2 aliases { 
3 can0 = &flexcan1; 
4 };
5 
6 cpus { 
7 #address-cells = <1>;
8 #size-cells = <0>;
9 
10 cpu0: cpu@0 {
11 compatible = "arm,cortex-a7";
12 device_type = "cpu";
13 reg = <0>;
14 };
15 };
16
17 intc: interrupt-controller@00a01000 {      //label:node-name@unit-address
18 compatible = "arm,cortex-a7-gic";
19 #interrupt-cells = <3>;
20 interrupt-controller;
21 reg = <0x00a01000 0x1000>,
22 <0x00a02000 0x100>;
23 };
24 }

3.3 标准属性

3.3.1 compatible属性

    这个属性非常重要,也叫做“兼容性”属性。属性的值是一个字符串列表,compatible属性用于将设备和驱动绑定起来。

"manufacture,model"

    manufacture代表厂商,model一般是模块对应的驱动的名字。

compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960"

    sound结点是开发板的音频设备节点,音频芯片采用的是欧胜的WM8960,其属性值如上。
    属性值有两个,其中"fsl"表示厂商是飞思卡尔,后面是驱动模块名字,sound这个设备首先使用第一个兼容值在内核里面查找,看能不能找到与之匹配的驱动文件,找不到再使用第二个兼容值查。
    一般驱动程序文件会有一个OF匹配表,其中保存着一些compatible的值,如果设备节点的compatible属性值和OF匹配表中的任何一个值相等,就表示可以使用这个驱动。

632 static const struct of_device_id imx_wm8960_dt_ids[] = {
633 { .compatible = "fsl,imx-audio-wm8960", },
634 { /* sentinel */ }
635 };
636 MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);
637
638 static struct platform_driver imx_wm8960_driver = {
639 .driver = {
640 .name = "imx-wm8960",
641 .pm = &snd_soc_pm_ops,
642 .of_match_table = imx_wm8960_dt_ids,
643 },
644 .probe = imx_wm8960_probe,
645 .remove = imx_wm8960_remove,
646 };

    在文件imx-wm8960.c中的内容如上,第632-635就是这个文件的匹配表,只有一个匹配值"fsl,imx-audio-wm8960",设备树中哪个节点的compatible属性值与此相等,就会使用此驱动文件。
    642行,wm8960采用了platform_driver驱动模式。设置.of_match_table为imx_wm8960_dt_ids,就是设置这个platform_driver所使用的OF匹配表。

3.3.2 model属性

    属性值是一个字符串,一般用于描述设备模块信息,比如名字什么的:

model = "wm8960-audio"
3.3.3 status属性

    和设备状态有关,属性值也是字符串,是设备状态信息。
在这里插入图片描述

3.3.4 #address-cells#size-cells属性

    都是无符号32位整型,用于描述子节点的地址信息。#address-cells决定了子节点reg属性中地址信息所占用的字长(32位),#size-cells属性值决定了子节点reg属性中长度信息所占用的字长。

1 spi4 { 
2 compatible = "spi-gpio"; 
3 #address-cells = <1>;//spi4节点reg属性值中起始地址占用字长为1
4 #size-cells = <0>;//地址长度占用字长为0
5 
6 gpio_spi: gpio_spi@0 { 
7 compatible = "fairchild,74hc595"; 
8 reg = <0>;
9 };
10 };
11
12 aips3: aips-bus@02200000 {
13 compatible = "fsl,aips-bus", "simple-bus";
14 #address-cells = <1>;
15 #size-cells = <1>;
16
17 dcp: dcp@02280000 {
18 compatible = "fsl,imx6sl-dcp";
19 reg = <0x02280000 0x4000>;
20 };
21 };
3.3.5 reg属性

    reg属性的值一般是(address,length)对,用于描述设备地址空间资源信息,一般是某个外设的寄存器地址范围信息。

323 uart1: serial@02020000 {
324 compatible = "fsl,imx6ul-uart",
325 "fsl,imx6q-uart", "fsl,imx21-uart";
326 reg = <0x02020000 0x4000>;
327 interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
328 clocks = <&clks IMX6UL_CLK_UART1_IPG>,
329 <&clks IMX6UL_CLK_UART1_SERIAL>;
330 clock-names = "ipg", "per";
331 status = "disabled";
332 };

4. 设备树匹配方法

4.1 使用设备树前匹配方法

    没有使用设备树的时候,uboot会向Linux内核传递一个叫做machine id的值,也就是设备id,告诉Linux内核自己是什么设备,再看Linux内核是否支持。针对每一个板子,Linux内核都用MACHINE_START和MACHINE_END来定义一个machine_desc结构体来描述这个设备。

613 MACHINE_START(MX35_3DS, "Freescale MX35PDK")
614 /* Maintainer: Freescale Semiconductor, Inc */
615 .atag_offset = 0x100,
616 .map_io = mx35_map_io,
617 .init_early = imx35_init_early,
618 .init_irq = mx35_init_irq,
619 .init_time = mx35pdk_timer_init,
620 .init_machine = mx35_3ds_init,
621 .reserve = mx35_3ds_reserve,
622 .restart = mxc_restart,
623 MACHINE_END

    上述定义了"Freescale MX35DPK"这个设备,其中MACHINE_START和MACHINE_END定义在文件arch/arm/include/asm/mach/arch.h中:

#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
 .nr = MACH_TYPE_##_type, \
 .name = _name,
#define MACHINE_END \
};

    将宏定义展开以后,得到如下所示:

1 static const struct machine_desc __mach_desc_MX35_3DS \
2 __used \ 
3 __attribute__((__section__(".arch.info.init"))) = { 
4 .nr = MACH_TYPE_MX35_3DS, 
5 .name = "Freescale MX35PDK", 
6 /* Maintainer: Freescale Semiconductor, Inc */
7 .atag_offset = 0x100, 8 .map_io = mx35_map_io, 9 .init_early = imx35_init_early,
10 .init_irq = mx35_init_irq,
11 .init_time = mx35pdk_timer_init,
12 .init_machine = mx35_3ds_init,
13 .reserve = mx35_3ds_reserve,
14 .restart = mxc_restart,
15 };

    这里定义了一个machine_desc类型的结构体变量_mach_desc_MX35_3DS。在文件include/generated/mach-types.h中,定义了大量的machine id。

15 #define MACH_TYPE_EBSA110 0
16 #define MACH_TYPE_RISCPC 1
17 #define MACH_TYPE_EBSA285 4
18 #define MACH_TYPE_NETWINDER 5
19 #define MACH_TYPE_CATS 6
20 #define MACH_TYPE_SHARK 15
21 #define MACH_TYPE_BRUTUS 16
22 #define MACH_TYPE_PERSONAL_SERVER 17
......
287 #define MACH_TYPE_MX35_3DS 1645
......
1000 #define MACH_TYPE_PFLA03 4575

4.2 使用设备树后的设备匹配方法

    引入设备树后就不再使用MACHINE_START了,而是换成了DT_MACHINE_START。

208 static const char *imx6ul_dt_compat[] __initconst = {
209 "fsl,imx6ul",
210 "fsl,imx6ull",
211 NULL,
212 };
213
214 DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
215 .map_io = imx6ul_map_io,
216 .init_irq = imx6ul_init_irq,
217 .init_machine = imx6ul_init_machine,
218 .init_late = imx6ul_init_late,
219 .dt_compat = imx6ul_dt_compat,
220 MACHINE_END

    machine_descjiegou体中有个.dt_compat成员变量,保存着设备的兼容属性。代码中设置的.dt_compat = imx6ul_dt_compat,imx6ul_dt_compat表里面有"fsl,imx6ul"和"fsl,imx6ull"两个兼容值。只要某个板子的根节点"/"的compatible属性值与imx6ul_dt_compat表中的任何一个值相等,就表示Linux内核支持该设备。
在这里插入图片描述

4.3 向节点追加或修改内容

    现在向I2C1接口添加一个设备fxls8471,就相当于在i2c1这个节点上添加一个fxls8471子节点。先看看I2C1接口对应的节点:

937 i2c1: i2c@021a0000 {
938 #address-cells = <1>;
939 #size-cells = <0>;
940 compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
941 reg = <0x021a0000 0x4000>;
942 interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
943 clocks = <&clks IMX6UL_CLK_I2C1>;
944 status = "disabled";
945 };

    i2c1结点是定义在imx6ull.dtsi文件中,相当于是设备树的头文件,如果直接在这里添加节点没救相当于直接在其他的所有板子上都添加了这个设备,因此要道对应开发板使用的设备树文件imx6ull-alinetek-emmc.dts中去进行内容添加。

224 &i2c1 {
225 clock-frequency = <100000>;
226 pinctrl-names = "default";
227 pinctrl-0 = <&pinctrl_i2c1>;
228 status = "okay";
229
230 mag3110@0e {
231 compatible = "fsl,mag3110";
232 reg = <0x0e>;
233 position = <2>;
234 };
235
236 fxls8471@1e {
237 compatible = "fsl,fxls8471";
238 reg = <0x1e>;
239 position = <0>;
240 interrupt-parent = <&gpio5>;
241 interrupts = <0 8>;
242 };
243 };

5. 创建小型模板设备树

    首先搭建一个仅有一个根节点"/"的基础的框架。

1 / { 
2 compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull"; 
3 }

    接下来添加CPU节点,I.MX6ULL采用的Cortex-A7架构,而且只有一个CPU,所以只有一个cpu0节点。

1 / { 
2 compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull"; 
3 }
4 cpus { 
5 #address-cells = <1>;
6 #size-cells = <0>;
7
8 //CPU0 节点
9 cpu0: cpu@0 {
10 compatible = "arm,cortex-a7";
11 device_type = "cpu";
12 reg = <0>;
13 };
14 };
15 }

    像uart、iic控制器等等这些都属于SOC内部外设,因此一般会创建一个叫做soc的父节点来管理这些SOC内部外设的子节点,添加soc节点后的内容:

1 / { 
2 compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull"; 
3 
4 cpus { 
5 #address-cells = <1>;
6 #size-cells = <0>;
7
8 //CPU0 节点
9 cpu0: cpu@0 {
10 compatible = "arm,cortex-a7";
11 device_type = "cpu";
12 reg = <0>;
13 };
14 };
15 
16 //soc 节点
17 soc {
18 #address-cells = <1>;
19 #size-cells = <1>;
20 compatible = "simple-bus";
21 ranges;
22 } 
23 }

    ocram是I.MX6ULL内部ram,因此要添加一个soc的子节点。

1 / { 
2 compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull"; 
3 
4 cpus { 
5 #address-cells = <1>;
6 #size-cells = <0>;
7
8 //CPU0 节点
9 cpu0: cpu@0 {
10 compatible = "arm,cortex-a7";
11 device_type = "cpu";
12 reg = <0>;
13 };
14 };
15 
16 //soc 节点
17 soc {
18 #address-cells = <1>;
19 #size-cells = <1>;
20 compatible = "simple-bus";
21 ranges;
22 
23 //ocram 节点
24 ocram: sram@00900000 {
25 compatible = "fsl,lpm-sram";
26 reg = <0x00900000 0x20000>;
27 };
28 } 
29 }

    I.MX6ULL内部分为3个域:aips1-3,三个域分管不同的外设控制器,aips1-3这三个域对应的内存范围如下所示:
在这里插入图片描述
在这里插入图片描述

1 / { 
2 compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull"; 
3 
4 cpus { 
5 #address-cells = <1>;
6 #size-cells = <0>;
7
8 //CPU0 节点
9 cpu0: cpu@0 {
10 compatible = "arm,cortex-a7";
11 device_type = "cpu";
12 reg = <0>;
13 };
14 };
15 
16 //soc 节点
17 soc {
18 #address-cells = <1>;
19 #size-cells = <1>;
20 compatible = "simple-bus";
21 ranges;
22 
23 //ocram 节点
24 ocram: sram@00900000 {
25 compatible = "fsl,lpm-sram";
26 reg = <0x00900000 0x20000>;
27 };
28 
29 //aips1 节点
30 aips1: aips-bus@02000000 {
31 compatible = "fsl,aips-bus", "simple-bus";
32 #address-cells = <1>;
33 #size-cells = <1>;
34 reg = <0x02000000 0x100000>;
35 ranges;
36 }
37

38 //aips2 节点
39 aips2: aips-bus@02100000 {
40 compatible = "fsl,aips-bus", "simple-bus";
41 #address-cells = <1>;
42 #size-cells = <1>;
43 reg = <0x02100000 0x100000>;
44 ranges;
45 }
46 
47 //aips3 节点 
48 aips3: aips-bus@02200000 {
49 compatible = "fsl,aips-bus", "simple-bus";
50 #address-cells = <1>;
51 #size-cells = <1>;
52 reg = <0x02200000 0x100000>;
53 ranges;
54 }
55 }
56 }

6. 设备树在系统中的体现

    Linux内核启动后会解析设备树文件中的各个节点和属性,在根目录下的/proc/device-tree目录下根据节点名字创建对应文件夹。
在这里插入图片描述
    根节点的属性如下:
在这里插入图片描述
    根节点中的子节点如下:
在这里插入图片描述

7. 特殊节点

    aliases节点的主要功能就是定义别名。

18 aliases {
19 can0 = &flexcan1;
20 can1 = &flexcan2;

21 ethernet0 = &fec1;
22 ethernet1 = &fec2;
23 gpio0 = &gpio1;
24 gpio1 = &gpio2;
......
42 spi0 = &ecspi1;
43 spi1 = &ecspi2;
44 spi2 = &ecspi3;
45 spi3 = &ecspi4;
46 usbphy0 = &usbphy1;
47 usbphy1 = &usbphy2;
48 };

    chosen节点并不是一个真实的设备,chosen节点是为了uboot向Linux内核传递数据,重点是bootargs参数,一般.dts文件中chosen节点通常为空或者内容很少。

18 chosen {
19 stdout-path = &uart1;
20 };

    但进入chosen目录中,会发现多了一个bootargs属性,内容如下,都是uboot中设置的bootargs环境变量的值。
在这里插入图片描述
在这里插入图片描述
    uboot会将bootargs中设置的环境变量的值传递给设备树文件的chosen节点。
    设备是以节点的形式挂到设备树上的,内核用device_node结构体来描述一个节点。

49 struct device_node {
50 const char *name; /* 节点名字 */
51 const char *type; /* 设备类型 */
52 phandle phandle;
53 const char *full_name; /* 节点全名 */
54 struct fwnode_handle fwnode;
55
56 struct property *properties; /* 属性 */
57 struct property *deadprops; /* removed 属性 */
58 struct device_node *parent; /* 父节点 */
59 struct device_node *child; /* 子节点 */
60 struct device_node *sibling;
61 struct kobject kobj;
62 unsigned long _flags;
63 void *data;
64 #if defined(CONFIG_SPARC)
65 const char *path_component_name;
66 unsigned int unique_id;
67 struct of_irq_controller *irq_trans;
68 #endif
69 };
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值