4. 设备树基础:设备树语法、节点属性、reg/interrupts属性解析

设备树(Device Tree)是嵌入式Linux系统中用于描述硬件拓扑结构和资源分配的关键机制。它以一种树形结构将CPU、内存、外设、中断控制器、GPIO控制器等硬件信息传递给内核,使得同一份内核镜像可以支持多种硬件平台。本章将深入解析设备树的语法、节点属性,并重点讲解reg和interrupts属性的定义与解析方法。

核心概念:设备树将硬件描述与内核代码分离。驱动开发者只需修改设备树源文件(.dts),无需重新编译内核即可适配新硬件,极大提升了驱动移植效率。

4.1 设备树语法基础

设备树源文件(.dts)采用树形层次结构,每个硬件单元对应一个节点(node),节点内包含若干属性(property)来描述其特性。基本语法规则如下:

  • 节点定义:node-name@unit-address { ... };,其中unit-address通常为寄存器基地址。
  • 属性赋值:property-name = <value>; 或 property-name = "string";。
  • 标签(label):用于引用节点,如 uart0: serial@10000000 { ... };。
  • 包含文件:#include "soc.dtsi",用于复用通用硬件描述。
示例:一个简单的设备树节点描述UART外设。
/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 = <0 33 4> 中断控制器(GIC) 解析中断类型、中断号、触发标志 内核中断子系统 irq_domain映射 → 虚拟中断号分配 platform驱动 platform_get_irq() 获取中断号 中断处理函数 request_irq(irq, handler, flags, name, dev) 关键函数 • of_irq_parse_one() • irq_create_of_mapping() • platform_get_irq()

上述流程中,内核首先解析设备树节点的interrupts属性,通过中断控制器驱动完成硬件中断号到Linux虚拟中断号的映射,最终驱动开发者通过标准API注册中断处理函数。

4.6 驱动兼容性测试中的设备树验证

在网关外设驱动移植过程中,设备树配置的正确性直接影响驱动兼容性。以下是设备树验证的关键检查点:

检查项 验证方法 常见问题
compatible匹配 检查驱动中of_match_table是否包含设备树中的字符串 字符串拼写错误、厂商前缀不一致
reg地址范围 使用cat /proc/iomem查看资源分配 地址重叠、长度不足导致映射失败
interrupts配置 使用cat /proc/interrupts确认中断号 中断类型错误、触发标志不匹配
interrupt-parent引用 检查phandle是否指向有效的中断控制器节点 缺少中断父节点、phandle值错误
status状态 确认设备节点状态为"okay" 默认"disabled"导致设备未使能
调试技巧:在Linux内核启动时添加earlycon和dtoc调试参数,可以打印设备树解析过程中的错误信息。使用dtc -I dtb -O dts命令反编译设备树二进制文件,验证实际加载的设备树内容。

4.7 设备树节点与驱动绑定关系

下图展示了设备树节点与内核驱动模型之间的绑定关系:

设备树节点与驱动绑定关系 设备树(Device Tree) 根节点 "/" soc 节点 uart0: serial@10000000 compatible = "vendor,uart-ip" 内核驱动模型 platform_driver of_match_table .compatible = "vendor,uart-ip" probe() 回调函数 解析reg/interrupts/clocks compatible 匹配 内核通过compatible属性匹配设备树节点与驱动,匹配成功后调用驱动的probe函数

驱动开发者需要在of_match_table中声明支持的设备树compatible字符串列表。当内核遍历设备树时,会逐一匹配节点与驱动,匹配成功后调用驱动的probe函数,驱动在probe中通过platform_get_resource和platform_get_irq解析reg和interrupts属性。

4.8 实战案例:网关UART驱动设备树配置

以网关设备中常用的UART外设驱动移植为例,展示完整的设备树配置与驱动解析过程:

设备树配置(.dts文件):
&uart0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&uart0_pins>;
    clock-frequency = <24000000>;
    current-speed = <115200>;
    auto-flow-control;
};
驱动解析代码(probe函数片段):
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属性是否被驱动正确解析,以及中断触发类型(电平/边沿)是否与硬件设计匹配。