多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

20、MTK M4U与DMA:给DMA设备装上“虚拟地址隔离”的保险

20、MTK M4U与DMA:给DMA设备装上“虚拟地址隔离”的保险 M4U。说白了它就是MTK家的IOMMU。你想想看在复杂的多媒体系统里多个DMA设备同时访问内存如果没有隔离机制那画面简直不敢想。一个摄像头DMA写错了地址把系统关键数据覆盖了系统直接崩给你看。M4U就是来解决这个问题的。它给每个DMA设备分配独立的虚拟地址空间让设备以为自己独占整个内存。设备驱动看到的地址是虚拟的M4U负责翻译成物理地址。这样A设备再怎么乱写也影响不到B设备的数据。核心概念M4U为每个DMA设备维护一张页表实现设备虚拟地址到物理地址的映射。每个设备有独立的地址空间互不干扰。20.1 M4U硬件架构速览先看硬件框图。M4U挂在系统总线上位于DMA设备和内存控制器之间。每个DMA master比如ISP、VENC、GPU都有一个对应的M4U端口。我习惯把M4U想象成一个“地址翻译官”设备发出的每个地址请求都要经过它检查并翻译。M4U内部有几个关键寄存器组页表基址寄存器TTBR存放当前设备页表的物理基地址地址空间控制寄存器ASID标识当前设备的地址空间ID故障状态寄存器FSR记录地址翻译失败的原因故障地址寄存器FAR记录导致故障的虚拟地址嗯这里要注意每个DMA master的M4U配置是独立的。你不能用一个配置去套所有设备必须逐个设备初始化。20.2 为DMA设备配置M4U映射配置M4U映射说白了就是三步走分配页表、填写映射、使能M4U。我给大家拆开讲。20.2.1 第一步分配页表内存页表必须放在物理内存中而且地址要对齐到页表大小。我个人习惯用dma_alloc_coherent来分配因为它保证内存是连续的且非缓存的。// 分配一级页表4KB支持4GB地址空间 struct page *pgtable_page; uint32_t *pgtable_virt; dma_addr_t pgtable_phys; pgtable_page alloc_pages(GFP_KERNEL, 0); // 一个4KB页 pgtable_virt page_address(pgtable_page); pgtable_phys page_to_phys(pgtable_page); // 清零页表 memset(pgtable_virt, 0, PAGE_SIZE);我在项目中遇到过一个问题分配页表时忘了清零结果M4U读到了随机数据翻译出来的地址全是乱的。从那以后我每次分配完页表第一件事就是memset清零这已经成了肌肉记忆。20.2.2 第二步建立虚拟地址到物理地址的映射M4U支持多种页表格式。MTK平台常用的是二级页表一级页表L1管理4MB大块二级页表L2管理4KB小块。对于DMA设备我建议用4KB粒度因为DMA buffer大小通常不是4MB的整数倍。// 建立映射虚拟地址vaddr映射到物理地址paddr大小size int m4u_map(struct m4u_device *m4u, uint32_t vaddr, uint32_t paddr, size_t size) { uint32_t l1_index (vaddr 0xFFF00000) 20; // L1索引 uint32_t l2_index (vaddr 0x000FF000) 12; // L2索引 uint32_t *l1_entry m4u-pgtable[l1_index]; uint32_t *l2_table; // 检查L1 entry是否有效 if (!(*l1_entry 0x1)) { // 分配L2页表 l2_table alloc_l2_page(); *l1_entry (uint32_t)virt_to_phys(l2_table) | 0x1; // 有效位 } else { l2_table phys_to_virt(*l1_entry 0xFFFFF000); } // 设置L2 entry l2_table[l2_index] (paddr 0xFFFFF000) | 0x3; // 有效可写 return 0; }避坑指南我曾经在映射时忘了设置页表项的权限位可读、可写、可执行结果DMA一访问就触发M4U缺页中断。记住页表项的低2位必须设置bit0是有效位bit1是可写位。20.2.3 第三步使能M4U并配置设备映射建好后需要把页表基址告诉M4U硬件然后使能对应端口的翻译功能。// 配置M4U端口 void m4u_enable_port(struct m4u_device *m4u, int port_id) { uint32_t reg_val; // 1. 设置页表基址 writel(m4u-pgtable_phys, m4u-base M4U_TTBR0(port_id)); // 2. 设置地址空间ID writel(port_id, m4u-base M4U_ASID(port_id)); // 3. 使能翻译 reg_val readl(m4u-base M4U_PORT_CTRL(port_id)); reg_val | M4U_ENABLE_BIT; writel(reg_val, m4u-base M4U_PORT_CTRL(port_id)); // 4. 清空TLB重要 writel(0x1, m4u-base M4U_TLB_INVAL); }你想想看为什么最后要清空TLB因为M4U内部有缓存如果之前这个端口用过其他页表TLB里还残留着旧映射。不清空的话新配置可能不生效。我刚开始做M4U驱动时就因为这个TLB没清空调试了整整两天。20.3 DMA传输中的M4U协同工作配置好M4U后DMA设备就可以使用虚拟地址了。驱动在启动DMA传输时传给硬件的地址是虚拟地址M4U自动完成翻译。// DMA传输示例使用M4U虚拟地址 void dma_start_transfer(struct dma_device *dma, uint32_t dev_vaddr, size_t len) { // 这里的dev_vaddr是M4U映射后的虚拟地址 // 硬件看到的是虚拟地址M4U负责翻译 writel(dev_vaddr, dma-base DMA_SRC_ADDR); writel(len, dma-base DMA_XFER_LEN); writel(DMA_START_BIT, dma-base DMA_CTRL); }重要提醒DMA传输完成后必须释放M4U映射。否则下次分配同一段虚拟地址时旧的映射还在会导致地址冲突。我见过一个案例某个模块反复申请释放DMA buffer但M4U映射只增不减最后页表内存耗尽系统直接OOM。20.4 调试M4U问题的三板斧M4U出问题时最常见的现象就是DMA传输超时或者数据错乱。我总结了三板斧查故障寄存器M4U检测到地址翻译失败时会在FSR和FAR中记录信息。读取这两个寄存器能快速定位是哪个虚拟地址出了问题。检查页表内容用dump_stack或者直接读物理内存确认页表项是否正确填写。我习惯在映射前后各打印一次页表对比差异。验证物理地址如果怀疑M4U翻译结果不对可以用逻辑分析仪抓总线看M4U输出的物理地址是否和预期一致。// 读取M4U故障信息 void m4u_dump_fault(struct m4u_device *m4u, int port_id) { uint32_t fsr readl(m4u-base M4U_FSR(port_id)); uint32_t far readl(m4u-base M4U_FAR(port_id)); pr_err(M4U fault on port %d: FSR0x%08x, FAR0x%08x\n, port_id, fsr, far); if (fsr M4U_FSR_TRANSLATION_FAULT) pr_err( Cause: Translation fault\n); if (fsr M4U_FSR_PERMISSION_FAULT) pr_err( Cause: Permission fault\n); }嗯说到调试我记得有一次客户反馈摄像头预览花屏。我第一反应就是M4U映射有问题。一查FSR果然报的是翻译故障。再查FAR发现虚拟地址落在了一个未映射的区域。原来是上层应用传错了buffer地址驱动没做校验就直接用了。从那以后我在DMA传输前都会加一层地址有效性检查。20.5 性能优化TLB预取与缓存策略M4U翻译是有开销的。每次地址翻译都要查页表如果页表不在缓存里就要访问内存延迟很大。MTK的M4U支持TLB预取功能可以提前加载页表项。我建议对频繁访问的DMA buffer开启TLB预取。配置方法很简单// 开启TLB预取 void m4u_enable_tlb_prefetch(struct m4u_device *m4u, int port_id) { uint32_t reg_val; reg_val readl(m4u-base M4U_MISC_CTRL(port_id)); reg_val | M4U_TLB_PREFETCH_EN; writel(reg_val, m4u-base M4U_MISC_CTRL(port_id)); }另外对于大块连续的DMA buffer可以考虑使用大页2MB或1GB。大页能减少页表层级提高TLB命中率。不过要注意大页要求物理内存连续分配起来比较麻烦。我个人习惯对于小于1MB的buffer用4KB页大于1MB的用2MB大页。总结一下M4U是MTK平台实现DMA设备虚拟地址隔离的关键。配置流程就是分配页表、建立映射、使能端口三步。调试时重点关注故障寄存器和页表内容。性能优化方面TLB预取和大页是两大法宝。好了这一节的内容就到这里。下一节我们会讲M4U的进阶用法——如何实现多个DMA设备共享同一段物理内存同时保持各自的虚拟地址隔离。这个场景在多媒体流水线中非常常见比如ISP和VENC共享一个buffer。
返回列表