GPU MMU框架和页表格式分析
整体架构
系统架构图
架构层次
应用层
↓
设备内存管理层 (devicemem)
↓
MMU通用管理层 (mmu_common)
↓
物理内存资源层 (PMR) + 设备特定层 (device)
↓
物理内存堆 (PhysHeap)
核心组件关系
MMU_CONTEXT
├── MMU_DEVICEATTRIBS (设备属性)
├── MMU_PHYSMEM_CONTEXT (物理内存上下文)
│ ├── RA_ARENA (资源分配器)
│ ├── MMU_CTX_CLEANUP_DATA (清理数据)
│ └── MMU_MEMORY_MAPPING (内存映射)
└── MMU_Levelx_INFO (多级页表信息)
├── MMU_MEMORY_DESC (内存描述符)
└── apsNextLevel[] (下级页表数组)
核心数据结构关系
页表层次结构
多级页表设计
Level 3 (PC - Page Catalogue) <- 顶级页表
↓
Level 2 (PD - Page Directory) <- 中级页表
↓
Level 1 (PT - Page Table) <- 叶子页表 (PTE指向数据页)
↓
Level 0 (Data Pages) <- 实际数据页面
虚拟地址分解
虚拟地址位字段分解:
┌─────────┬─────────┬─────────┬──────────────┐
│ PC Index│ PD Index│ PT Index│ Page Offset │
│ │ │ │ │
└─────────┴─────────┴─────────┴──────────────┘
地址计算函数:
- _CalcPCEIdx(): 计算页目录索引
- _CalcPDEIdx(): 计算页表目录索引
- _CalcPTEIdx(): 计算页表项索引
页表项格式
通用页表项结构
typedef struct _MMU_PxE_CONFIG_ {
IMG_UINT32 uiBytesPerEntry; // 页表项大小 (4字节或8字节)
IMG_UINT32 uiAddrLog2Align; // 地址对齐位数
IMG_UINT32 uiAddrShift; // 地址移位量
IMG_UINT64 uiAddrMask; // 地址掩码
IMG_UINT64 uiProtMask; // 保护位掩码
IMG_UINT64 uiValidEnMask; // 有效位掩码
IMG_UINT64 uiPendingEnMask; // 挂起位掩码
IMG_UINT64 uiParityBitMask; // 奇偶校验位掩码
IMG_UINT32 uiParityBitShift; // 奇偶校验位移位
const IMG_CHAR *pszPxLevelStr; // 级别名称字符串
} MMU_PxE_CONFIG;
64位页表项格式
64位页表项位字段布局:
| parity | Physical Address (对齐后的物理地址) |
Protection Flags |
Valid | Pending | Reserv |
|---|---|---|---|---|---|
| 63 | ... | ... | 2 | 1 | 0 |
字段说明:
- Physical Address: 对齐后的物理地址,右移后左移去除低位
- Protection Flags: 读写权限、缓存属性等
- Valid: 页表项是否有效
- Pending: 页面是否正在加载(用于页面换入换出)
- Parity: 奇偶校验位,用于错误检测
32位页表项格式
32位页表项位字段布局:
| Physical Address (对齐后的物理地址) |
Protection Flags |
Valid | Reserv |
|---|---|---|---|
| 31 | 1 | 0 |
页表项构造过程
// 64位页表项构造
ui64PxE64 = uiAddr // 物理地址
>> psConfig->uiAddrLog2Align // 右移去除对齐位
<< psConfig->uiAddrShift // 左移到正确位置
& psConfig->uiAddrMask; // 应用地址掩码
ui64PxE64 |= uiProtFlags; // 添加保护标志
// 添加奇偶校验位
if (psConfig->uiParityBitMask) {
ui64PxE64 |= _GetParityBit(psDevVAddr->uiAddr ^ psDevPAddr->uiAddr)
<< psConfig->uiParityBitShift;
}
设备特定配置
MMU设备属性结构
typedef struct _MMU_DEVICEATTRIBS_ {
const MMU_PxE_CONFIG *psBaseConfig; // 基础级别配置
const MMU_DEVVADDR_CONFIG *psTopLevelDevVAddrConfig; // 顶级虚拟地址配置
// 设备特定的保护位计算函数
IMG_UINT32 (*pfnDerivePCEProt4)(IMG_UINT32 uiProtFlags);
IMG_UINT64 (*pfnDerivePCEProt8)(IMG_UINT32 uiProtFlags, IMG_UINT32 uiLog2DataPageSize);
IMG_UINT32 (*pfnDerivePDEProt4)(IMG_UINT32 uiProtFlags);
IMG_UINT64 (*pfnDerivePDEProt8)(IMG_UINT32 uiProtFlags, IMG_UINT32 uiLog2DataPageSize);
IMG_UINT32 (*pfnDerivePTEProt4)(IMG_UINT32 uiProtFlags);
IMG_UINT64 (*pfnDerivePTEProt8)(IMG_UINT32 uiProtFlags, IMG_UINT32 uiLog2DataPageSize);
// 页面大小配置函数
PVRSRV_ERROR (*pfnGetPageSizeConfiguration)(IMG_UINT32 uiLog2DataPageSize,
const MMU_PxE_CONFIG **ppsMMUPDEConfig,
const MMU_PxE_CONFIG **ppsMMUPTEConfig,
const MMU_DEVVADDR_CONFIG **ppsMMUDevVAddrConfig,
IMG_HANDLE *phPriv);
MMU_TYPE eMMUType; // MMU类型
const IMG_CHAR *pszMMUPxPDumpMemSpaceName; // PDump内存空间名称
} MMU_DEVICEATTRIBS;
虚拟地址配置
typedef struct _MMU_DEVVADDR_CONFIG_ {
IMG_UINT64 uiPCIndexMask; // PC索引掩码
IMG_UINT32 uiPCIndexShift; // PC索引移位
IMG_UINT32 uiNumEntriesPC; // PC条目数
IMG_UINT64 uiPDIndexMask; // PD索引掩码
IMG_UINT32 uiPDIndexShift; // PD索引移位
IMG_UINT32 uiNumEntriesPD; // PD条目数
IMG_UINT64 uiPTIndexMask; // PT索引掩码
IMG_UINT32 uiPTIndexShift; // PT索引移位
IMG_UINT32 uiNumEntriesPT; // PT条目数
IMG_UINT64 uiPageOffsetMask; // 页面偏移掩码
IMG_UINT64 uiOffsetInBytes; // 字节偏移
} MMU_DEVVADDR_CONFIG;
页面大小支持
多页面大小架构
支持的页面大小:
├── 4KB (标准页面大小)
├── 16KB (中等页面大小)
├── 64KB (大页面大小)
├── 2MB (超大页面大小)
└── 1GB (巨大页面大小,某些架构)
页面大小配置获取
// 根据页面大小获取对应的MMU配置
eError = psDevAttrs->pfnGetPageSizeConfiguration(
uiLog2DataPageSize, // 输入: 页面大小(log2)
&psMMUPDEConfig, // 输出: PDE配置
&psMMUPTEConfig, // 输出: PTE配置
&psMMUDevVAddrConfig, // 输出: 虚拟地址配置
&hPriv // 输出: 私有句柄
);
特殊页面类型
后备页面系统
后备页面类型:
├── SCRATCH_PAGE (临时页面)
│ ├── 用于稀疏内存的占位符
│ ├── 读写权限
│ └── 初始化值: 0
└── DEV_ZERO_PAGE (零页面)
├── 用于零内存区域
├── 只读权限
└── 初始化值: 0
页面状态标识
// 页表项状态组合
enum {
MMU_INVALID = 0, // 00: 无效
MMU_VALID = 1, // 01: 有效
MMU_PENDING = 2, // 10: 挂起
MMU_INCONSIST = 3 // 11: 不一致(挂起+有效)
};
内存映射流程
地址转换流程
虚拟地址 → 物理地址转换:
1. 提取PC索引 → 查找PC表项 → 获取PD基址
2. 提取PD索引 → 查找PD表项 → 获取PT基址
3. 提取PT索引 → 查找PT表项 → 获取页面基址
4. 页面基址 + 页内偏移 → 最终物理地址
页表操作类型
页表操作:
├── 分配操作 (_MMU_AllocLevel)
│ ├── 递归分配多级页表
│ ├── 按需创建下级页表
│ └── 引用计数管理
├── 释放操作 (_MMU_FreeLevel)
│ ├── 递归释放页表
│ ├── 引用计数递减
│ └── 延迟物理内存释放
└── 映射操作 (MMU_MapPages/MMU_MapPMRFast)
├── 设置页表项
├── 缓存管理
└── TLB失效
优化特性
快速映射路径
快速映射优化:
├── 批量地址获取 (减少PMR调用)
├── 直接页表项写入 (避免函数调用开销)
├── 配置参数缓存 (避免重复计算)
├── 分块处理大范围映射
└── 重映射策略控制
缓存一致性管理
缓存管理层次:
├── CPU缓存 (PhysHeapPagesClean)
├── GPU MMU缓存 (pfnMMUCacheInvalidate)
├── TLB刷新 (MMU_LEVEL_1失效)
└── 延迟释放 (等待缓存失效完成)
调试和分析支持
故障诊断信息
typedef struct _MMU_FAULT_DATA_ {
MMU_FAULT_TYPE eType; // 故障类型
MMU_LEVEL eTopLevel; // 顶级页表类型
MMU_LEVEL_DATA sLevelData[MMU_MAX_LEVEL]; // 各级页表数据
} MMU_FAULT_DATA;
typedef struct _MMU_LEVEL_DATA_ {
IMG_UINT32 ui32Index; // 页表项索引
IMG_UINT32 ui32NumOfEntries; // 页表总条目数
IMG_UINT32 uiBytesPerEntry; // 每个条目的字节数
IMG_UINT64 ui64Address; // 页表项内容
const IMG_CHAR *psDebugStr; // 调试字符串
} MMU_LEVEL_DATA;
PDump集成
PDump支持:
├── 符号地址生成 (MMU_ContextDerivePCPDumpSymAddr)
├── 页表内容转储 (PDumpMMUDumpPxEntries)
├── MMU上下文管理 (PDUMP_MMU_ALLOC_MMUCONTEXT)
└── 连续转储支持 (PDUMP_FLAGS_CONTINUOUS)
GPU MMU代码详细分析
关键宏定义分析
核心宏定义
#define SCRATCH_PAGE 1
#define DEV_ZERO_PAGE 2
#define PVR_SCRATCH_PAGE_INIT_VALUE 0
#define PVR_ZERO_PAGE_INIT_VALUE 0
关键分析: 定义两种特殊页面类型
- SCRATCH_PAGE: 临时页面,用于稀疏内存分配时的占位符
- DEV_ZERO_PAGE: 零页面,用于只读零内存区域
- 这两种特殊页面是GPU MMU管理稀疏内存的核心机制
数据结构定义
MMU清理数据结构
typedef struct _MMU_CTX_CLEANUP_DATA_
{
ATOMIC_T iRef; // 引用计数
POS_LOCK hCleanupLock; // 清理锁
DLLIST_NODE sMMUCtxCleanupItemsHead; // 清理项链表
IMG_BOOL bMMUContextExists; // MMU上下文是否存在
#if defined(SUPPORT_CUSTOM_OSID_EMISSION)
IMG_UINT32 ui32OSid; // 操作系统ID
#endif
} MMU_CTX_CLEANUP_DATA;
设计要点: 管理延迟释放机制,页表不能立即释放,需要等待MMU cache失效
- 引用计数: 确保在所有清理任务完成前不会释放元数据,使用引用计数确保清理数据生命周期
- 清理锁: 保护并发访问
- 存在标志: 防止访问已销毁的MMU上下文
MMU清理项
typedef struct _MMU_CLEANUP_ITEM_
{
PVRSRV_CLEANUP_THREAD_WORK sCleanupThreadFn; // 清理线程工作项
DLLIST_NODE sMMUMappingHead; // 待释放的内存映射
DLLIST_NODE sMMUCtxCleanupItem; // 在上下文中的节点
MMU_CTX_CLEANUP_DATA *psMMUCtxCleanupData; // 清理元数据引用
PVRSRV_CLIENT_SYNC_PRIM *psSync; // 同步原语
IMG_UINT32 uiRequiredSyncVal; // 所需同步值
IMG_UINT32 uiRequiredPowerOffCounter; // 电源关闭计数器
PVRSRV_DEVICE_NODE *psDevNode; // 设备节点
} MMU_CLEANUP_ITEM;
关键机制:
- 延迟释放: 等待MMU缓存失效后再释放内存
- 同步原语: 确保GPU完成相关操作
- 电源状态: 考虑设备电源状态
工作流程:
物理内存上下文
typedef struct _MMU_PHYSMEM_CONTEXT_
{
struct _MMU_CONTEXT_ *psMMUContext; // 关联的MMU上下文
PVRSRV_DEVICE_NODE *psDevNode; // 父设备节点
IMG_UINT32 uiNumAllocations; // 分配计数
RA_ARENA *psPhysMemRA; // 物理内存资源分配器
IMG_CHAR *pszPhysMemRAName; // RA名称
size_t uiPhysMemRANameAllocSize; // 名称缓冲区大小
MMU_CTX_CLEANUP_DATA *psCleanupData; // 清理元数据
DLLIST_NODE sTmpMMUMappingHead; // 临时映射列表
#if defined(SUPPORT_CUSTOM_OSID_EMISSION)
IMG_UINT32 ui32OSid; // OS ID
IMG_UINT32 ui32OSidReg; // OS ID寄存器值
IMG_BOOL bOSidAxiProt; // AXI保护位
#endif
} MMU_PHYSMEM_CONTEXT;
架构要点: RA Arena机制:资源分配器(Resource Allocator),管理页表内存的分配和释放,支持导入/导出回调,提供内存碎片管理
- RA分配器: 使用Resource Allocator管理物理内存
- OS隔离: 支持多OS环境下的内存隔离
- 临时列表: 用于批量处理内存映射
内存映射结构
typedef struct _MMU_MEMORY_MAPPING_
{
MMU_PHYSMEM_CONTEXT *psContext; // 物理内存上下文
PG_HANDLE sMemHandle; // OS/系统句柄
void *pvCpuVAddr; // CPU虚拟地址
IMG_DEV_PHYADDR sDevPAddr; // 设备物理地址
size_t uiSize; // 大小
IMG_UINT32 uiCpuVAddrRefCount; // CPU虚拟地址引用计数
DLLIST_NODE sMMUMappingItem; // 链表节点
} MMU_MEMORY_MAPPING;
内存管理策略: 引用计数机制,多个MMU_MEMORY_DESC可以共享同一个MAPPING,只有引用计数为0时才真正释放
- 双地址空间: 同时管理CPU和设备地址
- 引用计数: 支持多次映射同一物理内存
- 句柄抽象: 封装OS特定的内存管理
内存描述符
typedef struct _MMU_MEMORY_DESC_
{
IMG_BOOL bValid; // 是否有效
IMG_DEV_PHYADDR sDevPAddr; // 设备物理地址
void *pvCpuVAddr; // CPU虚拟地址
MMU_MEMORY_MAPPING *psMapping; // 映射信息
IMG_UINT32 uiOffset; // 在mapping中的偏移
IMG_UINT32 uiSize; // 大小
} MMU_MEMORY_DESC;
设计特点:
- 有效性标志: 支持"空"描述符的批量分配
- 偏移机制: 一个mapping可以对应多个描述符
- 分离关注点: 描述符和映射分离
页表级别信息
typedef struct _MMU_Levelx_INFO_
{
IMG_UINT32 ui32NumOfEntries; // 条目数量
IMG_UINT32 ui32RefCount; // 引用计数
MMU_MEMORY_DESC sMemDesc; // 内存描述符
struct _MMU_Levelx_INFO_ *apsNextLevel[IMG_FLEX_ARRAY_MEMBER]; // 下一级页表数组(柔性数组成员)
} MMU_Levelx_INFO;
通用设计:
- 柔性数组: 支持不同级别的页表大小
- 引用计数: 精确的生命周期管理
- 递归结构: 支持多级页表遍历
层次结构:
Level 3 (PC): 页目录指针表
├── Level 2 (PD): 页目录
│ ├── Level 1 (PT): 页表
│ │ ├── PTE 0 -> 物理页0
│ │ ├── PTE 1 -> 物理页1
│ │ └── ...
│ └── ...
└── ...
MMU上下文主结构
struct _MMU_CONTEXT_
{
CONNECTION_DATA *psConnection; // 连接数据
MMU_DEVICEATTRIBS *psDevAttrs; // 设备属性
struct _MMU_PHYSMEM_CONTEXT_ *psPhysMemCtx; // 物理内存上下文
#if defined(PDUMP)
IMG_UINT32 uiPDumpContextID; // PDump上下文ID
IMG_UINT32 ui32PDumpContextIDRefCount; // PDump引用计数
#endif
ATOMIC_T sCacheFlags; // 缓存标志
POS_LOCK hLock; // 互斥锁
MMU_Levelx_INFO sBaseLevelInfo; // 基础级别信息
};
核心特性:
- 连接隔离: 每个连接有独立的MMU上下文
- 原子缓存标志: 高效的缓存失效管理
- PDump支持: 调试和分析工具集成
- 基础级别: 页表层次结构的根节点
工具函数和宏
上下文类型判断宏
#define _MMU_IS_FWKM_CTX(_ctx) ((_ctx)->psConnection == NULL)
#define _MMU_IS_FWKM_CTX_VZGUEST(_ctx)
(PVRSRV_VZ_MODE_IS(GUEST, DEVNODE, _ctx->psPhysMemCtx->psDevNode) && _MMU_IS_FWKM_CTX(_ctx))
用途:
- 固件上下文识别: NULL连接表示内核/固件上下文
- 虚拟化支持: 区分客户机固件上下文
地址计算函数
页表索引计算:PC索引计算
static IMG_UINT32 _CalcPCEIdx(IMG_DEV_VIRTADDR sDevVAddr,
const MMU_DEVVADDR_CONFIG *psDevVAddrConfig,
IMG_BOOL bRoundUp)
{
IMG_DEV_VIRTADDR sTmpDevVAddr = sDevVAddr;
if (bRoundUp) {
sTmpDevVAddr.uiAddr--;
}
IMG_UINT32 ui32RetVal = (IMG_UINT32)((sTmpDevVAddr.uiAddr & psDevVAddrConfig->uiPCIndexMask)
>> psDevVAddrConfig->uiPCIndexShift);
if (bRoundUp) {
ui32RetVal++;
}
return ui32RetVal;
}
巧妙设计:
- 向上舍入: 通过先减1再加1实现向上舍入
- 位操作: 高效的索引计算
- 统一接口: PDE和PTE索引计算使用相同模式
虚拟地址分解图:
虚拟地址(64位):
┌─────────┬─────────┬─────────┬─────────┬──────────┐
│ 保留 │ PC │ PD │ PT │ 偏移 │
│ │ 索引 │ 索引 │ 索引 │ │
└─────────┴─────────┴─────────┴─────────┴──────────┘
↑ ↑
PC Shift Page Offset
示例(4KB页,3级页表):
63 48 47 39 38 30 29 21 20 0
┌────────┬────────┬────────┬────────┬───────────┐
│保留(16)│PC(9) │PD(9) │PT(9) │偏移(21) │
└────────┴────────┴────────┴────────┴───────────┘
向上取整的技巧
// 假设要计算地址范围 [0x1000, 0x2500] 需要多少个4KB页
// 不向上取整:0x2500 >> 12 = 2 (错误!)
// 向上取整:(0x2500 - 1) >> 12 + 1 = 3 (正确!)
if (bRoundUp) {
sTmpDevVAddr.uiAddr--; // 0x2500 - 1 = 0x24FF
}
ui32RetVal = (sTmpDevVAddr.uiAddr & mask) >> shift; // 2
if (bRoundUp) {
ui32RetVal++; // 3
}
奇偶校验位计算
static inline IMG_UINT64 _GetParityBit(IMG_UINT64 uiSrc)
{
uiSrc ^= uiSrc >> 32U;
uiSrc ^= uiSrc >> 16U;
uiSrc ^= uiSrc >> 8U;
uiSrc ^= uiSrc >> 4U;
uiSrc ^= uiSrc >> 2U;
uiSrc ^= uiSrc >> 1U;
return (uiSrc & 1U);
}
算法分析:
- 异或折叠: 经典的奇偶校验计算
- 硬件友好: 可以高效地在硬件中实现
- 错误检测: 用于检测页表项传输错误
算法原理:
输入:0b11010110 (0xD6)
步骤1: 0b11010110 ^ 0b00000000 = 0b11010110
步骤2: 0b11010110 ^ 0b00001101 = 0b11011011
步骤3: 0b11011011 ^ 0b00000110 = 0b11011101
步骤4: 0b11011101 ^ 0b00000111 = 0b11011010
步骤5: 0b11011010 ^ 0b00000110 = 0b11011100
步骤6: 0b11011100 ^ 0b00000111 = 0b11011011
步骤7: 0b11011011 ^ 0b00000101 = 0b11011110
结果:0b11011110 & 1 = 0
实际上,这个算法计算的是64位数中1的个数的奇偶性
物理内存管理核心函数详解
RA导入分配函数
页表内存分配
static PVRSRV_ERROR _MMU_PhysMem_RAImportAlloc(RA_PERARENA_HANDLE hArenaHandle,
RA_LENGTH_T uiSize,
RA_FLAGS_T uiFlags,
RA_LENGTH_T uBaseAlignment,
const IMG_CHAR *pszAnnotation,
RA_IMPORT *psImport)
{
MMU_PHYSMEM_CONTEXT *psPhysMemCtx = (MMU_PHYSMEM_CONTEXT *)hArenaHandle;
PVRSRV_DEVICE_NODE *psDevNode = psPhysMemCtx->psDevNode;
MMU_MEMORY_MAPPING *psMapping;
PVRSRV_ERROR eError;
IMG_UINT32 uiPid = 0;
psMapping = OSAllocMem(sizeof(MMU_MEMORY_MAPPING));
PVR_GOTO_IF_NOMEM(psMapping, eError, e0);
// 分配物理页面
#if defined(SUPPORT_GPUVIRT_VALIDATION)
psMapping->sMemHandle.uiOSid = psPhysMemCtx->ui32OSid;
eError = PhysHeapPagesAllocGPV(...); // GPU虚拟化版本
#else
// 实际的物理内存分配
eError = PhysHeapPagesAlloc(psDevNode->psMMUPhysHeap,
TRUNCATE_64BITS_TO_SIZE_T(uiSize),
&psMapping->sMemHandle,
&psMapping->sDevPAddr,
uiPid);
psImport->hPriv = (RA_PERISPAN_HANDLE) psMapping;
psImport->base = (RA_BASE_T)psMapping->sDevPAddr.uiAddr;
psImport->uSize = uiSize;
return PVRSRV_OK;
}
关键机制: 为RA分配器导入物理内存
- RA集成: 与资源分配器紧密集成
- 句柄封装: 将MMU_MEMORY_MAPPING作为私有数据
- 地址转换: 物理地址作为RA基地址
流程图:
RA导入释放函数
页表内存释放
static void _MMU_PhysMem_RAImportFree(RA_PERARENA_HANDLE hArenaHandle,
RA_BASE_T uiBase,
RA_PERISPAN_HANDLE hPriv)
{
MMU_MEMORY_MAPPING *psMapping = (MMU_MEMORY_MAPPING *)hPriv;
MMU_PHYSMEM_CONTEXT *psPhysMemCtx = (MMU_PHYSMEM_CONTEXT *)hArenaHandle;
PVR_ASSERT(psMapping->uiCpuVAddrRefCount == 0);
// 添加到延迟释放列表而不是立即释放
psMapping->psContext = NULL;
dllist_add_to_tail(&psPhysMemCtx->sTmpMMUMappingHead, &psMapping->sMMUMappingItem);
}
延迟释放策略:
- 引用计数检查: 确保没有CPU映射
- 延迟队列: 加入临时列表而非立即释放
- 上下文清空: 防止后续访问
注意:不立即释放,而是加入临时列表,等待MMU cache失效
延时释放函数
延时释放机制
static void _SetupCleanup_FreeMMUMapping(MMU_PHYSMEM_CONTEXT *psPhysMemCtx)
{
MMU_CLEANUP_ITEM *psCleanupItem;
MMU_CTX_CLEANUP_DATA *psCleanupData = psPhysMemCtx->psCleanupData;
PVRSRV_DEVICE_NODE *psDevNode = psPhysMemCtx->psDevNode;
// 检查是否有需要释放的项
if (dllist_is_empty(&psPhysMemCtx->sTmpMMUMappingHead)) {
goto e0;
}
...
// 1. 分配清理项
psCleanupItem = OSAllocMem(sizeof(*psCleanupItem));
// 2. 设置同步信息
psCleanupItem->psSync = NULL; // 稍后由清理线程设置
psCleanupItem->uiRequiredSyncVal = psDevNode->ui32NextMMUInvalidateUpdate;
psCleanupItem->uiRequiredPowerOffCounter = psDevNode->uiPowerOffCounterNext;
// 3. 移动映射列表
dllist_replace_head(&psPhysMemCtx->sTmpMMUMappingHead,
&psCleanupItem->sMMUMappingHead);
// 4. 增加引用计数
OSAtomicIncrement(&psCleanupData->iRef);
// 5. 添加到清理线程
PVRSRVCleanupThreadAddWork(psDevNode, &psCleanupItem->sCleanupThreadFn);
return;
}
工作流程
清理线程函数
static PVRSRV_ERROR _CleanupThread_FreeMMUMapping(void* pvData)
{
MMU_CLEANUP_ITEM *psCleanup = (MMU_CLEANUP_ITEM *)pvData;
MMU_CTX_CLEANUP_DATA *psMMUCtxCleanupData = psCleanup->psMMUCtxCleanupData;
PVRSRV_DEVICE_NODE *psDevNode = psCleanup->psDevNode;
IMG_BOOL bFreeNow;
// 第一次调用时psSync为NULL
if (psCleanup->psSync == NULL)
{
IMG_UINT32 uiSyncVal;
// 请求MMU cache失效
eError = psDevNode->pfnMMUCacheInvalidateKick(psDevNode, &uiSyncVal);
if (eError != PVRSRV_OK) {
return PVRSRV_ERROR_RETRY; // 失败则重试
}
psCleanup->psSync = psDevNode->psMMUCacheSyncPrim;
}
// 检查同步值是否满足
bFreeNow = PVRSRVHasCounter32Advanced(
OSReadDeviceMem32(psCleanup->psSync->pui32LinAddr),
psCleanup->uiRequiredSyncVal);
// 检查超时(500ms)
if (!bFreeNow) {
IMG_UINT32 uiTimeStart = psCleanup->sCleanupThreadFn.ui32TimeStart;
IMG_UINT32 uiTimeEnd = psCleanup->sCleanupThreadFn.ui32TimeEnd;
if ((uiTimeEnd - uiTimeStart - 500) <= (OSClockms() - uiTimeStart)) {
bFreeNow = IMG_TRUE; // 超时,强制释放
}
}
}
状态机
物理内存分配
static PVRSRV_ERROR _MMU_PhysMemAlloc(MMU_PHYSMEM_CONTEXT *psPhysMemCtx,
MMU_MEMORY_DESC *psMemDesc,
size_t uiBytes,
size_t uiAlignment)
{
RA_BASE_T uiPhysAddr;
PVR_RETURN_IF_INVALID_PARAM(psMemDesc);
PVR_RETURN_IF_INVALID_PARAM(!psMemDesc->bValid);
// 从RA分配内存
eError = RA_Alloc(psPhysMemCtx->psPhysMemRA,
uiBytes,
RA_NO_IMPORT_MULTIPLIER,
0, // flags
uiAlignment,
"",
&uiPhysAddr,
NULL,
(RA_PERISPAN_HANDLE *)&psMemDesc->psMapping);
// 填充描述符
psMemDesc->bValid = IMG_TRUE;
psMemDesc->pvCpuVAddr = NULL;
psMemDesc->sDevPAddr.uiAddr = (IMG_UINT64)uiPhysAddr;
// 首次映射到CPU地址空间
if (psMemDesc->psMapping->uiCpuVAddrRefCount == 0)
{
eError = PhysHeapPagesMap(
psPhysMemCtx->psDevNode->psMMUPhysHeap,
&psMemDesc->psMapping->sMemHandle,
psMemDesc->psMapping->uiSize,
&psMemDesc->psMapping->sDevPAddr,
&psMemDesc->psMapping->pvCpuVAddr);
}
// 计算偏移和虚拟地址
psMemDesc->psMapping->uiCpuVAddrRefCount++;
psMemDesc->uiOffset = (psMemDesc->sDevPAddr.uiAddr -
psMemDesc->psMapping->sDevPAddr.uiAddr);
psMemDesc->pvCpuVAddr = (IMG_UINT8 *)psMemDesc->psMapping->pvCpuVAddr +
psMemDesc->uiOffset;
return PVRSRV_OK;
}
内存管理特性:
- RA分配: 使用资源分配器获取物理地址
- 懒惰CPU映射: 只在需要时创建CPU映射
- 引用计数: 支持同一物理内存的多个描述符
- 偏移计算: 支持子分配
内存分配流程
内存映射关系:
物理内存块:
┌─────────────────────────────────┐
│ MMU_MEMORY_MAPPING │
│ sDevPAddr = 0x1000 │
│ uiSize = 16KB │
│ pvCpuVAddr = 0xFFFF8000 │
└─────────────────────────────────┘
映射到多个描述符:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ MMU_MEMORY_ │ │ MMU_MEMORY_ │ │ MMU_MEMORY_ │
│ DESC 1 │ │ DESC 2 │ │ DESC 3 │
├──────────────┤ ├──────────────┤ ├──────────────┤
│ sDevPAddr: │ │ sDevPAddr: │ │ sDevPAddr: │
│ 0x1000 │ │ 0x2000 │ │ 0x3000 │
│ │ │ │ │ │
│ uiOffset: │ │ uiOffset: │ │ uiOffset: │
│ 0 │ │ 0x1000 │ │ 0x2000 │
│ │ │ │ │ │
│ pvCpuVAddr: │ │ pvCpuVAddr: │ │ pvCpuVAddr: │
│ 0xFFFF8000 │ │ 0xFFFF9000 │ │ 0xFFFFA000 │
└──────────────┘ └──────────────┘ └──────────────┘
整体调用路径
_MMU_AllocLevel()
└── _PxMemAlloc()
└── _MMU_PhysMemAlloc()
└── RA_Alloc()
└── PhysHeapPagesMap()
行为分析
eError = RA_Alloc(psPhysMemCtx->psPhysMemRA,
uiBytes, RA_NO_IMPORT_MULTIPLIER, 0, uiAlignment, "",
&uiPhysAddr, NULL, (RA_PERISPAN_HANDLE *)&psMemDesc->psMapping);
- 这里调用 Resource Allocator (RA) 管理的物理内存池。
-
uiBytes 就是 _PxMemAlloc 传下来的大小(即页表大小,经过 PVR_ALIGN 对齐)。
-
RA_Alloc 通常会返回连续的“设备物理地址”空间(不一定是 CPU 物理连续的)。
if (psMemDesc->psMapping->uiCpuVAddrRefCount == 0)
{
eError = PhysHeapPagesMap(..., &psMemDesc->psMapping->sMemHandle, ...);
}
-
第一次分配时会走 PhysHeapPagesMap(),进行 CPU 端映射;
-
返回的 pvCpuVAddr 通常通过 ioremap_wc 建立;
-
如果 RA 返回的物理区块是分散的(多个 span),PhysHeapPagesMap 可能循环调用 ioremap_page_range() 或多次 ioremap();
-
每个 mapping 都被记录在 psMapping 里以便复用。
这说明:
- 用的 PhysHeap(通常是 GPU MMU 的页表专用 heap)内部管理的是页粒度资源 (4K);
- RA 层将多个 4K 物理页拼成一个逻辑连续区;
-
CPU 映射层 (PhysHeapPagesMap) 则循环 ioremap 每页来建立映射。
也就是说:
-
只要 RA 分配得到的 psMapping 是新的(第一次引用),就会调用一次 PhysHeapPagesMap()。
-
PhysHeapPagesMap() 通常会调用 ioremap()(或者 vmap()),把物理页映射成 CPU 可访问的虚拟地址。
-
后续如果同一个 mapping 被重复引用,不会再次 ioremap(),只增加引用计数。
但问题在于:
- 大部分 _MMU_PhysMemAlloc() 的调用都会通过 RA_Alloc() 得到一个新的 mapping 结构,
- 所以几乎每次都会触发一次 ioremap()。
性能瓶颈点
- 每次 RA_Alloc 都返回新的 mapping → 导致每次 ioremap()
- 每个页表页通常 4K → 创建 1MB 映射就可能触发 256 次 ioremap()
-
ioremap() 涉及:
- 分配新的 vm_struct
-
修改内核页表(set_pte)
-
cache/TLB 同步
- 这就是页表初始化阶段非常耗时的根源。
标志位转换
static INLINE PVRSRV_ERROR _MMU_ConvertDevMemFlags(
IMG_BOOL bInvalidate,
PVRSRV_MEMALLOCFLAGS_T uiMappingFlags,
MMU_PROTFLAGS_T *puiMMUProtFlags,
MMU_CONTEXT *psMMUContext)
{
PVRSRV_ERROR eError = PVRSRV_OK;
IMG_UINT32 uiGPUCacheMode;
PVRSRV_DEVICE_NODE *psDevNode = psMMUContext->psPhysMemCtx->psDevNode;
/* 如果是无效映射,直接返回无效标志 */
if (bInvalidate == IMG_TRUE)
{
*puiMMUProtFlags |= MMU_PROTFLAGS_INVALID;
return eError;
}
/* 转换为有效映射的标志 */
*puiMMUProtFlags |= ((uiMappingFlags & PVRSRV_MEMALLOCFLAG_DEVICE_FLAGS_MASK)
>> PVRSRV_MEMALLOCFLAG_DEVICE_FLAGS_OFFSET)
<< MMU_PROTFLAGS_DEVICE_OFFSET;
/* 可读标志 */
if (PVRSRV_CHECK_GPU_READABLE(uiMappingFlags))
{
*puiMMUProtFlags |= MMU_PROTFLAGS_READABLE;
}
/* 可写标志 */
if (PVRSRV_CHECK_GPU_WRITEABLE(uiMappingFlags))
{
*puiMMUProtFlags |= MMU_PROTFLAGS_WRITEABLE;
}
/* 缓存模式转换 */
eError = DevmemDeviceCacheMode(uiMappingFlags, &uiGPUCacheMode);
PVR_RETURN_IF_ERROR(eError);
switch (uiGPUCacheMode)
{
case PVRSRV_MEMALLOCFLAG_GPU_UNCACHED:
case PVRSRV_MEMALLOCFLAG_GPU_UNCACHED_WC:
break; /* 不缓存,不设置任何标志 */
case PVRSRV_MEMALLOCFLAG_GPU_CACHED:
*puiMMUProtFlags |= MMU_PROTFLAGS_CACHED;
break;
default:
return PVRSRV_ERROR_INVALID_PARAMS;
}
/* 缓存一致性 */
if (DevmemDeviceCacheCoherency(psDevNode, uiMappingFlags))
{
*puiMMUProtFlags |= MMU_PROTFLAGS_CACHE_COHERENT;
}
/* 设备特定的保护标志调整 */
if (psDevNode->pfnMMUTweakProtFlags)
{
psDevNode->pfnMMUTweakProtFlags(psDevNode, psMMUContext->psDevAttrs,
uiMappingFlags, puiMMUProtFlags);
}
return PVRSRV_OK;
}
标志位映射图:
PVRSRV_MEMALLOCFLAGS_T (应用层标志)
┌─────────────────────────────────────────┐
│ GPU_READABLE │ GPU_WRITEABLE │ ... │
│ GPU_CACHED │ COHERENT │ ... │
└─────────────────────────────────────────┘
│ │
↓ ↓
MMU_PROTFLAGS_T (MMU层标志)
┌─────────────────────────────────────────┐
│ READABLE │ WRITEABLE │ ... │
│ CACHED │ CACHE_COHERENT│ ... │
└─────────────────────────────────────────┘
│ │
↓ ↓
硬件PTE标志 (硬件特定)
┌─────────────────────────────────────────┐
│ Valid │ Read │ Write │ Cache │ Parity │
└─────────────────────────────────────────┘
页表内存分配和初始化:_PxMemAlloc
static PVRSRV_ERROR _PxMemAlloc(
MMU_CONTEXT *psMMUContext,
IMG_UINT32 uiNumEntries,
const MMU_PxE_CONFIG *psConfig,
MMU_LEVEL eMMULevel,
MMU_MEMORY_DESC *psMemDesc,
#if defined(PVRSRV_MMU_PARITY_ON_PTALLOC_AND_PTEUNMAP)
IMG_DEV_VIRTADDR* psRunningDevVAddr,
IMG_UINT32 uiLog2DataPageSize,
#endif
IMG_UINT32 uiLog2Align)
{
PVRSRV_ERROR eError;
size_t uiBytes;
size_t uiAlign;
PVRSRV_DEVICE_NODE *psDevNode = psMMUContext->psPhysMemCtx->psDevNode;
PVR_ASSERT(psConfig->uiBytesPerEntry != 0);
/* 计算需要的字节数 */
uiBytes = uiNumEntries * psConfig->uiBytesPerEntry;
uiAlign = 1 << uiLog2Align;
/*
* 如果硬件要求对齐,需要将内存扩展到对齐边界
* 这是因为未初始化的数据可能被MMU当作有效的PTE读取
*/
uiBytes = PVR_ALIGN(uiBytes, uiAlign);
/* 分配物理内存 */
eError = _MMU_PhysMemAlloc(psMMUContext->psPhysMemCtx,
psMemDesc, uiBytes, uiAlign);
if (eError != PVRSRV_OK)
{
PVR_LOG_GOTO_WITH_ERROR("_MMU_PhysMemAlloc", eError,
PVRSRV_ERROR_OUT_OF_MEMORY, e0);
}
#if defined(PVRSRV_MMU_PARITY_ON_PTALLOC_AND_PTEUNMAP)
/* 如果需要奇偶校验位,使用预计算的模式 */
if (psConfig->uiParityBitMask && eMMULevel == MMU_LEVEL_1 &&
psRunningDevVAddr != NULL)
{
IMG_UINT32 uiParity = (IMG_UINT32)_GetParityBit(
psRunningDevVAddr->uiAddr ^ 0ULL);
/* 使用预计算的奇偶校验模式 */
OSCachedMemCopy(psMemDesc->pvCpuVAddr,
psMMUContext->psDevAttrs->pui64PrecomputedAllocParity[uiParity],
uiBytes);
/* 更新运行地址 */
psRunningDevVAddr->uiAddr += (1 << uiLog2DataPageSize) * uiNumEntries;
}
else
#endif
{
/* 清零页表 */
OSCachedMemSet(psMemDesc->pvCpuVAddr, 0, uiBytes);
}
/* 刷新CPU缓存 */
eError = PhysHeapPagesClean(psDevNode->psMMUPhysHeap,
&psMemDesc->psMapping->sMemHandle,
psMemDesc->uiOffset,
psMemDesc->uiSize);
PVR_GOTO_IF_ERROR(eError, e1);
#if defined(PDUMP)
PDUMPCOMMENT(psDevNode, "Alloc MMU object");
PDumpMMUMalloc(psDevNode,
psMMUContext->psDevAttrs->pszMMUPxPDumpMemSpaceName,
eMMULevel,
&psMemDesc->sDevPAddr,
uiBytes,
uiAlign,
psMMUContext->psDevAttrs->eMMUType);
PDumpMMUDumpPxEntries(...); /* PDump所有条目 */
#endif
return PVRSRV_OK;
e1:
_MMU_PhysMemFree(psMMUContext->psPhysMemCtx, psMemDesc);
e0:
return eError;
}
内存对齐的重要性:
示例:16字节的PT,但要求64字节对齐
不对齐初始化(危险):
┌────────────┬──────────────────────┐
│ PT (16B) │ 未初始化数据 (48B) │
│ 已清零 │ 可能含有垃圾数据 │
└────────────┴──────────────────────┘
↑
MMU可能读取这部分,
导致错误的pending标志
正确的对齐初始化:
┌────────────┬──────────────────────┐
│ PT (16B) │ 已清零数据 (48B) │
│ 已清零 │ 已清零 │
└────────────┴──────────────────────┘
↑
安全,MMU读取到的都是0
设置PTE:_SetupPTE
这是最核心的函数之一,负责设置页表项。
static INLINE PVRSRV_ERROR _SetupPTE(
MMU_CONTEXT *psMMUContext,
MMU_Levelx_INFO *psLevel,
IMG_UINT32 uiIndex,
const MMU_PxE_CONFIG *psConfig,
const IMG_DEV_PHYADDR *psDevPAddr,
const IMG_DEV_VIRTADDR *psDevVAddr,
IMG_BOOL bUnmap,
#if defined(PDUMP)
const IMG_CHAR *pszMemspaceName,
const IMG_CHAR *pszSymbolicAddr,
IMG_DEVMEM_OFFSET_T uiSymbolicAddrOffset,
#endif
IMG_UINT64 uiProtFlags)
{
MMU_MEMORY_DESC *psMemDesc = &psLevel->sMemDesc;
IMG_UINT64 ui64PxE64;
IMG_UINT64 uiAddr = psDevPAddr->uiAddr;
PVRSRV_DEVICE_NODE *psDevNode = psMMUContext->psPhysMemCtx->psDevNode;
#if defined(PVRSRV_MMU_PARITY_ON_PTALLOC_AND_PTEUNMAP)
IMG_BOOL bParity = psConfig->uiParityBitMask;
#else
IMG_BOOL bParity = psConfig->uiParityBitMask && !bUnmap;
#endif
PTE格式构造:
64位PTE结构(示例):
┌──┬─────────────────────────────────────┬───────────┬──┬──┬──┐
│P │ 物理地址 (40位) │ 保留 │C │W │V │
│ │ [51:12] │ │ │ │ │
└──┴─────────────────────────────────────┴───────────┴──┴──┴──┘
63 3 2 1 0
P = Parity bit (奇偶校验位)
C = Cached (缓存位)
W = Writeable (可写位)
V = Valid (有效位)
构造PTE的步骤:
/* 设备特定的地址验证/调整 */
if (psDevNode->pfnValidateOrTweakPhysAddrs)
{
PVRSRV_ERROR eErr = psDevNode->pfnValidateOrTweakPhysAddrs(
psDevNode,
psMMUContext->psDevAttrs,
&uiAddr);
PVR_LOG_RETURN_IF_ERROR(eErr, "_SetupPTE");
}
/* 计算PTE值 */
ui64PxE64 = uiAddr /* 物理地址 */
>> psConfig->uiAddrLog2Align /* 右移对齐位数 */
<< psConfig->uiAddrShift /* 左移到正确位置 */
& psConfig->uiAddrMask; /* 应用地址掩码 */
ui64PxE64 |= uiProtFlags; /* 添加保护标志 */
/* 添加奇偶校验位 */
if (bParity)
{
ui64PxE64 |= _GetParityBit(psDevVAddr->uiAddr ^ psDevPAddr->uiAddr)
<< psConfig->uiParityBitShift;
}
写入PTE:
/* 根据条目大小写入 */
if (psConfig->uiBytesPerEntry == 8)
{
IMG_UINT64 *pui64Px = psMemDesc->pvCpuVAddr;
#if defined(SUPPORT_VGPU_GUEST)
XdxUpdatePTE(&pui64Px[uiIndex], ui64PxE64, psDevVAddr->uiAddr);
#else
pui64Px[uiIndex] = ui64PxE64;
#endif
}
else if (psConfig->uiBytesPerEntry == 4)
{
IMG_UINT32 *pui32Px = psMemDesc->pvCpuVAddr;
PVR_ASSERT(ui64PxE64 == (ui64PxE64 & 0xffffffffU)); /* 确保不溢出 */
pui32Px[uiIndex] = (IMG_UINT32)ui64PxE64;
}
HTB日志记录:
/* 记录修改 */
HTBLOGK(HTB_SF_MMU_PAGE_OP_TABLE,
HTBLOG_PTR_BITS_HIGH(psLevel), HTBLOG_PTR_BITS_LOW(psLevel),
uiIndex, MMU_LEVEL_1,
HTBLOG_U64_BITS_HIGH(ui64PxE64), HTBLOG_U64_BITS_LOW(ui64PxE64),
!bUnmap);
#if defined(PDUMP)
PDumpMMUDumpPxEntries(...); /* PDump这个条目 */
#endif
return PVRSRV_OK;
}
设置PxE(高级页表)
用于设置PC和PD级别的条目。
static void _SetupPxE(
MMU_CONTEXT *psMMUContext,
MMU_Levelx_INFO *psLevel,
IMG_UINT32 uiIndex,
const MMU_PxE_CONFIG *psConfig,
MMU_LEVEL eMMULevel,
const IMG_DEV_PHYADDR *psDevPAddr,
#if defined(PDUMP)
const IMG_CHAR *pszMemspaceName,
const IMG_CHAR *pszSymbolicAddr,
IMG_DEVMEM_OFFSET_T uiSymbolicAddrOffset,
#endif
MMU_PROTFLAGS_T uiProtFlags,
IMG_UINT32 uiLog2DataPageSize)
{
PVRSRV_DEVICE_NODE *psDevNode = psMMUContext->psPhysMemCtx->psDevNode;
MMU_MEMORY_DESC *psMemDesc = &psLevel->sMemDesc;
IMG_UINT32 (*pfnDerivePxEProt4)(IMG_UINT32);
IMG_UINT64 (*pfnDerivePxEProt8)(IMG_UINT32, IMG_UINT32);
/* 处理无效条目 */
if (!psDevPAddr)
{
if (~uiProtFlags & MMU_PROTFLAGS_INVALID)
{
PVR_DPF((PVR_DBG_ERROR, "No physical address but not invalidating"));
uiProtFlags |= MMU_PROTFLAGS_INVALID;
}
psDevPAddr = &gsBadDevPhyAddr; /* 使用坏地址 */
}
else
{
if (uiProtFlags & MMU_PROTFLAGS_INVALID)
{
PVR_DPF((PVR_DBG_ERROR, "Has physical address but invalidating"));
}
}
获取保护标志派生函数:
/* 根据级别选择正确的派生函数 */
switch (eMMULevel)
{
case MMU_LEVEL_3:
pfnDerivePxEProt4 = psMMUContext->psDevAttrs->pfnDerivePCEProt4;
pfnDerivePxEProt8 = psMMUContext->psDevAttrs->pfnDerivePCEProt8;
break;
case MMU_LEVEL_2:
pfnDerivePxEProt4 = psMMUContext->psDevAttrs->pfnDerivePDEProt4;
pfnDerivePxEProt8 = psMMUContext->psDevAttrs->pfnDerivePDEProt8;
break;
case MMU_LEVEL_1:
pfnDerivePxEProt4 = psMMUContext->psDevAttrs->pfnDerivePTEProt4;
pfnDerivePxEProt8 = psMMUContext->psDevAttrs->pfnDerivePTEProt8;
break;
default:
PVR_ASSERT(0);
return;
}
写入PxE:
switch (psConfig->uiBytesPerEntry)
{
case 4:
{
IMG_UINT32 *pui32Px = psMemDesc->pvCpuVAddr;
IMG_UINT64 ui64PxE64;
ui64PxE64 = psDevPAddr->uiAddr
>> psConfig->uiAddrLog2Align
<< psConfig->uiAddrShift
& psConfig->uiAddrMask;
ui64PxE64 |= (IMG_UINT64)pfnDerivePxEProt4(uiProtFlags);
/* 不应该使无效的条目再次无效 */
if (uiProtFlags & MMU_PROTFLAGS_INVALID)
{
PVR_ASSERT(pui32Px[uiIndex] != ui64PxE64);
}
pui32Px[uiIndex] = (IMG_UINT32)ui64PxE64;
HTBLOGK(HTB_SF_MMU_PAGE_OP_TABLE, ...);
break;
}
case 8:
{
IMG_UINT64 *pui64Px = psMemDesc->pvCpuVAddr;
IMG_UINT64 ui64PxE64;
ui64PxE64 = psDevPAddr->uiAddr
>> psConfig->uiAddrLog2Align
<< psConfig->uiAddrShift
& psConfig->uiAddrMask;
ui64PxE64 |= pfnDerivePxEProt8(uiProtFlags, uiLog2DataPageSize);
pui64Px[uiIndex] = ui64PxE64;
HTBLOGK(HTB_SF_MMU_PAGE_OP_TABLE, ...);
break;
}
}
#if defined(PDUMP)
PDumpMMUDumpPxEntries(...);
#endif
/* 缓存失效 */
psDevNode->pfnMMUCacheInvalidate(psDevNode, psMMUContext,
eMMULevel,
uiProtFlags & MMU_PROTFLAGS_INVALID);
}
MMU层次分配:_MMU_AllocLevel
这是一个递归函数,用于分配多级页表。
static PVRSRV_ERROR _MMU_AllocLevel(
MMU_CONTEXT *psMMUContext,
MMU_Levelx_INFO *psLevel,
IMG_UINT32 auiStartArray[],
IMG_UINT32 auiEndArray[],
IMG_UINT32 auiEntriesPerPxArray[],
const MMU_PxE_CONFIG *apsConfig[],
MMU_LEVEL aeMMULevel[],
IMG_UINT32 *pui32CurrentLevel,
IMG_UINT32 uiStartIndex,
IMG_UINT32 uiEndIndex,
#if defined(PVRSRV_MMU_PARITY_ON_PTALLOC_AND_PTEUNMAP)
IMG_DEV_VIRTADDR* psRunningDevVAddr,
IMG_BOOL bSetParity,
#endif
IMG_BOOL bFirst,
IMG_BOOL bLast,
IMG_UINT32 uiLog2DataPageSize)
{
IMG_UINT32 uiThisLevel = *pui32CurrentLevel;
const MMU_PxE_CONFIG *psConfig = apsConfig[uiThisLevel];
PVRSRV_ERROR eError = PVRSRV_ERROR_OUT_OF_MEMORY;
IMG_UINT32 uiAllocState = 99;
IMG_UINT32 i;
PVRSRV_DEVICE_NODE *psDevNode = psMMUContext->psPhysMemCtx->psDevNode;
递归分配流程图:
核心循环:
for (i = uiStartIndex; i < uiEndIndex; i++)
{
/* 只在非最后一级分配 */
if (aeMMULevel[uiThisLevel] != MMU_LEVEL_1)
{
IMG_UINT32 uiNextStartIndex;
IMG_UINT32 uiNextEndIndex;
IMG_BOOL bNextFirst;
IMG_BOOL bNextLast;
/* 如果下一级不存在,分配它 */
if (!psLevel->apsNextLevel[i])
{
MMU_Levelx_INFO *psNextLevel;
IMG_UINT32 ui32AllocSize;
IMG_UINT32 uiNextEntries;
/* 分配下一级结构 */
uiNextEntries = auiEntriesPerPxArray[uiThisLevel + 1];
ui32AllocSize = sizeof(MMU_Levelx_INFO);
if (aeMMULevel[uiThisLevel + 1] != MMU_LEVEL_1)
{
ui32AllocSize += IMG_FLEX_ARRAY_SIZE(
sizeof(MMU_Levelx_INFO *), uiNextEntries);
}
psNextLevel = OSAllocZMem(ui32AllocSize);
PVR_GOTO_IF_NOMEM(psNextLevel, eError, e0);
/* 关联到本级 */
psLevel->apsNextLevel[i] = psNextLevel;
psNextLevel->ui32NumOfEntries = uiNextEntries;
psNextLevel->ui32RefCount = 0;
/* 分配物理内存 */
eError = _PxMemAlloc(psMMUContext, uiNextEntries,
apsConfig[uiThisLevel + 1],
aeMMULevel[uiThisLevel + 1],
&psNextLevel->sMemDesc,
#if defined(PVRSRV_MMU_PARITY_ON_PTALLOC_AND_PTEUNMAP)
psRunningDevVAddr,
uiLog2DataPageSize,
#endif
psConfig->uiAddrLog2Align);
PVR_GOTO_IF_ERROR(eError, e0);
/* 连接PxE */
_SetupPxE(psMMUContext,
psLevel,
i,
psConfig,
aeMMULevel[uiThisLevel],
&psNextLevel->sMemDesc.sDevPAddr,
#if defined(PDUMP)
NULL, NULL, 0,
#endif
0,
uiLog2DataPageSize);
psLevel->ui32RefCount++;
}
索引范围计算:
/* 计算下一级的索引范围 */
if (bFirst && (i == uiStartIndex))
{
uiNextStartIndex = auiStartArray[uiThisLevel + 1];
bNextFirst = IMG_TRUE;
}
else
{
uiNextStartIndex = 0;
bNextFirst = IMG_FALSE;
}
if (bLast && (i == (uiEndIndex - 1)))
{
uiNextEndIndex = auiEndArray[uiThisLevel + 1];
bNextLast = IMG_TRUE;
}
else
{
uiNextEndIndex = auiEntriesPerPxArray[uiThisLevel + 1];
bNextLast = IMG_FALSE;
}
索引范围示例:
假设分配虚拟地址范围:0x1000 - 0x5FFF (3个4KB页)
Level 2 (PD):
索引0: [0x0000-0x1FFF]
索引1: [0x2000-0x3FFF] ← 我们的范围
索引2: [0x4000-0x5FFF] ← 我们的范围
索引3: [0x6000-0x7FFF]
对于索引1(bFirst=TRUE):
- uiNextStartIndex = auiStartArray[Level1] = 1 (从PT的第1项开始)
- bNextFirst = TRUE
对于索引2(bLast=TRUE):
- uiNextEndIndex = auiEndArray[Level1] = 512 (到PT的最后)
- bNextLast = TRUE
递归调用和错误处理:
/* 递归到下一级 */
(*pui32CurrentLevel)++;
eError = _MMU_AllocLevel(psMMUContext, psLevel->apsNextLevel[i],
auiStartArray,
auiEndArray,
auiEntriesPerPxArray,
apsConfig,
aeMMULevel,
pui32CurrentLevel,
uiNextStartIndex,
uiNextEndIndex,
#if defined(PVRSRV_MMU_PARITY_ON_PTALLOC_AND_PTEUNMAP)
psRunningDevVAddr,
bSetParity,
#endif
bNextFirst,
bNextLast,
uiLog2DataPageSize);
(*pui32CurrentLevel)--;
if (eError != PVRSRV_OK)
{
uiAllocState = 3;
goto e0;
}
}
else
{
/* Level 1,只增加引用计数 */
psLevel->ui32RefCount++;
}
/* 检查引用计数溢出 */
if (psLevel->ui32RefCount > psLevel->ui32NumOfEntries)
{
uiAllocState = 4;
PVR_GOTO_WITH_ERROR(eError, PVRSRV_ERROR_BAD_MAPPING, e0);
}
}
刷新缓存:
/* Level 1在写入PTE时刷新,这里只刷新高级 */
if (aeMMULevel[uiThisLevel] != MMU_LEVEL_1)
{
eError = PhysHeapPagesClean(psDevNode->psMMUPhysHeap,
&psLevel->sMemDesc.psMapping->sMemHandle,
uiStartIndex * psConfig->uiBytesPerEntry +
psLevel->sMemDesc.uiOffset,
(uiEndIndex - uiStartIndex) *
psConfig->uiBytesPerEntry);
PVR_LOG_IF_ERROR(eError, "PhysHeapPagesClean");
}
return PVRSRV_OK;
错误回滚:
e0:
/* 处理当前索引i */
if (aeMMULevel[uiThisLevel] != MMU_LEVEL_1)
{
if (uiAllocState >= 3)
{
if (psLevel->apsNextLevel[i] != NULL &&
psLevel->apsNextLevel[i]->ui32RefCount == 0)
{
psLevel->ui32RefCount--;
}
}
if (uiAllocState >= 2)
{
if (psLevel->apsNextLevel[i] != NULL &&
psLevel->apsNextLevel[i]->ui32RefCount == 0)
{
_PxMemFree(psMMUContext, &psLevel->apsNextLevel[i]->sMemDesc,
aeMMULevel[uiThisLevel + 1]);
}
}
if (psLevel->apsNextLevel[i] != NULL &&
psLevel->apsNextLevel[i]->ui32RefCount == 0)
{
OSFreeMem(psLevel->apsNextLevel[i]);
psLevel->apsNextLevel[i] = NULL;
}
}
else
{
psLevel->ui32RefCount--;
}
e1:
i--; /* 回滚前面已分配的索引 */
/* 反向遍历,释放已分配的内容 */
for (/* i already set */; i >= uiStartIndex && i < uiEndIndex; i--)
{
if (aeMMULevel[uiThisLevel] != MMU_LEVEL_1)
{
IMG_UINT32 uiNextStartIndex;
IMG_UINT32 uiNextEndIndex;
IMG_BOOL bNextFirst;
IMG_BOOL bNextLast;
/* 重新计算索引范围 */
if (bFirst && (i == uiStartIndex))
{
uiNextStartIndex = auiStartArray[uiThisLevel + 1];
bNextFirst = IMG_TRUE;
}
else
{
uiNextStartIndex = 0;
bNextFirst = IMG_FALSE;
}
if (bLast && (i == (uiEndIndex - 1)))
{
uiNextEndIndex = auiEndArray[uiThisLevel + 1];
bNextLast = IMG_TRUE;
}
else
{
uiNextEndIndex = auiEntriesPerPxArray[uiThisLevel + 1];
bNextLast = IMG_FALSE;
}
/* 递归释放 */
(*pui32CurrentLevel)++;
if (_MMU_FreeLevel(psMMUContext, psLevel->apsNextLevel[i],
auiStartArray, auiEndArray,
auiEntriesPerPxArray, apsConfig,
aeMMULevel, pui32CurrentLevel,
uiNextStartIndex, uiNextEndIndex,
bNextFirst, bNextLast, uiLog2DataPageSize))
{
_PxMemFree(psMMUContext, &psLevel->apsNextLevel[i]->sMemDesc,
aeMMULevel[uiThisLevel + 1]);
OSFreeMem(psLevel->apsNextLevel[i]);
psLevel->apsNextLevel[i] = NULL;
psLevel->ui32RefCount--;
PVR_ASSERT(psLevel->ui32RefCount <= psLevel->ui32NumOfEntries);
}
(*pui32CurrentLevel)--;
}
else
{
psLevel->ui32RefCount--;
PVR_ASSERT(psLevel->ui32RefCount <= psLevel->ui32NumOfEntries);
}
}
return eError;
}
MMU层次释放:_MMU_FreeLevel
递归释放函数,与分配函数配对。
static IMG_BOOL _MMU_FreeLevel(
MMU_CONTEXT *psMMUContext,
MMU_Levelx_INFO *psLevel,
IMG_UINT32 auiStartArray[],
IMG_UINT32 auiEndArray[],
IMG_UINT32 auiEntriesPerPxArray[],
const MMU_PxE_CONFIG *apsConfig[],
MMU_LEVEL aeMMULevel[],
IMG_UINT32 *pui32CurrentLevel,
IMG_UINT32 uiStartIndex,
IMG_UINT32 uiEndIndex,
IMG_BOOL bFirst,
IMG_BOOL bLast,
IMG_UINT32 uiLog2DataPageSize)
{
IMG_UINT32 uiThisLevel = *pui32CurrentLevel;
const MMU_PxE_CONFIG *psConfig = apsConfig[uiThisLevel];
IMG_UINT32 i;
IMG_BOOL bFreed = IMG_FALSE;
PVRSRV_DEVICE_NODE *psDevNode = psMMUContext->psPhysMemCtx->psDevNode;
MMU_OBJ_DBG((PVR_DBG_ERROR,
"_MMU_FreeLevel: level = %u, range %u - %u, refcount = %u",
aeMMULevel[uiThisLevel], uiStartIndex, uiEndIndex,
(psLevel != NULL ? psLevel->ui32RefCount : IMG_UINT32_MAX)));
/* 参数检查 */
PVR_ASSERT(*pui32CurrentLevel < MMU_MAX_LEVEL);
if (psLevel == NULL)
{
PVR_DPF((PVR_DBG_ERROR, "%s: invalid MMU level data", __func__));
goto ErrReturn;
}
for (i = uiStartIndex; i < uiEndIndex; i++)
{
if (aeMMULevel[uiThisLevel] != MMU_LEVEL_1)
{
MMU_Levelx_INFO *psNextLevel = psLevel->apsNextLevel[i];
IMG_UINT32 uiNextStartIndex;
IMG_UINT32 uiNextEndIndex;
IMG_BOOL bNextFirst;
IMG_BOOL bNextLast;
/* 计算下一级范围 */
if (bFirst && (i == uiStartIndex))
{
uiNextStartIndex = auiStartArray[uiThisLevel + 1];
bNextFirst = IMG_TRUE;
}
else
{
uiNextStartIndex = 0;
bNextFirst = IMG_FALSE;
}
if (bLast && (i == (uiEndIndex - 1)))
{
uiNextEndIndex = auiEndArray[uiThisLevel + 1];
bNextLast = IMG_TRUE;
}
else
{
uiNextEndIndex = auiEntriesPerPxArray[uiThisLevel + 1];
bNextLast = IMG_FALSE;
}
/* 递归释放下一级 */
(*pui32CurrentLevel)++;
if (_MMU_FreeLevel(psMMUContext, psNextLevel, auiStartArray,
auiEndArray, auiEntriesPerPxArray,
apsConfig, aeMMULevel, pui32CurrentLevel,
uiNextStartIndex, uiNextEndIndex,
bNextFirst, bNextLast, uiLog2DataPageSize))
{
/* 下一级被完全释放,断开连接 */
_SetupPxE(psMMUContext,
psLevel,
i,
psConfig,
aeMMULevel[uiThisLevel],
NULL, /* 无效地址 */
#if defined(PDUMP)
NULL, NULL, 0,
#endif
MMU_PROTFLAGS_INVALID,
uiLog2DataPageSize);
/* 释放下一级的内存 */
_PxMemFree(psMMUContext, &psNextLevel->sMemDesc,
aeMMULevel[*pui32CurrentLevel]);
OSFreeMem(psNextLevel);
/* 减少引用计数 */
psLevel->ui32RefCount--;
psLevel->apsNextLevel[i] = NULL;
PVR_ASSERT(psLevel->ui32RefCount <= psLevel->ui32NumOfEntries);
}
(*pui32CurrentLevel)--;
}
else
{
/* Level 1,直接减少引用计数 */
psLevel->ui32RefCount--;
}
/* 如果引用计数为0且不是基础级别,标记为可释放 */
if ((psLevel->ui32RefCount == 0) &&
(psLevel != &psMMUContext->sBaseLevelInfo))
{
bFreed = IMG_TRUE;
}
}
/* 刷新缓存 */
if (aeMMULevel[uiThisLevel] != MMU_LEVEL_1)
{
PVRSRV_ERROR eError;
eError = PhysHeapPagesClean(psDevNode->psMMUPhysHeap,
&psLevel->sMemDesc.psMapping->sMemHandle,
uiStartIndex * psConfig->uiBytesPerEntry +
psLevel->sMemDesc.uiOffset,
(uiEndIndex - uiStartIndex) *
psConfig->uiBytesPerEntry);
PVR_LOG_IF_ERROR(eError, "PhysHeapPagesClean");
}
ErrReturn:
MMU_OBJ_DBG((PVR_DBG_ERROR, "_MMU_FreeLevel end: level = %u, refcount = %u",
aeMMULevel[uiThisLevel],
bFreed ? 0 : (psLevel != NULL ? psLevel->ui32RefCount : IMG_UINT32_MAX)));
return bFreed;
}
获取级别数据:_MMU_GetLevelData
static void _MMU_GetLevelData(
MMU_CONTEXT *psMMUContext,
IMG_DEV_VIRTADDR sDevVAddrStart,
IMG_DEV_VIRTADDR sDevVAddrEnd,
IMG_UINT32 uiLog2DataPageSize,
IMG_UINT32 auiStartArray[],
IMG_UINT32 auiEndArray[],
IMG_UINT32 auiEntriesPerPx[],
const MMU_PxE_CONFIG *apsConfig[],
MMU_LEVEL aeMMULevel[],
const MMU_DEVVADDR_CONFIG **ppsMMUDevVAddrConfig,
IMG_HANDLE *phPriv)
{
const MMU_PxE_CONFIG *psMMUPDEConfig;
const MMU_PxE_CONFIG *psMMUPTEConfig;
const MMU_DEVVADDR_CONFIG *psDevVAddrConfig;
MMU_DEVICEATTRIBS *psDevAttrs = psMMUContext->psDevAttrs;
PVRSRV_ERROR eError;
IMG_UINT32 i = 0;
/* 获取页大小配置 */
eError = psDevAttrs->pfnGetPageSizeConfiguration(uiLog2DataPageSize,
&psMMUPDEConfig,
&psMMUPTEConfig,
ppsMMUDevVAddrConfig,
phPriv);
PVR_LOG_RETURN_VOID_IF_ERROR(eError, "GetPageSizeConfiguration");
psDevVAddrConfig = *ppsMMUDevVAddrConfig;
填充级别数据:
/* Level 3 (PC) */
if (psDevVAddrConfig->uiPCIndexMask != 0)
{
auiStartArray[i] = _CalcPCEIdx(sDevVAddrStart, psDevVAddrConfig, IMG_FALSE);
auiEndArray[i] = _CalcPCEIdx(sDevVAddrEnd, psDevVAddrConfig, IMG_TRUE);
auiEntriesPerPx[i] = psDevVAddrConfig->uiNumEntriesPC;
apsConfig[i] = psDevAttrs->psBaseConfig;
aeMMULevel[i] = MMU_LEVEL_3;
i++;
}
/* Level 2 (PD) */
if (psDevVAddrConfig->uiPDIndexMask != 0)
{
auiStartArray[i] = _CalcPDEIdx(sDevVAddrStart, psDevVAddrConfig, IMG_FALSE);
auiEndArray[i] = _CalcPDEIdx(sDevVAddrEnd, psDevVAddrConfig, IMG_TRUE);
auiEntriesPerPx[i] = psDevVAddrConfig->uiNumEntriesPD;
if (i == 0)
{
apsConfig[i] = psDevAttrs->psBaseConfig;
}
else
{
apsConfig[i] = psMMUPDEConfig;
}
aeMMULevel[i] = MMU_LEVEL_2;
i++;
}
/* Level 1 (PT) - 总是存在 */
auiStartArray[i] = _CalcPTEIdx(sDevVAddrStart, psDevVAddrConfig, IMG_FALSE);
if (psDevVAddrConfig->uiPTIndexMask != 0)
{
auiEndArray[i] = _CalcPTEIdx(sDevVAddrEnd, psDevVAddrConfig, IMG_TRUE);
}
else
{
/* PT掩码为0意味着只有1个PTE */
auiEndArray[i] = auiStartArray[i] + 1;
}
auiEntriesPerPx[i] = psDevVAddrConfig->uiNumEntriesPT;
if (i == 0)
{
apsConfig[i] = psDevAttrs->psBaseConfig;
}
else
{
apsConfig[i] = psMMUPTEConfig;
}
aeMMULevel[i] = MMU_LEVEL_1;
}
数据结构示例:
假设分配虚拟地址范围:0x40201000 - 0x40203FFF (3个4KB页)
输出数组:
auiStartArray[] = {0x100, 0x201, 0x001} // PC, PD, PT 起始索引
auiEndArray[] = {0x100, 0x201, 0x003} // PC, PD, PT 结束索引
auiEntriesPerPx[] = {512, 512, 512} // 每级的条目数
aeMMULevel[] = {LEVEL_3, LEVEL_2, LEVEL_1}
虚拟地址分解:
0x40201000
├── PC索引: 0x100
├── PD索引: 0x201
└── PT索引: 0x001
0x40203FFF
├── PC索引: 0x100 (相同)
├── PD索引: 0x201 (相同)
└── PT索引: 0x003
页面映射:MMU_MapPages
这是用户空间最常用的函数之一。
PVRSRV_ERROR
MMU_MapPages(MMU_CONTEXT *psMMUContext,
PVRSRV_MEMALLOCFLAGS_T uiMappingFlags,
IMG_DEV_VIRTADDR sDevVAddrBase,
PMR *psPMR,
IMG_UINT32 ui32PhysPgOffset,
IMG_UINT32 ui32MapPageCount,
IMG_UINT32 *paui32MapIndices,
IMG_UINT32 uiLog2HeapPageSize)
{
PVRSRV_ERROR eError;
IMG_HANDLE hPriv;
MMU_Levelx_INFO *psLevel = NULL;
MMU_Levelx_INFO *psPrevLevel = NULL;
IMG_UINT32 uiPTEIndex = 0;
IMG_UINT32 uiPageSize = (1 << uiLog2HeapPageSize);
IMG_UINT32 uiLoop = 0;
IMG_DEVMEM_OFFSET_T uiPgOffset = 0;
IMG_UINT32 uiFlushEnd = 0, uiFlushStart = 0;
IMG_UINT64 uiProtFlags = 0, uiProtFlagsReadOnly = 0, uiDefProtFlags=0;
MMU_PROTFLAGS_T uiMMUProtFlags = 0;
const MMU_PxE_CONFIG *psConfig;
const MMU_DEVVADDR_CONFIG *psDevVAddrConfig;
IMG_DEV_VIRTADDR sDevVAddr = sDevVAddrBase;
IMG_DEV_PHYADDR asDevPAddr[PMR_MAX_TRANSLATION_STACK_ALLOC];
IMG_BOOL abValid[PMR_MAX_TRANSLATION_STACK_ALLOC];
IMG_DEV_PHYADDR *psDevPAddr;
IMG_DEV_PHYADDR sDevPAddr;
IMG_BOOL *pbValid;
IMG_BOOL bValid;
IMG_BOOL bScratchBacking = IMG_FALSE, bZeroBacking = IMG_FALSE;
PVRSRV_DEVICE_NODE *psDevNode;
映射流程图:
核心映射循环:
#if defined(PDUMP)
PDUMPCOMMENT(psDevNode, "Wire up Page Table entries...");
#endif
/* 参数验证 */
PVR_LOG_RETURN_IF_INVALID_PARAM(psMMUContext != NULL, "psMMUContext");
PVR_LOG_RETURN_IF_INVALID_PARAM(psPMR != NULL, "psPMR");
psDevNode = psMMUContext->psPhysMemCtx->psDevNode;
/* 分配地址和有效性数组 */
if (ui32MapPageCount > PMR_MAX_TRANSLATION_STACK_ALLOC)
{
psDevPAddr = OSAllocMem(ui32MapPageCount * sizeof(IMG_DEV_PHYADDR));
PVR_LOG_GOTO_IF_NOMEM(psDevPAddr, eError, ErrReturnError);
pbValid = OSAllocMem(ui32MapPageCount * sizeof(IMG_BOOL));
PVR_LOG_GOTO_IF_NOMEM(pbValid, eError, ErrFreePAddrMappingArray);
}
else
{
psDevPAddr = asDevPAddr;
pbValid = abValid;
}
/* 获取物理地址 */
if (NULL == paui32MapIndices) /* 连续映射 */
{
eError = PMR_DevPhysAddr(psPMR,
uiLog2HeapPageSize,
ui32MapPageCount,
((IMG_DEVMEM_OFFSET_T)ui32PhysPgOffset << uiLog2HeapPageSize),
psDevPAddr,
pbValid,
DEVICE_USE | MAPPING_USE);
PVR_GOTO_IF_ERROR(eError, ErrFreeValidArray);
}
/* 获取页表配置 */
_MMU_GetPTConfig(psMMUContext,
(IMG_UINT32)uiLog2HeapPageSize,
&psConfig,
&hPriv,
&psDevVAddrConfig);
/* 转换标志 */
eError = _MMU_ConvertDevMemFlags(IMG_FALSE,
uiMappingFlags,
&uiMMUProtFlags,
psMMUContext);
PVR_GOTO_IF_ERROR(eError, ErrPutPTConfig);
/* 获取保护标志 */
if (psConfig->uiBytesPerEntry == 8)
{
uiProtFlags = psMMUContext->psDevAttrs->pfnDerivePTEProt8(
uiMMUProtFlags, uiLog2HeapPageSize);
uiMMUProtFlags |= MMU_PROTFLAGS_READABLE;
uiProtFlagsReadOnly = psMMUContext->psDevAttrs->pfnDerivePTEProt8(
(uiMMUProtFlags & ~MMU_PROTFLAGS_WRITEABLE),
uiLog2HeapPageSize);
}
else if (psConfig->uiBytesPerEntry == 4)
{
uiProtFlags = psMMUContext->psDevAttrs->pfnDerivePTEProt4(uiMMUProtFlags);
uiMMUProtFlags |= MMU_PROTFLAGS_READABLE;
uiProtFlagsReadOnly = psMMUContext->psDevAttrs->pfnDerivePTEProt4(
(uiMMUProtFlags & ~MMU_PROTFLAGS_WRITEABLE));
}
/* 检查稀疏内存backing */
if (PMR_IsSparse(psPMR))
{
bScratchBacking = PVRSRV_IS_SPARSE_SCRATCH_BACKING_REQUIRED(uiMappingFlags);
if (bScratchBacking)
{
bZeroBacking = PVRSRV_IS_ZERO_BACKING_REQUIRED(uiMappingFlags);
}
}
#if defined(SUPPORT_PMR_DEFERRED_FREE)
PMRMarkForDeferFree(psPMR);
#endif
OSLockAcquire(psMMUContext->hLock);
/* 映射循环 */
for (uiLoop = 0; uiLoop < ui32MapPageCount; uiLoop++)
{
#if defined(PDUMP)
IMG_DEVMEM_OFFSET_T uiNextSymName;
#endif
/* 索引映射需要单独获取物理地址 */
if (NULL != paui32MapIndices)
{
uiPgOffset = paui32MapIndices[uiLoop];
sDevVAddr.uiAddr = sDevVAddrBase.uiAddr + (uiPgOffset * uiPageSize);
eError = PMR_DevPhysAddr(psPMR,
uiLog2HeapPageSize,
1,
uiPgOffset * uiPageSize,
&sDevPAddr,
&bValid,
DEVICE_USE | MAPPING_USE);
PVR_GOTO_IF_ERROR(eError, ErrUnlockAndUnmapPages);
}
else
{
uiPgOffset = uiLoop + ui32PhysPgOffset;
sDevPAddr = psDevPAddr[uiLoop];
bValid = pbValid[uiLoop];
}
uiDefProtFlags = uiProtFlags;
/* 处理有效或backing页面 */
if (bValid || bScratchBacking)
{
if (!bValid)
{
/* 使用backing页面 */
if (bZeroBacking)
{
eError = _MMU_GetBackingPage(psDevNode,
&sDevPAddr.uiAddr,
DEV_ZERO_PAGE);
uiDefProtFlags = uiProtFlagsReadOnly; /* 零页只读 */
}
else
{
eError = _MMU_GetBackingPage(psDevNode,
&sDevPAddr.uiAddr,
SCRATCH_PAGE);
}
PVR_LOG_GOTO_IF_ERROR(eError, "_MMU_GetBackingPage",
ErrUnlockAndUnmapPages);
}
else
{
/* 检查物理地址对齐 */
PVR_ASSERT((sDevPAddr.uiAddr & (uiPageSize-1)) == 0);
}
#if defined(DEBUG)
/* 检查物理地址位宽 */
{
IMG_INT32 i32FeatureVal = PVRSRV_GET_DEVICE_FEATURE_VALUE(
psDevNode, PHYS_BUS_WIDTH);
if (i32FeatureVal > 0)
{
IMG_UINT32 ui32BitLength = FloorLog2(sDevPAddr.uiAddr);
if (ui32BitLength > (IMG_UINT32)i32FeatureVal)
{
PVR_DPF((PVR_DBG_ERROR,
"%s Failed. Physical address bitlength (%u) > chip (%d).",
__func__, ui32BitLength, i32FeatureVal));
PVR_ASSERT(ui32BitLength <= (IMG_UINT32)i32FeatureVal);
eError = PVRSRV_ERROR_INVALID_PARAMS;
goto ErrUnlockAndUnmapPages;
}
}
}
#endif
#if defined(PDUMP)
if (bValid)
{
eError = PMR_PDumpSymbolicAddr(psPMR, uiPgOffset * uiPageSize,
sizeof(aszMemspaceName), &aszMemspaceName[0],
sizeof(aszSymbolicAddress), &aszSymbolicAddress[0],
&uiSymbolicAddrOffset,
&uiNextSymName);
PVR_LOG_IF_ERROR(eError, "PMR_PDumpSymbolicAddr");
}
#endif
psPrevLevel = psLevel;
/* 获取PTE信息 */
if (!_MMU_GetPTInfo(psMMUContext, sDevVAddr, psDevVAddrConfig,
&psLevel, &uiPTEIndex))
{
PVR_GOTO_WITH_ERROR(eError, PVRSRV_ERROR_MAPPING_NOT_FOUND,
ErrUnlockAndUnmapPages);
}
/* 更新刷新范围 */
if (psPrevLevel == psLevel)
{
if (uiPTEIndex > uiFlushEnd)
uiFlushEnd = uiPTEIndex;
else if (uiPTEIndex < uiFlushStart)
uiFlushStart = uiPTEIndex;
}
else
{
/* 刷新前一个PT */
if (psPrevLevel != NULL)
{
eError = PhysHeapPagesClean(psDevNode->psMMUPhysHeap,
&psPrevLevel->sMemDesc.psMapping->sMemHandle,
uiFlushStart * psConfig->uiBytesPerEntry +
psPrevLevel->sMemDesc.uiOffset,
(uiFlushEnd+1 - uiFlushStart) *
psConfig->uiBytesPerEntry);
PVR_GOTO_IF_ERROR(eError, ErrUnlockAndUnmapPages);
}
uiFlushStart = uiPTEIndex;
uiFlushEnd = uiFlushStart;
}
HTBLOGK(HTB_SF_MMU_PAGE_OP_MAP, ...);
/* 设置PTE */
eError = _SetupPTE(psMMUContext,
psLevel,
uiPTEIndex,
psConfig,
&sDevPAddr,
&sDevVAddr,
IMG_FALSE,
#if defined(PDUMP)
(bValid)?aszMemspaceName:
(psMMUContext->psDevAttrs->pszMMUPxPDumpMemSpaceName),
(bValid)?aszSymbolicAddress:
((bZeroBacking)?DEV_ZERO_PAGE_STR:SCRATCH_PAGE_STR),
(bValid)?uiSymbolicAddrOffset:0,
#endif
uiDefProtFlags);
PVR_LOG_GOTO_IF_ERROR(eError, "_SetupPTE", ErrUnlockAndUnmapPages);
if (bValid)
{
PVR_ASSERT(psLevel->ui32RefCount <= psLevel->ui32NumOfEntries);
}
}
sDevVAddr.uiAddr += uiPageSize;
}
/* 刷新最后一个PT */
if (psLevel != NULL)
{
eError = PhysHeapPagesClean(psDevNode->psMMUPhysHeap,
&psLevel->sMemDesc.psMapping->sMemHandle,
uiFlushStart * psConfig->uiBytesPerEntry +
psLevel->sMemDesc.uiOffset,
(uiFlushEnd+1 - uiFlushStart) *
psConfig->uiBytesPerEntry);
PVR_GOTO_IF_ERROR(eError, ErrUnlockAndUnmapPages);
}
/* 失效MMU TLB */
psDevNode->pfnMMUCacheInvalidate(psDevNode,
psMMUContext,
MMU_LEVEL_1,
IMG_FALSE);
/* 如果需要立即失效 */
if (PVRSRV_CHECK_KICK_PT_INVALIDATE(uiMappingFlags) &&
psDevNode->pfnMMUCacheInvalidateKickAndWait != NULL)
{
eError = psDevNode->pfnMMUCacheInvalidateKickAndWait(psDevNode);
PVR_LOG_GOTO_IF_ERROR(eError, "pfnMMUCacheInvalidateKickAndWait",
ErrUnlockAndUnmapPages);
}
OSLockRelease(psMMUContext->hLock);
_MMU_PutPTConfig(psMMUContext, hPriv);
if (psDevPAddr != asDevPAddr)
{
OSFreeMem(pbValid);
OSFreeMem(psDevPAddr);
}
#if defined(PDUMP)
PDUMPCOMMENT(psDevNode, "Wired up %d Page Table entries (out of %d)",
ui32MappedCount, ui32MapPageCount);
#endif
return PVRSRV_OK;
ErrUnlockAndUnmapPages:
(void)MMU_UnmapPagesUnlocked(psMMUContext,
PMR_IsSparse(psPMR) ? uiMappingFlags : 0,
sDevVAddrBase,
uiLoop,
paui32MapIndices,
uiLog2HeapPageSize);
OSLockRelease(psMMUContext->hLock);
ErrPutPTConfig:
_MMU_PutPTConfig(psMMUContext, hPriv);
ErrFreeValidArray:
if (psDevPAddr != asDevPAddr)
{
OSFreeMem(pbValid);
}
ErrFreePAddrMappingArray:
if (psDevPAddr != asDevPAddr)
{
OSFreeMem(psDevPAddr);
}
ErrReturnError:
return eError;
}
快速映射:MMU_MapPMRFast
针对连续PMR的优化版本。
PVRSRV_ERROR
MMU_MapPMRFast(MMU_CONTEXT *psMMUContext,
IMG_DEV_VIRTADDR sDevVAddrBase,
PMR *psPMR,
IMG_DEVMEM_SIZE_T uiSizeBytes,
PVRSRV_MEMALLOCFLAGS_T uiMappingFlags,
IMG_UINT32 uiLog2HeapPageSize,
MMU_PTE_REMAP_POLICY eRemapPolicy)
{
PVRSRV_ERROR eError = PVRSRV_OK;
PVRSRV_DEVICE_NODE *psDevNode = psMMUContext->psPhysMemCtx->psDevNode;
const MMU_PxE_CONFIG *psConfig;
const MMU_DEVVADDR_CONFIG *psDevVAddrConfig;
IMG_HANDLE hPriv;
IMG_UINT32 i, uiChunkStart, uiLastPTEIndex, uiNumEntriesToWrite;
IMG_UINT32 ui32PagesDone=0, uiPTEIndex=0;
IMG_UINT8 uiAddrLog2Align, uiAddrShift, uiParityShift;
IMG_UINT64 uiAddrMask, uiProtFlags;
IMG_UINT32 uiBytesPerEntry;
IMG_UINT64 uiParityBit = 0;
IMG_BOOL bSetParity = IMG_FALSE;
IMG_DEV_VIRTADDR sDevVAddrRunning, sDevVAddrBaseCopy = sDevVAddrBase;
IMG_UINT64* pui64LevelBase;
IMG_UINT32* pui32LevelBase;
MMU_PROTFLAGS_T uiMMUProtFlags = 0;
MMU_Levelx_INFO *psLevel = NULL;
IMG_DEV_PHYADDR asDevPAddr[PMR_MAX_TRANSLATION_STACK_ALLOC];
IMG_BOOL abValid[PMR_MAX_TRANSLATION_STACK_ALLOC];
IMG_UINT32 uiNumPages = uiSizeBytes >> uiLog2HeapPageSize;
IMG_BOOL bValidateOrTweak = psDevNode->pfnValidateOrTweakPhysAddrs ?
IMG_TRUE : IMG_FALSE;
快速映射优化:
批量写入循环:
/* 获取配置 */
_MMU_GetPTConfig(psMMUContext, (IMG_UINT32)uiLog2HeapPageSize,
&psConfig, &hPriv, &psDevVAddrConfig);
eError = _MMU_ConvertDevMemFlags(IMG_FALSE,
uiMappingFlags,
&uiMMUProtFlags,
psMMUContext);
PVR_GOTO_IF_ERROR(eError, put_mmu_context);
/* 提取配置常量以避免重复解引用 */
uiAddrLog2Align = psConfig->uiAddrLog2Align;
uiAddrShift = psConfig->uiAddrShift;
uiAddrMask = psConfig->uiAddrMask;
uiBytesPerEntry = psConfig->uiBytesPerEntry;
bSetParity = psConfig->uiParityBitMask;
uiParityShift = psConfig->uiParityBitShift;
sDevVAddrRunning.uiAddr = sDevVAddrBase.uiAddr;
/* 计算保护标志 */
if (uiBytesPerEntry == 8)
{
uiProtFlags = psMMUContext->psDevAttrs->pfnDerivePTEProt8(
uiMMUProtFlags, uiLog2HeapPageSize);
}
else if (uiBytesPerEntry == 4)
{
uiProtFlags = psMMUContext->psDevAttrs->pfnDerivePTEProt4(uiMMUProtFlags);
}
else
{
PVR_LOG_GOTO_WITH_ERROR("psConfig->uiBytesPerEntry", eError,
PVRSRV_ERROR_MMU_CONFIG_IS_WRONG, put_mmu_context);
}
#if defined(SUPPORT_PMR_DEFERRED_FREE)
PMRMarkForDeferFree(psPMR);
#endif
OSLockAcquire(psMMUContext->hLock);
do
{
/* 定位PT */
if (!_MMU_GetPTInfo(psMMUContext, sDevVAddrBase, psDevVAddrConfig,
&psLevel, &uiPTEIndex))
{
PVR_GOTO_WITH_ERROR(eError, PVRSRV_ERROR_MAPPING_NOT_FOUND,
unlock_mmu_context);
}
pui64LevelBase = (IMG_UINT64*)psLevel->sMemDesc.pvCpuVAddr;
pui32LevelBase = (IMG_UINT32*)psLevel->sMemDesc.pvCpuVAddr;
/* 计算要写入的条目数 */
uiLastPTEIndex = MIN(uiPTEIndex + uiNumPages - ui32PagesDone,
psDevVAddrConfig->uiNumEntriesPT);
uiNumEntriesToWrite = uiLastPTEIndex - uiPTEIndex;
/* 批量获取物理地址 */
for (uiChunkStart = 0; uiChunkStart < uiNumEntriesToWrite;
uiChunkStart += PMR_MAX_TRANSLATION_STACK_ALLOC)
{
IMG_UINT32 uiNumPagesInBlock = MIN(uiNumEntriesToWrite - uiChunkStart,
PMR_MAX_TRANSLATION_STACK_ALLOC);
eError = PMR_DevPhysAddr(psPMR,
uiLog2HeapPageSize,
uiNumPagesInBlock,
(IMG_UINT64)(ui32PagesDone + uiChunkStart) <<
uiLog2HeapPageSize,
asDevPAddr,
abValid,
DEVICE_USE | MAPPING_USE);
PVR_GOTO_IF_ERROR(eError, unlock_mmu_context);
/* 验证/调整物理地址 */
if (bValidateOrTweak)
{
for (i=0; i<uiNumPagesInBlock; i++)
{
PVRSRV_ERROR eErr = psDevNode->pfnValidateOrTweakPhysAddrs(
psDevNode,
psMMUContext->psDevAttrs,
&asDevPAddr[i].uiAddr);
PVR_GOTO_IF_ERROR(eErr, unlock_mmu_context);
}
}
/* 批量写入PTE */
if (uiBytesPerEntry == 8)
{
for (i=0; i<uiNumPagesInBlock; i++)
{
if (bSetParity)
{
uiParityBit = _GetParityBit(sDevVAddrRunning.uiAddr ^
asDevPAddr[i].uiAddr);
uiParityBit <<= uiParityShift;
sDevVAddrRunning.uiAddr += (IMG_UINT64_C(1) << uiLog2HeapPageSize);
}
/* 检查重映射策略 */
if (eRemapPolicy == MMU_PTE_REMAP_POLICY_BLOCK &&
pui64LevelBase[uiPTEIndex + uiChunkStart + i] &
psConfig->uiValidEnMask &&
(uiProtFlags & psConfig->uiValidEnMask))
{
PVR_GOTO_WITH_ERROR(eError, PVRSRV_ERROR_MMU_REMAP_BLOCKED,
unlock_mmu_context);
}
/* 写入PTE */
pui64LevelBase[uiPTEIndex + uiChunkStart + i] =
(((asDevPAddr[i].uiAddr >> uiAddrLog2Align) << uiAddrShift) &
uiAddrMask) | uiProtFlags | uiParityBit;
}
}
else if (uiBytesPerEntry == 4)
{
for (i=0; i<uiNumPagesInBlock; i++)
{
if (eRemapPolicy == MMU_PTE_REMAP_POLICY_BLOCK &&
pui32LevelBase[uiPTEIndex + uiChunkStart + i] &
(IMG_UINT32)psConfig->uiValidEnMask &&
(uiProtFlags & (IMG_UINT32)psConfig->uiValidEnMask))
{
PVR_GOTO_WITH_ERROR(eError, PVRSRV_ERROR_MMU_REMAP_BLOCKED,
unlock_mmu_context);
}
pui32LevelBase[uiPTEIndex + uiChunkStart + i] =
(((asDevPAddr[i].uiAddr >> uiAddrLog2Align) << uiAddrShift) &
uiAddrMask) | uiProtFlags;
}
}
#if defined(PDUMP)
/* PDump批量条目 */
for (i=0; i<uiNumPagesInBlock; i++)
{
IMG_DEVMEM_OFFSET_T uiNextSymName;
IMG_UINT64 uiParityBit = psConfig->uiParityBitMask ?
_GetParityBit(sDevVAddrRunningPdump.uiAddr ^ 0ULL) : 0;
eError = PMR_PDumpSymbolicAddr(...);
PDumpMMUDumpPxEntries(...);
sDevVAddrRunningPdump.uiAddr += (1 << uiLog2HeapPageSize);
}
#endif
#if defined(PVRSRV_ENABLE_HTB)
/* HTB日志 */
if (bHTBLog)
{
for (i=0; i<uiNumPagesInBlock; i++)
{
HTBLOGK(HTB_SF_MMU_PAGE_OP_PMRMAP, ...);
HTBLOGK(HTB_SF_MMU_PAGE_OP_TABLE, ...);
}
}
#endif
}
/* 刷新这批PTE */
eError = PhysHeapPagesClean(psDevNode->psMMUPhysHeap,
&psLevel->sMemDesc.psMapping->sMemHandle,
uiPTEIndex * uiBytesPerEntry +
psLevel->sMemDesc.uiOffset,
(uiNumEntriesToWrite) * uiBytesPerEntry);
PVR_GOTO_IF_ERROR(eError, unlock_mmu_context);
sDevVAddrBase.uiAddr += uiNumEntriesToWrite * (1 << uiLog2HeapPageSize);
ui32PagesDone += uiNumEntriesToWrite;
} while (ui32PagesDone < uiNumPages);
OSLockRelease(psMMUContext->hLock);
/* 失效MMU TLB */
psDevNode->pfnMMUCacheInvalidate(psDevNode,
psMMUContext,
MMU_LEVEL_1,
IMG_FALSE);
_MMU_PutPTConfig(psMMUContext, hPriv);
return PVRSRV_OK;
unlock_mmu_context:
if (eError != PVRSRV_ERROR_MMU_REMAP_BLOCKED)
{
(void)MMU_UnmapPMRFastUnlocked(psMMUContext,
sDevVAddrBaseCopy,
uiNumPages,
uiLog2HeapPageSize);
}
OSLockRelease(psMMUContext->hLock);
put_mmu_context:
_MMU_PutPTConfig(psMMUContext, hPriv);
return eError;
}
性能对比:
映射1000个页面的性能对比:
传统方法 (MMU_MapPages):
- 1000次 PT查找
- 1000次 PTE设置
- 约1000次 缓存刷新操作
总时间:~10ms
快速方法 (MMU_MapPMRFast):
- 2-3次 PT查找(跨越PT边界)
- 1次批量获取物理地址
- 直接内存写入PTE
- 2-3次批量缓存刷新
总时间:~2ms
加速比:5x
快速取消映射
static PVRSRV_ERROR MMU_UnmapPMRFastUnlocked(MMU_CONTEXT *psMMUContext,
IMG_DEV_VIRTADDR sDevVAddrBase,
IMG_UINT32 ui32PageCount,
IMG_UINT32 uiLog2PageSize)
{
IMG_UINT32 uiPTEIndex = 0, uiLastPTEIndex = 0, ui32PagesDone = 0;
IMG_UINT32 uiPageSize = 1 << uiLog2PageSize;
MMU_Levelx_INFO *psLevel = NULL;
void* pvPTStart;
const MMU_PxE_CONFIG *psConfig;
const MMU_DEVVADDR_CONFIG *psDevVAddrConfig;
IMG_UINT64 uiEntry = 0;
// 获取配置
_MMU_GetPTConfig(psMMUContext, uiLog2PageSize, &psConfig, &hPriv, &psDevVAddrConfig);
// 预计算无效页表项值
if (psConfig->uiBytesPerEntry == 8) {
IMG_UINT64 ui64BadPhysAddr = gsBadDevPhyAddr.uiAddr & ~(psConfig->uiProtMask | psConfig->uiParityBitMask);
uiEntry = ui64BadPhysAddr | uiProtFlags;
} else if (psConfig->uiBytesPerEntry == 4) {
uiEntry = (((IMG_UINT32) gsBadDevPhyAddr.uiAddr) & ~psConfig->uiProtMask) | (IMG_UINT32) uiProtFlags;
}
do {
// 获取页表信息
if (!_MMU_GetPTInfo(psMMUContext, sDevVAddr, psDevVAddrConfig,
&psLevel, &uiPTEIndex)) {
PVR_GOTO_WITH_ERROR(eError, PVRSRV_ERROR_MAPPING_NOT_FOUND, e0);
}
pvPTStart = psLevel->sMemDesc.pvCpuVAddr;
uiLastPTEIndex = MIN(uiPTEIndex + ui32PageCount - ui32PagesDone,
psDevVAddrConfig->uiNumEntriesPT);
uiNumEntriesToWrite = uiLastPTEIndex - uiPTEIndex;
// 批量写入无效项
if (psConfig->uiBytesPerEntry == 8) {
for (i = uiPTEIndex; i < uiLastPTEIndex; i++) {
IMG_UINT64 uiParityBit = 0;
#if defined(PVRSRV_MMU_PARITY_ON_PTALLOC_AND_PTEUNMAP)
if (bSetParity) {
uiParityBit = psMMUContext->psDevAttrs->pui64PrecomputedAllocParity[uiParityPatternIdx][i] ^
ui64BadPhysAddrParity;
}
#endif
((IMG_UINT64*)pvPTStart)[i] = uiEntry | uiParityBit;
}
} else {
for (i = uiPTEIndex; i < uiLastPTEIndex; i++) {
((IMG_UINT32*)pvPTStart)[i] = (IMG_UINT32) uiEntry;
}
}
// 缓存清理
PhysHeapPagesClean(psDevNode->psMMUPhysHeap, &psLevel->sMemDesc.psMapping->sMemHandle,
uiPTEIndex * psConfig->uiBytesPerEntry + psLevel->sMemDesc.uiOffset,
uiNumEntriesToWrite * psConfig->uiBytesPerEntry);
sDevVAddr.uiAddr += uiNumEntriesToWrite * uiPageSize;
ui32PagesDone += uiNumEntriesToWrite;
} while (ui32PagesDone < ui32PageCount);
_MMU_PutPTConfig(psMMUContext, hPriv);
// TLB刷新
psDevNode->pfnMMUCacheInvalidate(psDevNode, psMMUContext, MMU_LEVEL_1, IMG_TRUE);
return PVRSRV_OK;
}
快速取消映射特点:
- 预计算无效值: 避免在循环中重复计算
- 批量写入: 简单的循环写入操作
- 奇偶校验优化: 使用预计算的奇偶校验模式
重要的公共API
MMU上下文创建:MMU_ContextCreate
PVRSRV_ERROR
MMU_ContextCreate(CONNECTION_DATA *psConnection,
PVRSRV_DEVICE_NODE *psDevNode,
MMU_CONTEXT **ppsMMUContext,
MMU_DEVICEATTRIBS *psDevAttrs)
{
MMU_CONTEXT *psMMUContext;
const MMU_DEVVADDR_CONFIG *psDevVAddrConfig;
const MMU_PxE_CONFIG *psConfig;
MMU_PHYSMEM_CONTEXT *psPhysMemCtx;
IMG_UINT32 ui32BaseObjects;
IMG_UINT32 ui32Size;
IMG_CHAR sBuf[40];
PVRSRV_ERROR eError = PVRSRV_OK;
#if defined(PDUMP)
PDUMPCOMMENT(psDevNode, "MMU context create");
#endif
psConfig = psDevAttrs->psBaseConfig;
psDevVAddrConfig = psDevAttrs->psTopLevelDevVAddrConfig;
/* 确定基础级别的条目数 */
switch (psDevAttrs->psBaseConfig->ePxLevel)
{
case MMU_LEVEL_3:
ui32BaseObjects = psDevVAddrConfig->uiNumEntriesPC;
break;
case MMU_LEVEL_2:
ui32BaseObjects = psDevVAddrConfig->uiNumEntriesPD;
break;
case MMU_LEVEL_1:
ui32BaseObjects = psDevVAddrConfig->uiNumEntriesPT;
break;
default:
PVR_LOG_GOTO_WITH_ERROR("psDevAttrs->psBaseConfig->ePxLevel", eError,
PVRSRV_ERROR_INVALID_PARAMS, e0);
}
/* 分配MMU上下文(包括基础级别的指针数组) */
ui32Size = sizeof(MMU_CONTEXT) +
((ui32BaseObjects - 1) * sizeof(MMU_Levelx_INFO *));
psMMUContext = OSAllocZMem(ui32Size);
PVR_LOG_GOTO_IF_NOMEM(psMMUContext, eError, e0);
psMMUContext->psDevAttrs = psDevAttrs;
/* 分配物理内存上下文 */
psPhysMemCtx = OSAllocZMem(sizeof(MMU_PHYSMEM_CONTEXT));
PVR_LOG_GOTO_IF_NOMEM(psPhysMemCtx, eError, e1);
psMMUContext->psPhysMemCtx = psPhysMemCtx;
psMMUContext->psConnection = psConnection;
psPhysMemCtx->psDevNode = psDevNode;
psPhysMemCtx->psMMUContext = psMMUContext;
/* 设置OS ID(如果支持) */
#if defined(SUPPORT_CUSTOM_OSID_EMISSION)
if (!_MMU_IS_FWKM_CTX(psMMUContext))
{
psPhysMemCtx->ui32OSid = psConnection->ui32OSid;
psPhysMemCtx->ui32OSidReg = psConnection->ui32OSidReg;
psPhysMemCtx->bOSidAxiProt = psConnection->bOSidAxiProtReg;
}
#endif
/* 创建RA名称 */
OSSNPrintf(sBuf, sizeof(sBuf), "pgtables %p", psPhysMemCtx);
psPhysMemCtx->uiPhysMemRANameAllocSize = OSStringLength(sBuf)+1;
psPhysMemCtx->pszPhysMemRAName = OSAllocMem(
psPhysMemCtx->uiPhysMemRANameAllocSize);
PVR_LOG_GOTO_IF_NOMEM(psPhysMemCtx->pszPhysMemRAName, eError, e2);
OSStringSafeCopy(psPhysMemCtx->pszPhysMemRAName, sBuf,
psPhysMemCtx->uiPhysMemRANameAllocSize);
/* 创建RA */
psPhysMemCtx->psPhysMemRA = RA_Create(
psPhysMemCtx->pszPhysMemRAName,
PhysHeapGetPageShift(psDevNode->psMMUPhysHeap),
RA_LOCKCLASS_2,
_MMU_PhysMem_RAImportAlloc,
_MMU_PhysMem_RAImportFree,
psPhysMemCtx,
RA_POLICY_DEFAULT);
if (psPhysMemCtx->psPhysMemRA == NULL)
{
OSFreeMem(psPhysMemCtx->pszPhysMemRAName);
PVR_GOTO_WITH_ERROR(eError, PVRSRV_ERROR_OUT_OF_MEMORY, e3);
}
/* 创建清理数据 */
psPhysMemCtx->psCleanupData = OSAllocMem(sizeof(*(psPhysMemCtx->psCleanupData)));
PVR_LOG_GOTO_IF_NOMEM(psPhysMemCtx->psCleanupData, eError, e4);
OSLockCreate(&psPhysMemCtx->psCleanupData->hCleanupLock);
psPhysMemCtx->psCleanupData->bMMUContextExists = IMG_TRUE;
dllist_init(&psPhysMemCtx->psCleanupData->sMMUCtxCleanupItemsHead);
OSAtomicWrite(&psPhysMemCtx->psCleanupData->iRef, 1);
/* 分配基础级别对象 */
if (!_MMU_IS_FWKM_CTX_VZGUEST(psMMUContext))
{
if (_PxMemAlloc(psMMUContext,
ui32BaseObjects,
psConfig,
psDevAttrs->psBaseConfig->ePxLevel,
&psMMUContext->sBaseLevelInfo.sMemDesc,
#if defined(PVRSRV_MMU_PARITY_ON_PTALLOC_AND_PTEUNMAP)
NULL,
0U,
#endif
psDevAttrs->ui32BaseAlign))
{
PVR_LOG_GOTO_WITH_ERROR("_PxMemAlloc", eError,
PVRSRV_ERROR_OUT_OF_MEMORY, e5);
}
}
dllist_init(&psMMUContext->psPhysMemCtx->sTmpMMUMappingHead);
psMMUContext->sBaseLevelInfo.ui32NumOfEntries = ui32BaseObjects;
psMMUContext->sBaseLevelInfo.ui32RefCount = 0;
/* 设备特定的workaround */
if (psDevNode->pfnMMUTopLevelPxWorkarounds != NULL)
{
MMU_MEMORY_DESC *psMemDesc = &psMMUContext->sBaseLevelInfo.sMemDesc;
psDevNode->pfnMMUTopLevelPxWorkarounds(psConnection,
psDevNode,
psMemDesc->sDevPAddr,
psMemDesc->pvCpuVAddr);
eError = PhysHeapPagesClean(psDevNode->psMMUPhysHeap,
&psMemDesc->psMapping->sMemHandle,
psMemDesc->uiOffset,
psMemDesc->uiSize);
PVR_LOG_IF_ERROR(eError, "PhysHeapPagesClean");
}
/* 创建锁 */
eError = OSLockCreate(&psMMUContext->hLock);
PVR_LOG_GOTO_IF_ERROR(eError, "OSLockCreate", e6);
*ppsMMUContext = psMMUContext;
return PVRSRV_OK;
/* 错误处理路径 */
e6:
_PxMemFree(psMMUContext, &psMMUContext->sBaseLevelInfo.sMemDesc,
psDevAttrs->psBaseConfig->ePxLevel);
e5:
OSFreeMem(psPhysMemCtx->psCleanupData);
e4:
RA_Delete(psPhysMemCtx->psPhysMemRA);
e3:
OSFreeMem(psPhysMemCtx->pszPhysMemRAName);
e2:
OSFreeMem(psPhysMemCtx);
e1:
OSFreeMem(psMMUContext);
e0:
return eError;
}
创建流程图:
MMU上下文销毁:MMU_ContextDestroy
void
MMU_ContextDestroy(MMU_CONTEXT *psMMUContext)
{
PVRSRV_DATA *psPVRSRVData = PVRSRVGetPVRSRVData();
PDLLIST_NODE psNode, psNextNode;
PVRSRV_DEVICE_NODE *psDevNode = psMMUContext->psPhysMemCtx->psDevNode;
MMU_CTX_CLEANUP_DATA *psCleanupData = psMMUContext->psPhysMemCtx->psCleanupData;
PVR_DPF((PVR_DBG_MESSAGE, "%s: Enter", __func__));
if (psPVRSRVData->eServicesState == PVRSRV_SERVICES_STATE_OK)
{
/* 确保没有活动页面 */
PVR_ASSERT(psMMUContext->sBaseLevelInfo.ui32RefCount == 0);
}
/* 必须按正确顺序获取锁以避免死锁 */
OSLockAcquire(psCleanupData->hCleanupLock);
OSLockAcquire(psMMUContext->hLock);
/* 释放顶级MMU对象 */
if (!_MMU_IS_FWKM_CTX_VZGUEST(psMMUContext))
{
_PxMemFree(psMMUContext,
&psMMUContext->sBaseLevelInfo.sMemDesc,
psMMUContext->psDevAttrs->psBaseConfig->ePxLevel);
}
/* 清空临时延迟释放列表 */
_FreeMMUMapping(psDevNode, &psMMUContext->psPhysMemCtx->sTmpMMUMappingHead);
PVR_ASSERT(dllist_is_empty(&psMMUContext->psPhysMemCtx->sTmpMMUMappingHead));
/* 清空延迟释放列表 */
dllist_foreach_node(&psCleanupData->sMMUCtxCleanupItemsHead,
psNode,
psNextNode)
{
MMU_CLEANUP_ITEM *psCleanup = IMG_CONTAINER_OF(psNode,
MMU_CLEANUP_ITEM,
sMMUCtxCleanupItem);
_FreeMMUMapping(psDevNode, &psCleanup->sMMUMappingHead);
dllist_remove_node(psNode);
}
PVR_ASSERT(dllist_is_empty(&psCleanupData->sMMUCtxCleanupItemsHead));
psCleanupData->bMMUContextExists = IMG_FALSE;
/* 释放物理内存上下文 */
RA_Delete(psMMUContext->psPhysMemCtx->psPhysMemRA);
OSFreeMem(psMMUContext->psPhysMemCtx->pszPhysMemRAName);
OSFreeMem(psMMUContext->psPhysMemCtx);
OSLockRelease(psMMUContext->hLock);
OSLockRelease(psCleanupData->hCleanupLock);
/* 如果清理数据引用计数为0,释放它 */
if (OSAtomicDecrement(&psCleanupData->iRef) == 0)
{
OSLockDestroy(psCleanupData->hCleanupLock);
OSFreeMem(psCleanupData);
}
OSLockDestroy(psMMUContext->hLock);
/* 释放上下文本身 */
OSFreeMem(psMMUContext);
PVR_DPF((PVR_DBG_MESSAGE, "%s: Exit", __func__));
}
整体工作流程总结
映射操作完整流程
解映射和释放流程
页表层次结构示例
虚拟地址:0x0000_4020_1FFF (1GB + 2MB + 8KB - 1)
三级页表结构 (4KB页):
┌─────────────────────────────────────┐
│ MMU_CONTEXT │
│ sBaseLevelInfo (Level 3 - PC) │
│ ui32NumOfEntries = 512 │
└─────────┬───────────────────────────┘
│
├─[0] → NULL
├─[1] → NULL
├─[2] → Level 2 (PD) ←── PC索引 = 2
│ ┌──────────────────────┐
│ │ ui32NumOfEntries=512 │
│ │ sMemDesc (物理内存) │
│ └────┬─────────────────┘
│ │
│ ├─[0] → NULL
│ ├─[1] → NULL
│ ├─[2] → Level 1 (PT) ←── PD索引 = 2
│ │ ┌──────────────────────┐
│ │ │ ui32NumOfEntries=512 │
│ │ │ sMemDesc (物理内存) │
│ │ └────┬─────────────────┘
│ │ │
│ │ ├─[0] → PTE (物理页0) = 0x8000_0000
│ │ ├─[1] → PTE (物理页1) = 0x8000_1000
│ │ ├─[2] → PTE (物理页2) = 0x8000_2000 ←── PT索引 = 2
│ │ │ ┌─────────────────────────┐
│ │ │ │ 物理地址: 0x8000_2000 │
│ │ │ │ 标志: Valid|Read|Write │
│ │ │ │ 奇偶校验位: 1 │
│ │ │ └─────────────────────────┘
│ │ └─[511] → ...
│ └─[511] → ...
└─[511] → ...
虚拟地址分解:
0x0000_4020_1FFF
= 0000_0000_0000_0000_0100_0000_0010_0000_0001_1111_1111_1111
PC索引 (bits 47-39): 000000010 = 2
PD索引 (bits 38-30): 000000010 = 2
PT索引 (bits 29-21): 000000001 = 1
页内偏移 (bits 20-0): 00001_1111_1111_1111 = 0x1FFF (8KB - 1)
物理地址:0x8000_1000 + 0x1FFF = 0x8000_2FFF
内存对象生命周期
关键数据结构关系总结
MMU_CONTEXT
│
├─── psDevAttrs (设备属性)
│ ├─ psBaseConfig (基础配置)
│ ├─ psTopLevelDevVAddrConfig (虚拟地址配置)
│ └─ 各种回调函数
│
├─── psPhysMemCtx (物理内存上下文)
│ │
│ ├─── psPhysMemRA (RA分配器)
│ │ └─ 管理页表物理内存
│ │
│ ├─── psCleanupData (清理元数据)
│ │ ├─ sMMUCtxCleanupItemsHead
│ │ │ └─ 链接所有清理项
│ │ └─ bMMUContextExists
│ │
│ └─── sTmpMMUMappingHead
│ └─ 临时延迟释放列表
│
└─── sBaseLevelInfo (基础级别)
│
├─── sMemDesc (内存描述符)
│ ├─ sDevPAddr (设备物理地址)
│ ├─ pvCpuVAddr (CPU虚拟地址)
│ └─ psMapping
│ ├─ sMemHandle (OS句柄)
│ ├─ uiCpuVAddrRefCount
│ └─ sMMUMappingItem (延迟释放节点)
│
└─── apsNextLevel[] (下一级数组)
└─ 递归结构,形成页表树
性能优化要点
/* 1. 批量操作避免重复锁 */
OSLockAcquire(psMMUContext->hLock);
for (i = 0; i < count; i++) {
// 批量处理
}
OSLockRelease(psMMUContext->hLock);
/* 2. 缓存配置避免重复查询 */
_MMU_GetPTConfig(psMMUContext, uiLog2PageSize, ...);
// 使用缓存的配置
_MMU_PutPTConfig(psMMUContext, hPriv);
/* 3. 延迟刷新,批量刷新 */
// 不要每个PTE都刷新,而是记录范围
if (psPrevLevel != psLevel) {
// 切换PT时才刷新
PhysHeapPagesClean(..., uiFlushStart, uiFlushEnd);
}
/* 4. 栈数组避免动态分配 */
if (count <= 128) {
// 使用栈数组
asDevPAddr[128];
} else {
// 动态分配
psDevPAddr = OSAllocMem(...);
}
/* 5. 预计算奇偶校验模式 */
// 在初始化时预计算
pui64PrecomputedAllocParity[2][512];
// 使用时直接拷贝
OSCachedMemCopy(dest, precomputed, size);
错误处理模式
/* 统一的错误处理模式 */
PVRSRV_ERROR Function(...)
{
PVRSRV_ERROR eError;
Resource *psResource1 = NULL;
Resource *psResource2 = NULL;
// 分配资源1
psResource1 = AllocResource1();
PVR_LOG_GOTO_IF_NOMEM(psResource1, eError, e0);
// 分配资源2
psResource2 = AllocResource2();
PVR_LOG_GOTO_IF_NOMEM(psResource2, eError, e1);
// 执行操作
eError = DoSomething();
PVR_GOTO_IF_ERROR(eError, e2);
return PVRSRV_OK;
// 反向释放资源
e2:
FreeResource2(psResource2);
e1:
FreeResource1(psResource1);
e0:
return eError;
}
并发控制
/* 锁的层次结构(避免死锁) */
Level 1: psCleanupData->hCleanupLock
└─ Level 2: psMMUContext->hLock
└─ Level 3: 其他资源锁
/* 正确的获取顺序 */
OSLockAcquire(psCleanupData->hCleanupLock);
OSLockAcquire(psMMUContext->hLock);
// ... 操作 ...
OSLockRelease(psMMUContext->hLock);
OSLockRelease(psCleanupData->hCleanupLock);
/* 原子操作用于简单计数 */
OSAtomicIncrement(&psCleanupData->iRef);
OSAtomicDecrement(&psCleanupData->iRef);
OSAtomicRead(&psMMUContext->sCacheFlags);
调试和诊断
MMU故障诊断:MMU_CheckFaultAddress
void MMU_CheckFaultAddress(MMU_CONTEXT *psMMUContext,
IMG_DEV_VIRTADDR *psDevVAddr,
MMU_FAULT_DATA *psOutFaultData)
{
MMU_DEVICEATTRIBS *psDevAttrs = psMMUContext->psDevAttrs;
MMU_LEVEL eMMULevel = psDevAttrs->psBaseConfig->ePxLevel;
const MMU_PxE_CONFIG *psConfig;
const MMU_DEVVADDR_CONFIG *psMMUDevVAddrConfig;
IMG_HANDLE hPriv;
MMU_Levelx_INFO *psLevel = NULL;
PVRSRV_ERROR eError;
IMG_UINT64 uiIndex;
IMG_UINT32 ui32PCIndex = 0xFFFFFFFF;
IMG_UINT32 ui32PDIndex = 0xFFFFFFFF;
IMG_UINT32 ui32PTIndex = 0xFFFFFFFF;
MMU_FAULT_DATA sMMUFaultData = {0};
MMU_LEVEL_DATA *psMMULevelData;
OSLockAcquire(psMMUContext->hLock);
/* 假设4KB页面大小获取配置 */
eError = psDevAttrs->pfnGetPageSizeConfiguration(12,
&psMMUPDEConfig,
&psMMUPTEConfig,
&psMMUDevVAddrConfig,
&hPriv);
psLevel = &psMMUContext->sBaseLevelInfo;
psConfig = psDevAttrs->psBaseConfig;
sMMUFaultData.eTopLevel = psDevAttrs->psBaseConfig->ePxLevel;
sMMUFaultData.eType = MMU_FAULT_TYPE_NON_PM;
/* 遍历每一级页表 */
for (; eMMULevel > MMU_LEVEL_0; eMMULevel--)
{
if (eMMULevel == MMU_LEVEL_3)
{
/* PC级别 */
uiIndex = psDevVAddr->uiAddr & psDevVAddrConfig->uiPCIndexMask;
uiIndex = uiIndex >> psDevVAddrConfig->uiPCIndexShift;
ui32PCIndex = (IMG_UINT32)uiIndex;
psMMULevelData = &sMMUFaultData.sLevelData[MMU_LEVEL_3];
psMMULevelData->uiBytesPerEntry = psConfig->uiBytesPerEntry;
psMMULevelData->ui32Index = ui32PCIndex;
if (ui32PCIndex >= psLevel->ui32NumOfEntries)
{
psMMULevelData->ui32NumOfEntries = psLevel->ui32NumOfEntries;
break; /* 索引越界 */
}
/* 获取PCE数据 */
(void)_MMU_GetPxEFaultLevelData(psMMUContext, psDevVAddr, psConfig,
psLevel->sMemDesc.pvCpuVAddr,
ui32PCIndex,
psMMULevelData, NULL);
psLevel = psLevel->apsNextLevel[ui32PCIndex];
if (!psLevel)
{
break; /* PC未分配 */
}
psConfig = psMMUPDEConfig;
continue;
}
if (eMMULevel == MMU_LEVEL_2)
{
/* PD级别 */
uiIndex = psDevVAddr->uiAddr & psDevVAddrConfig->uiPDIndexMask;
uiIndex = uiIndex >> psDevVAddrConfig->uiPDIndexShift;
ui32PDIndex = (IMG_UINT32)uiIndex;
psMMULevelData = &sMMUFaultData.sLevelData[MMU_LEVEL_2];
psMMULevelData->uiBytesPerEntry = psConfig->uiBytesPerEntry;
psMMULevelData->ui32Index = ui32PDIndex;
if (ui32PDIndex >= psLevel->ui32NumOfEntries)
{
psMMULevelData->ui32NumOfEntries = psLevel->ui32NumOfEntries;
break;
}
/* 获取PDE数据和页大小 */
(void)_MMU_GetPxEFaultLevelData(psMMUContext, psDevVAddr, psConfig,
psLevel->sMemDesc.pvCpuVAddr,
ui32PDIndex,
psMMULevelData, &ui32Log2PageSize);
/* 如果页大小不是4KB,重新获取配置 */
if (ui32Log2PageSize != 12)
{
psDevAttrs->pfnPutPageSizeConfiguration(hPriv);
eError = psDevAttrs->pfnGetPageSizeConfiguration(ui32Log2PageSize,
...);
}
psLevel = psLevel->apsNextLevel[ui32PDIndex];
if (!psLevel)
{
break; /* PD未分配 */
}
psConfig = psMMUPTEConfig;
continue;
}
if (eMMULevel == MMU_LEVEL_1)
{
/* PT级别 */
uiIndex = psDevVAddr->uiAddr & psMMUDevVAddrConfig->uiPTIndexMask;
uiIndex = uiIndex >> psMMUDevVAddrConfig->uiPTIndexShift;
ui32PTIndex = (IMG_UINT32)uiIndex;
psMMULevelData = &sMMUFaultData.sLevelData[MMU_LEVEL_1];
psMMULevelData->uiBytesPerEntry = psConfig->uiBytesPerEntry;
psMMULevelData->ui32Index = ui32PTIndex;
if (ui32PTIndex >= psLevel->ui32NumOfEntries)
{
psMMULevelData->ui32NumOfEntries = psLevel->ui32NumOfEntries;
break;
}
/* 获取PTE数据 */
(void)_MMU_GetPxEFaultLevelData(psMMUContext, psDevVAddr, psConfig,
psLevel->sMemDesc.pvCpuVAddr,
ui32PTIndex,
psMMULevelData, NULL);
break;
}
}
psDevAttrs->pfnPutPageSizeConfiguration(hPriv);
OSLockRelease(psMMUContext->hLock);
*psOutFaultData = sMMUFaultData;
}
故障诊断输出示例:
MMU Fault at Virtual Address: 0x0000_4020_1FFF
Level 3 (PC):
Index: 2
Entry: 0x0000_0008_0000_0003
Status: valid
Points to PD at: 0x8000_0000
Level 2 (PD):
Index: 2
Entry: 0x0000_0008_0001_0003
Status: valid
Points to PT at: 0x8001_0000
Level 1 (PT):
Index: 1
Entry: 0x0000_0000_0000_0000
Status: not valid ← 故障原因:PTE无效
诊断:页表项未映射或已被释放
页表项故障数据获取
static IMG_BOOL _MMUGetPxEFaultLevelData(const MMU_CONTEXT *psMMUContext,
IMG_DEV_VIRTADDR *psDevVAddr,
const MMU_PxE_CONFIG *psConfig,
void *pvCpuVAddr,
IMG_UINT32 ui32PxIndex,
MMU_LEVEL_DATA *psMMULevelDataOut,
IMG_UINT32 *ui32Log2PageSizeOut)
{
// 有效性字符串定义
static const IMG_CHAR *apszMMUValidStr[4] = {
"not valid", // --
"valid", // -V
"pending", // P-
"inconsistent" // PV
};
if (psConfig->uiBytesPerEntry == 4) {
IMG_UINT32 *pui32Ptr = pvCpuVAddr;
psMMULevelDataOut->ui64Address = pui32Ptr[ui32PxIndex];
// 检查有效性和挂起位
if ((psConfig->uiPendingEnMask | psConfig->uiValidEnMask) <= 0xFFFFFFFF) {
IMG_UINT32 uiValidBits = (((pui32Ptr[ui32PxIndex] & psConfig->uiPendingEnMask) != 0) << 1) |
(((pui32Ptr[ui32PxIndex] & psConfig->uiValidEnMask) != 0) << 0);
psMMULevelDataOut->psDebugStr = apszMMUValidStr[uiValidBits];
} else {
psMMULevelDataOut->psDebugStr = "";
}
// 获取页面大小
if (ui32Log2PageSizeOut != NULL) {
if (psDevAttrs->pfnGetPageSizeFromPDE4(pui32Ptr[ui32PxIndex], ui32Log2PageSizeOut) != PVRSRV_OK) {
return IMG_FALSE;
}
}
} else {
IMG_UINT64 *pui64Ptr = pvCpuVAddr;
psMMULevelDataOut->ui64Address = pui64Ptr[ui32PxIndex];
// 计算有效性状态
IMG_UINT32 uiValidBits = (((pui64Ptr[ui32PxIndex] & psConfig->uiPendingEnMask) != 0) << 1) |
(((pui64Ptr[ui32PxIndex] & psConfig->uiValidEnMask) != 0) << 0);
psMMULevelDataOut->psDebugStr = apszMMUValidStr[uiValidBits];
// 获取页面大小
if (ui32Log2PageSizeOut != NULL) {
if (psDevAttrs->pfnGetPageSizeFromVirtAddr != NULL) {
// MMU版本 >= 4的处理方式
if (psDevAttrs->pfnGetPageSizeFromVirtAddr(psMMUContext->psPhysMemCtx->psDevNode,
*psDevVAddr, ui32Log2PageSizeOut) != PVRSRV_OK) {
return IMG_FALSE;
}
} else if (psDevAttrs->pfnGetPageSizeFromPDE8(pui64Ptr[ui32PxIndex], ui32Log2PageSizeOut) != PVRSRV_OK) {
// MMU版本 < 4的处理方式
return IMG_FALSE;
}
}
}
return IMG_TRUE;
}
调试信息特点:
- 状态解析: 将页表项的位字段转换为可读状态
- 版本适配: 支持不同MMU版本的页面大小获取方式
- 完整信息: 提供地址和状态的完整描述
特殊功能和优化
虚拟地址有效性检查
IMG_BOOL MMU_IsVDevAddrValid(MMU_CONTEXT *psMMUContext,
IMG_UINT32 uiLog2PageSize,
IMG_DEV_VIRTADDR sDevVAddr)
{
IMG_BOOL bStatus;
const MMU_PxE_CONFIG *psConfig;
IMG_HANDLE hPriv;
const MMU_DEVVADDR_CONFIG *psDevVAddrConfig;
_MMU_GetPTConfig(psMMUContext, uiLog2PageSize, &psConfig, &hPriv, &psDevVAddrConfig);
MMU_GetVDevAddrPTE(psMMUContext, psConfig, psDevVAddrConfig, sDevVAddr, &bStatus);
_MMU_PutPTConfig(psMMUContext, hPriv);
return bStatus;
}
static IMG_UINT64 MMU_GetVDevAddrPTE(MMU_CONTEXT *psMMUContext,
const MMU_PxE_CONFIG *psConfig,
const MMU_DEVVADDR_CONFIG *psDevVAddrConfig,
IMG_DEV_VIRTADDR sDevVAddr,
IMG_BOOL *pbStatusOut)
{
MMU_Levelx_INFO *psLevel = NULL;
IMG_UINT32 uiIndex = 0;
IMG_BOOL bStatus = IMG_FALSE;
IMG_UINT64 ui64Entry = 0;
OSLockAcquire(psMMUContext->hLock);
switch (psMMUContext->psDevAttrs->psBaseConfig->ePxLevel) {
case MMU_LEVEL_3:
uiIndex = _CalcPCEIdx(sDevVAddr, psDevVAddrConfig, IMG_FALSE);
psLevel = psMMUContext->sBaseLevelInfo.apsNextLevel[uiIndex];
if (psLevel == NULL) break;
__fallthrough;
case MMU_LEVEL_2:
uiIndex = _CalcPDEIdx(sDevVAddr, psDevVAddrConfig, IMG_FALSE);
if (psLevel != NULL)
psLevel = psLevel->apsNextLevel[uiIndex];
else
psLevel = psMMUContext->sBaseLevelInfo.apsNextLevel[uiIndex];
if (psLevel == NULL) break;
__fallthrough;
case MMU_LEVEL_1:
uiIndex = _CalcPTEIdx(sDevVAddr, psDevVAddrConfig, IMG_FALSE);
if (psLevel == NULL)
psLevel = &psMMUContext->sBaseLevelInfo;
ui64Entry = ((IMG_UINT64 *)psLevel->sMemDesc.pvCpuVAddr)[uiIndex];
bStatus = ui64Entry & psConfig->uiValidEnMask;
break;
default:
PVR_LOG(("MMU_IsVDevAddrValid: Unsupported MMU setup"));
break;
}
OSLockRelease(psMMUContext->hLock);
*pbStatusOut = bStatus;
return ui64Entry;
}
地址检查特点:
- 多级遍历: 完整遍历页表层次
- 早期退出: 任何级别失败都立即返回
- 原始数据: 同时返回状态和原始页表项值
后备页面管理
static PVRSRV_ERROR _MMU_AllocBackingPage(PVRSRV_DEVICE_NODE *psDevNode,
IMG_INT uiInitValue,
IMG_INT uiDefPage)
{
PVRSRV_DEF_PAGE *psDefPage;
IMG_CHAR *pcDefPageName;
eError = _MMU_GetDefPage(psDevNode, uiDefPage, &psDefPage, &pcDefPageName);
PVR_RETURN_IF_ERROR(eError);
OSLockAcquire(psDefPage->psPgLock);
if (psDefPage->ui64PgPhysAddr != MMU_BAD_PHYS_ADDR) {
goto UnlockAndReturn; // 已经分配过了
}
// 分配和初始化后备页面
eError = DevPhysMemAlloc(psDevNode, (1 << psDefPage->ui32Log2PgSize), 0, uiInitValue, IMG_TRUE,
psDevNode->psMMUDevAttrs->pszMMUPxPDumpMemSpaceName,
pcDefPageName, &psDefPage->hPdumpPg,
PVR_SYS_ALLOC_PID, &psDefPage->sPageHandle, &sDevPAddr);
psDefPage->ui64PgPhysAddr = sDevPAddr.uiAddr;
UnlockAndReturn:
OSLockRelease(psDefPage->psPgLock);
return eError;
}
static PVRSRV_ERROR _MMU_GetBackingPage(PVRSRV_DEVICE_NODE *psDevNode,
IMG_UINT64 *pui64PgPhysAddr,
IMG_INT uiDefPage)
{
PVRSRV_DEF_PAGE *psDefPage;
IMG_CHAR *pcDefPageName;
eError = _MMU_GetDefPage(psDevNode, uiDefPage, &psDefPage, &pcDefPageName);
PVR_RETURN_IF_ERROR(eError);
OSLockAcquire(psDefPage->psPgLock);
if (psDefPage->ui64PgPhysAddr == MMU_BAD_PHYS_ADDR) {
eError = PVRSRV_ERROR_NOT_FOUND;
goto UnlockAndReturn;
}
if (pui64PgPhysAddr)
*pui64PgPhysAddr = psDefPage->ui64PgPhysAddr;
UnlockAndReturn:
OSLockRelease(psDefPage->psPgLock);
return eError;
}
后备页面特点:
- 单例模式: 每种后备页面在设备中只有一个实例
- 延迟分配: 只在实际需要时才分配
- 线程安全: 使用锁保护并发访问
- 初始化值: 支持不同的初始化值(零页面vs临时页面)
缓存管理接口
void MMU_AppendCacheFlags(MMU_CONTEXT *psMMUContext, IMG_UINT32 ui32AppendFlags)
{
PVR_ASSERT(psMMUContext != NULL);
if (psMMUContext == NULL) return;
OSAtomicOr(&psMMUContext->sCacheFlags, (IMG_INT)ui32AppendFlags);
}
IMG_UINT32 MMU_GetAndResetCacheFlags(MMU_CONTEXT *psMMUContext)
{
IMG_UINT32 uiFlags;
PVR_ASSERT(psMMUContext != NULL);
if (psMMUContext == NULL) return 0;
uiFlags = (IMG_UINT32) OSAtomicExchange(&psMMUContext->sCacheFlags, 0);
#if defined(SUPPORT_PMR_DEFERRED_FREE)
// 检查是否有需要清理的僵尸PMR
if (PMRQueueZombiesForCleanup(psMMUContext->psPhysMemCtx->psDevNode)) {
BITMASK_SET(uiFlags, RGXFWIF_MMUCACHEDATA_FLAGS_CTX_ALL | RGXFWIF_MMUCACHEDATA_FLAGS_PT);
}
#endif
return uiFlags;
}
缓存管理特点:
- 原子操作: 使用原子操作确保线程安全
- 累积标志: 支持多个标志的累积
- PMR集成: 与PMR延迟释放机制集成
设备初始化和清理
设备MMU初始化
PVRSRV_ERROR MMU_InitDevice(struct _PVRSRV_DEVICE_NODE_ *psDevNode)
{
// 设置页面顺序为0(单页面)
psDevNode->sScratchPage.sPageHandle.uiOrder = 0;
psDevNode->sDevZeroPage.sPageHandle.uiOrder = 0;
// 设置后备页面大小为最大页面大小
if (psDevNode->ui32Non4KPageSizeLog2 != 0) {
psDevNode->sScratchPage.ui32Log2PgSize = psDevNode->ui32Non4KPageSizeLog2;
psDevNode->sDevZeroPage.ui32Log2PgSize = psDevNode->ui32Non4KPageSizeLog2;
} else {
psDevNode->sScratchPage.ui32Log2PgSize = OSGetPageShift();
psDevNode->sDevZeroPage.ui32Log2PgSize = OSGetPageShift();
}
// 初始化物理地址为无效值
psDevNode->sScratchPage.ui64PgPhysAddr = MMU_BAD_PHYS_ADDR;
psDevNode->sDevZeroPage.ui64PgPhysAddr = MMU_BAD_PHYS_ADDR;
// 创建后备页面锁(可能从MISR路径访问)
eError = OSLockCreate(&psDevNode->sScratchPage.psPgLock);
PVR_LOG_GOTO_IF_ERROR(eError, "OSLockCreate.Scratch", ErrReturnError);
eError = OSLockCreate(&psDevNode->sDevZeroPage.psPgLock);
PVR_LOG_GOTO_IF_ERROR(eError, "OSLockCreate.Zero", ErrFreeScratchPageLock);
// 分配和初始化后备页面
eError = _MMU_AllocBackingPage(psDevNode, PVR_SCRATCH_PAGE_INIT_VALUE, SCRATCH_PAGE);
PVR_LOG_GOTO_IF_ERROR(eError, "_MMU_AllocBackingPage.Scratch", ErrFreeZeroPageLock);
eError = _MMU_AllocBackingPage(psDevNode, PVR_ZERO_PAGE_INIT_VALUE, DEV_ZERO_PAGE);
PVR_LOG_GOTO_IF_ERROR(eError, "_MMU_AllocBackingPage.Zero", ErrFreeScratchPage);
return PVRSRV_OK;
ErrFreeScratchPage:
_MMU_FreeBackingPage(psDevNode, SCRATCH_PAGE);
ErrFreeZeroPageLock:
OSLockDestroy(psDevNode->sDevZeroPage.psPgLock);
psDevNode->sDevZeroPage.psPgLock = NULL;
ErrFreeScratchPageLock:
OSLockDestroy(psDevNode->sScratchPage.psPgLock);
psDevNode->sScratchPage.psPgLock = NULL;
ErrReturnError:
return eError;
}
设备初始化特点:
- 后备页面预分配: 在设备初始化时就分配好后备页面
- 页面大小适配: 根据设备能力选择最优的页面大小
- 错误恢复: 完整的资源清理路径
- MISR兼容: 锁设计考虑了中断处理路径的需求
设备MMU清理
void MMU_DeInitDevice(struct _PVRSRV_DEVICE_NODE_ *psDevNode)
{
if (psDevNode->sScratchPage.psPgLock != NULL) {
_MMU_FreeBackingPage(psDevNode, SCRATCH_PAGE);
OSLockDestroy(psDevNode->sScratchPage.psPgLock);
psDevNode->sScratchPage.psPgLock = NULL;
}
if (psDevNode->sDevZeroPage.psPgLock) {
_MMU_FreeBackingPage(psDevNode, DEV_ZERO_PAGE);
OSLockDestroy(psDevNode->sDevZeroPage.psPgLock);
psDevNode->sDevZeroPage.psPgLock = NULL;
}
}
清理特点:
- 条件检查: 防御性编程,检查指针有效性
- 资源配对: 与初始化操作严格配对
- 状态重置: 清理后重置指针为NULL
PDump集成和调试支持
PDump符号地址生成
PVRSRV_ERROR MMU_ContextDerivePCPDumpSymAddr(MMU_CONTEXT *psMMUContext,
IMG_CHAR *pszPDumpSymbolicNameBuffer,
size_t uiPDumpSymbolicNameBufferSize)
{
size_t uiCount;
IMG_UINT64 ui64PhysAddr;
PVRSRV_DEVICE_IDENTIFIER *psDevId = &psMMUContext->psPhysMemCtx->psDevNode->sDevId;
if (!psMMUContext->sBaseLevelInfo.sMemDesc.bValid) {
return PVRSRV_ERROR_MMU_API_PROTOCOL_ERROR;
}
ui64PhysAddr = (IMG_UINT64)psMMUContext->sBaseLevelInfo.sMemDesc.sDevPAddr.uiAddr;
// 生成符号名称格式: :device:MMUPC_<16位十六进制地址>
uiCount = OSSNPrintf(pszPDumpSymbolicNameBuffer, uiPDumpSymbolicNameBufferSize,
":%s:%s%016"IMG_UINT64_FMTSPECX,
psDevId->pszPDumpDevName,
psMMUContext->sBaseLevelInfo.sMemDesc.bValid ? "MMUPC_" : "XXX",
ui64PhysAddr);
if (uiCount + 1 > uiPDumpSymbolicNameBufferSize) {
return PVRSRV_ERROR_INVALID_PARAMS;
}
return PVRSRV_OK;
}
PDump符号特点:
- 唯一标识: 基于物理地址生成唯一符号
- 设备区分: 包含设备名称区分不同设备
- 格式标准: 遵循PDump工具的命名约定
PDump页目录基址写入
PVRSRV_ERROR MMU_PDumpWritePageCatBase(MMU_CONTEXT *psMMUContext,
const IMG_CHAR *pszSpaceName,
IMG_DEVMEM_OFFSET_T uiOffset,
IMG_UINT32 ui32WordSize,
IMG_UINT32 ui32AlignShift,
IMG_UINT32 ui32Shift,
PDUMP_FLAGS_T uiPdumpFlags)
{
PVRSRV_ERROR eError;
IMG_CHAR aszPageCatBaseSymbolicAddr[100];
eError = MMU_ContextDerivePCPDumpSymAddr(psMMUContext,
&aszPageCatBaseSymbolicAddr[0],
sizeof(aszPageCatBaseSymbolicAddr));
if (eError == PVRSRV_OK) {
eError = PDumpWriteSymbAddress(psMMUContext->psPhysMemCtx->psDevNode,
pszSpaceName, uiOffset,
aszPageCatBaseSymbolicAddr, 0,
psMMUContext->psDevAttrs->pszMMUPxPDumpMemSpaceName,
ui32WordSize, ui32AlignShift, ui32Shift,
uiPdumpFlags | PDUMP_FLAGS_CONTINUOUS);
}
return eError;
}
PDump写入特点:
- 符号引用: 使用符号地址而非直接地址
- 参数透传: 支持各种PDump写入参数
- 连续标志: 自动添加连续标志
PDump MMU上下文管理
PVRSRV_ERROR MMU_AcquirePDumpMMUContext(MMU_CONTEXT *psMMUContext,
IMG_UINT32 *pui32PDumpMMUContextID,
IMG_UINT32 ui32PDumpFlags)
{
PVRSRV_DEVICE_IDENTIFIER *psDevId = &psMMUContext->psPhysMemCtx->psDevNode->sDevId;
if (!psMMUContext->ui32PDumpContextIDRefCount) {
PDUMP_MMU_ALLOC_MMUCONTEXT(psMMUContext->psPhysMemCtx->psDevNode,
psDevId->pszPDumpDevName,
psMMUContext->sBaseLevelInfo.sMemDesc.sDevPAddr,
psMMUContext->psDevAttrs->eMMUType,
&psMMUContext->uiPDumpContextID,
ui32PDumpFlags);
}
psMMUContext->ui32PDumpContextIDRefCount++;
*pui32PDumpMMUContextID = psMMUContext->uiPDumpContextID;
return PVRSRV_OK;
}
PVRSRV_ERROR MMU_ReleasePDumpMMUContext(MMU_CONTEXT *psMMUContext,
IMG_UINT32 ui32PDumpFlags)
{
PVRSRV_DEVICE_IDENTIFIER *psDevId = &psMMUContext->psPhysMemCtx->psDevNode->sDevId;
PVR_ASSERT(psMMUContext->ui32PDumpContextIDRefCount != 0);
psMMUContext->ui32PDumpContextIDRefCount--;
if (psMMUContext->ui32PDumpContextIDRefCount == 0) {
PDUMP_MMU_FREE_MMUCONTEXT(psMMUContext->psPhysMemCtx->psDevNode,
psDevId->pszPDumpDevName,
psMMUContext->uiPDumpContextID,
ui32PDumpFlags);
}
return PVRSRV_OK;
}
PDump上下文特点:
- 引用计数: 支持多个使用者共享PDump上下文
- 延迟分配: 只在首次获取时分配PDump上下文
- 资源配对: 获取和释放严格配对
架构特点总结
设计原则
- 分层架构:
- 应用层 → devicemem → mmu_common → pmr/device
- 清晰的职责分离和接口抽象
- 资源管理:
- 引用计数确保资源生命周期正确性
- 延迟释放解决缓存一致性问题
- RA集成提供灵活的物理内存管理
- 并发安全:
- 多级锁保护不同粒度的数据
- 原子操作用于高频访问的标志
- 清理线程避免阻塞主路径
- 性能优化:
- 快速映射路径减少函数调用开销
- 批量操作减少系统调用
- 配置缓存避免重复计算
- 错误处理:
- 完整的错误恢复路径
- 防御性编程防止崩溃
- 详细的调试信息支持
关键技术
- 多级页表管理:
- 通用的递归结构支持不同MMU架构
- 按需分配节省内存
- 智能引用计数管理
- 稀疏内存支持:
- 后备页面机制
- 零页面和临时页面的智能选择
- 动态映射/解映射
- 虚拟化和安全:
- OSID隔离支持多OS环境
- 客户机/主机模式支持
- 可信设备集成
- 调试和分析:
- 完整的PDump集成
- 详细的故障分析能力
- HTB性能日志记录
代码质量特点
- 可维护性:
- 清晰的命名约定
- 充分的注释说明
- 模块化的函数设计
- 可移植性:
- OS抽象层使用
- 设备特定功能通过函数指针
- 条件编译支持不同配置
- 鲁棒性:
- 参数验证
- 资源泄漏检测
- 异常情况处理
页表项的硬件特定实现
不同GPU架构的页表项格式会有差异。从代码中可以看出,通过设备特定的函数指针来处理这些差异:
// PowerVR可能的页表项格式
pfnDerivePTEProt8(uiProtFlags, uiLog2DataPageSize)
这种设计允许同一套MMU代码支持不同的GPU架构,每个架构只需要实现自己的保护位计算函数。
1. 奇偶校验机制的重要性
代码中的奇偶校验不仅仅是数据完整性检查,更重要的是:
- 硬件错误检测: GPU访问页表时如果发生位翻转,可以被检测出来
- 调试支持: 帮助定位内存传输问题
- 可靠性提升: 在高频GPU操作中确保页表项的正确性
-
延迟释放的深层原因
延迟释放机制解决的核心问题是GPU的异步特性: -
流水线深度: GPU有深度的指令流水线,页表访问可能有延迟
- 缓存层次: 多级缓存需要时间来失效
- 并发访问: 多个GPU单元可能同时访问同一页表
这种设计确保在页表被实际释放前,所有可能的访问都已经完成。
3. 虚拟化支持的考虑
代码中的OSID(Operating System ID)支持体现了现代GPU在虚拟化环境中的应用:
- 多OS隔离: 在虚拟机环境中隔离不同操作系统的内存访问
- 安全性: 防止一个虚拟机访问另一个虚拟机的内存
- 性能: 避免频繁的页表切换开销
这个MMU框架的设计充分体现了现代GPU系统的复杂性需求,在性能、可靠性、安全性和可维护性之间找到了平衡点。
这个MMU管理系统代表了现代GPU驱动程序的复杂性和精密性,充分考虑了性能、安全性、调试能力和维护性等多个方面的需求。
JINHU