3. 设备树常用属性:compatible、reg、interrupts、status等属性的含义与用法
好,咱们今天聊聊设备树里最常用的一批属性。说实话,我刚接触设备树那会儿,看着满屏的compatible、reg、interrupts,心里也犯嘀咕——这些到底怎么用?后来踩了不少坑,才慢慢摸清楚门道。
你想想看,设备树本质上就是个描述硬件的数据结构。那描述硬件,总得告诉内核几个关键信息吧:你是谁?你在哪?你怎么通知我?你现在能不能用?嗯,这四个问题,正好对应我们今天要讲的四个属性。
3.1 compatible:你是谁?
compatible属性,说白了就是设备的身份证。内核通过它来匹配对应的驱动。
它的格式很简单:
compatible = "厂商,型号";
举个例子:
uart0: serial@10000000 {
compatible = "fsl,imx6q-uart", "fsl,imx21-uart";
reg = <0x10000000 0x4000>;
interrupts = <0 26 4>;
status = "okay";
};
这里有个细节我特别想强调:兼容列表是有优先级的。内核会从左到右依次尝试匹配驱动。第一个匹配上了就用第一个,匹配不上再试第二个。
重要原则:越具体的型号放前面,越通用的放后面。这样既能精确匹配,又能保证兼容性。
我在项目中遇到过一件事:有个同事把compatible写反了顺序,结果内核总是匹配到通用驱动,导致一些特殊功能没法用。查了两天才发现是顺序问题。嗯,从那以后我每次写compatible都会多看一眼顺序。
3.2 reg:你在哪?
reg属性描述设备的地址空间信息。它告诉内核:你的寄存器在哪个地址范围。
格式是这样的:
reg = <地址1 长度1 地址2 长度2 ...>;
每个地址+长度对,就是一个地址区域。一个设备可以有多个地址区域。
看个实际例子:
i2c@21a0000 {
compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <0 36 4>;
status = "okay";
};
这里reg = <0x021a0000 0x4000>表示:I2C控制器的寄存器从0x021a0000开始,占用0x4000字节的空间。
小技巧:我习惯在写reg之前,先翻翻芯片手册,确认寄存器基地址和范围。千万别凭感觉写,我见过有人把长度写错,导致驱动访问到别人的地址空间,系统直接崩溃。
你可能会问:为什么要有多个地址区域?举个例子,一个网卡设备,可能有控制寄存器区、数据缓冲区区、配置寄存器区。每个区域独立描述,驱动用起来更清晰。
3.3 interrupts:你怎么通知我?
设备要跟CPU通信,总不能一直轮询吧?太浪费CPU了。所以就有了中断机制。interrupts属性就是描述设备用哪个中断号、怎么触发中断的。
格式:
interrupts = <中断号 触发类型>;
触发类型常见的有:
| 值 | 含义 |
|---|---|
| 0 | 高电平触发 |
| 1 | 低电平触发 |
| 2 | 上升沿触发 |
| 3 | 下降沿触发 |
| 4 | 高电平触发(带中断控制器) |
看个例子:
gpio@20a0000 {
compatible = "fsl,imx6q-gpio", "fsl,imx21-gpio";
reg = <0x020a0000 0x4000>;
interrupts = <0 66 4>;
status = "okay";
};
这里interrupts = <0 66 4>表示:中断号66,高电平触发。第一个0是中断控制器编号(通常为0),66是中断号,4是触发类型。
注意:中断号的编号方式因芯片而异。有的芯片用物理中断号,有的用逻辑中断号。我建议你写之前,先确认一下芯片手册里的中断映射表。我曾经因为搞混了中断号,调试了整整一个下午。
3.4 status:你现在能用吗?
status属性控制设备的启用状态。它只有几个取值:
| 值 | 含义 |
|---|---|
| "okay" | 设备可用,驱动会加载 |
| "disabled" | 设备禁用,驱动不会加载 |
| "reserved" | 保留,通常用于特殊场景 |
| "fail" | 设备检测失败 |
这个属性在板级适配时特别有用。比如一个芯片支持两个I2C控制器,但你的板子上只用了I2C1,那I2C2就可以设成disabled:
&i2c1 {
status = "okay";
};
&i2c2 {
status = "disabled";
};
最佳实践:我个人的习惯是,在dtsi(芯片级设备树)里把所有外设都设成disabled,然后在dts(板级设备树)里按需打开。这样既清晰又安全,不会因为忘记关闭某个外设而出问题。
3.5 综合示例:一个完整的设备节点
好了,我们把上面四个属性串起来,看一个完整的例子:
/* 一个完整的UART设备节点 */
uart1: serial@21e8000 {
compatible = "fsl,imx6q-uart", "fsl,imx21-uart";
reg = <0x021e8000 0x4000>;
interrupts = <0 27 4>;
status = "okay";
/* 其他可选属性 */
clock-frequency = <24000000>;
fsl,uart-has-rtscts;
};
这个节点告诉内核:
- 我是飞思卡尔i.MX6Q的UART(兼容i.MX21的UART驱动)
- 我的寄存器在0x021e8000,占16KB空间
- 我用中断号27,高电平触发
- 我现在是启用状态
避坑指南:我曾经在调试一个项目时,发现UART驱动死活不工作。查了半天,原来是status写成了"ok"而不是"okay"。内核只认"okay",少一个字母都不行。这种小错误,真的让人抓狂。
3.6 其他常用属性
除了上面四个,还有几个属性也经常用到:
- clocks:描述设备的时钟源
- resets:描述设备的复位信号
- pinctrl:描述设备的引脚复用配置
- dmas:描述设备的DMA通道
这些属性我们后面会专门讲,今天先有个印象就行。
3.7 小结
嗯,今天的内容就到这里。总结一下:
compatible:设备身份证,驱动匹配的关键reg:设备地址,告诉内核寄存器在哪interrupts:中断配置,设备怎么通知CPUstatus:设备状态,控制启用还是禁用
这四个属性,是设备树里最基础也最重要的。你写设备树的时候,每个节点基本都离不开它们。下次我们聊聊更高级的属性,比如clocks和pinctrl,这些在实际项目中更常用。
有什么问题,欢迎随时交流。咱们下节课见。