3. reg属性深度解析:标准格式与多段地址空间

reg 属性是设备树中描述设备地址资源的核心属性,它定义了设备在总线上的地址范围、寄存器基址、内存窗口等物理地址信息。驱动通过 of_iomap()、platform_get_resource() 等 API 解析 reg 属性,任何格式错误或地址不匹配都会导致驱动无法正确访问硬件。

3.1 reg属性的标准格式

reg 属性的标准格式由 address-cells 和 size-cells 两个父节点属性共同决定,其通用结构为:

reg = <address1 size1 [address2 size2 ...]>;

每个地址-大小对称为一个 reg 段,表示一段连续的地址空间。地址和大小分别由 #address-cells 和 #size-cells 指定占用的 32 位整数个数。

属性 含义 常见值 说明
#address-cells 地址字段占用的 32 位单元数 1 或 2 1 表示 32 位地址,2 表示 64 位地址
#size-cells 大小字段占用的 32 位单元数 0 或 1 0 表示无大小(仅地址),1 表示 32 位大小
reg 地址资源列表 变长 按 (address, size) 对排列
关键规则: 每个 reg 段的总长度 = #address-cells + #size-cells 个 32 位整数。解析时按此长度循环读取,直到 reg 属性结束。

3.2 多段地址空间表示

一个设备可能拥有多个独立的地址窗口,例如:控制寄存器区、数据缓冲区、DMA 描述符区等。设备树通过 reg 属性中的多个地址-大小对来表示这些不连续的地址段。

示例:双段地址空间(32位地址)

spi_controller: spi@ff110000 {
    compatible = "vendor,spi-v2";
    #address-cells = <1>;
    #size-cells = <1>;
    reg = <0xff110000 0x1000>,   /* 控制寄存器:基址 0xff110000,大小 4KB */
          <0xff120000 0x2000>;   /* 数据缓冲区:基址 0xff120000,大小 8KB */
    interrupts = <0 39 4>;
};

示例:64位地址 + 32位大小

pcie_ep: pcie-ep@4000000000 {
    compatible = "vendor,pcie-ep";
    #address-cells = <2>;
    #size-cells = <1>;
    reg = <0x00000040 0x00000000 0x10000000>;  /* 地址 0x4000000000,大小 256MB */
    reg-names = "dbi";
};
最佳实践: 当设备有多个 reg 段时,建议使用 reg-names 属性为每个段命名,驱动通过 platform_get_resource_byname() 按名称获取,提高代码可读性和可维护性。

3.3 reg属性解析流程与常见错误

内核解析 reg 属性的流程如下:

  1. 读取父节点的 #address-cells 和 #size-cells,确定地址和大小字段长度。
  2. 按段循环解析:每次读取 address-cells + size-cells 个 32 位整数,构成一个资源段。
  3. 地址转换:如果设备有父级总线(如 PCI、AMBA),内核会调用 of_translate_address() 将 reg 中的地址转换为 CPU 物理地址。
  4. 填充 resource 结构:将解析结果填入 struct resource,供驱动使用。
常见错误 现象 排查方法
#address-cells / #size-cells 缺失或错误 reg 解析为 0 或乱码 检查父节点是否正确定义这两个属性
reg 段数量与硬件不匹配 驱动访问到错误的地址 对照 datasheet 确认 reg 段数量和顺序
64 位地址但 #address-cells = <1> 高 32 位被截断 确保 64 位地址使用 #address-cells = <2>
大小字段为 0 ioremap 返回 NULL 检查 #size-cells 是否应为 1

3.4 reg属性结构示意图

下图展示了 reg 属性在设备树中的层级关系与解析过程:

reg属性解析结构图 reg 属性解析结构图 父节点 (Bus Controller) #address-cells = <1> #size-cells = <1> 子节点 (Device) reg = <0x1000 0x100>, <0x2000 0x200>; 解析结果 段1: start=0x1000, len=0x100 段2: start=0x2000, len=0x200 解析流程 ① 读取父节点 #address-cells=1, #size-cells=1 ② 每个 reg 段长度 = 1 + 1 = 2 个 32 位整数 ③ 段1: 地址=0x1000, 大小=0x100 ④ 段2: 地址=0x2000, 大小=0x200 常见错误排查 错误1:address-cells 缺失 默认值可能为 0,导致解析失败 错误2:大小字段为 0 ioremap 返回 NULL 错误3:64位地址用 1 cell 高 32 位被截断

3.5 驱动中解析reg的典型代码

以下代码展示了驱动如何安全地解析 reg 属性中的多段地址空间:

#include <linux/platform_device.h>
#include <linux/of_address.h>

static int my_driver_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct resource *res;
    void __iomem *base;
    int i;

    /* 方法1:按索引获取资源 */
    for (i = 0; i < pdev->num_resources; i++) {
        res = platform_get_resource(pdev, IORESOURCE_MEM, i);
        if (!res) {
            dev_err(dev, "Failed to get resource %d\n", i);
            return -ENXIO;
        }
        dev_info(dev, "reg[%d]: start=0x%llx, len=0x%llx\n",
                 (unsigned long long)res->start,
                 (unsigned long long)resource_size(res));
    }

    /* 方法2:按名称获取资源(需要 reg-names 属性) */
    res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "control");
    if (res) {
        base = devm_ioremap_resource(dev, res);
        if (IS_ERR(base))
            return PTR_ERR(base);
    }

    return 0;
}
注意: 使用 devm_ioremap_resource() 时会自动检查资源是否已被其他驱动占用,并打印清晰的错误信息。避免直接使用 ioremap(),后者不提供资源冲突检测。

3.6 多段地址空间的实际应用场景

设备类型 reg 段数 典型布局
UART 控制器 1 控制寄存器区
DMA 控制器 2 控制寄存器 + 描述符表
GPU 3+ 控制寄存器 + 显存 + 命令队列 + 状态页
PCIe EP 2 配置空间 + BAR 空间
多媒体编解码器 4+ 控制 + 输入缓冲区 + 输出缓冲区 + 工作队列