设备树基础语法:从DTS文件到中断绑定
各位同学,今天我们来啃设备树语法这块硬骨头。说实话,我刚接触设备树那会儿,看着满屏的#address-cells和interrupt-parent也是一头雾水。但后来在移植U-Boot时被它折腾过几回,慢慢就摸出门道了。
设备树说白了就是一套描述硬件信息的"说明书"。U-Boot启动时把它读进来,内核启动时也用它。你想想看,如果没有这套标准,每个板子都得写死代码,那移植工作得多痛苦?
2.1 DTS文件结构:一棵倒着长的树
DTS(Device Tree Source)是设备树的源文件,编译后生成DTB。它的结构很简单——就是一个树形结构,根节点是/,下面挂各种子节点。
/dts-v1/;
/ {
compatible = "vendor,board-name";
#address-cells = <1>;
#size-cells = <1>;
cpu@0 {
compatible = "arm,cortex-a7";
reg = <0x0>;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>;
};
};
嗯,这里要注意几点:
/dts-v1/;是版本声明,必须写在最前面- 根节点用
/表示,整个文件只有一个根 - 节点名后面可以跟
@地址,比如cpu@0 - 每个节点用花括号
{ }包裹,最后加分号
#address-cells 和 #size-cells 写好。这两个属性决定了地址和长度用几个32位整数表示,后面所有子节点都继承这个规则。忘了写?编译不会报错,但运行时地址解析全乱套——我踩过这个坑。
2.2 节点与属性:硬件的"身份证"
每个节点代表一个硬件设备,属性就是描述这个设备的各种参数。常用的属性我列个表:
| 属性名 | 作用 | 示例 |
|---|---|---|
compatible |
设备兼容性标识,驱动匹配用 | "ti,am335x-uart" |
reg |
寄存器地址和长度 | <0x48020000 0x1000> |
interrupts |
中断号、触发类型 | <0 56 4> |
gpios |
GPIO引脚绑定 | <&gpio1 15 GPIO_ACTIVE_HIGH> |
status |
设备状态(okay/disabled) | "okay" |
节点之间可以嵌套,也可以引用。比如:
&uart0 {
status = "okay";
pinctrl-0 = <&uart0_pins>;
};
这里的 &uart0 就是引用其他地方定义的节点标签。我在移植时经常用这种方式——先在根节点下定义好所有外设,然后在板级文件里通过引用来使能或配置它们,代码复用率很高。
2.3 reg属性与地址编码:告诉CPU设备在哪
reg 属性是设备树里最重要的属性之一。它告诉系统:这个设备的寄存器在哪个地址,占多大空间。
uart@48020000 {
compatible = "ti,am335x-uart";
reg = <0x48020000 0x1000>;
interrupts = <72>;
};
这里 0x48020000 是基地址,0x1000 是地址范围(4KB)。但为什么有时候 reg 里只有两个数,有时候却有四个?
答案就在 #address-cells 和 #size-cells 里:
#address-cells表示地址占几个32位整数#size-cells表示长度占几个32位整数
比如64位地址的SoC:
/ {
#address-cells = <2>; // 地址用两个32位,即64位
#size-cells = <2>; // 长度也用两个32位
memory@800000000 {
device_type = "memory";
reg = <0x00000008 0x00000000 0x00000000 0x80000000>;
// 地址: 0x00000008_00000000
// 长度: 0x00000000_80000000 (2GB)
};
};
#size-cells 写成了1,导致高32位地址被截断。所以写 reg 之前,一定先确认父节点的 #address-cells 和 #size-cells 对不对。
2.4 中断绑定:设备怎么"喊"CPU
中断绑定的核心是三个要素:中断控制器、中断号和触发类型。看个例子:
/ {
interrupt-parent = <&gic>; // 全局中断控制器
gic: interrupt-controller@48241000 {
compatible = "arm,cortex-a7-gic";
reg = <0x48241000 0x1000>;
interrupt-controller;
#interrupt-cells = <3>; // 每个中断用3个cell描述
};
uart@48020000 {
compatible = "ti,am335x-uart";
reg = <0x48020000 0x1000>;
interrupts = <0 56 4>;
// 0: 中断类型(SPI)
// 56: 中断号
// 4: 触发类型(高电平触发)
};
};
这里 #interrupt-cells = <3> 表示每个中断描述需要3个32位整数。具体含义由中断控制器自己定义,GIC的标准是:
- 第一个cell:中断类型(0=PPI,1=SPI)
- 第二个cell:中断号(SPI从32开始)
- 第三个cell:触发类型(1=上升沿,4=高电平)
如果设备有多个中断:
interrupts = <0 56 4>, <0 57 4>; // 两个中断
2.5 GPIO绑定:控制引脚的"开关"
GPIO绑定比中断稍微复杂一点,因为涉及引脚号和电气特性。标准写法:
gpio1: gpio@4804c000 {
compatible = "ti,omap4-gpio";
reg = <0x4804c000 0x1000>;
gpio-controller;
#gpio-cells = <2>; // 每个GPIO用2个cell
};
led {
compatible = "gpio-leds";
led-red {
gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>;
label = "red-led";
};
};
这里 gpios 属性的含义:
&gpio1:引用GPIO控制器15:引脚号(GPIO1_15)GPIO_ACTIVE_HIGH:高电平有效
如果引脚需要配置上下拉或驱动能力,可以在 pinctrl 里定义:
&gpio1 {
pinctrl-0 = <&led_pins>;
pinctrl-names = "default";
};
led_pins: pinmux_led_pins {
pinctrl-single,pins = <
0x0c 0x07 // 引脚复用配置
>;
};
核心要点:设备树的GPIO绑定本质上是把"哪个控制器、哪个引脚、什么电平"这三个信息打包成一个属性。U-Boot在解析时,会根据 #gpio-cells 的值来拆分这些信息。我建议你在写GPIO绑定时,先确认GPIO控制器的 #gpio-cells 是2还是3——有些SoC需要3个cell来指定引脚复用功能。
2.6 知识体系总览
下面这张图把本章的核心知识点串起来了,你可以对照着看:
这张图把设备树语法的核心脉络理清楚了。从DTS文件结构出发,三个分支分别对应节点属性、地址编码和中断GPIO绑定,最终汇聚到U-Boot解析DTB并初始化硬件这个终点。你在写设备树时,脑子里要有这张图——每个节点、每个属性都不是孤立的,它们共同构成了一套完整的硬件描述语言。