3. 设备树核心属性:compatible、reg、interrupts、clocks、status、model、device_type

设备树里属性很多,但真正天天打交道的,其实就这几个。我刚开始学设备树那会儿,看着几百行的 .dts 文件头皮发麻,后来发现只要把这七个属性吃透,百分之八十的节点都能看懂。今天咱们一个一个过,每个我都会讲讲我在项目里踩过的坑。

3.1 compatible —— 驱动匹配的身份证

compatible 是设备树里最重要的属性,没有之一。它的作用就一句话:告诉内核,这个设备该用哪个驱动。

compatible = "vendor,device";

格式是「厂商,设备名」。比如:

compatible = "ti,am335x-uart", "ns16550";

这里有个细节:字符串可以写多个,内核会按顺序匹配。第一个最精确,后面的作为 fallback。我在做 AM335x 项目时,就靠这个 fallback 机制,让新板子直接复用内核自带的 ns16550 驱动,省了不少事。

个人习惯:我写 compatible 时,一定会把最精确的厂商型号放第一位,通用型号放后面。这样既保证新板子能用,又给后续优化留了空间。

3.2 reg —— 告诉驱动你的地盘在哪

reg 描述设备的地址空间。说白了,就是告诉驱动:「你的寄存器从哪个地址开始,占多大地方」。

reg = <0x48020000 0x1000>;

第一个数字是起始地址,第二个是长度。如果是 64 位地址,用两个 <>:

reg = <0x00000000 0x48020000 0x00000000 0x1000>;

嗯,这里要注意:#address-cells 和 #size-cells 决定了 reg 里每个字段占几个 cell。父节点定义这两个属性,子节点按规矩写。我见过有人把这两个属性写反,结果驱动读到的地址全是错的,查了两天才发现。

#address-cells #size-cells reg 格式
1 1 <addr size>
2 1 <addr_hi addr_lo size>
1 0 <addr>(无大小)

3.3 interrupts —— 中断怎么连

中断描述比 reg 稍微复杂一点,因为它涉及中断控制器。先看个例子:

interrupts = <0 31 4>;
interrupt-parent = <&gic>;

这里三个数字分别代表:中断类型(0=SPI,1=PPI)、中断号、触发方式(1=上升沿,4=高电平)。

你想想看,如果设备树里不写 interrupt-parent,内核怎么知道这个中断发给谁?它会往父节点找,直到找到 interrupt-controller 属性。我建议每个设备节点都显式写上 interrupt-parent,别偷懒。

我曾经踩过的坑:有一次把触发方式写成了 3(既不是沿触发也不是电平触发),结果中断要么不触发,要么疯狂触发。后来查手册才发现,触发方式的值必须严格按芯片手册来,不能自己想当然。

3.4 clocks —— 时钟的父子关系

时钟属性描述设备用哪些时钟源。格式类似中断:

clocks = <&clk_mcu 0>, <&clk_per 1>;
clock-names = "mcu", "per";

clocks 里每个条目是一个 phandle 加一个索引,clock-names 给每个时钟起个名字。驱动里通过名字找时钟,比用索引更可读。

我在做 FPGA 加速卡驱动时,设备树里挂了三个时钟:一个给 AXI 总线,一个给逻辑核心,一个给 DDR 控制器。用 clock-names 区分,驱动代码里 devm_clk_get(dev, "axi") 一目了然。

3.5 status —— 设备的开关

status 只有几个值,但用错了会出大问题:

值 含义
okay 设备启用,驱动正常加载
disabled 设备禁用,驱动不加载
reserved 保留给固件,OS 别碰
fail 设备检测失败,但节点保留

最常见的用法是在 .dtsi 里把节点设为 disabled,然后在板级 .dts 里改成 okay。比如:

// am335x.dtsi
uart0: serial@48020000 {
    status = "disabled";
};

// myboard.dts
&uart0 {
    status = "okay";
};

我个人习惯:所有外设默认 disabled,用到的才打开。这样既安全又清晰。

3.6 model —— 板子叫什么名字

model 就是个字符串,描述硬件平台名称。比如:

model = "TI AM335x BeagleBone Black";

它不影响驱动匹配,但内核启动时会打印出来,方便你确认当前跑的是哪个板子的设备树。我在调试时经常先看 dmesg 里的 model 信息,确认设备树有没有加载对。

3.7 device_type —— 老古董,但还得知道

device_type 是 PowerPC 时代留下的遗产,ARM 平台基本不用了。唯一还在用的地方是 cpu 节点:

cpu@0 {
    device_type = "cpu";
    compatible = "arm,cortex-a9";
};

新写的设备树里,除了 CPU 节点,其他地方别用这个属性。我在 review 代码时看到有人给 i2c 设备加 device_type,直接打回去重写。

3.8 核心属性关系图

下面这张图总结了七个属性各自负责什么,以及它们和驱动之间的协作关系:

设备树核心属性与驱动协作关系 设备树节点 (描述硬件信息) compatible reg interrupts clocks status model 内核驱动 (匹配、初始化、操作) 硬件设备 (寄存器、中断、时钟) compatible 匹配驱动,reg/interrupts/clocks 描述硬件资源,status 控制开关,model/device_type 提供辅助信息

3.9 实战建议

写设备树时,我建议你按这个顺序检查每个节点:

  1. 先写 compatible —— 确定驱动能不能认出来
  2. 再写 reg —— 地址范围对不对,和芯片手册一致吗
  3. 然后 interrupts —— 中断号、触发方式、中断控制器都配齐了吗
  4. 接着 clocks —— 时钟源和名字别写反了
  5. 最后 status —— 确认是 okay 还是 disabled
核心原则:设备树是给内核看的硬件描述文档。你写得越准确,驱动出问题的概率就越低。我见过太多「设备树随便写写,驱动里再改」的案例,最后都是加班查 bug 的命。