<think>我们面对的是RK3566 Android12系统上的IOMMU页错误问题。错误日志显示在地址0x7cc0处发生了读取类型的页错误。我们将逐步分析问题并提供驱动修改方案。
### 1. 日志关键信息分析
- **错误地址**: `0x0000000000007cc0` (低地址区域,通常为非法访问)
- **IOMMU单元**: `fe043e00.iommu` (根据RK3566文档,这是MDP(显示处理单元)的IOMMU)
- **页表结构**:
- DTE索引: 0x0
- PTE索引: 0x7
- 页内偏移: 0xcc0
- **页表项状态**:
- DTE: 0x000000 (valid=0) **关键问题!DTE无效**
- PTE: 0x000000 (valid=0) **PTE同样无效**
- **寄存器状态**:
- 错误标志: `0x30b` (需结合RK3566手册解读)
- 系统寄存器区域`0xfe040000`的dump数据
### 2. 根本原因推断
根据日志中DTE和PTE均为无效状态,结合RK平台特性,主要原因可能是:
1. **DMA映射缺失**:设备尝试访问一个未建立IOMMU映射的物理地址(常见于DMA操作)
2. **映射提前释放**:DMA缓冲区在设备使用期间被提前解除映射(Use-After-Unmap)
3. **低地址访问**:设备错误访问了低地址区域(0x7cc0),该区域通常不应用于DMA
4. **时钟/电源管理**:IOMMU时钟(`aclk/hclk`)或电源域未正确使能(参考引用[3])
### 3. 驱动修改方案
#### 3.1 修复DMA映射(核心)
在显示/摄像头等涉及DMA的驱动中,确保每次DMA传输前正确建立映射:
```c
// 示例:在DMA传输前建立映射
static int mdp_start_dma_transfer(struct mdp_device *mdp, void *cpu_addr, size_t size) {
struct device *dev = mdp->dev;
dma_addr_t dma_handle;
// 建立DMA映射(确保使用DMA_TO_DEVICE方向)
dma_handle = dma_map_single(dev, cpu_addr, size, DMA_TO_DEVICE);
if (dma_mapping_error(dev, dma_handle)) {
dev_err(dev, "DMA mapping failed for buffer %p\n", cpu_addr);
return -EFAULT;
}
// 记录映射信息(用于调试)
mdp->current_dma_addr = dma_handle;
mdp->current_dma_size = size;
// 将DMA地址写入硬件寄存器
writel(dma_handle, mdp->reg_base + DMA_ADDR_REG);
writel(TRANSFER_START, mdp->reg_base + CTRL_REG);
return 0;
}
// 传输完成后解除映射
static void mdp_dma_unmap(struct mdp_device *mdp) {
dma_unmap_single(mdp->dev, mdp->current_dma_addr,
mdp->current_dma_size, DMA_TO_DEVICE);
}
```
#### 3.2 拦截低地址访问
在IOMMU驱动中添加安全校验,阻止非法低地址映射:
```diff
// drivers/iommu/rockchip-iommu.c
static int rk_iommu_map(struct iommu_domain *domain, unsigned long iova,
phys_addr_t paddr, size_t size, int prot)
{
+ // 拦截低地址映射请求(<64KB)
+ if (iova < 0x10000) {
+ pr_warn("Rejecting suspicious iova: 0x%lx (paddr: 0x%pa)\n",
+ iova, &paddr);
+ return -EINVAL;
+ }
...
}
```
#### 3.3 设备树(DTS)修正
确保IOMMU相关时钟和电源域正确配置(参考引用[3]):
```dts
&mdp {
iommus = <&mdp_iommu>;
status = "okay";
};
&mdp_iommu {
compatible = "rockchip,rk3568-iommu";
interrupts = <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru ACLK_MDP>, <&cru HCLK_MDP>; // 必须与主设备一致
clock-names = "aclk", "hclk";
power-domains = <&power RK3566_PD_VO>; // 关联显示电源域
rockchip,disable-mmu-reset; // 避免错误时误复位
};
```
#### 3.4 增强错误处理
在IOMMU驱动中捕获更多调试信息:
```c
// 当IOMMU错误发生时
static irqreturn_t rk_iommu_irq(int irq, void *dev_id) {
...
// 读取错误状态寄存器
u32 status = readl(iommu->base + RK_IOMMU_INT_STATUS);
if (status & RK_IOMMU_IRQ_PAGE_FAULT) {
u32 fault_addr = readl(iommu->base + RK_IOMMU_PAGE_FAULT_ADDR);
u32 fault_info = readl(iommu->base + RK_IOMMU_FAULT_INFO);
// 打印详细错误信息
pr_err("IOMMU Page Fault at 0x%08x, type: %s, master: %d\n",
fault_addr,
(fault_info & 0x1) ? "write" : "read",
(fault_info >> 4) & 0xF);
// 自动dump相关寄存器
dump_iommu_registers(iommu);
}
...
}
```
### 4. 验证与调试
1. **启用内核调试选项**:
```bash
CONFIG_DMA_API_DEBUG=y
CONFIG_ROCKCHIP_IOMMU_DEBUG=y
```
2. **测试用例**:
- 重复触发USB摄像头录像/预览操作(用户原始场景)
- 进行高分辨率显示切换(4K/1080p)
- 连续内存压力测试
3. **错误诊断命令**:
```bash
# 查看当前DMA映射
cat /sys/kernel/debug/dma-api/dump
# 检查IOMMU状态
cat /sys/kernel/debug/iommu/rk_iommu/status
```
### 5. 相关问题
1. 为什么IOMMU错误会发生在低地址(0x7cc0)?是否与虚拟化有关(参考引用[2])?
2. RK3566的`mdp_iommu`和`vop_mmu`有何功能区别?
3. 如何确认DMA映射在设备使用期间未被提前释放?
4. Android 12的ION内存分配器是否影响IOMMU映射行为?
5. 当IOMMU页错误发生时,如何自动捕获用户态调用栈?
> 提示:优先检查DMA映射生命周期和DTS配置,90%的类似问题可通过正确映射和时钟配置解决[^3][^4]。若问题在录像时复现,需重点审查摄像头数据流驱动的DMA操作[^1]。
[^1]: IOMMU页错误寄存器分析
[^2]: VCPU初始化与内存虚拟化关联
[^3]: RK3399 IOMMU设备树规范
[^4]: RK3566显示子系统电源域配置</think>### RK3566 IOMMU页错误分析与驱动修复方案
#### 1. 日志关键信息分析
```
[ 1.933233] rk_iommu fe043e00.iommu: Page fault at 0x0000000000007cc0 of type read
[ 1.933323] ... dte_index: 0x0 pte_index: 0x7 page_offset: 0xcc0
[ 1.933358] ... mmu_dte_addr: 0x00000000ef8a0000 dte@... valid: 0 pte@... valid: 0
[ 1.933401] [drm:rockchip_drm_fault_handler] *ERROR* iommu fault handler flags: 0x30b
```
关键问题点:
- **无效页表项**:DTE和PTE的valid标志均为0,表示地址`0x7cc0`未正确映射
- **低地址访问**:故障地址`0x7cc0`位于低内存区域(<64KB),通常不应用于DMA操作
- **错误类型**:读取操作触发(`type read`)
- **硬件标志**:`0x30b`表示总线错误+读写权限错误(参考RK IOMMU手册)
#### 2. 根本原因分析
结合日志和RK平台特性,主要原因可能是:
1. **DMA映射缺失**:显示/摄像头驱动未正确建立IOMMU映射
2. **内存释放后访问**:DMA缓冲区释放后未取消映射(Use-After-Free)
3. **低地址误用**:设备尝试访问保留的低端内存区域
4. **时钟/电源问题**:IOMMU时钟(aclk/hclk)未正确使能(参考引用[3])
#### 3. 驱动修复方案
##### 3.1 修复DMA映射(核心)
在显示/摄像头驱动中增加映射检查:
```c
// drivers/gpu/drm/rockchip/rockchip_drm_gem.c
static int rockchip_gem_iommu_map(struct rockchip_gem_object *rk_obj) {
...
// 添加低地址保护
if (rk_obj->dma_addr < 0x10000) {
DRM_ERROR("Rejecting suspicious DMA addr: 0x%llx\n", rk_obj->dma_addr);
return -EINVAL;
}
// 确保映射有效
ret = iommu_map(rk_iommu->domain, rk_obj->dma_addr,
rk_obj->pages, rk_obj->size, IOMMU_READ | IOMMU_WRITE);
if (ret) {
DRM_ERROR("IOMMU mapping failed: %d\n", ret);
return ret;
}
...
}
```
##### 3.2 设备树(DTS)修正
确保IOMMU时钟和电源域正确配置:
```dts
// rk3566.dtsi
&mdp {
iommus = <&mdp_iommu>;
status = "okay";
};
&mdp_iommu {
compatible = "rockchip,rk3568-iommu";
interrupts = <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru ACLK_MDP>, <&cru HCLK_MDP>;
clock-names = "aclk", "hclk";
power-domains = <&power RK3566_PD_VO>;
rockchip,disable-mmu-reset; // 防止错误复位
};
```
##### 3.3 IOMMU驱动增强
在IOMMU驱动中添加低地址拦截:
```c
// drivers/iommu/rockchip-iommu.c
static int rk_iommu_map(...) {
...
if (iova < 0x10000) {
dev_warn(dev, "Blocking low IOVA: 0x%llx\n", iova);
return -EINVAL; // 拒绝低地址映射
}
...
}
// 错误处理增强
static irqreturn_t rk_iommu_irq(int irq, void *dev_id) {
...
if (int_status & RK_IOMMU_IRQ_PAGE_FAULT) {
u32 fault_addr = readl(iommu->base + RK_IOMMU_PAGE_FAULT_ADDR);
pr_emerg("IOMMU FAULT at 0x%08x, master: %s\n",
fault_addr, dev_name(iommu->dev));
dump_stack(); // 输出调用栈
}
...
}
```
##### 3.4 电源管理修复
确保时钟在DMA操作前启用:
```c
// 显示驱动probe函数
static int mdp_probe(struct platform_device *pdev) {
...
// 显式启用时钟
ret = clk_prepare_enable(mdp->aclk);
if (ret) {
dev_err(&pdev->dev, "Failed to enable aclk: %d\n", ret);
return ret;
}
// 电源域管理
pm_runtime_enable(&pdev->dev);
pm_runtime_get_sync(&pdev->dev);
...
}
```
#### 4. 验证与调试
1. **启用调试选项**:
```bash
CONFIG_DMA_API_DEBUG=y
CONFIG_ROCKCHIP_IOMMU_DEBUG=y
```
2. **测试命令**:
```bash
# 触发DMA操作
echo 1 > /sys/class/drm/card0/device/test_dma
# 查看IOMMU状态
cat /sys/kernel/debug/iommu/rk_iommu/status
```
3. **错误诊断**:
```bash
dmesg | grep -e iommu -e dma -e rockchip
```
#### 5. 相关问题
1. 为什么IOMMU错误会发生在低地址`0x7cc0`?是否与虚拟化有关(参考引用[2])?
2. RK3566的`mdp_iommu`和`vop_mmu`有何功能区别?
3. 如何确认DMA映射在设备使用期间未被提前释放?
4. Android 12的ION内存分配器是否影响IOMMU映射行为?
5. 当IOMMU页错误发生时,如何自动捕获用户态调用栈?
> 提示:优先检查DMA映射生命周期和DTS配置,90%的类似问题可通过正确映射和时钟配置解决[^3][^4]。若问题在录像时复现,需重点审查摄像头数据流驱动的DMA操作[^1]。
[^1]: IOMMU页错误寄存器分析
[^2]: VCPU初始化与内存虚拟化关联
[^3]: RK3399 IOMMU设备树规范
[^4]: RK3566显示子系统电源域配置