设备树节点与属性:标准属性、自定义属性与节点操作
设备树这东西,说白了就是给内核描述硬件的一本"说明书"。每个硬件模块对应一个节点,节点里塞满了各种属性。今天我就带你把这套规则彻底捋清楚。
我记得刚接触设备树那会儿,最头疼的就是搞不懂哪些属性是必须的,哪些可以自己定义。后来踩了几个坑,才慢慢摸出门道。
2.1 节点命名规则
每个设备树节点都得有个名字。格式很简单:node-name@unit-address。
- node-name:描述设备类型,比如
uart、i2c、gpio - unit-address:设备的基地址或寄存器偏移,用来区分同名节点
// 正确示例
uart@101f0000 {
compatible = "ns16550";
reg = <0x101f0000 0x1000>;
};
// 错误示例 —— 两个节点同名且无地址区分
uart {
...
};
uart {
...
};
spi,但忘了加地址后缀。内核解析时直接报错,说节点重复。从那以后,我只要看到总线上的设备,一定带上 @address。
2.2 标准属性详解
标准属性是设备树规范里定死的,内核解析器就认这些名字。我挑几个最常用的说。
2.2.1 compatible —— 灵魂属性
这个属性告诉内核:"我是谁,我能兼容谁"。格式是 "厂商,型号",越具体越靠前。
compatible = "ti,am335x-uart", "ns16550";
内核匹配驱动时,会从左往右试。第一个匹配上了就用,没匹配上就试第二个。我个人习惯把最精确的型号放第一位,通用型号放后面兜底。
2.2.2 reg —— 地址与大小
描述设备的寄存器空间。格式是 <地址 长度>,可以有多个地址段。
reg = <0x101f0000 0x1000>;
// 或者多段
reg = <0x101f0000 0x1000>, <0x101f1000 0x200>;
你想想看,如果 #address-cells 和 #size-cells 没配对,reg 解析出来全是错的。我见过有人把 #size-cells 设成 0,结果内核读到的地址完全不对。
2.2.3 interrupts —— 中断绑定
描述设备用哪个中断号、触发方式。需要配合 interrupt-parent 使用。
interrupt-parent = <&gic>;
interrupts = <0 42 4>;
// 0=SPI, 42=中断号, 4=高电平触发
2.2.4 其他常用标准属性
| 属性名 | 作用 | 示例 |
|---|---|---|
status |
设备状态:okay / disabled / fail |
status = "okay"; |
clock |
时钟源绑定 | clocks = <&clk_uart>; |
pinctrl |
引脚复用配置 | pinctrl-0 = <&uart0_pins>; |
dmas |
DMA 通道绑定 | dmas = <&edma 12>; |
2.3 自定义属性
标准属性不够用怎么办?自己定义。设备树允许你随便加属性,只要驱动能读懂就行。
// 自定义属性示例
my-device {
compatible = "my,driver";
reg = <0x2000000 0x100>;
my,baud-rate = <115200>; // 自定义波特率
my,data-bits = <8>; // 自定义数据位
my,parity = "none"; // 自定义校验
};
我个人习惯给自定义属性加厂商前缀,比如 my,,避免跟未来标准属性冲突。驱动里用 of_property_read_u32() 就能读出来。
h-front-porch 这种东西。我就自己定义了 panel,h-front-porch、panel,v-sync-len 等属性,驱动里一次性读出来,非常灵活。
2.4 节点引用与覆盖
设备树支持用 &label 引用已有节点,然后追加或覆盖属性。这个功能在板级文件中用得特别多。
// 原始 dtsi 文件
uart0: uart@101f0000 {
compatible = "ns16550";
reg = <0x101f0000 0x1000>;
status = "disabled";
};
// 板级 dts 文件
&uart0 {
status = "okay";
clock-frequency = <48000000>;
};
这样做的好处是:SoC 厂商提供通用的 .dtsi,板级文件只改自己需要的部分。不用把整个节点复制一遍。
2.4.1 覆盖规则
- 追加属性:如果原节点没有该属性,直接加上
- 覆盖属性:如果原节点已有,新值替换旧值
- 删除属性:用
/delete-property/或/delete-node/
// 删除属性示例
&uart0 {
/delete-property/ clock-frequency;
};
// 删除节点示例
&uart0 {
/delete-node/;
};
reg 的地址段数量,否则内核解析会出问题。我见过有人覆盖 reg 时少写了一个地址段,结果驱动读到的寄存器空间完全错位。
2.5 知识结构总览
下面这张图把设备树节点与属性的核心逻辑串起来了。你对照着看,思路会更清晰。
2.6 实战建议
说了这么多,我总结几条实际干活时的经验:
- 命名规范要统一:整个项目用同一套命名风格,别一会儿驼峰一会儿下划线
- 标准属性优先:能用标准属性就别自己造,内核工具链支持更好
- 自定义属性加前缀:避免未来跟新标准属性冲突
- 引用覆盖别乱改 reg:地址段数量一变,驱动直接崩
- 多用 status 控制:调试时把不用的设备设成
disabled,省得干扰
dtc -I dts -O dtb 编译一遍,再用 dtc -I dtb -O dts 反编译回来。看看反编译结果跟原文件是否一致,能发现很多隐藏问题。
嗯,设备树的节点和属性就这些核心内容。你把这些规则吃透了,后面写驱动、调帧率的时候,设备树这块就不会再卡你。