我是靠谱客的博主 甜美河马,这篇文章主要介绍设备树学习篇一,现在分享给大家,希望可以做个参考。

  • 设备树的基本概念

一、什么是设备树?
简单来说,设备树是一种数据结构,它通过特有的语法格式描述片上片外的设备信息。由BootLoader传递给kernel,kernel进行解析后形成和驱动程序关联的dev结构供驱动代码使用。

二、那么为什么要使用设备树来替代传统的总线设备驱动模型?

首先,传统的总线设备驱动是将设备信息描述在C代码中,这样当要修改驱动相关的硬件信息时,就需要去具体的修改代码文件,然后全体编译内核。这个操作太过繁琐而且不利于代码的维护和移植性。尤其是内核在支持各种soc的硬件平台时,比如s3c2440,50,51等型号变更就需要对应每个小型号提供支持单独的驱动程序,会有大量的冗余代码产生。

内核开发的大牛们就设计了设备树的方式将驱动和设备完全分离开。将驱动程序设计成硬件无关的类型,一切设备资源(比如memory,interrupt,clk,pinctrl)在设备树文件中定义。内核来适配驱动和设备信息。将有效的设备信息通过参数传递给驱动的probe函数,再进行具体硬件的初始化。这样当我们的硬件出现变更时(各公司基于芯片公版单独设计PCB等情况),只需要去修改对应的设备树文件,而完全不用去更改驱动代码。驱动的通用性也会大大提供。比如s3c2410的i2c驱动也同样支持2440,2451。这样多个系列芯片只需要共用同一套驱动代码,差分设备树文件就可以了。

三、下面介绍设备树的使用方法

设备树的代码文件是dts文件和dtsi文件。dtsi文件类似include头文件,可以被dts文件包含。dts文件会被dtc(设备树编译器)编译为dtb(device tree block)的二进制文件。该文件会被烧写到内存的特定地址(由BootLoader指定,原则上随意,只要不覆盖了boot和kernel的内容就好)。再由BootLoader将地址通过参数传递给kernel。kernel根据dtb文件的特定格式解析出有效的设备信息,从而传递给驱动代码。这样说感觉会比较抽象,后续会有更清晰的介绍。这部分内容先集中在dts文件本身的语法上。

四、DTS及DTSI文件语法介绍
example1是一个简单的设备树文件的例子,摘自kernel的Document/devicetree/目录下usage-model.txt文件。下面会逐个讲解其含义和语法。

复制代码
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
/*#example 1#*/ /{ compatible = "nvidia,harmony", "nvidia,tegra20"; #address-cells = <1>; #size-cells = <1>; interrupt-parent = <&intc>; chosen { }; aliases { }; memory { device_type = "memory"; reg = <0x00000000 0x40000000>; }; soc { compatible = "nvidia,tegra20-soc", "simple-bus"; #address-cells = <1>; #size-cells = <1>; ranges; intc: interrupt-controller@50041000 { compatible = "nvidia,tegra20-gic"; interrupt-controller; #interrupt-cells = <1>; reg = <0x50041000 0x1000>, < 0x50040100 0x0100 >; }; serial@70006300 { compatible = "nvidia,tegra20-uart"; reg = <0x70006300 0x100>; interrupts = <122>; }; i2s1: i2s@70002800 { compatible = "nvidia,tegra20-i2s"; reg = <0x70002800 0x100>; interrupts = <77>; codec = <&wm8903>; }; i2c@7000c000 { compatible = "nvidia,tegra20-i2c"; #address-cells = <1>; #size-cells = <0>; reg = <0x7000c000 0x100>; interrupts = <70>; wm8903: codec@1a { compatible = "wlf,wm8903"; reg = <0x1a>; interrupts = <347>; }; }; }; sound { compatible = "nvidia,harmony-sound"; i2s-controller = <&i2s1>; i2s-codec = <&wm8903>; }; };

首先,设备树的基本单元是节点,由根节点(/)和其子节点(name@addr)组成,子节点也可以有子节点,形成一个树状结构。每个设备树的根节点是唯一的。
如下图根节点中有四个属性,compatible属性可以理解为节点的兼容ID,它的值是一个字符串列表,内核通过它和驱动进行匹配。根节点的compatible更多表示该设备树所支持的平台类型。比如compatible = “samsung,smdk2410”,"samsung,smdk2440”;则表示当前设备树支持三星smdk2410的平台和smdk2440平台。当内核运行对应arch目录下的mach平台文件时,会匹配到这个设备树,然后进行加载。ps:arch/arm/mach-s3c24xx/mach-smdk2440.c文件中有该字符串的注册。

复制代码
1
2
3
4
5
6
7
/{ compatible = "samsung,smdk2410", "samsung,smdk2440"; #address-cells = <1>; #size-cells = <1>; interrupt-parent = <&intc>; }

cells在属性中的意义是用多少个32bit的立即数来表示对应的属性。#address-cells = <1>;意味着在定义该属性的节点下用一个32bit的立即数来表示address的值,#size-cells = <1>;意味着在定义该属性的节点下用一个32bit的立即数来表示size的大小。(子节点也包含在其中)比如:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
soc { compatible = "samsung,smdk2410", "samsung,smdk2440"; #address-cells = <2>; #size-cells = <2>; ranges; intc: interrupt-controller@50041000 { compatible = "nvidia,tegra20-gic"; interrupt-controller; #interrupt-cells = <1>; reg = <0x0 0x50041000 0x0 0x1000>, <0x100 0x50040100 0x1000 0x0100 >; //reg的地址是由0x0和0x50041000组成的64位数表示,它的地址size由0x0和0x1000组成的64位数表示 }; };

在soc中reg的第一个地址是0x50041000,它的大小是0x1000。第二个地址是0x10050040100,它的大小是0x10000100。代表这个设备占了两块寄存器地址空间,每块的起始地址和偏移量都指明了。(我胡诌的地址和size,为了说明上面的property)。

根节点中的interrupt-parent = <&intc>;属性表明该设备树所使用的中断源是intc(也是以设备节点的形式描述在设备树中)。这里的intc并不是设备节点的名称,而是设备节点的label。节点以[label]:name@addr{ };的形式定义标签。方便在设备树的其他位置进行引用,引用格式为&label。

在中断控制器中有一个没有赋值的interrupt-controller属性,该属性是告诉内核,此节点为一个中断控制器。中断控制器在设备树中也是以树状结构存在的,在上述的例子中并没有体现。一个设备树可以有一个根中断节点和多个多级子中断节点。关于详细的中断控制器,我打算专门写一篇博客介绍,在此,不再赘述。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
/*完整设备树结构见example1*/ /{ interrupt-parent = <&intc>; intc: interrupt-controller@50041000 { compatible = "nvidia,tegra20-gic"; interrupt-controller; #interrupt-cells = <1>; reg = <0x50041000 0x1000>, < 0x50040100 0x0100 >; }; };

chosen { };和aliases { };是两个比较特殊的节点。chosen是用来给内核传递参数,比如启动参数bootargs,不代表实际的设备。其父节点必须是根节点。aliases节点中主要给其他的节点定义别名,方便引用。不使用别名引用节点要使用绝对路径,类似/soc/serial0。主要供设备树外部使用,设备内部引用节点使用label即可。

memory节点是设备树文件的必备节点,device_type属性表明该节点的类型是memory,它定义了系统物理内存的layout,即起始地址和长度。memory节点可以有多个,代表多段内存。

复制代码
1
2
3
4
5
memory { device_type = "memory"; reg = <0x00000000 0x40000000>; };
  • DTB文件的解读
    dts文件被编译成dtb文件,简单的解读dtb文件的格式可以更好的理解内核加载设备树的过程也能有效的帮助我们进行相关的debug。以下是我简单编写的基于s3c2440的设备树文件dtb文件。根据这个例子进行详细说明。(在arch/arm/boot/dts/目录下的Makefile中加入该dts文件的编译选项,内核目录下make dtbs就可得到对应的dtb二进制文件)
复制代码
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
#define S3C2410_GPF(_nr) ((5<<16) + (_nr)) /dts-v1/; / { model = "SMDK24440"; compatible = "samsung,smdk2440"; #address-cells = <1>; #size-cells = <1>; memory@30000000 { device_type = "memory"; reg = <0x30000000 0x4000000>; }; chosen { bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200"; }; led { compatible = "jz2440_led"; pin = <S3C2410_GPF(6)>; }; };

dtb的文件结构如下图所示,由header,memory map,Device-tree structure和strings组成。我们分别介绍。
-------------

base
struct boot_param_header
(alignment gap) (*)
memory reserve map
(alignment gap)
device-tree structure
(alignment gap)
device-tree strings

在header中保存着struct boot_param_header{}结构体。它的组成如下

复制代码
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
struct boot_param_header { u32 magic; /* magic word OF_DT_HEADER */ u32 totalsize; /* total size of DT block */ u32 off_dt_struct; /* offset to structure */ u32 off_dt_strings; /* offset to strings */ u32 off_mem_rsvmap; /* offset to memory reserve map */ u32 version; /* format version */ u32 last_comp_version; /* last compatible version */ /* version 2 fields below */ u32 boot_cpuid_phys; /* Which physical CPU id we're booting on */ /* version 3 fields below */ u32 size_dt_strings; /* size of the strings block */ /* version 17 fields below */ u32 size_dt_struct; /* size of the DT structure block */ }; //结构体中相关宏定义 #define OF_DT_HEADER 0xd00dfeed /* 4: version, 4: total size */ #define OF_DT_BEGIN_NODE 0x1 /* Start node: full name */ #define OF_DT_END_NODE 0x2 /* End node */ #define OF_DT_PROP 0x3 /* Property: name off, size, content */ #define OF_DT_END 0x9

magic中保存的32bit为OF_DT_HEADER(0xd00dfeed),内核通过这个变量中的值判断获取到的是否是一个有效的DTB文件。第二个u32 totalsize代表整个DTB的大小,如dtb图1为0x1c1 byte。内核需要读取它为设备树在内核中分配空间保存整个dtb数据,方便运行时读取。off_dt_struct代表device-tree structure相对于设备树头地址的偏移量。off_dt_struct代表device-tree strings相对于设备树头的偏移量。步长为byte。在dtb文件中property和它的赋值是分开存放的,从dtb图1中可以看出所有structure类似SMDK24440(编译的时候没注意多打了个4,和上面的dts文件是匹配的)被顺序存放在一起,所有代表property的字符串被顺序存放在一起。

dtb图1中off_dt_struct 0x38的偏移量后对应0x0001,它代表着一个node的开始,在这里它意味着根节点node的开始。紧接着就是node name,根节点没有node name所以是0x0000。0x0003代表着一个property的开始,它之后的两个u32分别代表着length和name_offset。length为该属性的长度,而name_offset代表该属性对应字符串名在device-tree strings的偏移量。剩下的boot_param_header成员参考代码注释可以很好的理解。

下一篇会从代码的角度剖析内核是如何加载dtb文件以及如果配置为具体的dev结构。
dtb 图1

复制代码
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
00000000h: D0 0D FE ED 00 00 01 C1 00 00 00 38 00 00 01 78 ; ?...?..8...x 00000010h: 00 00 00 28 00 00 00 11 00 00 00 10 00 00 00 00 ; ...(............ 00000020h: 00 00 00 49 00 00 01 40 00 00 00 00 00 00 00 00 ; ...I...@........ 00000030h: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ; ................ //根node开始 00000040h: 00 00 00 03 00 00 00 0A 00 00 00 00 53 4D 44 4B ; ............SMDK 00000050h: 32 34 34 34 30 00 00 00 00 00 00 03 00 00 00 11 ; 24440........... //property开始 0x11代表该property长度,即“Samsung,smdk2440” 00000060h: 00 00 00 06 73 61 6D 73 75 6E 67 2C 73 6D 64 6B ; ....samsung,smdk //0x0006代表它的name_offset 00000070h: 32 34 34 30 00 00 00 00 00 00 00 03 00 00 00 04 ; 2440............ 00000080h: 00 00 00 11 00 00 00 01 00 00 00 03 00 00 00 04 ; ................ 00000090h: 00 00 00 20 00 00 00 01 00 00 00 01 6D 65 6D 6F ; ... ........memo 000000a0h: 72 79 40 33 30 30 30 30 30 30 30 00 00 00 00 03 ; ry@30000000..... 000000b0h: 00 00 00 07 00 00 00 2C 6D 65 6D 6F 72 79 00 00 ; .......,memory.. 000000c0h: 00 00 00 03 00 00 00 08 00 00 00 38 30 00 00 00 ; ...........80... 000000d0h: 04 00 00 00 00 00 00 02 00 00 00 01 63 68 6F 73 ; ............chos 000000e0h: 65 6E 00 00 00 00 00 03 00 00 00 45 00 00 00 3C ; en.........E...< 000000f0h: 6E 6F 69 6E 69 74 72 64 20 72 6F 6F 74 3D 2F 64 ; noinitrd root=/d 00000100h: 65 76 2F 6D 74 64 62 6C 6F 63 6B 34 20 72 77 20 ; ev/mtdblock4 rw 00000110h: 69 6E 69 74 3D 2F 6C 69 6E 75 78 72 63 20 63 6F ; init=/linuxrc co 00000120h: 6E 73 6F 6C 65 3D 74 74 79 53 41 43 30 2C 31 31 ; nsole=ttySAC0,11 00000130h: 35 32 30 30 00 00 00 00 00 00 00 02 00 00 00 01 ; 5200............ 00000140h: 6C 65 64 00 00 00 00 03 00 00 00 0B 00 00 00 06 ; led............. 00000150h: 6A 7A 32 34 34 30 5F 6C 65 64 00 00 00 00 00 03 ; jz2440_led...... 00000160h: 00 00 00 04 00 00 00 45 00 05 00 06 00 00 00 02 ; .......E........ 00000170h: 00 00 00 02 00 00 00 09 6D 6F 64 65 6C 00 63 6F ; ........model.co 00000180h: 6D 70 61 74 69 62 6C 65 00 23 61 64 64 72 65 73 ; mpatible.#addres 00000190h: 73 2D 63 65 6C 6C 73 00 23 73 69 7A 65 2D 63 65 ; s-cells.#size-ce 000001a0h: 6C 6C 73 00 64 65 76 69 63 65 5F 74 79 70 65 00 ; lls.device_type. 000001b0h: 72 65 67 00 62 6F 6F 74 61 72 67 73 00 70 69 6E ; reg.bootargs.pin 000001c0h: 00 ; .

最后

以上就是甜美河马最近收集整理的关于设备树学习篇一的全部内容,更多相关设备树学习篇一内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(109)

评论列表共有 0 条评论

立即
投稿
返回
顶部