4. 设备树基础:设备树语法、节点属性、reg/interrupts属性解析
设备树(Device Tree)是嵌入式Linux系统中用于描述硬件拓扑结构和资源分配的关键机制。它以一种树形结构将CPU、内存、外设、中断控制器、GPIO控制器等硬件信息传递给内核,使得同一份内核镜像可以支持多种硬件平台。本章将深入解析设备树的语法、节点属性,并重点讲解reg和interrupts属性的定义与解析方法。
4.1 设备树语法基础
设备树源文件(.dts)采用树形层次结构,每个硬件单元对应一个节点(node),节点内包含若干属性(property)来描述其特性。基本语法规则如下:
- 节点定义:
node-name@unit-address { ... };,其中unit-address通常为寄存器基地址。 - 属性赋值:
property-name = <value>;或property-name = "string";。 - 标签(label):用于引用节点,如
uart0: serial@10000000 { ... };。 - 包含文件:
#include "soc.dtsi",用于复用通用硬件描述。
/dts-v1/;
/ {
model = "Gateway-Device";
compatible = "vendor,gateway-v1";
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges;
uart0: serial@10000000 {
compatible = "vendor,uart-ip";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
clock-frequency = <24000000>;
status = "okay";
};
};
};
上述代码中,uart0节点描述了基地址为0x10000000、大小为0x1000字节的UART控制器,中断号为33,时钟频率24MHz。
4.2 节点属性详解
设备树属性是描述硬件特性的键值对。下表列出了网关外设驱动移植中最常用的属性:
| 属性名 | 类型 | 说明 | 示例 |
|---|---|---|---|
compatible |
字符串列表 | 驱动匹配标识,内核通过此属性查找对应驱动 | "vendor,device" |
reg |
u32/u64数组 | 描述设备地址空间(基地址+长度) | <0x10000000 0x1000> |
interrupts |
u32数组 | 描述中断号、触发类型等信息 | <0 33 4> |
interrupt-parent |
phandle | 指定该设备连接的中断控制器节点 | <&gic> |
clocks |
phandle+时钟ID | 指定设备使用的时钟源 | <&clk_uart 0> |
status |
字符串 | 设备状态:"okay"、"disabled"等 |
"okay" |
#address-cells和#size-cells决定了reg属性中地址和长度的编码位数。对于32位系统,通常设为<1>;对于64位系统,设为<2>。
4.3 reg属性深度解析
reg属性用于描述设备在总线上的地址空间,其格式由父节点的#address-cells和#size-cells决定。每个reg条目由地址和长度两部分组成:
- 地址部分:占用
#address-cells个32位单元,表示基地址。 - 长度部分:占用
#size-cells个32位单元,表示地址空间大小。
对于内存映射外设,reg通常包含寄存器基地址和映射范围。对于I2C或SPI设备,reg则用于表示从设备地址。
ethernet@20000000 {
compatible = "vendor,gmac";
reg = <0x20000000 0x10000>, /* 寄存器区域 */
<0x30000000 0x2000>; /* DMA描述符区域 */
reg-names = "mac_regs", "dma_desc";
};
内核驱动通过platform_get_resource(pdev, IORESOURCE_MEM, index)函数获取reg属性中定义的地址资源,其中index对应条目序号(从0开始)。
4.4 interrupts属性深度解析
interrupts属性描述设备的中断请求线,其格式由中断控制器节点的#interrupt-cells属性定义。常见的中断控制器类型及编码规则如下:
| 中断控制器类型 | #interrupt-cells | 编码格式 | 说明 |
|---|---|---|---|
| GIC(通用中断控制器) | 3 | <中断类型 中断号 触发标志> |
类型0=SPI共享外设中断,1=PPI私有外设中断 |
| 简单中断控制器 | 1 | <中断号> |
仅包含中断号 |
| GPIO中断控制器 | 2 | <GPIO号 触发标志> |
触发标志:1=上升沿,2=下降沿,4=高电平,8=低电平 |
对于ARM GIC,中断类型0表示SPI(共享外设中断),中断号范围为32~1019;类型1表示PPI(私有外设中断),中断号范围为16~31。触发标志通常使用IRQ_TYPE_*宏定义:
4:上升沿触发(IRQ_TYPE_EDGE_RISING)8:下降沿触发(IRQ_TYPE_EDGE_FALLING)1:高电平触发(IRQ_TYPE_LEVEL_HIGH)2:低电平触发(IRQ_TYPE_LEVEL_LOW)
platform_get_irq(pdev, index)获取中断号,然后使用request_irq()或devm_request_irq()注册中断处理函数。
4.5 设备树中断映射流程
下图展示了从设备树节点到内核中断处理函数的完整映射流程:
上述流程中,内核首先解析设备树节点的interrupts属性,通过中断控制器驱动完成硬件中断号到Linux虚拟中断号的映射,最终驱动开发者通过标准API注册中断处理函数。
4.6 驱动兼容性测试中的设备树验证
在网关外设驱动移植过程中,设备树配置的正确性直接影响驱动兼容性。以下是设备树验证的关键检查点:
| 检查项 | 验证方法 | 常见问题 |
|---|---|---|
compatible匹配 |
检查驱动中of_match_table是否包含设备树中的字符串 |
字符串拼写错误、厂商前缀不一致 |
reg地址范围 |
使用cat /proc/iomem查看资源分配 |
地址重叠、长度不足导致映射失败 |
interrupts配置 |
使用cat /proc/interrupts确认中断号 |
中断类型错误、触发标志不匹配 |
interrupt-parent引用 |
检查phandle是否指向有效的中断控制器节点 | 缺少中断父节点、phandle值错误 |
status状态 |
确认设备节点状态为"okay" |
默认"disabled"导致设备未使能 |
earlycon和dtoc调试参数,可以打印设备树解析过程中的错误信息。使用dtc -I dtb -O dts命令反编译设备树二进制文件,验证实际加载的设备树内容。
4.7 设备树节点与驱动绑定关系
下图展示了设备树节点与内核驱动模型之间的绑定关系:
驱动开发者需要在of_match_table中声明支持的设备树compatible字符串列表。当内核遍历设备树时,会逐一匹配节点与驱动,匹配成功后调用驱动的probe函数,驱动在probe中通过platform_get_resource和platform_get_irq解析reg和interrupts属性。
4.8 实战案例:网关UART驱动设备树配置
以网关设备中常用的UART外设驱动移植为例,展示完整的设备树配置与驱动解析过程:
&uart0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart0_pins>;
clock-frequency = <24000000>;
current-speed = <115200>;
auto-flow-control;
};
static int uart_probe(struct platform_device *pdev)
{
struct resource *res;
struct uart_port *port;
int irq, ret;
/* 获取reg属性定义的寄存器基地址 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENXIO;
/* 获取interrupts属性定义的中断号 */
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
/* 获取clock-frequency属性 */
of_property_read_u32(pdev->dev.of_node,
"clock-frequency", &clk_freq);
/* 注册UART端口 */
port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
port->mapbase = res->start;
port->irq = irq;
port->uartclk = clk_freq;
ret = uart_add_one_port(&uart_driver, port);
if (ret)
return ret;
platform_set_drvdata(pdev, port);
return 0;
}
clock-frequency与实际晶振频率一致,auto-flow-control属性是否被驱动正确解析,以及中断触发类型(电平/边沿)是否与硬件设计匹配。