设备树节点精讲:compatible / reg / 中断属性配置
作者:黒漂技术佬 | 系列:Linux内核配置与移植
引言
上一篇把设备树的来龙去脉讲清楚了,这一篇我们深入到节点内部,来看看设备树到底是怎么"描述"一个硬件的。设备树看起来像 JSON,但它的语义远比 JSON 更严格——每一个属性名都有固定的含义,写错一个字,驱动就找不到设备。本文把最核心的三个属性掰开揉碎来讲:compatible(谁是谁)、reg(在哪)、interrupts(怎么通知)。
一、设备树节点基本结构
一个完整的设备树节点长这样:
node-name@unit-address { property-name = <value>; compatible = "vendor,device"; reg = <address size>; status = "okay"; sub-node { /* 子节点 */ }; };节点命名规则:
node-name:节点类型名称(如uart、i2c、gpio)@unit-address:可选的地址后缀,值需与reg属性中的首地址一致&label:引用其他节点的标签
属性值的四种基本格式:
| 格式 | 示例 | 说明 |
|---|---|---|
整数数组< > | reg = <0x1000 0x100>; | 32位大端整数序列 |
字符串" " | compatible = "arm,pl011"; | 普通字符串 |
字节数组[ ] | mac-address = [00 11 22 33 44 55]; | 原始十六进制字节 |
组合引用<& > | clocks = <&clkc 24>; | 引用其他节点 phandle |
二、compatible 属性——驱动的"身份证"
2.1 定义与格式
compatible是设备树中最重要的属性,没有之一。它用一个字符串列表告诉内核"这个设备是什么",驱动通过它来匹配设备。
uart0: serial@101f1000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x101f1000 0x1000>; };格式规范:"manufacturer,model"
- 第一部分是厂商名(arm、ti、nxp、st 等)
- 第二部分是设备型号(pl011、omap-uart、stm32-usart 等)
- 可以用多个字符串,从左到右匹配,越具体的放前面
- 如果第一个匹配不到,内核会尝试下一个
2.2 驱动侧如何匹配
在驱动代码里,compatible对应这样的匹配表:
// drivers/tty/serial/amba-pl011.cstaticconststructof_device_idsbsa_uart_of_match[]={{.compatible="arm,sbsa-uart"},{.compatible="arm,pl011"},{/* sentinel */},};MODULE_DEVICE_TABLE(of,sbsa_uart_of_match);staticstructplatform_driverpl011_driver={.driver={.name="uart-pl011",.of_match_table=sbsa_uart_of_match,// ← 在这里匹配 DTS 中的 compatible},};匹配流程:
DTS: compatible = "ti,am3352-uart", "ns16550a"; ↓ 内核: of_match_table 逐个对比 "ti,am3352-uart" 匹配? → YES → 绑定该驱动 "ns16550a" 匹配? → (不再尝试)2.3 常见 compatible 示例
| 设备类型 | compatible 字符串示例 |
|---|---|
| ARM GIC 中断控制器 | "arm,gic-400" |
| PL011 串口 | "arm,pl011" |
| STM32 USART | "st,stm32-usart" |
| I2C 控制器 | "nxp,lpc1788-i2c" |
| GPIO 控制器 | "ti,omap4-gpio" |
| 以太网卡 | "smsc,lan9115" |
| 温度传感器 | "nxp,lm75a" |
| SD卡控制器 | "rockchip,rk3288-dw-mshc" |
三、reg 属性——寄存器的"门牌号"
3.1 基本格式
reg描述了设备的寄存器或内存地址范围:
reg = <起始地址1 大小1 起始地址2 大小2 ...>;格式为成对的(address, length)。具体的含义由父节点的#address-cells和#size-cells决定。
3.2 address-cells 和 size-cells
这两个属性定义在父节点上,告诉解析器reg中一个地址或长度占几个 32 位字:
soc { #address-cells = <1>; /* 地址用1个32位数表示 */ #size-cells = <1>; /* 大小用1个32位数表示 */ uart0: serial@40002000 { compatible = "arm,pl011"; reg = <0x40002000 0x1000>; /* ↑ 地址 ↑ 大小 (4KB) */ }; };各种组合示例:
#address-cells | #size-cells | reg 示例 | 适用场景 |
|---|---|---|---|
| 1 | 1 | <0x4000 0x100> | 32位处理器外设(最常见) |
| 1 | 0 | <0x123> | 只有地址,没有大小(如 I2C 设备) |
| 2 | 1 | <0x0 0x4000 0x100> | 64位地址空间 |
| 2 | 2 | <0x0 0x4000 0x0 0x100> | 64位地址 + 64位大小 |
3.3 不同类型总线上的 reg
内存映射外设(MMIO):
&amba { #address-cells = <1>; #size-cells = <1>; uart@101f1000 { reg = <0x101f1000 0x1000>; /* 基地址 + 寄存器范围 */ }; };I2C 从设备:
&i2c0 { #address-cells = <1>; #size-cells = <0>; /* I2C 设备没有地址空间概念 */ eeprom@50 { /* @50 表示 I2C 从设备地址 0x50 */ compatible = "atmel,24c02"; reg = <0x50>; /* 这里的 reg 是 I2C 地址,不是 MMIO */ }; };SPI 从设备:
&spi0 { #address-cells = <1>; #size-cells = <0>; flash@0 { /* @0 表示 SPI 片选 CS0 */ compatible = "spi-nand"; reg = <0>; /* 这里的 reg 是片选编号 */ }; };四、interrupts 属性——硬件如何"喊你"
4.1 基本格式
interrupts描述了设备使用的中断信息:
uart@40002000 { compatible = "arm,pl011"; reg = <0x40002000 0x1000>; interrupts = <0 12 4>; };这里<0 12 4>的含义取决于中断控制器的绑定规范。对于 ARM GIC 中断控制器:
| 字段 | 含义 | 值 0 12 4 的解释 |
|---|---|---|
| 第1个值 | 中断类型 | 0 = SPI(共享外设中断) |
| 第2个值 | 中断号 | 12 = 第12号中断 |
| 第3个值 | 触发方式 | 4 = 高电平触发 |
GIC 中断触发方式编码:
IRQ_TYPE_NONE = 0 // 未定义 IRQ_TYPE_EDGE_RISING = 1 // 上升沿触发 IRQ_TYPE_EDGE_FALLING = 2 // 下降沿触发 IRQ_TYPE_EDGE_BOTH = 3 // 双边沿触发 IRQ_TYPE_LEVEL_HIGH = 4 // 高电平触发 IRQ_TYPE_LEVEL_LOW = 8 // 低电平触发4.2 interrupt-parent 和 interrupts-extended
当设备的中断控制器不是它的直接父节点时,需要显式指定:
/* 方式一:单独指定 interrupt-parent */ i2c@40005000 { compatible = "vendor,i2c"; reg = <0x40005000 0x1000>; interrupt-parent = <&intc>; /* 指定中断控制器 */ interrupts = <0 21 4>; /* SPI 21, 高电平 */ }; /* 方式二:interrupts-extended 一步到位 */ i2c@40005000 { compatible = "vendor,i2c"; reg = <0x40005000 0x1000>; interrupts-extended = <&intc 0 21 4>; /* 一行搞定 */ };4.3 中断控制器的固定定义(interrupt-controller)
中断控制器自身在设备树中也有特定的声明:
intc: interrupt-controller@1e001000 { compatible = "arm,cortex-a9-gic"; reg = <0x1e001000 0x1000>, <0x1e000100 0x100>; #interrupt-cells = <3>; /* interrupts 属性每个单元有3个数 */ interrupt-controller; /* 宣告:我是中断控制器 */ };interrupt-controller:声明这是一个中断控制器(空属性,有就表示是)#interrupt-cells:声明引用该控制器时,interrupts属性中每个中断用几个整数来描述(GIC 是 3)
五、其他常用属性速查
| 属性 | 作用 | 示例 |
|---|---|---|
reg-names | 给 reg 中的地址区域起名字 | reg-names = "base", "alias"; |
clocks | 指定设备使用的时钟源 | clocks = <&clkc 15>; |
clock-names | 给时钟起名字 | clock-names = "apb_pclk"; |
dmas | 指定 DMA 通道 | dmas = <&dmac 0 2>; |
pinctrl-0 | 引脚复用配置 | pinctrl-0 = <&uart0_pins>; |
gpios | GPIO 引脚引用 | gpios = <&gpio0 5 0>; |
status | 设备状态 | "okay"/"disabled"/"reserved" |
phandle | 节点唯一标识(通常自动生成) | DTC 自动为有 label 的节点生成 |
status 的取值含义:
| 值 | 含义 |
|---|---|
"okay" | 设备可用,正常初始化 |
"disabled" | 设备不可用(硬件存在但软件不用) |
"reserved" | 设备预留(驱动不配置,但不要更改寄存器) |
"fail" | 设备探测失败 |
六、实操:写一个完整的设备节点
综合以上知识,下面是 STM32 板卡上一个温度传感器的完整设备树节点:
&i2c1 { #address-cells = <1>; #size-cells = <0>; status = "okay"; lm75: temperature-sensor@48 { compatible = "nxp,lm75a"; reg = <0x48>; /* I2C 从设备地址 */ status = "okay"; interrupts-extended = <&gpiof 3 IRQ_TYPE_EDGE_FALLING>; /* ↑PF3引脚 下降沿触发 */ #thermal-sensor-cells = <0>; pinctrl-0 = <&lm75_int_pin>; /* 引脚复用 */ }; };逐行解读:
&i2c1—— 引用 SoC 级别定义好的 I2C1 控制器#address-cells = <1>—— I2C 设备地址用一个整数#size-cells = <0>—— I2C 没有地址空间@48—— 设备 I2C 地址是 0x48(传感器硬连接的地址脚决定)compatible = "nxp,lm75a"—— 匹配内核的 lm75 驱动reg = <0x48>—— 确认 I2C 地址(要和 @ 后面的值一致)interrupts-extended—— 温度超限时通过 PF3 引脚触发中断pinctrl-0—— 指定 PF3 的引脚复用功能
总结
设备树的三个核心属性记住这个类比:compatible 是身份证(告诉内核"我是谁"),reg 是门牌号(告诉内核"我在哪"),interrupts 是电话号码(告诉内核"怎么找我")。掌握了这三个,再加上#address-cells和#size-cells这对规则说明书,你就能读写 80% 的设备树节点了。