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 属性的流程如下:
- 读取父节点的
#address-cells和#size-cells,确定地址和大小字段长度。 - 按段循环解析:每次读取
address-cells + size-cells个 32 位整数,构成一个资源段。 - 地址转换:如果设备有父级总线(如 PCI、AMBA),内核会调用
of_translate_address()将 reg 中的地址转换为 CPU 物理地址。 - 填充 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 属性在设备树中的层级关系与解析过程:
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+ | 控制 + 输入缓冲区 + 输出缓冲区 + 工作队列 |