Linux下能否指定DMA缓冲区的dma_handle(IO虚拟地址)低38位全0?
我们的SoC设计包含一款带RISC-V核的设备模块,工作流程如下:通过dma_alloc_coherent分配DMA缓冲区,将RISC-V程序复制至该缓冲区后,让RISC-V核从缓冲区执行指令;初始程序计数器(PC)设为该函数返回的dma_handle(IO虚拟地址),设备的IOMMU会在指令和数据访存时完成IO虚拟地址到物理地址的转换。
当前遇到硬件bug:RISC-V核的64位mtvec寄存器(陷阱表基地址寄存器)仅高26位可配置,低38位固定为0x1c0。触发陷阱时,无论mtvec的高26位(记为GGGGGGG)如何设置,最终都会跳转到0xGGGGGGG0_000001c0。正常情况下,我们只需分配小缓冲区存放陷阱处理程序,将返回的dma_handle设置给mtvec即可,但该bug导致此方案失效。
内核返回的dma_handle来自为设备预留的IO虚拟地址范围,我们无法直接控制其值,仅能使用返回结果。请问是否有办法在申请DMA缓冲区时告知内核,要求返回的dma_handle低38位全为0?
指定超大对齐参数调用
dma_alloc_coherentdma_alloc_coherent支持通过对齐参数指定分配地址的对齐粒度。低38位全0的要求等价于地址需对齐到2^38字节,你可以尝试将对齐参数设置为(1ULL << 38)。需要注意的是,该方案能否生效取决于内核DMA映射框架是否支持这么大的对齐粒度,以及设备预留的IO地址范围中是否存在符合该对齐要求的连续空闲区域。创建自定义对齐DMA池
如果默认DMA分配器无法满足需求,可以通过dma_pool_create创建自定义DMA池,明确指定对齐参数为2^38,同时确保该池使用的内存区域来自设备预留IO地址中低38位全0的区间。这需要提前在设备树或驱动初始化阶段,为该DMA池预留好符合要求的地址空间。驱动层适配地址分配逻辑
若前两种方法不可行,可在驱动中手动校验分配得到的dma_handle:检查其低38位是否为0,若不符合则重新发起分配(此方法效率较低,且存在分配失败风险)。或者,你可以定制内核DMA子系统的映射回调逻辑,强制为该设备返回低38位全0的IO虚拟地址,这需要对内核DMA框架有一定了解。软硬件配合的临时规避方案
利用陷阱固定跳转地址的特性,调整陷阱处理程序的存放位置:分配一个大小至少为0x1c0 + 陷阱处理程序大小的缓冲区,将陷阱处理程序复制到缓冲区的0x1c0偏移处;随后将dma_handle的高26位配置到mtvec中,这样触发陷阱时,硬件会自动跳转到0xGGGGGGG0_000001c0,正好指向缓冲区中陷阱处理程序的起始位置。
内容的提问来源于stack exchange,提问作者Chan Kim

