3. 设备树地址编码:reg属性、ranges属性、地址映射与DMA地址空间配置
设备树(Device Tree)是描述硬件拓扑与资源分配的核心数据结构。地址编码是设备树中最关键也最容易出错的部分,它决定了CPU如何访问外设寄存器、外设如何访问系统内存,以及DMA引擎如何正确寻址。本章深入剖析reg属性、ranges属性、地址映射机制以及DMA地址空间的配置方法。
3.1 reg属性:设备资源的基础描述
reg属性用于描述设备在总线上的地址空间范围,其格式为:
reg = <address1 length1 [address2 length2 ...]>;
每个address length对称为一个地址区域(region)。地址和长度的位数由父节点的#address-cells和#size-cells决定。
| 属性 | 含义 | 典型值 |
|---|---|---|
#address-cells |
地址字段占用的32位单元数 | 1(32位地址)、2(64位地址) |
#size-cells |
长度字段占用的32位单元数 | 1(32位长度)、2(64位长度) |
reg |
地址与长度交替排列的数组 | <0x10000000 0x1000> |
示例:一个UART控制器在32位地址空间中的寄存器基址为0x10000000,大小为4KB。
uart@10000000 {
compatible = "ns16550";
reg = <0x10000000 0x1000>;
interrupts = <33>;
};
3.2 ranges属性:地址转换的桥梁
ranges属性定义了子总线地址到父总线地址的映射关系。当子节点使用不同于父节点的地址空间时,必须通过ranges进行转换。
格式:
ranges = <child-bus-address parent-bus-address length>;
每个条目包含三个字段,其宽度由#address-cells(子节点)、父节点的#address-cells和#size-cells共同决定。
关键理解:如果
ranges属性为空(ranges;),表示子地址与父地址是1:1直接映射。如果ranges属性不存在,则子节点无法被父总线访问。
下面是一个典型的PCIe桥接设备树片段,展示了地址映射关系:
pcie@40000000 {
compatible = "pci-host-ecam-generic";
reg = <0x40000000 0x10000000>; // ECAM配置空间
#address-cells = <3>;
#size-cells = <2>;
device_type = "pci";
ranges = <0x02000000 0x00 0x50000000
0x00 0x50000000
0x00 0x10000000>; // 非预取内存窗口
};
3.3 地址映射的层级与解析过程
设备树地址解析是一个自底向上的过程。内核从设备节点出发,逐级向上查找ranges属性,直到到达根节点(CPU地址空间)。
- 读取节点自身的
reg:获取设备在本地总线上的地址。 - 查找父节点的
ranges:将本地地址转换为父总线地址。 - 递归向上:重复步骤2,直到根节点。
- 最终映射:得到CPU可直接访问的物理地址。
常见错误:忘记在桥接节点中定义
ranges属性,导致子设备地址无法被CPU访问。内核会报错“OF: translation failed”,驱动probe失败。
3.4 DMA地址空间配置
DMA(直接内存访问)设备需要独立于CPU视角的地址空间。在设备树中,通过dma-ranges属性描述DMA引擎的地址映射能力。
| 属性 | 作用 | 示例 |
|---|---|---|
dma-ranges |
定义DMA地址到CPU物理地址的映射 | <0x00 0x80000000 0x00 0x80000000 0x00 0x10000000> |
dma-coherent |
声明设备支持DMA一致性(无需缓存刷新) | 空属性 |
dma-noncoherent |
声明设备不支持DMA一致性 | 空属性 |
典型场景:一个64位DMA控制器只能访问低32位地址空间,需要配置dma-ranges进行地址窗口映射。
dma-controller@30000000 {
compatible = "my-dma-engine";
reg = <0x30000000 0x1000>;
#dma-cells = <3>;
dma-ranges = <0x00 0x80000000 // DMA地址起始
0x00 0x80000000 // CPU物理地址起始
0x00 0x10000000>; // 映射大小256MB
dma-coherent;
};
3.5 地址编码避坑指南
- #address-cells / #size-cells 不匹配:父子节点之间的地址单元数必须一致,否则解析结果错误。
- ranges 缺失导致地址不可达:桥接节点必须显式提供
ranges,否则子设备无法被CPU访问。 - DMA地址越界:DMA引擎的物理地址窗口必须与
dma-ranges定义一致,否则DMA操作会访问无效内存。 - 64位地址使用2个cell:对于64位地址空间,
#address-cells = <2>,#size-cells = <2>,地址和长度各占64位。 - dma-coherent 与缓存一致性:如果设备声明
dma-coherent,内核不会对DMA缓冲区执行缓存刷新操作;如果硬件不支持,会导致数据不一致。
调试技巧:使用
of_dump或devicetree.dump内核参数可以打印完整的设备树地址解析过程。在驱动中调用of_address_to_resource()和of_dma_configure()可以验证地址映射是否正确。
3.6 实战:配置一个带DMA的网卡设备树节点
以下是一个完整的设备树节点示例,集成了reg、ranges和dma-ranges配置:
soc {
compatible = "simple-bus";
#address-cells = <2>;
#size-cells = <2>;
ranges;
eth@1c00000 {
compatible = "my-gmac";
reg = <0x00 0x1c00000 0x00 0x10000>; // 寄存器基址
interrupts = <0 45 4>;
dma-ranges = <0x00 0x40000000 // DMA地址
0x00 0x40000000 // CPU物理地址
0x00 0x20000000>; // 512MB映射
dma-coherent;
phy-mode = "rgmii";
phy-handle = <&phy0>;
mdio {
#address-cells = <1>;
#size-cells = <0>;
phy0: ethernet-phy@0 {
reg = <0>;
};
};
};
};
该节点中:
reg定义了网卡寄存器在SoC总线上的位置(0x1c00000,64KB)。ranges为空,表示SoC总线地址与CPU地址1:1映射。dma-ranges将DMA地址0x40000000映射到CPU物理地址0x40000000,大小512MB。dma-coherent声明网卡硬件保证DMA一致性。
通过正确配置地址编码,驱动可以无缝访问寄存器、发起DMA传输,并确保数据一致性。这是嵌入式Linux设备驱动开发中最基础也最关键的一步。