4. 设备树地址与DMA映射:#address-cells、#size-cells、ranges、dma-ranges属性解析
设备树(Device Tree)是描述硬件拓扑与资源分布的核心数据结构。在嵌入式Linux系统中,CPU、内存、外设之间的地址映射关系以及DMA(直接内存访问)传输的地址空间转换,完全由设备树中的地址相关属性定义。本章深入解析 #address-cells、#size-cells、ranges 和 dma-ranges 四个关键属性,帮助开发者准确理解地址编码规则与映射机制。
4.1 地址编码基础:#address-cells 与 #size-cells
设备树中,每个总线节点或设备节点都需要声明其子节点地址域的编码宽度。#address-cells 定义地址字段占用的32位单元数,#size-cells 定义长度字段占用的32位单元数。这两个属性共同决定了 reg 属性中地址与长度的解析方式。
| 属性 | 含义 | 常见取值 | 示例 |
|---|---|---|---|
#address-cells |
子节点地址字段的32位字数量 | 1(32位地址)、2(64位地址) | #address-cells = <1> |
#size-cells |
子节点长度字段的32位字数量 | 0(无长度)、1(32位长度) | #size-cells = <1> |
#address-cells 和 #size-cells 决定了其直接子节点中 reg 属性的解析格式。若父节点未显式声明,则默认值为 #address-cells = <2>、#size-cells = <1>。
以下是一个典型的设备树片段,展示地址编码的层级关系:
soc {
compatible = "simple-bus";
#address-cells = <2>; /* 64位地址 */
#size-cells = <2>; /* 64位长度 */
ranges;
uart0: serial@101f0000 {
reg = <0x0 0x101f0000 0x0 0x1000>;
/* 地址: 0x101f0000, 长度: 0x1000 */
};
gpio0: gpio@101e0000 {
reg = <0x0 0x101e0000 0x0 0x100>;
/* 地址: 0x101e0000, 长度: 0x100 */
};
};
dtc -I dtb -O dts 反编译设备树二进制文件,检查 #address-cells 和 #size-cells 是否与硬件手册的地址宽度一致。不一致会导致内核解析 reg 时出现偏移错误。
4.2 地址转换桥梁:ranges 属性
ranges 属性定义了父总线地址空间与子总线地址空间之间的映射关系。它允许子节点使用本地地址(local address),而父节点通过 ranges 将其转换为全局地址(global address)。ranges 是一个可选的地址转换表,每个条目包含三个字段:子地址、父地址、映射长度。
| 字段 | 含义 | 宽度 |
|---|---|---|
| 子地址(child address) | 子总线域内的起始地址 | 子节点的 #address-cells |
| 父地址(parent address) | 父总线域内的起始地址 | 父节点的 #address-cells |
| 映射长度(length) | 地址映射的连续范围大小 | 子节点的 #size-cells |
当 ranges 属性为空(ranges;)时,表示子地址空间与父地址空间是1:1直接映射,即子地址等于父地址。若节点不包含 ranges 属性,则子地址空间与父地址空间完全隔离,内核无法自动进行地址转换。
ranges,导致挂载在该总线下的外设无法被内核正确访问。调试时可通过 cat /proc/iomem 检查物理地址映射是否完整。
以下是一个包含 ranges 映射的完整示例:
pcie@40000000 {
compatible = "pci-host-ecam-generic";
#address-cells = <3>; /* PCIe 地址域:3个32位单元 */
#size-cells = <2>; /* PCIe 长度域:2个32位单元 */
reg = <0x0 0x40000000 0x0 0x10000000>;
/* 将PCIe地址空间映射到CPU地址空间 */
ranges = <0x01000000 0x0 0x00000000 /* PCIe I/O 空间 */
0x0 0x3eff0000
0x0 0x00010000>,
<0x02000000 0x0 0x50000000 /* PCIe 内存空间 */
0x0 0x50000000
0x0 0x20000000>;
};
4.3 DMA地址映射:dma-ranges 属性
dma-ranges 属性与 ranges 结构完全相同,但语义不同:它描述的是设备DMA引擎看到的地址空间(设备地址域)与CPU地址空间之间的映射关系。当设备通过DMA访问内存时,硬件DMA控制器使用设备地址,而CPU使用物理地址。dma-ranges 定义了这两者之间的转换规则。
| 属性 | 作用域 | 转换方向 | 典型应用场景 |
|---|---|---|---|
ranges |
CPU访问外设 | 子地址 → 父地址 | MMIO、配置空间访问 |
dma-ranges |
外设DMA访问内存 | 设备地址 → CPU地址 | DMA传输、SMMU/IOMMU配置 |
ranges 用于CPU发起的访问(如 readl/writel),而 dma-ranges 用于设备发起的访问(如DMA写内存)。在存在IOMMU/SMMU的系统中,dma-ranges 通常描述经过IOMMU转换后的地址映射。
以下是一个包含 dma-ranges 的典型设备树片段:
pcie@40000000 {
compatible = "pci-host-ecam-generic";
#address-cells = <3>;
#size-cells = <2>;
reg = <0x0 0x40000000 0x0 0x10000000>;
/* CPU访问PCIe配置空间和MMIO */
ranges = <0x01000000 0x0 0x00000000
0x0 0x3eff0000
0x0 0x00010000>,
<0x02000000 0x0 0x50000000
0x0 0x50000000
0x0 0x20000000>;
/* PCIe设备DMA访问内存的地址映射 */
dma-ranges = <0x02000000 0x0 0x00000000
0x0 0x80000000
0x0 0x40000000>;
/* 设备地址 0x00000000-0x3FFFFFFF
映射到 CPU 物理地址 0x80000000-0xBFFFFFFF */
};
4.4 内核启动流程中的地址解析
在Linux内核启动过程中,设备树地址属性的解析发生在 unflatten_device_tree() 之后,由 of_address 子系统完成。关键流程如下:
- 设备树展开:内核将扁平设备树(FDT)展开为树形结构,建立
device_node链表。 - 地址域初始化:遍历每个总线节点,读取
#address-cells和#size-cells,缓存到struct device_node的_flags字段。 - reg 属性解析:调用
of_address_to_resource()将reg属性转换为struct resource,包含起始地址和长度。 - ranges 映射计算:通过
of_translate_address()沿父节点链向上查找ranges,逐级计算全局物理地址。 - dma-ranges 映射注册:调用
of_dma_configure()解析dma-ranges,设置设备的dma_pfn_offset和dma_mask。
drivers/of/address.c 中添加 printk 或使用 trace_printk 跟踪 of_translate_address() 的调用栈,观察地址转换的中间结果。内核启动参数 of_debug 可启用设备树调试信息。
4.5 常见问题与调试技巧
| 问题现象 | 可能原因 | 调试方法 |
|---|---|---|
| 外设寄存器读写无响应 | ranges 缺失或映射错误 |
检查 /proc/iomem 确认物理地址范围 |
| DMA传输数据损坏 | dma-ranges 未正确配置 |
使用 dma_debug 内核参数跟踪DMA映射 |
内核崩溃在 of_translate_address |
#address-cells 与 reg 字段数不匹配 |
反编译DTB,核对地址字段宽度 |
| PCIe设备无法枚举 | PCIe域 ranges 条目格式错误 |
使用 lspci -vvv 查看桥接窗口 |
#address-cells 和 #size-cells 在父子节点间的一致性。父节点声明了 #address-cells = <2>,则子节点的 reg 属性中地址字段必须包含两个32位单元,否则内核解析会错位,导致地址值完全错误。
4.6 本章小结
#address-cells和#size-cells定义了设备树地址编码的宽度,是解析reg属性的基础。ranges属性实现CPU地址空间到外设地址空间的映射,是CPU访问外设的桥梁。dma-ranges属性实现设备DMA地址空间到CPU物理地址空间的映射,是DMA传输正确性的保障。- 内核启动时通过
of_address_to_resource()和of_dma_configure()自动解析这些属性,驱动开发者应理解其底层机制以便调试。