前言
-
当前新版本的 Linux 内核 设备驱动框架,与设备树(Device Tree)结合密切,整体 设备树的设备驱动框架,比较的庞大,但又非常的经典。
-
一个个的 设备树解析函数,都是前人【智慧】的结晶,了解 设备树的实现,了解设备树的解析,对Linux 设备驱动开发非常有利,并且可以大大提高开发编码能力
-
虽然Linux 内核庞大、开源,但是Linux 内核各个模块的实现都是经典,非常适合学习深造
设备树 dtb 文件的由来
-
Linux 内核启动后,载入的设备树文件是 dtb,而不是 dts,也就是 设备树 dts 源文件经过类似编译(二进制化)的方式生成 设备树 dtb 文件
-
那么问题就是: 设备树 dts 或者 dtsi 中文本描述的设备树节点 node 与属性 property,如何呈现在设备树 dtb 文件中?直接改为二进制,还是有一定的转换规则?
-
经过对比设备树源文件 dts 与 设备树生成的 dtb 二进制文件,并查看对应的代码,找到了转换的规则,并不是简单的二进制化。
二进制化规则
-
设备树源文件,大概有三类文件组成:
- (1)设备树源文件 dts,一个 dts 文件生成一个 dtb 文件
- (2)设备树源文件的头文件 dtsi,类似于 C 语言的
include
头文件,可以有多个,dtsi 中也可以包含 更多的 dtsi - (3)设备树
dt-bindings
C 语言的头文件,这些头文件中,大部分都是【宏定义】,并不包含 C 语言的结构体,用于代替设备树中的【宏】
-
其实 设备树 源文件生成 设备树 dtb 的过程,有点类似于 C 语言的编译过程,大概有两个过程:【预处理】部分,把 设备树 dts 文件 当成 C 源文件,把 设备树 dtsi 与 设备树 C 语言的
.h
头文件当成 C 语言 头文件,预处理后就生成一个设备树文件(中间文件),然后再使用 dtc 工具 进行转换(类似于 C 语言的编译)。
设备树 dtb 构成
-
设备树 dtb 文件构成可以参考 设备树的规范文件:设备树规范可以通过
https://github.com/devicetree-org/devicetree-specification
获取到,当前版本是devicetree-specification-v0.4.pdf
-
设备树很复杂?其实就是由 节点 (node) 与 属性 (property)构成的
-
可以有多个节点,一个节点下面,可以包含多个节点。以实际树做个比喻,树干、树枝、树叶,树枝可以包含多级树枝,最后是树叶。
-
设备树中 树根、树干、树枝、树叶,都用 【节点】或者【容器】来表示。而属性 property,一个设备树节点的描述细节,可以有多个,附属于某个设备树节点。
-
如果设备树 节点 是个树叶,那么 这个设备树 节点的属性可以有大小、颜色等描述用的属性
设备树 dtb 中 C 语言的描述与实现
-
设备树 dtc 工具如何转换 设备树源文件到目标 dtb 文件?设备树 dtc 工具也是由 C 语言编写的,位置在 Linux 内核
scripts\dtc\dtc.c
-
dtc 的 设备树源文件只能输入一个,如果一个设备树包含 dts、多个 dtsi、多个
.h
dt-bindings
C 语言的头文件,需要借助 gcc 工具进行预处理,这样转换为一个中间的 设备树 源文件,也就是 包含的dtsi 文件被真实文件内容填充,【宏定义】被真实的数据代替。为了管理(解析)方便,再经过 dtc 工具,转换为 最终的 dtb 格式的文件。 -
设备树 dtb 的 C 语言描述,
scripts\dtc\libfdt\fdt.h
,节点fdt_node_header
与 属性fdt_property
struct fdt_node_header {fdt32_t tag;char name[];
};struct fdt_property {fdt32_t tag;fdt32_t len;fdt32_t nameoff;char data[];
};
-
可以看到,节点就是一个【容器】的概念,每个节点包含一个或者多个 属性
struct fdt_property
-
节点用于归类或者分组,类似于定义一个【结构体】,属性
struct fdt_property
是其中的成员。 -
注意这里 的
struct fdt_property
与Linux 内核 of 中 解析使用的struct property
不太一样,因为这里的struct fdt_property
关注的是 属性的物理存放规则,struct property
是设备树【树化】展开使用的属性描述节点(包含链表等层次结构化成员)。 -
struct fdt_property
的设备树节点,就是【并排】的堆叠操作,可以认为是 flat 扁平化的,【属性1】【属性2】。。。【属性N】。
如何区分 设备树节点与设备树 属性
- 设备树节点使用
FDT_BEGIN_NODE
,也就是 32位值00 00 00 01
四个字节表示一个设备树节点的开始, 使用FDT_END_NODE
,也就是 32位值00 00 00 02
四个字节表示一个设备树节点的结束
#define FDT_BEGIN_NODE 0x1 /* Start node: full name */
#define FDT_END_NODE 0x2 /* End node */
- 设备树节点的属性,使用
FDT_PROP
,32位值,00 00 00 03
作为 tag 的方式,用于设备树属性的开始
#define FDT_PROP 0x3 /* Property: name off,size, content */
- 设备树 结构部分:设备树节点、设备树属性,结束使用
FDT_END
表示,也就是 32位值00 00 00 09
表示 设备树结构部分的 【结束】
#define FDT_END 0x9
-
设备树 dt 并不是 设备树源文件的 直接二进制化,而是有个转换(翻译)规则,比如 设备树节点或者属性都有
name
属性,而name
属性本身也是个字符串,而这些name
属性都聚集到了一起,形成了string
区域,所以类似于compatible = "rockchip,rk3368-i2c", "rockchip,rk3288-i2c";
属性, 存储到 dtb 时,compatible
字符串 放在了string 字符串区域
,struct fdt_property
中使用 字符串区域的偏移fdt32_t nameoff
来表示,而"rockchip,rk3368-i2c", "rockchip,rk3288-i2c"
属性值,是以实际字符串的方式存储(4字节对齐)。 -
如此看来, 设备树 dtb 文件有三个区域组成:
- (1)设备树 头部,结构体是
struct fdt_header
, - (2)设备树节点与属性存放区域
dt_struct
,包括struct fdt_node_header
与struct fdt_property
- (3)设备树 字符串区域,主要是 设备树节点与属性的
name
字符串,用于索引,好处就是【公用】,比如很多设备树节点中都有compatible
属性,这里公用一个compatible
name
字符串,也就是 属性中的name
字符串也要存储,只是为了【公用】,并且索引管理方便,集中存放在一个区域。
- (1)设备树 头部,结构体是
小结
-
本篇简单描述 dtb 文件的存储构成,了解了 dtb 文件的构成(内容),才能更好的管理与解析设备树,或者更好的利用好设备树。
-
设备树不只是用于Linux 这个系统,一些嵌入式系统,理论上也可以使用设备树,当然可能占用一些额外的资源,比如
u-boot
中已经普及使用设备树了