3. 设备树常用属性:compatible、reg、interrupts、status、clocks、gpios 等标准属性解析
设备树里的属性,说白了就是描述硬件“长什么样、怎么用”的标签。每个外设节点都靠这些属性告诉内核:我是谁、我接在哪、我怎么被控制。我刚开始学设备树时,总觉得属性太多记不住,后来发现只要抓住几个核心的,其他都是变体。
这一章,我把最常用的六个属性掰开揉碎讲清楚。你跟着走一遍,以后看任何 dts 文件都不会发怵。
3.1 compatible —— 你是谁?
compatible 是设备树里最重要的属性,没有之一。内核通过它来匹配驱动。格式很简单:
compatible = "厂商,型号", "通用型号";
前面是精确匹配,后面是降级兼容。举个例子:
uart0: serial@10000000 {
compatible = "fsl,imx6q-uart", "fsl,imx21-uart";
reg = <0x10000000 0x4000>;
interrupts = <0 58 4>;
};
内核先找 fsl,imx6q-uart 的驱动,找不到就试 fsl,imx21-uart。我遇到过有人把顺序写反了,结果驱动匹配到了不兼容的老版本,串口能注册但收发数据全是乱的。
核心原则:越精确的型号放越前面,越通用的放越后面。不要只写一个字符串,至少留一个降级选项。
我的习惯:写 compatible 时,我会先查内核里 Documentation/devicetree/bindings/ 目录下的绑定文档。那里有官方推荐的字符串格式,照着写最保险。
3.2 reg —— 你在哪?
reg 描述设备的地址空间。它由一组“地址 + 长度”对组成,格式取决于父节点的 #address-cells 和 #size-cells。
| #address-cells | #size-cells | reg 含义 |
|---|---|---|
| 1 | 1 | 32 位地址 + 32 位长度 |
| 2 | 1 | 64 位地址 + 32 位长度 |
| 1 | 0 | 仅地址,无长度(常用于 GPIO 等) |
看个实际例子:
soc {
#address-cells = <1>;
#size-cells = <1>;
ethernet@20200000 {
reg = <0x20200000 0x1000>;
// 基址 0x20200000,长度 4KB
};
gpio0: gpio@20900000 {
reg = <0x20900000 0x1000>;
};
};
我曾经调试过一个网卡驱动,死活读不到 MAC 地址。查了半天,发现 reg 里的长度写成了 0x100,只映射了 256 字节,而 MAC 寄存器刚好在偏移 0x200 的位置。嗯,这种低级错误最坑人。
注意:reg 的地址值必须和节点名中的 @ 后面的地址一致。不一致时,内核会报 warning,但不会拒绝加载——这种隐晦问题最容易漏掉。
3.3 interrupts —— 你怎么通知我?
interrupts 描述设备的中断信息。它依赖父节点的 #interrupt-cells 来解析。最常见的是 ARM GIC(通用中断控制器),它用 3 个 cell:
interrupts = <中断类型 中断号 触发方式>;
中断类型:0 表示 SPI(共享外设中断),1 表示 PPI(私有外设中断)。
触发方式:1 是上升沿,2 是下降沿,4 是高电平,8 是低电平。
举个例子:
i2c@30000000 {
compatible = "snps,designware-i2c";
reg = <0x30000000 0x1000>;
interrupts = <0 45 4>;
// SPI 中断,中断号 45,高电平触发
};
我踩过一个坑:某款传感器芯片要求下降沿触发,但设备树里写成了上升沿。结果中断疯狂触发,CPU 占用率直接飙到 100%。后来用示波器一看,信号波形是下降沿,跟设备树配置完全反了。
避坑指南:我曾经在移植 BSP 时,发现中断号总是偏移了 32。后来才明白,有些 SoC 的 SPI 中断号是从 0 开始编号的,而 GIC 规范里 SPI 是从 32 开始。检查芯片手册里的中断映射表,比猜靠谱得多。
3.4 status —— 你开了吗?
status 控制设备是否启用。就这么几个值:
| 值 | 含义 |
|---|---|
| okay | 设备启用,驱动正常加载 |
| disabled | 设备禁用,驱动不加载 |
| reserved | 保留给固件使用,OS 不能碰 |
| fail | 设备检测失败,但节点保留 |
实际项目中,status 最常用的场景是:SoC 的 dtsi 文件里把所有外设默认 status = "disabled",板级 dts 里按需打开。
// imx6q.dtsi(SoC 级)
uart1: serial@2020000 {
status = "disabled";
};
// my-board.dts(板级)
&uart1 {
status = "okay";
pinctrl-0 = <&pinctrl_uart1>;
};
我见过有人把 okay 拼写成 ok,内核直接忽略,设备不工作。这种问题查驱动日志根本看不出来,得打开 CONFIG_OF_DEBUG 才能看到解析警告。
记住:status 只写 okay 或 disabled,别搞花活。其他值在内核里的支持程度参差不齐。
3.5 clocks —— 你用什么时钟?
clocks 和 clock-names 配合使用,指定设备需要的时钟源。格式:
clocks = <&clk_node1>, <&clk_node2>;
clock-names = "clk1", "clk2";
驱动通过 clock-names 来获取对应的时钟句柄,不用关心顺序。看个例子:
usdhc@2190000 {
compatible = "fsl,imx6q-usdhc";
reg = <0x02190000 0x4000>;
interrupts = <0 22 4>;
clocks = <&clks IMX6Q_CLK_USDHC1>,
<&clks IMX6Q_CLK_USDHC2>;
clock-names = "ipg", "ahb";
};
驱动里这样用:
clk_ipg = devm_clk_get(&pdev->dev, "ipg");
clk_ahb = devm_clk_get(&pdev->dev, "ahb");
我调试过一个 SD 卡驱动,发现时钟频率总是不对。查了半天,原来是 clock-names 里写的是 "ipg",但驱动里要的是 "per"。名字对不上,devm_clk_get 返回 NULL,驱动直接用了默认频率。
注意:clocks 属性的 cell 个数由时钟控制器的 #clock-cells 决定。如果 #clock-cells = <1>,每个时钟需要两个值:时钟控制器句柄 + 时钟索引。少写一个 cell,整个时钟树都会乱掉。
3.6 gpios —— 你怎么控制引脚?
gpios 描述设备使用的 GPIO 引脚。格式:
gpios = <&gpio_controller 引脚号 标志>;
标志位常用值:
0—— 高电平有效,默认输出GPIO_ACTIVE_LOW (1)—— 低电平有效GPIO_OPEN_DRAIN (16)—— 开漏输出GPIO_PULL_UP (32)—— 内部上拉
实际例子:
leds {
compatible = "gpio-leds";
led-red {
gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
label = "red-led";
};
};
这里 GPIO_ACTIVE_LOW 表示写 1 时灯灭,写 0 时灯亮。我遇到过硬件工程师把 LED 正极接 GPIO、负极接 VCC,结果设备树里写成了高电平有效,灯永远不亮。最后飞线才搞定。
我的经验:调试 GPIO 问题时,先看原理图确认引脚电平极性,再查设备树的标志位。很多“驱动不工作”的问题,其实是 GPIO 有效电平写反了。用 /sys/kernel/debug/gpio 可以实时查看引脚状态,非常方便。
3.7 属性之间的关系图
下面这张图总结了六个核心属性各自描述了什么,以及它们之间的协作关系:
3.8 综合示例:一个完整的 I2C 设备节点
把上面六个属性串起来,看一个完整的例子:
i2c@30000000 {
compatible = "snps,designware-i2c";
reg = <0x30000000 0x1000>;
interrupts = <0 45 4>;
clocks = <&clks I2C_CLK>;
clock-names = "i2c";
status = "okay";
temperature_sensor: sensor@48 {
compatible = "ti,tmp102";
reg = <0x48>;
interrupts = <0 60 4>;
gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
};
};
这个节点里:
- I2C 控制器本身用
compatible匹配驱动,reg定义寄存器基址,interrupts定义中断,clocks定义时钟,status启用它。 - 子节点(温度传感器)用
compatible匹配驱动,reg定义 I2C 地址,interrupts定义报警中断,gpios定义就绪引脚。
你看,不管多复杂的设备树,核心就是这几个属性来回组合。掌握了它们,读任何 dts 文件都不会觉得陌生。
总结一句话:设备树属性就是硬件的“身份证 + 地址簿 + 电话线 + 开关 + 资源清单”。把这六个搞明白,设备树就算入门了。