Linux设备树实战:如何用of_address_to_resource解析reg属性(附完整代码示例)
Linux设备树实战:如何用of_address_to_resource解析reg属性(附完整代码示例)
在嵌入式Linux开发中,设备树(Device Tree)已经成为硬件描述的标准方式。对于驱动开发者来说,正确解析设备树中的寄存器地址映射是基本功之一。今天我们就来深入探讨of_address_to_resource这个关键函数的使用场景和实战技巧。
1. 设备树寄存器映射基础
设备树中的reg属性用于描述设备寄存器在地址空间中的位置和大小。一个典型的reg属性定义如下:
dma@7e007000 { compatible = "brcm,bcm2835-dma"; reg = <0x7e007000 0xf00>; };这里的0x7e007000表示寄存器块的起始地址,0xf00表示大小。但在实际系统中,这个地址可能需要经过多级转换才能得到最终的物理地址。这就是of_address_to_resource函数的用武之地。
1.1 地址转换的必要性
现代SoC通常采用多级地址映射架构,主要原因包括:
- 总线地址转换:不同总线域(如AXI、AHB)之间的地址映射
- 内存重映射:Bootloader可能对内存区域进行重新映射
- 虚拟化支持:需要区分物理地址和总线地址
考虑以下典型场景:
soc { compatible = "simple-bus"; #address-cells = <1>; #size-cells = <1>; ranges = <0x7e000000 0x3f000000 0x1000000>; dma@7e007000 { reg = <0x7e007000 0xf00>; }; };这里ranges属性定义了从子地址空间(0x7e000000)到父地址空间(0x3f000000)的映射关系,大小为0x1000000。因此,设备树中看到的0x7e007000实际上对应物理地址0x3f007000。
2. of_address_to_resource函数详解
of_address_to_resource是Linux内核提供的核心API,用于将设备树中的reg属性转换为可用的struct resource。其函数原型如下:
int of_address_to_resource(struct device_node *dev, int index, struct resource *r);2.1 函数工作流程
该函数的内部实现可以分为三个关键步骤:
- 获取原始地址信息:通过
of_get_address获取reg属性的原始地址、大小和标志 - 获取寄存器名称:如果有
reg-names属性,则获取对应的寄存器区域名称 - 地址转换:通过
__of_address_to_resource完成实际的地址转换
让我们看一个完整的调用示例:
struct resource res; struct device_node *np = ...; // 获取设备节点 if (of_address_to_resource(np, 0, &res)) { dev_err(dev, "Failed to get resource\n"); return -EINVAL; } dev_info(dev, "Registers at %pa, size %llx\n", &res.start, (u64)resource_size(&res));2.2 处理多组reg属性
许多设备会有多组寄存器区域,这时可以通过index参数指定要获取哪一组:
device@12340000 { reg = <0x12340000 0x1000>, <0x12341000 0x200>; reg-names = "control", "data"; };对应的代码处理:
struct resource ctrl_res, data_res; of_address_to_resource(np, 0, &ctrl_res); // 获取control寄存器 of_address_to_resource(np, 1, &data_res); // 获取data寄存器3. 实战:复杂地址转换案例分析
让我们通过一个实际案例来演示如何处理带有多级地址转换的设备树。
3.1 设备树示例
/ { #address-cells = <1>; #size-cells = <1>; bus@10000000 { compatible = "vendor,bus-controller"; #address-cells = <2>; #size-cells = <1>; ranges = <0x0 0x0 0x10000000 0x10000>; device@2000 { compatible = "vendor,my-device"; reg = <0x0 0x2000 0x100>; }; }; };3.2 解析代码实现
static int my_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct resource res; void __iomem *regs; if (of_address_to_resource(np, 0, &res)) { dev_err(&pdev->dev, "Failed to get resource\n"); return -EINVAL; } regs = devm_ioremap_resource(&pdev->dev, &res); if (IS_ERR(regs)) { return PTR_ERR(regs); } dev_info(&pdev->dev, "Mapped registers at %pa, size %llx\n", &res.start, (u64)resource_size(&res)); // 使用映射后的寄存器... return 0; }3.3 调试技巧
当地址转换出现问题时,可以通过以下方法调试:
- 检查dmesg输出:内核会在地址转换过程中打印调试信息
- 验证设备树结构:使用
dtc工具反编译DTB文件 - 手动计算地址:根据
ranges属性手动验证转换结果
例如,在驱动中添加调试打印:
pr_debug("Original reg: %pOF, index %d\n", np, index); if (of_address_to_resource(np, index, &res)) { pr_debug("Translation failed\n"); } else { pr_debug("Translated: %pa-%pa\n", &res.start, &res.end); }4. 高级应用与常见问题
4.1 处理不同类型的资源
of_address_to_resource不仅可以处理内存映射寄存器,还能处理其他类型的资源:
| 资源类型 | 标志位 | 典型用途 |
|---|---|---|
| 内存映射 | IORESOURCE_MEM | 寄存器、共享内存 |
| IO端口 | IORESOURCE_IO | x86架构的IO端口 |
| 中断 | IORESOURCE_IRQ | 中断线 |
4.2 常见错误处理
地址转换失败:
- 检查设备树中的
ranges属性是否正确 - 确认父节点的
#address-cells和#size-cells设置正确
- 检查设备树中的
资源冲突:
- 使用
request_mem_region检查资源是否已被占用 - 在
/proc/iomem中查看已分配的资源
- 使用
大小不匹配:
- 确保
reg属性中的大小与实际硬件一致 - 检查是否有地址对齐要求
- 确保
4.3 性能优化建议
对于频繁访问的寄存器区域:
- 使用
devm_ioremap_resource自动管理资源生命周期 - 考虑使用
ioremap_cache代替ioremap提高访问性能 - 对于大量小寄存器访问,可以使用
regmap框架
struct regmap_config my_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, }; struct regmap *regmap = devm_regmap_init_mmio(&pdev->dev, regs, &my_regmap_config);在实际项目中,我发现正确理解设备树地址转换机制可以避免很多难以调试的硬件问题。特别是在使用第三方IP核时,仔细检查其设备树绑定文档中的地址映射要求非常重要。
