4. DMA映射概念:物理地址与总线地址、IOMMU/SMMU的作用、映射表与描述符
在QNX BSP移植中,DMA(直接内存访问)映射是连接外设与系统内存的核心桥梁。理解物理地址、总线地址以及IOMMU/SMMU的作用,是定位DMA相关故障的前提。本节将系统阐述这些概念及其在BSP中的实现机制。
4.1 物理地址与总线地址的差异
在嵌入式系统中,物理地址(Physical Address, PA)是CPU和内存控制器看到的地址空间,而总线地址(Bus Address, BA)是外设(如网卡、存储控制器)在DMA操作中使用的地址。两者在无IOMMU的系统中可能相同,但在现代SoC中往往不同。
| 地址类型 | 定义 | 使用者 | 典型范围 |
|---|---|---|---|
| 物理地址(PA) | CPU通过内存总线访问的实际硬件地址 | CPU、MMU、Cache | 0x80000000 – 0xFFFFFFFF(32位) |
| 总线地址(BA) | 外设通过DMA引擎访问的地址 | DMA控制器、外设设备 | 0x40000000 – 0x7FFFFFFF(可能不同) |
| 虚拟地址(VA) | 进程看到的地址,需MMU转换 | 应用程序、内核线程 | 0x00010000 – 0xBFFFFFFF |
关键提示:在QNX BSP中,
mmap_device_memory()返回的是物理地址映射后的虚拟地址,而DMA描述符中必须填写总线地址。混淆这两者将导致DMA传输目标错误或数据损坏。
4.2 IOMMU/SMMU的作用
IOMMU(I/O Memory Management Unit)或SMMU(System Memory Management Unit)是连接外设与系统内存的硬件单元,其核心功能包括:
- 地址转换:将外设发出的总线地址转换为物理地址,使外设可以访问非连续的物理内存。
- 访问权限控制:限制外设只能访问被授权的内存区域,防止恶意或错误的DMA操作破坏系统。
- 隔离与虚拟化:在虚拟化环境中,为不同虚拟机的外设提供独立的内存映射。
| 特性 | IOMMU(x86/PCIe) | SMMU(ARM) |
|---|---|---|
| 架构标准 | Intel VT-d / AMD-Vi | ARM SMMUv2/v3 |
| 转换粒度 | 4KB页 / 2MB大页 | 4KB页 / 2MB / 1GB块 |
| 中断重映射 | 支持(Interrupt Remapping) | 支持(MSI转换) |
| QNX支持 | io-pgtbl 驱动 |
arm-smmu 驱动 |
BSP移植要点:在QNX中,IOMMU/SMMU的初始化通常在启动代码(
startup)中完成,包括:
- 解析设备树(DTB)中的IOMMU节点。
- 配置页表基地址和转换规则。
- 注册IOMMU回调到DMA框架。
4.3 映射表与描述符
映射表(Mapping Table)是IOMMU/SMMU内部维护的页表结构,定义了总线地址到物理地址的转换规则。DMA描述符(Descriptor)是外设与DMA控制器之间传递传输信息的控制结构。
4.3.1 映射表结构
在QNX BSP中,映射表通常以多级页表形式存在。以ARM SMMUv2为例,典型的二级页表结构如下:
/* 一级页表(L1)条目:指向二级页表 */
struct smmu_l1_entry {
uint32_t base_addr : 20; // 二级页表基地址(高位)
uint32_t reserved : 10;
uint32_t valid : 1; // 有效位
uint32_t type : 1; // 0=页表, 1=块
};
/* 二级页表(L2)条目:指向物理页 */
struct smmu_l2_entry {
uint32_t pfn : 20; // 物理页帧号
uint32_t attr : 10; // 内存属性(缓存、权限等)
uint32_t valid : 1;
uint32_t type : 1;
};
常见故障:映射表未正确初始化或页表地址对齐错误,会导致IOMMU缺页异常(Translation Fault)。在QNX中,此类故障通常表现为
SYSPAGE_IOMMU_FAULT中断,需检查smmu_fault_handler的日志输出。
4.3.2 DMA描述符格式
DMA描述符的格式因外设而异,但通常包含以下关键字段:
| 字段 | 偏移(字节) | 描述 |
|---|---|---|
| 源地址 | 0x00 | DMA读取数据的起始总线地址 |
| 目的地址 | 0x04 | DMA写入数据的目标总线地址 |
| 传输长度 | 0x08 | 本次传输的字节数(通常为4的倍数) |
| 控制标志 | 0x0C | 中断使能、链式传输、方向等 |
| 下一个描述符指针 | 0x10 | 链式DMA中下一个描述符的总线地址 |
4.4 QNX中的DMA映射API
QNX提供了一套标准的DMA映射接口,BSP开发者需在io-pkt或驱动中正确调用:
/* 分配DMA缓冲区并获取总线地址 */
dma_addr_t dma_alloc_coherent(size_t size, uint32_t flags);
/* 将虚拟地址映射为DMA总线地址 */
dma_addr_t dma_map_single(void *cpu_addr, size_t size, int direction);
/* 同步DMA缓冲区(Cache一致性维护) */
void dma_sync_single(dma_addr_t bus_addr, size_t size, int direction);
/* 释放DMA映射 */
void dma_unmap_single(dma_addr_t bus_addr, size_t size);
调试建议:当DMA传输数据异常时,首先检查:
- 描述符中的总线地址是否在IOMMU映射范围内。
- 映射表条目是否标记为
valid且权限正确。 - Cache一致性操作是否在正确时机调用(
dma_sync_single)。
4.5 故障定位要点总结
| 故障现象 | 可能原因 | 检查方法 |
|---|---|---|
| DMA传输无响应 | 描述符中总线地址错误或IOMMU缺页 | 查看smmu_fault_status寄存器 |
| 数据内容错乱 | Cache未同步或映射表属性错误 | 检查dma_sync_single调用顺序 |
| 系统崩溃/异常 | IOMMU权限不足或描述符越界 | 启用QNX内核的dma_debug选项 |
| 性能低下 | 映射表粒度过小或TLB未命中 | 使用2MB大页映射连续内存 |
核心原则:DMA映射的本质是建立外设与内存之间的信任通道。物理地址是CPU的视角,总线地址是外设的视角,IOMMU是翻译官,描述符是合同。BSP移植者的任务就是确保这份“合同”准确无误。