4. 设备树中的地址编码:reg属性、#address-cells、#size-cells、ranges映射

设备树(Device Tree)是描述硬件拓扑与资源分布的核心数据结构。在嵌入式Linux驱动开发中,地址编码是设备树与驱动之间最关键的接口之一。本章深入解析 reg 属性、#address-cells、#size-cells 以及 ranges 映射的语义与用法,帮助开发者准确描述设备地址空间,确保platform总线能够正确匹配与访问硬件资源。

4.1 地址编码基础:address-cells 与 size-cells

设备树使用 #address-cells 和 #size-cells 两个属性来定义地址和长度字段的字宽(以32位字为单位)。这两个属性通常出现在总线节点或桥接节点中,影响其子节点的 reg 属性解析方式。

属性名 含义 常见取值 说明
#address-cells 地址字段占用的32位字数量 1 或 2 1表示32位地址,2表示64位地址
#size-cells 长度字段占用的32位字数量 0 或 1 0表示无长度(如寄存器),1表示32位长度
关键规则:子节点的 reg 属性中,地址部分占用 #address-cells 个32位字,长度部分占用 #size-cells 个32位字。父节点定义规则,子节点遵循规则。
// 示例:32位地址总线,带长度
soc {
    #address-cells = <1>;
    #size-cells = <1>;

    uart0: serial@101f0000 {
        reg = <0x101f0000 0x1000>;
        // 地址: 0x101f0000 (1个字)
        // 长度: 0x1000 (1个字)
    };
};

// 示例:64位地址总线,无长度(仅寄存器基址)
pcie-controller {
    #address-cells = <2>;
    #size-cells = <0>;

    pcie@0 {
        reg = <0x0 0x10000000>;
        // 地址: 0x00000000_10000000 (2个字)
        // 长度: 无 (0个字)
    };
};

4.2 reg 属性:设备地址空间的描述

reg 属性是设备树中最核心的资源描述属性,用于定义设备在总线上的地址范围。其格式为:

reg = <address1 length1 [address2 length2] ...>;

每个 address + length 对称为一个 reg 条目。一个设备可以拥有多个不连续的地址区间,例如同时描述寄存器区、DMA区、FIFO区等。

驱动开发要点:在platform驱动中,通过 platform_get_resource() 或 of_iomap() 解析 reg 属性,自动获取物理地址与长度,无需手动计算。
// 多区间 reg 示例
ethernet@20200000 {
    compatible = "vendor,eth-controller";
    reg = <0x20200000 0x1000   // 控制寄存器区
           0x20201000 0x200    // DMA描述符区
           0x20202000 0x400>;  // 数据FIFO区
    #address-cells = <1>;
    #size-cells = <1>;
};

4.3 ranges 映射:地址转换与桥接

ranges 属性用于定义父总线地址空间与子总线地址空间之间的映射关系。它通常出现在桥接节点(如PCIe控制器、内存控制器、总线桥)中,实现地址域的转换。

ranges 条目字段 含义 字宽依据
子地址 子总线侧的起始地址 子节点的 #address-cells
父地址 父总线侧的起始地址 父节点的 #address-cells
映射长度 地址映射的区间大小 子节点的 #size-cells
// ranges 映射示例:片选外设总线桥
soc {
    #address-cells = <1>;
    #size-cells = <1>;

    external-bus {
        #address-cells = <2>;   // 子地址:片选号 + 偏移
        #size-cells = <1>;
        ranges = <0 0 0x10000000 0x10000   // 片选0: 0x10000000~0x1000FFFF
                  1 0 0x20000000 0x10000>; // 片选1: 0x20000000~0x2000FFFF

        device@0,0 {
            reg = <0 0 0x1000>;  // 片选0, 偏移0, 长度0x1000
        };
    };
};
常见陷阱:如果 ranges 属性为空(ranges;),表示子地址与父地址直接映射(1:1透传)。如果 ranges 属性不存在,则子节点地址在父地址空间中不可见,驱动无法直接访问。

4.4 地址编码与 platform 总线接口

在 platform 驱动框架中,设备树地址编码通过以下流程与驱动对接:

  1. 设备树解析:内核在启动时解析设备树,为每个节点创建 platform_device。
  2. 资源提取:of_device_make_bus_id() 和 of_platform_device_create() 将 reg 属性转换为 struct resource。
  3. 驱动匹配:驱动通过 platform_get_resource(pdev, IORESOURCE_MEM, index) 获取地址资源。
  4. 地址映射:驱动调用 ioremap() 或 devm_ioremap_resource() 将物理地址映射到内核虚拟地址空间。
// 驱动中获取 reg 资源的典型代码
static int my_driver_probe(struct platform_device *pdev)
{
    struct resource *res;
    void __iomem *base;

    // 获取第0个MEM资源(对应reg第一个条目)
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res)
        return -ENXIO;

    // 映射物理地址到虚拟地址
    base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(base))
        return PTR_ERR(base);

    dev_info(&pdev->dev, "reg base=0x%llx, size=0x%llx\n",
             (unsigned long long)res->start,
             (unsigned long long)resource_size(res));
    return 0;
}

4.5 地址编码综合流程 SVG

设备树地址编码与platform驱动对接流程 设备树地址编码 → platform驱动资源获取流程 设备树节点 #address-cells / #size-cells reg 属性 地址 + 长度 条目 ranges 映射 子→父地址转换 定义字宽 桥接地址域 内核解析阶段 of_platform_device_create() struct resource start / end / flags platform驱动 platform_get_resource() 解析reg/ranges 转换 获取 ioremap / devm_ioremap_resource 物理地址 → 内核虚拟地址 映射 驱动访问硬件寄存器 readl / writel 说明:设备树地址编码经过内核解析、资源转换、驱动获取、地址映射四个阶段,最终使驱动能够安全访问硬件寄存器

4.6 地址编码常见问题与调试技巧

问题场景 现象 根因 解决方案
#address-cells 不匹配 reg 解析出错误地址 父节点与子节点字宽不一致 检查父节点定义,确保子节点 reg 格式正确
ranges 缺失 驱动 ioremap 返回 NULL 子地址在父空间不可见 添加正确的 ranges 映射或使用 ranges; 透传
reg 长度字段为0 resource_size 返回0 #size-cells = <0> 但驱动期望长度 确认设备类型,寄存器类应设 #size-cells = <1>
多 reg 条目索引错误 驱动访问到错误寄存器 platform_get_resource 索引号不对 核对设备树 reg 条目顺序与驱动索引一致
调试命令:使用 ls /sys/devices/platform/.../of_node/ 查看设备树节点属性;使用 cat /proc/iomem 确认物理地址映射是否正确;使用 dtc -I fs -O dts /sys/firmware/devicetree/base 反编译当前设备树。

4.7 本章小结

  • #address-cells 和 #size-cells 定义地址编码的字宽,是设备树地址解析的元规则。
  • reg 属性描述设备的物理地址区间,支持多条目,是驱动获取资源的主要来源。
  • ranges 属性实现子总线到父总线的地址映射,是桥接设备地址域的关键。
  • platform 驱动通过 platform_get_resource() 和 ioremap() 将设备树地址编码转化为可访问的虚拟地址。
  • 地址编码错误是设备树调试中最常见的问题之一,掌握其语义和调试方法能大幅提升开发效率。