[内核学习]DMA与IOMMU

/ 0评 / 0赞

CPU 一纳秒也等不了外设,大批量数据搬运靠 DMA:设备自己读写内存,CPU 只管下命令.听起来简单,难在"设备眼中的内存"和"CPU 眼中的内存"不是一回事.这篇把 dma 地址和物理地址的关系,两种映射 API,swiotlb 和 CMA 这些救火队员,还有 IOMMU 的翻译和隔离,全部摆到实机上验证.

这篇回答几件事:

先破一个常见误解:DMA 地址是设备总线视角的地址,物理地址是内存阵列视角的地址,两者恰好相等的情形有,但那是运气不是定律.实测板上的对照,自建 platform 设备,64... 40 位掩码,dma_map_single 一把:

dma_map_single: kbuf=ffff000000d0d000 dma_addr=0x0000000000d0d000
                kbuf_phys=0x0d0d000

kbuf 物理地址 0xd0d000,dma_map_single 返回 0x0d0d000,逐字节相等 — 因为 RK3399 没有通用 IOMMU,设备直接看物理总线,这种平台叫 direct DMA.dma API 存在的第一个理由就是把这层关系抽象掉:驱动拿到 dma_addr 给设备填寄存器,映射怎么来的是架构的事,同一份驱动在有 IOMMU 的机器上跑,dma_addr 就变成 IOVA(I/O 虚拟地址),由 IOMMU 翻译回物理,驱动无感.

两种映射 API 对应两种使用姿势.coherent 一致性映射,dma_alloc_coherent 一次分配两边可用,CPU 写完设备立即可见,不需要任何同步动作,代价是这内存在有些架构上要禁缓存,贵且有限,适合描述符环,控制块这类双方高频共读共写的结构.实测输出:

dma_alloc_coherent: cpu_va=ffff800081c5d000 dma_addr=0x0000000006601000

注意 CPU 侧地址在 0xffff8000 开头的区域 — arm64 专门为一致性内存留的映射区,和普通 kmalloc 的线性映射(0xffff0000 开头)分区而治.streaming 流式映射,dma_map_single 把普通 kmalloc 的缓冲临时借给设备,方向敏感(DMA_TO_DEVICE/DMA_FROM_DEVICE),借出期间 CPU 别碰,还回来时缓存一致性由 unmap 或 sync 动作处理.大块数据传输用它,别用 coherent 硬扛.

然后是两位救火队员.swiotlb,software IO TLB:设备的寻址能力比内存小时(经典是 32 位 PCI 卡摸不到 4GB 以上),内核把你的缓冲复制进一块低位弹跳区,设备实际搬的是弹跳区.开销是内存拷贝,能不用就不用,但有些硬件组合没它根本跑不起来.QEMU 内核的启动日志明明白白:

software IO TLB: mapped [mem 0x7c980000-0x7cb80000] (2MB)

板上虽然编了 CONFIG_SWIOTLB,启动日志一行没有 — 40 位总线和 40 位 DMA 能力,根本用不上弹跳,这就是"编了但闲置"的样板.CMA,contiguous memory allocator:大块物理连续内存的预留池,摄像头,视频编解码这类要几百 KB 到几兆连续缓冲的设备靠它.QEMU 内核留了 32MiB("cma: Reserved 32 MiB at 0x7cc00000"),板上的 Memory 行写着 0K cma-reserved — 没有这类设备,一页不占.它和 buddy 的关系是:CMA 区域空闲时正常当普通页用,有设备来要连续大块时把散住户迁走腾地方.

重头戏 IOMMU.QEMU 的 virt 机器能仿真 ARM SMMUv3,启动参数 iommu=smmuv3 加一块 virtio 网卡,内核日志:

arm-smmu-v3 9050000.smmuv3: ias 44-bit, oas 44-bit (features 0x01088705)
arm-smmu-v3 9050000.smmuv3: allocated 65536 entries for cmdq
pci 0000:00:00.0: Adding to iommu group 0
pci 0000:00:01.0: Adding to iommu group 1

SMMU 初始化,命令队列,事件队列,然后每个设备被分进一个 iommu group — sysfs 里 /sys/kernel/iommu_groups/ 下每个组一个目录,组里挂设备.这个组是隔离的单位:同组设备共享地址空间,跨组完全看不见.它带来三个改变.一是地址翻译,设备的 IOVA 随便给,物理页可以不连续,散射列表映射后设备看到一片连续;二是权限,IOMMU 页表能限制设备只能碰授权过的页,野指针 DMA 直接 fault;三是安全隔离,虚拟机直通设备(pass-through)就是靠每组一张翻译表,VM A 的网卡永远打不进 VM B 的内存.板上那颗 RK3399 没有通用 SMMU,只有 VOP/VPU 等专用 IOMMU,所以前面才是直通式 DMA — 两套硬件,一个 dma API,这就是抽象层的价值.

最后留几个容易忽略的细节:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注