3. 设备树常用属性:compatible、reg、interrupts、status等核心属性详解
好,咱们今天聊聊设备树里最常用的几个属性。说实话,我刚开始接触设备树时,看着那一堆属性名也挺懵的。但用久了你会发现,真正天天打交道的,其实就那么几个。今天我就把这几张「王牌」给你讲透。
3.1 compatible —— 灵魂匹配属性
这个属性,说白了就是告诉内核:「我是谁」。内核通过它来找到对应的驱动。
它的格式很简单:"厂商,型号"。比如:
compatible = "ti,am335x-uart", "ns16550";
这里有个细节我特别想强调:顺序很重要。内核匹配驱动时,是从左往右匹配的。第一个最精确,后面的作为备选。我在项目中遇到过,有人把通用型号写在前面,结果内核永远匹配不到精确驱动,折腾了半天。
核心要点:
- 第一个值要尽量精确,包含厂商和具体型号
- 后面的值可以写兼容的通用型号
- 匹配失败时,内核会依次尝试后面的值
我的习惯:写compatible时,我通常会先查芯片手册,确认官方推荐的字符串。实在找不到,就参考Linux内核里已有的写法,保持风格一致。
3.2 reg —— 地址与空间描述
reg属性描述设备的地址空间信息。它由一组组的地址和长度组成。
看个例子:
uart0: serial@44e09000 {
compatible = "ti,am335x-uart", "ns16550";
reg = <0x44e09000 0x2000>;
};
这里0x44e09000是基地址,0x2000是地址范围。嗯,要注意的是,这个长度不是随便写的。我见过有人把长度写成0x1000,结果驱动访问到后面寄存器时直接崩溃。后来一查,原来这个UART模块实际占用了0x2000的空间。
如果设备有多个地址段,可以这样写:
reg = <0x44e09000 0x2000>, <0x44e0b000 0x1000>;
每个地址段对应一个#address-cells和#size-cells组合。这两个属性决定了地址和长度的位数。
| 属性 | 含义 | 常见值 |
|---|---|---|
| #address-cells | 地址占几个32位单元 | 1(32位地址)或2(64位地址) |
| #size-cells | 长度占几个32位单元 | 1或0(无长度信息) |
避坑指南:我曾经在移植U-Boot时,发现某个外设怎么都访问不到。查了两天才发现,是父节点的#address-cells和#size-cells没配对。子节点的reg解析完全错了。记住:子节点的reg格式,由父节点的这两个属性决定。
3.3 interrupts —— 中断配置
中断属性描述设备使用的中断资源。它通常包含中断控制器、中断号和触发类型。
基本写法:
interrupts = <0 56 4>;
这三个数字分别代表:
- 第一个:中断类型(0=SPI共享中断,1=PPI私有中断)
- 第二个:中断号
- 第三个:触发类型(1=上升沿,2=下降沿,4=高电平,8=低电平)
你想想看,为什么要有触发类型?因为不同的外设,中断信号的行为不一样。比如按键按下是电平变化,而DMA传输完成是脉冲信号。
如果设备有多个中断,可以这样:
interrupts = <0 56 4>, <0 57 4>;
每个中断对应一个interrupt-names属性中的名字:
interrupt-names = "rx", "tx";
我的经验:中断号千万别搞错。我建议你直接看芯片手册里的中断向量表,或者参考官方设备树文件。我曾经因为中断号写错一位,导致系统一运行就死机,排查了整整一个下午。
3.4 status —— 设备状态控制
status属性控制设备是否启用。它虽然简单,但用好了能省不少事。
常用值:
okay:设备启用disabled:设备禁用reserved:保留,不用于系统fail:设备检测失败
实际项目中,我经常这样用:
&uart0 {
status = "okay";
pinctrl-0 = &uart0_pins;
pinctrl-names = "default";
};
为什么要在板级文件里单独写status?因为SoC级设备树里,很多外设默认是disabled的。到了具体的板子上,再根据需要打开。这样设计很灵活,你想想看,一个SoC可能用在几十种板子上,每个板子用的外设不一样,总不能改SoC级文件吧?
实用技巧:调试时,如果某个外设不工作,先检查status是不是"okay"。我遇到过好几次,明明驱动没问题,就是status忘了改,白白浪费了时间。
3.5 其他常用属性
除了上面四个,还有几个属性我经常用到:
pinctrl-*:引脚复用配置。这个在嵌入式开发里太重要了。一个引脚可能同时是GPIO、UART、I2C,你得告诉系统当前用哪个功能。
pinctrl-0 = &uart0_pins;
pinctrl-names = "default";
clocks:时钟配置。很多外设需要时钟才能工作,这个属性指定了时钟源和频率。
clocks = <&clk_uart0 0>;
dmas:DMA配置。对于需要高速数据传输的设备,DMA几乎是标配。
dmas = <&edma 0 2>, <&edma 1 2>;
dma-names = "rx", "tx";
我的建议:刚开始学设备树时,别想着把所有属性都记住。先把compatible、reg、interrupts、status这四个吃透,其他的用到时再查。我到现在写设备树,也经常翻参考手册。
3.6 实战:组合使用这些属性
最后,咱们看一个完整的例子。这是一个I2C控制器在设备树中的描述:
i2c0: i2c@44e0b000 {
compatible = "ti,am335x-i2c", "ti,omap4-i2c";
reg = <0x44e0b000 0x1000>;
interrupts = <0 70 4>;
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
clocks = <&clk_i2c0>;
pinctrl-0 = <&i2c0_pins>;
pinctrl-names = "default";
};
然后在板级文件里:
&i2c0 {
status = "okay";
clock-frequency = <400000>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
};
你看,每个属性各司其职。compatible告诉内核用哪个驱动,reg告诉驱动寄存器在哪,interrupts告诉中断怎么用,status控制设备开关。配合起来,一个完整的设备描述就出来了。
嗯,今天就先聊到这。这些属性看着简单,但用好了真能省不少事。下次咱们聊聊设备树里更高级的用法,比如中断映射和时钟管理。