2、设备树基础:设备树语法入门、节点与属性、设备树编译与反编译、设备树在Linux启动中的作用
2.1 设备树到底是个啥?
说实话,我刚接触嵌入式Linux那会儿,对设备树也是一头雾水。那时候还在用老式的板级文件(board-xxx.c),每次换个外设都得改内核源码,重新编译,麻烦得要命。
设备树(Device Tree)说白了,就是一套描述硬件信息的“说明书”。它用文本文件的形式,告诉内核:你的CPU是什么、内存多大、I2C总线上挂了哪些设备、GPIO怎么配置……等等。内核启动时读这份说明书,就知道该怎么初始化硬件了。
我个人习惯把设备树比作“硬件的身份证”。你想想看,没有身份证,别人怎么知道你是谁?内核也一样,没有设备树,它连你板子上有啥外设都不知道。
2.2 设备树语法入门
设备树的语法其实挺简单的。它用的是树形结构,根节点是“/”,下面挂各种子节点。每个节点代表一个硬件设备或总线。
来看个最基本的例子:
/dts-v1/;
/ {
compatible = "my_company,my_board";
#address-cells = <1>;
#size-cells = <1>;
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2";
};
};
这里有几个关键点:
- /dts-v1/:版本声明,告诉编译器这是DTS v1格式
- compatible:兼容性字符串,内核用它来匹配驱动
- reg:地址和大小信息,格式是 <地址 长度>
- chosen:特殊节点,用来传启动参数
2.3 节点与属性详解
节点是设备树的基本单元。每个节点可以有多个属性,属性就是键值对。常见的属性类型有:
| 属性类型 | 示例 | 说明 |
|---|---|---|
| 字符串 | compatible = "ti,am335x"; | 单个字符串 |
| 字符串列表 | compatible = "ti,am335x", "ti,omap"; | 多个字符串,用逗号分隔 |
| 32位整数 | reg = <0x48000000 0x1000>; | 尖括号括起来 |
| 64位整数 | reg = <0x00000000 0x80000000>; | 两个32位拼一个64位 |
| 布尔值 | status = "disabled"; | 用字符串表示,常见"okay"和"disabled" |
节点命名也有讲究。我建议用“设备名@地址”的格式。比如:
i2c@48000000 {
compatible = "ti,omap4-i2c";
reg = <0x48000000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
};
这里i2c@48000000表示I2C控制器,地址是0x48000000。它下面挂了一个eeprom,地址是0x50。这种层级关系,内核解析起来非常直观。
2.4 设备树编译与反编译
设备树源文件(.dts)不能直接被内核使用,得编译成二进制格式(.dtb)。反过来,有时候拿到一个dtb文件,想看看里面是啥,就得反编译。
编译和反编译的工具是dtc(Device Tree Compiler)。用法很简单:
# 编译:dts -> dtb
dtc -I dts -O dtb -o my_board.dtb my_board.dts
# 反编译:dtb -> dts
dtc -I dtb -O dts -o my_board.dts my_board.dtb
参数说明:
- -I:输入格式(dts或dtb)
- -O:输出格式
- -o:输出文件名
还有个常用选项是-@,用来生成符号表。如果你要做设备树插件(DT Overlay),这个选项必须加。
fdtdump my_board.dtb | less
2.5 设备树在Linux启动中的作用
Linux启动时,设备树扮演了“硬件向导”的角色。整个过程大致是这样的:
- Bootloader加载dtb:U-Boot或别的bootloader从存储介质(Flash、SD卡等)读取dtb文件,放到内存里。
- 传递dtb地址给内核:Bootloader通过寄存器或参数传递dtb的内存地址。
- 内核解析dtb:内核启动早期,会解析dtb,构建出设备树结构体(device_node树)。
- 驱动匹配:内核遍历设备树,根据compatible属性找到对应的驱动,然后调用驱动的probe函数。
- 设备初始化:驱动读取设备树中的reg、interrupts等属性,完成硬件初始化。
你想想看,如果没有设备树,内核就得靠板级文件里硬编码的地址和配置来初始化硬件。换一块板子就得改内核源码,那得多麻烦?
我记得有一次调试一个音频驱动,死活不出声。后来用ls /proc/device-tree/一看,发现设备树里音频节点的reg地址写错了。改过来重新编译,声音就出来了。所以说,设备树是调试硬件问题的第一站。
earlycon和dtb=...参数,可以提前看到设备树解析的日志。如果设备树有问题,这里会直接报错,省得你后面猜来猜去。
2.6 小结
设备树是嵌入式Linux开发的必修课。语法不难,但细节很多。我建议你从简单的板子开始,自己写一个dts文件,编译、加载、调试,走一遍完整的流程。遇到问题别怕,多用dtc和fdtdump工具看看,慢慢就熟练了。
下一章我们会深入讲时钟和复位节点的设计,那是设备树里最让人头疼的部分之一。做好准备,咱们接着干!