RK平台Linux IOMMU开发:从原理到实战
在瑞芯微(RK)芯片的Linux开发中,IOMMU(输入输出内存管理单元)是个关键部件——它能实现设备虚拟地址(IOVA)与物理地址的转换,还能控制读写权限、处理缺页/总线异常,广泛用于显示(VOP)、编解码(VPU/HEVC)等场景。今天就从原理、驱动、实战、问题排查、Linux内存管理支撑五个维度,带大家快速上手RK平台IOMMU开发。
一、先搞懂:RK IOMMU的核心结构
RK IOMMU采用二级页表设计(类似Linux内核页表),配合32位地址划分,逻辑清晰且易扩展。
1.二级页表:地址转换的“目录-页码”体系
可以把二级页表理解为“图书馆查书系统”:
•一级页表(Directory Table, DT):相当于“图书目录”,每个条目(DTE)指向一本“页码簿”(二级页表);
•二级页表(Page Table, PT):相当于“页码簿”,每个条目(PTE)指向实际的“书页”(物理内存页)。
结构示意图如下:
MMU_DTE_ADDR(一级页表基地址) → 一级页表(DT)→ 二级页表(PT)→ 物理内存页
代码中定义了页表大小:
•一级页表(DT):1024个条目(NUM_DT_ENTRIES = 1024),每个条目4字节,刚好占1个4KB页(SPAGE_SIZE = 4096);
•二级页表(PT):同样1024个条目(NUM_PT_ENTRIES = 1024),也占1个4KB页。
2. 32位地址划分:3段式拆分
RK IOMMU的32位虚拟地址(IOVA)被拆成3部分,对应二级页表的索引和页内偏移:
| 地址范围(bit)
|
作用
|
大小
|
说明
|
| 31~22
|
一级页表索引(DTE)
|
10位
|
对应DT的1024个条目
|
| 21~12
|
二级页表索引(PTE)
|
10位
|
对应PT的1024个条目
|
| 11~0
|
页内偏移
|
12位
|
对应4KB页的每个字节位置
|
比如虚拟地址0x00001000:
•DTE索引=0x00001000 >> 22 = 0;
•PTE索引=(0x00001000 & 0x3FF000) >> 12 = 1;
•页内偏移=0x00001000 & 0xFFF = 0。
3. DTE与PTE:页表条目的“开关与权限”
每个页表条目(DTE/PTE)都有固定格式,核心是“存在位”和“权限位”,相当于给地址转换加了“安全锁”。
(1)DTE(一级页表条目):指向二级页表
| 字段
|
位范围
|
作用
|
| PT地址
|
31~12
|
二级页表的物理基地址
|
| 保留位
|
11~1
|
未使用,设为0
|
| 存在位(Valid)
|
bit0
|
1 =二级页表存在;0 =不存在
|
代码中通过rk_mk_dte()生成DTE,rk_dte_is_pt_valid()判断二级页表是否存在。
(2)PTE(二级页表条目):指向物理内存页
| 字段
|
位范围
|
作用
|
| 物理页地址
|
31~12
|
实际物理内存页的基地址
|
| 保留位
|
11~9
|
未使用,设为0
|
| 缓存/属性位
|
8~3
|
控制缓存策略(如读缓存、写缓冲)
|
| 写权限位
|
bit2
|
1 =允许写;0 =只读
|
| 读权限位
|
bit1
|
1 =允许读;0 =禁止读
|
| 存在位(Valid)
|
bit0
|
1 =物理页存在;0 =不存在
|
代码中通过rk_mk_pte()生成PTE,rk_pte_is_page_valid()判断物理页是否存在。
二、驱动与配置:让IOMMU “跑起来”
RK IOMMU驱动基于Linux内核IOMMU框架实现,核心是驱动文件和DTS节点配置,两者配合才能让硬件生效。
1.驱动文件:核心代码在哪?
RK IOMMU驱动源码路径:
drivers/iommu/rockchip-iommu.c
该文件实现了Linux IOMMU框架的核心回调(struct iommu_ops rk_iommu_ops),比如:
•domain_alloc:申请IOMMU域(管理页表的容器);
•map/unmap:建立/解除虚拟地址与物理地址的映射;
•attach_dev/detach_dev:绑定/解绑设备与域;
•flush_iotlb_all:清空IOMMU TLB缓存(避免旧映射干扰)。
2. DTS节点配置:告诉内核硬件信息
DTS(设备树)是内核识别IOMMU硬件的关键,需要配置中断、时钟、电源域等信息。参考文档:Documentation/devicetree/bindings/iommu/rockchip,iommu.txt。
关键配置项说明(以RK3399 VOPL IOMMU为例):
vopl_iommu: iommu@ff8f3f00 {compatible ="rockchip,iommu"; //硬件版本,v2用"rockchip,iommu-v2"reg = <0x00xff8f3f000x00x1000>; //IOMMU寄存器基地址+大小interrupts =119 IRQ_TYPE_LEVEL_HIGH0>; //异常中断(缺页/总线错)clocks = <&cru ACLK_VOP1>, <&cru HCLK_VOP1>; //IOMMU依赖的时钟clock-names ="aclk","hclk"; //时钟名称,与clocks对应power-domains = <&power RK3399_PD_VOPL>; //电源域,控制IOMMU供电iommu-cells = <0>; //固定为0,IOMMU框架要求rockchip,disable-mmu-reset; //可选,禁用MMU复位(规避部分芯片bug)};
配置项核心作用:
•compatible:区分IOMMU硬件版本(v1支持32位地址,v2支持40位地址);
•interrupts:缺页/总线异常时触发中断,内核通过rk_pagefault_done()处理;
•clocks/power-domains:控制IOMMU的时钟和供电(必须先开时钟/电源才能访问寄存器)。
三、实战开发:从0到1使用IOMMU
掌握基础后,实战分为内核配置、基础流程、调试技巧三部分,新手可按步骤操作。
1.第一步:开启内核IOMMU支持
首先需要在Linux内核中启用RK IOMMU选项,步骤如下:
1.进入内核源码目录,执行make menuconfig;
2.按路径找到选项:
Device Drivers → IOMMU Hardware Support → Rockchip IOMMU Support;
3.勾选该选项(设为y),依赖项(IOMMU_SUPPORT、ARM/ARM64)会自动启用;
4.保存配置并编译内核(make -j8),烧录镜像。
2.基础使用流程:4步实现地址映射
IOMMU的核心是“域(domain)管理映射,设备绑定域后使用映射”,最简流程如下(代码示例):
步骤1:申请IOMMU域(domain)
域是管理页表的容器,一个域可绑定多个设备(共享页表):
// 申请域(platform_bus_type对应平台设备,如VOP、VPU)structiommu_domain*domain =iommu_domain_alloc(&platform_bus_type);if(!domain) {pr_err("IOMMU domain alloc failed!n");return-ENOMEM;}
步骤2:建立虚拟地址-物理地址映射
通过iommu_map()创建映射,参数需注意地址对齐(必须是4KB的整数倍):
dma_addr_tiova =0x10000000; // 要映射的IOMMU虚拟地址(IOVA)phys_addr_tpaddr =0x80000000; // 对应的物理地址(需通过Linux内存分配接口获取)size_tsize =0x1000; // 映射大小(4KB,必须是4KB的倍数)intprot = IOMMU_READ | IOMMU_WRITE; // 权限:可读可写// 建立映射intret =iommu_map(domain, iova, paddr, size, prot);if(ret) {pr_err("IOMMU map failed! ret=%dn", ret);gotoerr_free_domain;}
步骤3:绑定设备与域
将设备(如VOP)绑定到域,设备才能使用该域的映射:
structdevice *dev = &vopl_dev; // 要绑定的设备(如VOPL设备)// 绑定设备ret = iommu_attach_device(domain, dev);if(ret) {pr_err("IOMMU attach device failed! ret=%dn", ret);gotoerr_unmap;}
步骤4:启动设备访问IOMMU
绑定完成后,设备(如VOP)访问0x10000000(IOVA)时,会自动转换为0x80000000(物理地址)。
3.进阶:调试时如何Dump页表?
开发中若遇到“地址映射错误”,可通过Dump页表排查问题(以RK3399 VOPL IOMMU为例):
假设要查虚拟地址0x10000000的映射:
1.查一级页表基地址:读IOMMU寄存器MMU_DTE_ADDR(地址0xff8f3f00),命令:
io -4 0xff8f3f00→得到DT基地址(如0x90000000);
2.算DTE索引与地址:
DTE索引=0x10000000 >> 22 = 4→ DTE地址=0x90000000 + 4*4 = 0x90000010;
3.查二级页表基地址:读DTE地址,命令:
io -4 0x90000010→得到PT基地址(如0x90001000);
4.算PTE索引与地址:
PTE索引=(0x10000000 & 0x3FF000) >> 12 = 0→ PTE地址=0x90001000 + 0*4 = 0x90001000;
5.查物理页地址:读PTE地址,命令:
io -4 0x90001000→得到物理页基地址(如0x80000000);
6.算最终物理地址:
物理地址=物理页基地址+页内偏移=0x80000000 + (0x10000000 & 0xFFF) = 0x80000000。
通过以上步骤,可验证映射是否正确。
四、避坑指南:常见问题与解决方案
RK IOMMU开发中,这些问题很容易遇到,提前掌握解决方案能少走弯路:
| 问题现象
|
可能原因
|
解决方案
|
| 报“pagefault中断”
|
1.访问未映射的IOVA;2.越界访问;3.未映射就访问
|
1.检查iommu_map的IOVA范围;2.确认访问地址未超出映射大小;3.确保map在attach前执行
|
| enable stall异常
|
缺页中断未处理,设备继续访问IOMMU
|
先通过rk_pagefault_done()处理缺页,再重新使能stall
|
| IOMMU寄存器无法访问
|
未开启IOMMU的电源域(PD)或时钟
|
调用pm_runtime_get_sync(dev)开启电源,clk_bulk_enable()开启时钟
|
| 持续报中断
|
DTS中中断号配置错误
|
核对芯片手册,修正interrupts字段的中断号
|
| 开机闪屏(VOP场景)
|
使能IOMMU时VOP正在取帧
|
等待VOP帧显示完成后,再使能IOMMU
|
| 刷TLB导致性能下降
|
离散buffer多次刷TLB
|
添加标志位,批量映射后只刷一次TLB(参考代码shootdown_entire)
|
五、Linux系统内存管理:IOMMU开发的底层支撑
IOMMU的核心是“设备虚拟地址→物理地址”转换,而物理地址的分配、管理依赖Linux内存管理机制。以下从核心概念、内存分配、地址转换、TLB协同四个方面,讲解与IOMMU开发强相关的内存管理逻辑。
1.核心概念:Linux的“地址空间与页表”
Linux采用“虚拟地址统一管理”机制,所有CPU访问(内核/用户)、设备访问(需IOMMU)都基于虚拟地址,最终通过页表转换到物理地址。
(1)地址空间划分
Linux将32/64位地址空间分为“用户空间”和“内核空间”(以ARM64为例):
| 地址空间
|
范围(ARM64)
|
用途
|
与IOMMU关联
|
| 用户空间
|
0x00000000_00000000 ~ 0x0000007F_FFFFFFFF
|
应用程序代码/数据
|
设备一般不直接访问,需通过内核中转
|
| 内核空间
|
0xFFFF0000_00000000 ~ 0xFFFFFFFF_FFFFFFFF
|
内核代码/驱动/共享内存
|
IOMMU映射的物理内存多来自此空间
|
(2)Linux内核页表层级(与IOMMU页表对比)
Linux内核(如ARM64)采用四级页表(比RK IOMMU的二级页表更精细),用于CPU的虚拟地址→物理地址转换:
•PGD(页全局目录):最高级页表,对应地址高位(如ARM64的63~48位);
•PUD(页上级目录):二级页表,对应地址中位(47~39位);
•PMD(页中间目录):三级页表,对应地址中低位(38~30位);
•PTE(页表项):最低级页表,对应物理页基地址(29~12位)+权限位。
与RK IOMMU页表的区别:
| 对比维度
|
Linux内核页表
|
RK IOMMU页表
|
| 服务对象
|
CPU(内核/用户空间访问)
|
外设(如VOP、VPU)
|
| 页表层级
|
四级(PGD→PUD→PMD→PTE)
|
二级(DT→PT)
|
| 虚拟地址类型
|
CPU虚拟地址(内核/用户VA)
|
设备虚拟地址(IOVA)
|
| 管理主体
|
内核内存管理模块(MM)
|
RK IOMMU驱动
|
2.内存分配:IOMMU所需物理地址从哪来?
IOMMU映射的paddr(物理地址),需通过Linux内核提供的内存分配接口获取,核心接口分三类:
(1)伙伴系统:分配连续物理页(适合大块内存)
伙伴系统是Linux内核最底层的内存分配机制,管理“物理页帧”(4KB/2MB/1GB等),适合分配连续物理内存(IOMMU常需连续内存,避免设备访问离散地址出错)。
核心接口:
// 分配order个连续页(大小=2^order * 4KB),返回页结构体指针structpage*alloc_pages(gfp_tgfp_mask,unsignedintorder);// 简化接口:分配1个页(4KB),返回内核虚拟地址void*__get_free_page(gfp_tgfp_mask);// 示例:分配4个连续页(16KB),用于IOMMU映射structpage*pages =alloc_pages(GFP_KERNEL | __GFP_DMA,2); // order=2 → 2^2=4页if(!pages) {pr_err("Alloc contiguous pages failed!n");return-ENOMEM;}// 转换为物理地址(供iommu_map使用)phys_addr_tpaddr =page_to_phys(pages);
•gfp_mask:分配标志,GFP_KERNEL表示内核可睡眠等待内存,__GFP_DMA表示从DMA可用区域分配(适合设备访问);
•order:分配页数的指数,order=0→1页(4KB),order=1→2页(8KB),最大order=11→2048页(8MB)。
(2)Slab分配器:分配小内存(适合小块对象)
Slab分配器基于伙伴系统,将连续页拆分为“小对象”(如结构体、缓冲区),适合分配小于4KB的内存,不适合IOMMU映射(IOMMU需物理页对齐的地址)。
核心接口:
// 分配size字节内存,返回内核虚拟地址(地址页对齐)void*kmalloc(size_tsize,gfp_tgfp_mask);// 示例:分配256字节内存(用于驱动私有数据,不用于IOMMU映射)structiommu_priv*priv =kmalloc(sizeof(*priv), GFP_KERNEL);
(3)DMA分配接口:直接分配“设备可访问内存”
DMA分配接口是IOMMU开发的核心接口,它直接返回“物理地址”和“内核虚拟地址”,且确保内存可被设备直接访问(如避开不可缓存区域、锁定页面不被回收)。
核心接口:
// 分配size字节连续物理内存,返回:// - 内核虚拟地址(virt_addr):供内核访问;// - DMA地址(dma_addr):即物理地址,供IOMMU映射使用;void*dma_alloc_coherent(structdevice *dev,size_tsize,dma_addr_t*dma_addr,gfp_tgfp_mask);// 示例:为VOP设备分配4KB内存,用于IOMMU映射dma_addr_tpaddr; // 输出物理地址(供iommu_map)void*virt_addr =dma_alloc_coherent(&vopl_dev,0x1000, &paddr, GFP_KERNEL);if(!virt_addr) {pr_err("DMA alloc failed!n");return-ENOMEM;}// 后续调用iommu_map(domain, iova, paddr, 0x1000, prot)
为什么优先用dma_alloc_coherent?
•自动确保内存“设备可访问”(如在RK芯片的DMA区域);
•直接返回物理地址(dma_addr),无需手动转换;
•自动锁定页面(避免被内核回收或swap到磁盘,导致设备访问失效)。
3.地址转换:虚拟地址与物理地址的转换
IOMMU开发中,常需在“内核虚拟地址(VA)”与“物理地址(PA)”之间转换,核心转换接口:
| 转换方向
|
接口函数
|
适用场景
|
| 内核VA →物理PA
|
phys_addr_t virt_to_phys(const void *virt)
|
已知内核虚拟地址,获取物理地址供IOMMU映射
|
| 物理PA →内核VA
|
void *phys_to_virt(phys_addr_t phys)
|
已知物理地址,获取内核虚拟地址供内核访问
|
| 内核VA → DMA地址(PA)
|
dma_addr_t virt_to_dma(struct device *dev, const void *virt)
|
设备相关的物理地址转换
|
| 页结构体→物理PA
|
phys_addr_t page_to_phys(const struct page *page)
|
从alloc_pages返回的page获取PA
|
示例:结合IOMMU映射的转换流程
// 1. 用alloc_pages分配页structpage*page =alloc_pages(GFP_KERNEL,0);// 2. 转换为物理地址(供iommu_map)phys_addr_tpaddr =page_to_phys(page);// 3. 转换为内核虚拟地址(供内核写数据)void*virt_addr =page_address(page);// 4. 内核写数据到虚拟地址memcpy(virt_addr,"IOMMU test data",16);// 5. IOMMU映射(IOVA→PA)iommu_map(domain,0x10000000, paddr,0x1000, IOMMU_READ | IOMMU_WRITE);
4. TLB协同:内核TLB与IOMMU TLB的刷新
TLB(Translation Lookaside Buffer)是“页表缓存”,用于加速地址转换(CPU有CPU TLB,IOMMU有IOMMU TLB)。当页表修改后(如iommu_map/iommu_unmap),需刷新TLB,否则旧映射会干扰新映射。
(1)Linux内核TLB刷新(CPU用)
内核修改页表后(如mmap/kmap),需调用以下接口刷新CPU TLB:
// 刷新指定虚拟地址范围的TLB(用户空间)voidflush_tlb_range(structvm_area_struct *vma,unsignedlongstart,unsignedlongend);// 刷新整个内核空间的TLBvoidflush_tlb_kernel_range(unsignedlongstart,unsignedlongend);
(2)IOMMU TLB刷新(设备用)
RK IOMMU驱动提供专用接口,刷新IOMMU TLB:
// 刷新整个IOMMU TLB(最常用,如unmap后)voidiommu_flush_iotlb_all(structiommu_domain *domain);// 刷新指定IOVA范围的TLB(精细刷新,减少性能损耗)voidiommu_flush_iotlb_range(structiommu_domain *domain,dma_addr_tiova,size_tsize);
实战注意点:
•修改IOMMU页表后(如iommu_unmap),必须调用iommu_flush_iotlb_all,否则设备仍会使用旧映射;
•批量修改映射时,建议先完成所有iommu_map,再统一刷新TLB(避免多次刷新导致性能下降)。
5.内存锁定:避免IOMMU映射的内存被回收
Linux内核会对“不常用内存”进行回收(如swap到磁盘),但IOMMU映射的物理内存若被回收,会导致设备访问“无效地址”(报pagefault)。因此需通过内存锁定接口,禁止内核回收相关内存。
核心接口:
// 锁定指定页,禁止被回收或移动intget_page(structpage *page);// 解锁页,允许回收(需与get_page成对调用)voidput_page(structpage *page);// 示例:锁定IOMMU映射的页structpage *page = alloc_pages(GFP_KERNEL,0);get_page(page); // 锁定页// ... 执行IOMMU映射、设备访问 ...put_page(page); // 设备停止访问后,解锁页
DMA分配的内存无需手动锁定:dma_alloc_coherent会自动锁定内存,直到调用dma_free_coherent释放,无需额外调用get_page。
六、总结
RK平台IOMMU开发的核心是“Linux内存管理为底层支撑+ IOMMU实现设备地址转换”,关键逻辑可总结为3步:
1.内存分配:通过dma_alloc_coherent/alloc_pages获取物理地址(paddr),确保内存可被设备访问;
2.地址映射:调用iommu_map建立IOVA→paddr的映射,刷新IOMMU TLB;
3.设备访问:设备绑定IOMMU域后,通过IOVA访问物理内存,内核通过页表确保CPU与设备访问的一致性。
开发中需重点关注:
•物理地址需从Linux内存接口获取,不可硬编码(不同芯片物理地址范围不同);
•页表修改后必须刷新TLB(CPU TLB/IOMMU TLB分别处理);
•设备访问的内存需锁定,避免被内核回收。
若需进一步深入,可参考Linux内核文档:Documentation/mm/(内存管理原理)、Documentation/devicetree/bindings/iommu/(IOMMU设备树规范),或RK官方芯片手册的“内存控制器”章节。
有疑问的小伙伴欢迎在评论区留言,一起交流IOMMU与内存管理的协同开发技巧~
- 随机文章
- 热门文章
- ups电源—UPS电源蜂鸣不停这样搞定
- 安科瑞开关柜局放监测方案,守护电力设备安全运行,国网电科院报告认证!
- 信号发生器AFG31052在多载波信号生成中的应用
- 聚焦无刷吊扇技术痛点,自适应技术如何驱动行业升级?--【其利天下】
- 开疆智能ModbusTCP转Canopen网关连接汇川AM403PLC与编码器配置案例
- 阻燃橡胶板是什么材料的好一点
- 南湖之滨,百年老字号真真老老以技术创新铸造食品安全防线
- 中国中药协会第六届杜仲大会召开 推动中医药产业应用创新
- 5月SUV批发销量榜爆冷!冠亚军销量下跌,星越L逆袭杀入三甲
- 探访北宋东京城顺天门遗址博物馆
- 1感染甲流后该如何科学调养?饮食起居这样做,感染甲流后该如何科学调养?饮食起居这样做
- 2马克龙去的这所大学,太宝藏了吧!,马克龙去的这所大学,太宝藏了吧!
- 3北方多地迎来降雪降温天气 各部门联动“战”寒潮筑牢安全防线,北方多地迎来降雪降温天气 各部门联动“战”寒潮筑牢安全防线
- 4“无保护”攀岩真的无保护吗?“无保护”攀岩真的无保护吗?
- 5福州发布公告:吴石故居将封闭施工,展开系统性修缮
- 611月份“菜篮子”产品价格呈现季节性上涨 多因素推高生产成本,11月份“菜篮子”产品价格呈现季节性上涨 多因素推高生产成本
- 7科学家的照片排在董事长之上,科学家的照片排在董事长之上
- 8城中话债|激活民间投资:让有效率的资本站上C位
- 9何立峰:有力有序有效做好2026年金融重点工作
- 10晚间重磅!又一万亿级券商将诞生 券业并购潮涌
