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 驱动框架中,设备树地址编码通过以下流程与驱动对接:
- 设备树解析:内核在启动时解析设备树,为每个节点创建
platform_device。 - 资源提取:
of_device_make_bus_id()和of_platform_device_create()将reg属性转换为struct resource。 - 驱动匹配:驱动通过
platform_get_resource(pdev, IORESOURCE_MEM, index)获取地址资源。 - 地址映射:驱动调用
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
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()将设备树地址编码转化为可访问的虚拟地址。 - 地址编码错误是设备树调试中最常见的问题之一,掌握其语义和调试方法能大幅提升开发效率。