PowerVR PMR

2025-09-23 77 0

文件概述与架构

内部标志位定义

/* 内部标志位 - 这些标志不暴露给外部API */
#define PMR_FLAG_INTERNAL_SPARSE_ALLOC        (1 << 0)  // 稀疏分配标记
#define PMR_FLAG_INTERNAL_NO_LAYOUT_CHANGE    (1 << 1)  // 禁止布局变更
#define PMR_FLAG_INTERNAL_DEFER_FREE          (1 << 2)  // 延迟释放
#define PMR_FLAG_INTERNAL_IS_ZOMBIE_PMR       (1 << 3)  // 僵尸PMR
#define PMR_FLAG_INTERNAL_IS_EXCLUSIVE        (1 << 4)  // 独占模式
#define PMR_FLAG_INTERNAL_IS_ZOMBIE_PMR_EMPTY (1 << 5)  // 空僵尸PMR

标志位详解:

标志位 含义 使用场景
SPARSE_ALLOC 稀疏分配 逻辑地址空间 > 物理内存
NO_LAYOUT_CHANGE 布局固定 导出后不可修改
DEFER_FREE 延迟释放 等待GPU缓存刷新
IS_ZOMBIE_PMR 僵尸状态 引用计数为0,待清理
IS_EXCLUSIVE 独占模式 不与其他资源共享

系统架构图

┌─────────────────────────────────────────────────────────────┐
│                      应用层 (Application)                   │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐     │
│  │  OpenGL  │  │  Vulkan  │  │  OpenCL  │  │   应用   │     │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘     │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│                    DeviceMemory API层                       │
│  ┌──────────────────────────────────────────────────────┐   │
│  │  DevmemIntAllocatePMR / DevmemIntMapPMR              │   │
│  │  DevmemIntExportPMR / DevmemIntImportPMR             │   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│                    PMR核心层 (pmr.c)                         │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐       │
│  │ PMR管理器     │  │ 引用计数     │  │ 地址转换      │       │
│  │ _PMRCreate   │  │ _Ref/_Unref  │  │ LogicalToPhys│       │
│  └──────────────┘  └──────────────┘  └──────────────┘       │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐       │
│  │ 稀疏内存     │   │ 延迟释放     │  │ 多设备支持    │       │
│  │ ChangeSparse │  │ Zombie清理   │  │ CrossDevice  │       │
│  └──────────────┘  └──────────────┘  └──────────────┘       │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│               PMR实现层 (各种Factory)                        │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐     │
│  │ OS Pages │  │ DMA Buf  │  │ ION      │  │ Custom   │     │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘     │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│                    物理内存层                                │
│  ┌──────────────────────────────────────────────────────┐   │
│  │  PHYS_HEAP - 物理堆管理                               │   │
│  │  ├─ UMA Heap (统一内存架构)                           │   │
│  │  ├─ LMA Heap (本地内存架构)                           │   │
│  │  └─ DMA Heap (DMA内存)                               │   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│                    硬件层                                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐                   │
│  │ 物理RAM  │  │ GPU MMU  │  │ 缓存系统  │                   │
│  └──────────┘  └──────────┘  └──────────┘                   │
└─────────────────────────────────────────────────────────────┘

主要数据结构

全局上下文结构 (PMR_CTX)

static struct _PMR_CTX_
{
    /* 序列号生成器 - 每个PMR的唯一标识 */
    IMG_UINT64 uiNextSerialNum;     // 从1开始递增

    /* 安全密钥生成器 - 用于PMR访问验证 */
    IMG_UINT64 uiNextKey;           // 伪随机密钥

    /* 活跃PMR统计 - 用于调试和资源跟踪 */
    ATOMIC_T uiNumLivePMRs;         // 原子变量,线程安全

    /* 上下文保护锁 */
    POS_LOCK hLock;                 // 保护串行号和密钥生成

    /* 模块初始化标志 */
    IMG_BOOL bModuleInitialised;    // 防止重复初始化
} _gsSingletonPMRContext = { 
    1,           // uiNextSerialNum初始值
    0,           // uiNextKey初始值
    {0},         // uiNumLivePMRs初始值
    NULL,        // hLock未初始化
    IMG_FALSE    // bModuleInitialised
};

逐行解析:

  1. uiNextSerialNum = 1
    • 从1开始,0保留为无效值
    • 每创建一个PMR递增1
    • 用于日志追踪和调试
  2. uiNextKey = 0
    • 初始密钥为0
    • 通过LCG算法生成后续密钥
    • 密钥生成公式:
Key生成算法:
uiNextKey = (0x80200003 * uiNextKey) ^ (0xf00f0081 * ptr_address)
新Key = (0x80200003 × 旧Key) XOR (0xf00f0081 × 内存地址)

这是一个简单的伪随机数生成器,确保每个PMR有唯一标识。

  1. uiNumLivePMRs
    • 使用原子类型ATOMIC_T
    • 增加:OSAtomicIncrement(&uiNumLivePMRs)
    • 减少:OSAtomicDecrement(&uiNumLivePMRs)
    • 用于检测内存泄漏
  2. hLock
    • 普通互斥锁(不是自旋锁)
    • 保护序列号和密钥生成的原子性
    • 初始化:OSLockCreate(&hLock)

这是一个全局单例上下文,管理所有PMR的基本状态。

全局上下文初始化流程

PVRSRV_ERROR PMRInit(void)
{
    PVRSRV_ERROR eError;

    /* 检查是否已经初始化 */
    if (_gsSingletonPMRContext.bModuleInitialised)
    {
        // 防止重复初始化
        PVR_GOTO_WITH_ERROR(eError, PVRSRV_ERROR_PMR_UNRECOVERABLE_ERROR, out);
    }

    /* 创建保护锁 */
    eError = OSLockCreate(&_gsSingletonPMRContext.hLock);
    PVR_LOG_GOTO_IF_ERROR(eError, "OSLockCreate", out);

    /* 设置初始值 */
    _gsSingletonPMRContext.uiNextSerialNum = 1;
    _gsSingletonPMRContext.uiNextKey = 0x8300f001 * (uintptr_t)&_gsSingletonPMRContext;
    _gsSingletonPMRContext.bModuleInitialised = IMG_TRUE;
    OSAtomicWrite(&_gsSingletonPMRContext.uiNumLivePMRs, 0);

    return PVRSRV_OK;

out:
    return eError;
}

初始化流程图:

开始 PMRInit()
    ↓
检查 bModuleInitialised
    ↓ (未初始化)
创建全局锁 hLock
    ↓
设置 uiNextSerialNum = 1
    ↓
初始化 uiNextKey (使用地址混淆)
    ↓
设置 uiNumLivePMRs = 0
    ↓
标记 bModuleInitialised = TRUE
    ↓
返回 PVRSRV_OK

PMR结构体

PMR结构体完整定义

struct _PMR_
{
#if defined(SUPPORT_PMR_DEFERRED_FREE)
    /* 延迟释放头部 - 用于僵尸列表管理 */
    PMR_HEADER sHeader;
    /*
     * typedef struct _PMR_HEADER_ {
     *     DLLIST_NODE sZombieNode;      // 双向链表节点
     *     PMR_ZOMBIE_TYPE eZombieType;  // 僵尸类型
     * } PMR_HEADER;
     */
#endif

    /* ============ 物理内存管理 ============ */

    /* 物理堆指针 - 指向这个PMR所属的物理内存堆 */
    PHYS_HEAP *psPhysHeap;
    /*
     * 物理堆类型:
     * - PHYS_HEAP_TYPE_UMA: 统一内存架构(CPU和GPU共享)
     * - PHYS_HEAP_TYPE_LMA: 本地内存架构(GPU专用)
     * - PHYS_HEAP_TYPE_DMA: DMA内存
     */

    /* ============ 引用计数管理 ============ */

    /* 主引用计数 - 为0时触发销毁 */
    IMG_UINT32 uiRefCount;
    /* 引用计数保护锁 - 自旋锁,快速操作 */
    POS_SPINLOCK hRefCountLock;

    /*
     * 引用计数增加的场景:
     * 1. PMR创建时初始为0,第一次Lock时变为1
     * 2. PMRLockPhysAddresses() 每次+1
     * 3. PMRExportPMR() 导出时+1
     * 4. PMRImportPMR() 导入时+1
     * 5. PMRRefPMR() 显式增加引用+1
     * 
     * 引用计数减少的场景:
     * 1. PMRUnlockPhysAddresses() 每次-1
     * 2. PMRUnexportPMR() 取消导出-1
     * 3. PMRUnimportPMR() 取消导入-1
     * 4. PMRUnrefPMR() 显式减少引用-1
     */

    /* ============ 映射计数 ============ */

    /* 客户端CPU映射计数 - 用户空间映射次数 */
    ATOMIC_T iClientCpuMapCount;
    /*
     * 增加:DevmemAcquireCpuVirtAddr()
     * 减少:DevmemReleaseCpuVirtAddr()
     * 作用:当>0时禁止稀疏内存布局变更
     */

#if defined(SUPPORT_LINUX_OSPAGE_MIGRATION)
    /* 内核CPU映射计数 - 内核空间映射次数 */
    ATOMIC_T iKernelCpuMapCount;
    /*
     * 增加:PMRAcquireKernelMappingData()
     * 减少:PMRReleaseKernelMappingData()
     * 作用:>0时禁止页面迁移
     */

    /* GPU映射列表 - 所有GPU映射的链表 */
    DLLIST_NODE sGpuMappingListHead;
    /*
     * 用于页面迁移时更新所有GPU页表
     */

    /* PMR状态 */
    PMR_STATE eState;
    /*
     * PMR_STATE_INIT: 初始化状态
     * PMR_STATE_ACTIVE: 活跃状态
     * PMR_STATE_PAGES_IN_MIGRATE: 页面迁移中
     */
#endif

    /* GPU预留资源计数 - 关联的GPU映射数量 */
    IMG_INT32 iAssociatedResCount;
    /*
     * 增加:PMRLinkGPUMapping()
     * 减少:PMRUnlinkGPUMapping()
     * 作用:跟踪有多少个GPU映射引用此PMR
     */

    /* 物理地址锁定计数 */
    ATOMIC_T iLockCount;
    /*
     * 增加:PMRLockPhysAddresses()
     * 减少:PMRUnlockPhysAddresses()
     * 
     * 特殊规则:
     * - 如果是on-demand分配,初始值为0
     * - 如果不是on-demand,初始值为1(已经backed)
     */

    /* ============ 同步机制 ============ */

    /* 主锁 - 保护PMR结构体的复杂操作 */
    POS_LOCK hLock;
    /*
     * 使用场景:
     * - 地址转换操作
     * - 稀疏内存变更
     * - 页面迁移
     * - 延迟释放处理
     * 
     * 锁类型:可睡眠的互斥锁
     */

    /* 位图锁 - 保护标志位和位图操作 */
    POS_SPINLOCK hBitmapLock;
    /*
     * 保护:
     * - uiInternalFlags
     * - uiDevImportBitmap
     * 
     * 锁类型:不可睡眠的自旋锁
     */

#if defined(SUPPORT_PMR_DEVICE_IMPORT_DEFERRED_FREE) || defined(PVRSRV_ENABLE_XD_MEM)
    /* 设备导入位图 - 记录哪些设备导入了此PMR */
    IMG_UINT64 uiDevImportBitmap;
    /*
     * 每个bit代表一个设备:
     * bit 0: Device 0
     * bit 1: Device 1
     * ...
     * 最多支持64个设备
     */
#endif

#if defined(SUPPORT_PMR_DEVICE_IMPORT_DEFERRED_FREE)
    /* 跨设备导入列表 */
    DLLIST_NODE sXDeviceImports;
    /*
     * 链表元素类型:PMR_DEVICE_IMPORT
     * 记录所有导入此PMR的设备信息
     */
#endif

    /* ============ 标识和安全 ============ */

    /* 全局唯一序列号 */
    IMG_UINT64 uiSerialNum;
    /*
     * 从全局上下文获取,递增分配
     * 用于:
     * - 日志追踪
     * - PDump符号名生成
     * - 调试信息
     */

    /* 安全密钥 - 用于验证PMR访问权限 */
    PMR_PASSWORD_T uiKey;
    /*
     * 64位伪随机数
     * 导出时传递给导入方
     * 导入时必须提供正确的密钥
     */

    /* ============ 函数表和类型信息 ============ */

    /* 实现函数表指针 - 指向具体的PMR实现 */
    const PMR_IMPL_FUNCTAB *psFuncTab;
    /*
     * 函数表包含:
     * - pfnLockPhysAddresses: 锁定物理地址
     * - pfnUnlockPhysAddresses: 解锁物理地址
     * - pfnDevPhysAddr: 获取设备物理地址
     * - pfnAcquireKernelMappingData: 获取内核映射
     * - pfnReleaseKernelMappingData: 释放内核映射
     * - pfnReadBytes: 读取数据
     * - pfnWriteBytes: 写入数据
     * - pfnChangeSparseMem: 修改稀疏内存布局
     * - pfnFinalize: 销毁PMR
     * - ... 更多函数
     */

    /* 类型特定私有数据 */
    PMR_IMPL_PRIVDATA pvFlavourData;
    /*
     * 不同类型PMR的私有数据:
     * - OS Pages: 页面数组
     * - DMA-BUF: DMA-BUF句柄
     * - ION: ION缓冲区信息
     * 由具体Factory分配和管理
     */

    /* 环境特定数据 */
    DECLARE_PMR_ENV_DATA(sEnvData)
    /*
     * 平台相关数据,如:
     * - Linux: struct file指针
     * - 其他平台的特定数据
     */

    /* PMR类型标识 */
    PMR_IMPL_TYPE eFlavour;
    /*
     * PMR_TYPE_OSPAGES: OS页面
     * PMR_TYPE_DMABUF: DMA缓冲区
     * PMR_TYPE_ION: ION内存
     * PMR_TYPE_PHYSMEM: 物理内存
     * ... 更多类型
     */

    /* ============ PDump支持 ============ */

    /* PDump默认内存空间名称 */
    const IMG_CHAR *pszPDumpDefaultMemspaceName;
    /*
     * 从物理堆获取,如:
     * - "SYSMEM": 系统内存
     * - "FBMEM": Framebuffer内存
     * 用于PDump脚本生成
     */

#if defined(PDUMP)
    /* PDump分配句柄 */
    IMG_HANDLE hPDumpAllocHandle;
    /*
     * 数组,每个逻辑块一个句柄
     * 用于PDump记录和回放
     */

    /* PDump块数量 */
    IMG_UINT32 uiNumPDumpBlocks;
    /*
     * 等于逻辑块数量
     * 用于管理PDump句柄数组
     */
#endif

    /* ============ 内存布局信息 ============ */

    /* 逻辑大小 - PMR的虚拟地址空间大小 */
    PMR_SIZE_T uiLogicalSize;
    /*
     * 对于稀疏PMR:逻辑大小 >= 物理大小
     * 对于连续PMR:逻辑大小 == 物理大小
     * 单位:字节
     */

    /* 映射表指针 */
    PMR_MAPPING_TABLE *psMappingTable;
    /*
     * typedef struct {
     *     IMG_UINT32 ui32NumLogicalChunks;  // 逻辑块数量
     *     IMG_UINT32 ui32NumPhysChunks;     // 物理块数量
     *     PMR_SIZE_T uiChunkSize;           // 块大小
     *     IMG_UINT32 aui32Translation[];    // 翻译数组
     * } PMR_MAPPING_TABLE;
     * 
     * 翻译数组:
     * - 下标:逻辑块索引
     * - 值:物理块索引(0xFFFFFFFF表示无效)
     */

    /* 连续性保证 - log2(页面大小) */
    PMR_LOG2ALIGN_T uiLog2ContiguityGuarantee;
    /*
     * 示例:
     * - 12: 4KB页面 (1 << 12 = 4096)
     * - 16: 64KB页面 (1 << 16 = 65536)
     * - 21: 2MB页面 (1 << 21 = 2097152)
     * 
     * 保证物理内存的最小连续单元大小
     */

    /* ============ 标志位 ============ */

    /* 公开标志 - 从PVRSRV_MEMALLOCFLAGS_T转换 */
    PMR_FLAGS_T uiFlags;
    /*
     * 包含:
     * - CPU可读/可写权限
     * - GPU可读/可写权限
     * - 缓存策略
     * - On-demand分配标志
     * - 安全内存标志
     * ... 更多标志
     */

    /* 内部标志 - 私有状态位 */
    IMG_UINT32 uiInternalFlags;
    /*
     * 见第1.3节的标志位定义
     */

    /* ============ 调试信息 ============ */

    /* 注释字符串 - 用于调试和追踪 */
    IMG_CHAR szAnnotation[DEVMEM_ANNOTATION_MAX_LEN];
    /*
     * 由应用设置,通常包含:
     * - 缓冲区名称
     * - 纹理名称
     * - 用途描述
     * 最大长度通常为64或128字节
     */

#if defined(PVRSRV_ENABLE_GPU_MEMORY_INFO)
    /* RI (Resource Information) 句柄 */
    void *hRIHandle;
    /*
     * 用于内存信息统计和查询
     * 记录到全局RI数据库
     */
#endif

#if defined(XDXGPU_SMMU_SUPPORT)
    /* SMMU映射列表 */
    DLLIST_NODE SmmuMapList;
    /*
     * 记录所有SMMU映射
     * 用于多GPU场景
     */
#endif

#if defined(XDXGPU)
    /* DMA预留对象 - 用于同步 */
    DMA_RESV *resv;
    DMA_RESV _resv;
    /*
     * 用于DMA-BUF fence机制
     * 同步CPU和GPU访问
     */
#endif

    /* ============ 上下文引用 ============ */

    /* 全局上下文指针 - 指向单例上下文 */
    struct _PMR_CTX_ *psContext;
    /*
     * 通常指向 &_gsSingletonPMRContext
     * 用于访问全局资源
     */
};

PMR结构体内存布局示意图

PMR结构体内存布局 (假设64位系统)

含义 地址
sHeader (仅在DEFERRED_FREE时存在) 0x00 - 0x18
psPhysHeap (指针, 8字节) 0x18 - 0x20
uiRefCount (4字节) 0x20 - 0x24
padding (4字节对齐) 0x24 - 0x28
hRefCountLock (指针, 8字节) 0x28 - 0x30
iClientCpuMapCount (ATOMIC, 4字节) 0x30 - 0x34
iKernelCpuMapCount (ATOMIC, 4字节) 0x34 - 0x38
sGpuMappingListHead (双向链表, 16字节) 0x38 - 0x48
eState (4字节) 0x48 - 0x4C
iAssociatedResCount (4字节) 0x4C - 0x50
iLockCount (ATOMIC, 4字节) 0x50 - 0x54
padding (4字节) 0x54 - 0x58
hLock (指针, 8字节) 0x58 - 0x60
hBitmapLock (指针, 8字节) 0x60 - 0x68
uiDevImportBitmap (8字节) 0x68 - 0x70
sXDeviceImports (双向链表, 16字节) 0x70 - 0x80
uiSerialNum (8字节) 0x80 - 0x88
uiKey (8字节) 0x88 - 0x90
psFuncTab (指针, 8字节) 0x90 - 0x98
pvFlavourData (指针, 8字节) 0x98 - 0xA0
eFlavour (4字节) 0xA0 - 0xA4
padding (4字节) 0xA4 - 0xA8
pszPDumpDefaultMemspaceName (指针, 8字节) xA8 - 0xB0
szAnnotation[64] (字符数组, 64字节) 0xB0 - 0xF0
hPDumpAllocHandle (指针, 8字节) 0xF0 - 0xF8
uiNumPDumpBlocks (4字节) 0xF8 - 0xFC
padding (4字节) 0xFC - 0x100
uiLogicalSize (8字节) 0x100 - 0x108
psMappingTable (指针, 8字节) 0x108 - 0x110
uiLog2ContiguityGuarantee (4字节) 0x110 - 0x114
padding (4字节) 0x114 - 0x118
uiFlags (8字节) 0x118 - 0x120
uiInternalFlags (4字节) 0x120 - 0x124
padding (4字节) 0x124 - 0x128
psContext (指针, 8字节) 0x128 - 0x130
hRIHandle (指针, 8字节) 0x130 - 0x138

总大小: 约 312 字节 (0x138)


PMR创建流程

_PMRCreate 函数逐行分析

/**
 * @brief PMR内部创建函数
 * @param uiLogicalSize 逻辑大小(虚拟地址空间大小)
 * @param ui32NumPhysChunks 物理块数量
 * @param ui32NumLogicalChunks 逻辑块数量
 * @param pui32MappingTable 映射表数组
 * @param uiLog2ContiguityGuarantee 连续性保证(log2页面大小)
 * @param uiFlags PMR标志位
 * @param ppsPMR 输出:创建的PMR指针
 */
static PVRSRV_ERROR
_PMRCreate(PMR_SIZE_T uiLogicalSize,
           IMG_UINT32 ui32NumPhysChunks,
           IMG_UINT32 ui32NumLogicalChunks,
           IMG_UINT32 *pui32MappingTable,
           PMR_LOG2ALIGN_T uiLog2ContiguityGuarantee,
           PMR_FLAGS_T uiFlags,
           PMR **ppsPMR)
{
    void *pvPMRLinAddr;                  // PMR线性地址(包含PMR+映射表)
    PMR *psPMR;                          // PMR指针
    PMR_MAPPING_TABLE *psMappingTable;   // 映射表指针
    struct _PMR_CTX_ *psContext;         // 全局上下文
    IMG_UINT32 i, ui32Temp = 0;
    PVRSRV_ERROR eError;
    IMG_BOOL bSparse = IMG_FALSE;        // 是否稀疏分配
    PMR_SIZE_T uiChunkSize;              // 块大小

    psContext = &_gsSingletonPMRContext;

    /* ========== 第1步:判断是否为稀疏分配 ========== */
    /*
     * 稀疏分配的判断条件:
     * 1. 逻辑块数 != 物理块数(有些逻辑块没有对应物理内存)
     * 2. 逻辑块数 > 1(分成多个块)
     */
    if ( (ui32NumLogicalChunks != ui32NumPhysChunks) ||
         (ui32NumLogicalChunks > 1) )
    {
        bSparse = IMG_TRUE;
        // 稀疏分配时,块大小由连续性保证决定
        uiChunkSize = 1ULL << uiLog2ContiguityGuarantee;
        /*
         * 示例:
         * uiLog2ContiguityGuarantee = 12 (4KB)
         * uiChunkSize = 1 << 12 = 4096 bytes
         */
    }
    else
    {
        // 连续分配时,块大小就是整个逻辑大小
        uiChunkSize = uiLogicalSize;
    }

    /* ========== 第2步:稀疏分配额外验证 ========== */
    if (bSparse)
    {
        /*
         * 验证公式:逻辑大小 = 块大小 × 逻辑块数
         * 
         * 示例:
         * 逻辑大小 = 1MB = 1048576 bytes
         * 块大小 = 4KB = 4096 bytes
         * 逻辑块数 = 1048576 / 4096 = 256
         */
        if (uiLogicalSize != (uiChunkSize * ui32NumLogicalChunks))
        {
            PVR_DPF((PVR_DBG_ERROR, 
                    "%s: Bad mapping size (uiLogicalSize = 0x%llx, "
                    "uiChunkSize = 0x%llx, ui32NumLogicalChunks = %d)",
                    __func__, 
                    (unsigned long long)uiLogicalSize, 
                    (unsigned long long)uiChunkSize, 
                    ui32NumLogicalChunks));
            return PVRSRV_ERROR_PMR_BAD_MAPPINGTABLE_SIZE;
        }
    }

    /* ========== 第3步:一次性分配内存 ========== */
    /*
     * 内存布局:
     * ┌─────────────┐
     * │  PMR结构体  │ sizeof(*psPMR)
     * ├─────────────┤
     * │  映射表结构 │ sizeof(*psMappingTable)
     * ├─────────────┤
     * │  翻译数组   │ sizeof(IMG_UINT32) * ui32NumLogicalChunks
     * └─────────────┘
     * 
     * 优点:
     * 1. 减少内存碎片
     * 2. 提高缓存局部性
     * 3. 简化释放流程(只需一次free)
     */
    pvPMRLinAddr = OSAllocMem(sizeof(*psPMR) + 
                              sizeof(*psMappingTable) + 
                              IMG_FLEX_ARRAY_SIZE(sizeof(IMG_UINT32), ui32NumLogicalChunks));
    PVR_RETURN_IF_NOMEM(pvPMRLinAddr);

    // PMR结构体在起始位置
    psPMR = (PMR *) pvPMRLinAddr;

    // 映射表紧跟在PMR结构体后面
    psMappingTable = IMG_OFFSET_ADDR(pvPMRLinAddr, sizeof(*psPMR));
    /*
     * IMG_OFFSET_ADDR宏展开:
     * #define IMG_OFFSET_ADDR(ptr, offset) ((void*)((char*)(ptr) + (offset)))
     */

    /* ========== 第4步:初始化映射表 ========== */
    psMappingTable->uiChunkSize = uiChunkSize;
    psMappingTable->ui32NumLogicalChunks = ui32NumLogicalChunks;
    psMappingTable->ui32NumPhysChunks = ui32NumPhysChunks;

    /*
     * 初始化翻译数组为0xFF(TRANSLATION_INVALID)
     * 
     * 为什么先全部设为无效?
     * - 稀疏分配中,不是所有逻辑块都有对应物理块
     * - 先标记为无效,再填充有效的映射
     */
    OSCachedMemSet(psMappingTable->aui32Translation, 0xFF,
                   sizeof(psMappingTable->aui32Translation[0]) * ui32NumLogicalChunks);

    /*
     * 填充有效映射
     * 
     * 示例:
     * pui32MappingTable = [0, 2, 4]  // 物理块0,2,4有效
     * ui32NumPhysChunks = 3
     * 
     * 循环后:
     * aui32Translation[0] = 0  // 逻辑块0 -> 物理块0
     * aui32Translation[1] = 0xFF (无效)
     * aui32Translation[2] = 2  // 逻辑块2 -> 物理块2
     * aui32Translation[3] = 0xFF (无效)
     * aui32Translation[4] = 4  // 逻辑块4 -> 物理块4
     */
    for (i=0; i<ui32NumPhysChunks; i++)
    {
        ui32Temp = pui32MappingTable[i];  // 获取逻辑索引
        PVR_ASSERT(ui32Temp < ui32NumLogicalChunks);
        psMappingTable->aui32Translation[ui32Temp] = ui32Temp;
        /*
         * 注意:这里假设逻辑索引==物理索引
         * 实际可能需要独立的物理索引数组
         */
    }

    /* ========== 第5步:创建同步锁 ========== */

    // 创建主锁(可睡眠)
    eError = OSLockCreate(&psPMR->hLock);
    PVR_GOTO_IF_ERROR(eError, ErrFreePMR);

    // 创建位图锁(自旋锁)
    eError = OSSpinLockCreate(&psPMR->hBitmapLock);
    PVR_GOTO_IF_ERROR(eError, ErrFreePMRLock);

    // 创建引用计数锁(自旋锁)
    eError = OSSpinLockCreate(&psPMR->hRefCountLock);
    PVR_GOTO_IF_ERROR(eError, ErrFreeBitmapLock);

    /* ========== 第6步:初始化PMR基本字段 ========== */

    psPMR->uiRefCount = 0;  // 初始引用计数为0

    // 初始化映射计数
    OSAtomicWrite(&psPMR->iClientCpuMapCount, 0);

#if defined(SUPPORT_LINUX_OSPAGE_MIGRATION)
    OSAtomicWrite(&psPMR->iKernelCpuMapCount, 0);
    dllist_init(&psPMR->sGpuMappingListHead);  // 初始化GPU映射列表
#endif

    psPMR->iAssociatedResCount = 0;  // GPU资源计数

    /*
     * 锁计数初始化逻辑:
     * - On-demand分配:iLockCount = 0(未backing)
     * On-demand 分配(按需分配) 指的是:
     * 在创建或映射一个逻辑内存对象(例如 PMR 或 GPU 虚拟地址空间)时,不立即分配所有物理页,而是 在首次访问(或明确请求)时才分配物理页。也可以理解为:延迟分配 (lazy allocation) 或 稀疏分配 (sparse allocation)。
     * - 立即分配:iLockCount = 1(已backing)
     */
    OSAtomicWrite(&psPMR->iLockCount, 
                  (PVRSRV_CHECK_ON_DEMAND(uiFlags) ? 0 : 1));

    psPMR->psContext = psContext;
    psPMR->uiLogicalSize = uiLogicalSize;
    psPMR->uiLog2ContiguityGuarantee = uiLog2ContiguityGuarantee;
    psPMR->uiFlags = uiFlags;
    psPMR->psMappingTable = psMappingTable;

    // 设置内部标志
    psPMR->uiInternalFlags = bSparse ? PMR_FLAG_INTERNAL_SPARSE_ALLOC : 0;

    // 清空注释
    psPMR->szAnnotation[0] = '\0';

    // 初始化环境数据
    PMR_ENV_INITIALIZE(psPMR, sEnvData);

#if defined(SUPPORT_PMR_DEVICE_IMPORT_DEFERRED_FREE) || defined(PVRSRV_ENABLE_XD_MEM)
    psPMR->uiDevImportBitmap = 0;  // 清空设备导入位图
#endif

#if defined(SUPPORT_PMR_DEFERRED_FREE)
    psPMR->sHeader.eZombieType = PMR_ZOMBIE_TYPE_PMR;
    dllist_init(&psPMR->sHeader.sZombieNode);
#if defined(SUPPORT_PMR_DEVICE_IMPORT_DEFERRED_FREE)
    dllist_init(&psPMR->sXDeviceImports);
#endif
#endif

#if defined(PVRSRV_ENABLE_GPU_MEMORY_INFO)
    psPMR->hRIHandle = NULL;  // RI句柄稍后设置
#endif

#ifdef XDXGPU
    OSDmaResvInit(&psPMR->_resv);        // 初始化DMA预留对象
    PMRSetDmaReservation(psPMR, &psPMR->_resv);
#endif

    /* ========== 第7步:生成唯一标识 ========== */
    OSLockAcquire(psContext->hLock);  // 获取全局锁

    // 分配序列号
    psPMR->uiKey = psContext->uiNextKey;
    psPMR->uiSerialNum = psContext->uiNextSerialNum;

    /*
     * 密钥生成算法(线性同余生成器 + 地址混淆)
     * 
     * 公式:
     * next_key = (A * current_key) XOR (B * address)
     * 
     * 其中:
     * A = 0x80200003 (大质数)
     * B = 0xf00f0081 (混淆因子)
     * address = PMR内存地址
     */
    psContext->uiNextKey = (0x80200003 * psContext->uiNextKey)
                            ^ (0xf00f0081 * (uintptr_t)pvPMRLinAddr);

    psContext->uiNextSerialNum++;  // 序列号递增

    *ppsPMR = psPMR;  // 输出创建的PMR

    OSLockRelease(psContext->hLock);  // 释放全局锁

    PVR_DPF((PVR_DBG_MESSAGE, 
            "%s: 0x%p, key:0x%016" IMG_UINT64_FMTSPECX ", numLive:%d",
            __func__, psPMR, psPMR->uiKey, 
            OSAtomicRead(&psPMR->psContext->uiNumLivePMRs)));

    /* ========== 第8步:增加活跃PMR计数 ========== */
    OSAtomicIncrement(&psContext->uiNumLivePMRs);

    return PVRSRV_OK;

    /* ========== 错误处理路径 ========== */
ErrFreeBitmapLock:
    OSSpinLockDestroy(psPMR->hBitmapLock);
ErrFreePMRLock:
    OSLockDestroy(psPMR->hLock);
ErrFreePMR:
    OSFreeMem(psPMR);

    return eError;
}

PMR创建流程图

┌─────────────────────────────────────────────────────────────┐
│                    开始 _PMRCreate()                        │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  第1步:判断内存类型                                         │
│  ┌───────────────────────────────────────────────────────┐  │
│  │ if (逻辑块数 != 物理块数 || 逻辑块数 > 1)               │  │
│  │     bSparse = TRUE                                    │  │
│  │     uiChunkSize = 1 << uiLog2ContiguityGuarantee      │  │
│  │ else                                                  │  │
│  │     bSparse = FALSE                                   │  │
│  │     uiChunkSize = uiLogicalSize                       │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  第2步:验证参数                                             │
│  ┌───────────────────────────────────────────────────────┐  │
│  │ if (bSparse)                                          │  │
│  │     检查:uiLogicalSize == uiChunkSize * 逻辑块数      │  │
│  │     失败 → 返回 PVRSRV_ERROR_PMR_BAD_MAPPINGTABLE_SIZE │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  第3步:内存分配                                             │
│  ┌───────────────────────────────────────────────────────┐  │
│  │ 分配大小 = sizeof(PMR) + sizeof(映射表) +              │  │
│  │           sizeof(uint32) * 逻辑块数                   │   │
│  │                                                       │  │
│  │ 布局:                                                 │  │
│  │ ┌─────────────┐                                       │  │
│  │ │ PMR结构体   │ ← psPMR                               │  │
│  │ ├─────────────┤                                      │  │
│  │ │ 映射表头    │ ← psMappingTable                      │  │
│  │ ├─────────────┤                                      │  │
│  │ │ 翻译数组    │ ← aui32Translation[]                  │  │
│  │ └─────────────┘                                      │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  第4步:初始化映射表                                          │
│  ┌───────────────────────────────────────────────────────┐  │
│  │ psMappingTable->uiChunkSize = ...                     │  │
│  │ psMappingTable->ui32NumLogicalChunks = ...            │  │
│  │ psMappingTable->ui32NumPhysChunks = ...               │  │
│  │                                                       │  │
│  │ // 先全部设为无效                                      │  │
│  │ memset(aui32Translation, 0xFF, ...)                   │  │
│  │                                                       │  │
│  │ // 填充有效映射                                        │  │
│  │ for (i = 0; i < ui32NumPhysChunks; i++)               │  │
│  │     aui32Translation[pui32MappingTable[i]] = i        │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  第5步:创建同步锁                                            │
│  ┌───────────────────────────────────────────────────────┐  │
│  │ OSLockCreate(&psPMR->hLock)              // 主锁       │  │
│  │ OSSpinLockCreate(&psPMR->hBitmapLock)    // 位图锁     │  │
│  │ OSSpinLockCreate(&psPMR->hRefCountLock)  // 引用锁     │  │
│  │                                                       │  │
│  │ 失败处理:逐级释放已创建的资源                           │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  第6步:初始化PMR字段                                       │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ uiRefCount = 0                                        │ │
│  │ iClientCpuMapCount = 0                                │ │
│  │ iLockCount = (on-demand ? 0 : 1)                      │ │
│  │ uiLogicalSize = ...                                   │ │
│  │ uiLog2ContiguityGuarantee = ...                       │ │
│  │ uiFlags = ...                                         │ │
│  │ psMappingTable = ...                                  │ │
│  │ uiInternalFlags = (bSparse ? SPARSE_ALLOC : 0)        │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  第7步:生成唯一标识                                        │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ OSLockAcquire(psContext->hLock)                       │ │
│  │                                                       │ │
│  │ psPMR->uiSerialNum = psContext->uiNextSerialNum++     │ │
│  │ psPMR->uiKey = psContext->uiNextKey                   │ │
│  │                                                       │ │
│  │ // LCG密钥生成                                         │ │
│  │ psContext->uiNextKey =                                │ │
│  │     (0x80200003 * uiNextKey) ^ (0xf00f0081 * addr)    │ │
│  │                                                       │ │
│  │ OSLockRelease(psContext->hLock)                       │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  第8步:更新统计                                            │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ OSAtomicIncrement(&psContext->uiNumLivePMRs)          │ │
│  │                                                       │ │
│  │ 输出调试信息:                                         │ │
│  │ "PMR: 0x%p, key:0x%016llx, numLive:%d"                │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│                    返回 PVRSRV_OK                           │
└─────────────────────────────────────────────────────────────┘

内存分配策略对比

策略 连续分配 稀疏分配
逻辑块数 1 > 1
物理块数 1 ≤ 逻辑块数
块大小 等于逻辑大小 由连续性保证决定
映射表 简化(1:1映射) 完整翻译数组
内存使用 逻辑 == 物理 逻辑≥物理
优势 性能最优 节省物理内存
劣势 可能浪费内存 地址转换开销
典型用途 大型纹理/缓冲区 虚拟内存管理

引用计数管理

引用计数增加 _Ref()

/**
 * @brief 增加PMR引用计数
 * @param psPMR PMR指针
 * @param uiRefCount 增加的数量
 * @return PVRSRV_OK 成功,其他值失败
 * 
 * 线程安全:使用自旋锁保护
 */
static PVRSRV_ERROR
_Ref(PMR *psPMR, IMG_UINT32 uiRefCount)
{
    OS_SPINLOCK_FLAGS uiFlags;

    /* ========== 获取自旋锁 ========== */
    /*
     * 为什么使用自旋锁?
     * - 引用计数操作非常快速
     * - 不涉及睡眠操作
     * - 需要严格的原子性
     */
    OSSpinLockAcquire(psPMR->hRefCountLock, uiFlags);

    /* ========== 安全检查1:引用计数为0 ========== */
    /*
     * 如果引用计数已经为0,说明PMR已经在销毁过程中
     * 这时不应该再增加引用
     * 
     * 可能的原因:
     * - 竞态条件:一个线程正在释放,另一个线程试图获取
     * - 程序错误:在对象销毁后仍然访问
     */
    if (psPMR->uiRefCount == 0)
    {
        OSSpinLockRelease(psPMR->hRefCountLock, uiFlags);

        PVR_DPF((PVR_DBG_ERROR, 
                "pmr.c: Ref Count == 0 PMR: @0x%p Annot: %s",
                psPMR,
                psPMR->szAnnotation));

        OSWarnOn(1);  // 触发内核警告(但不panic)

        return PVRSRV_ERROR_REFCOUNT_OVERFLOW;
    }

    /* ========== 安全检查2:防止溢出 ========== */
    /*
     * 检查增加后是否会溢出
     * 
     * 溢出场景:
     * - 恶意攻击:故意大量增加引用导致永不释放
     * - 程序错误:循环中重复引用但未释放
     * 
     * 防御:
     * - 检查 current + delta < MAX
     * - 拒绝会导致溢出的操作
     */
    if (psPMR->uiRefCount >= IMG_UINT32_MAX - uiRefCount)
    {
        OSSpinLockRelease(psPMR->hRefCountLock, uiFlags);

        PVR_DPF((PVR_DBG_ERROR, 
                "pmr.c: Ref Count >= IMG_UINT32_MAX PMR: @0x%p "
                "Annot: %s RefCount: %u",
                psPMR,
                psPMR->szAnnotation,
                uiRefCount));

        OSWarnOn(1);

        return PVRSRV_ERROR_REFCOUNT_OVERFLOW;
    }
    /* ========== 安全增加引用计数 ========== */
    /*
     * 通过上面的检查后,可以安全地增加
     * 此时仍在自旋锁保护下,保证原子性
     */
    psPMR->uiRefCount += uiRefCount;

    /* ========== 释放自旋锁 ========== */
    OSSpinLockRelease(psPMR->hRefCountLock, uiFlags);

    return PVRSRV_OK;
}

引用计数减少 _Unref()

/**
 * @brief 减少PMR引用计数
 * @param psPMR PMR指针
 * @param uiRefCount 减少的数量
 * @param pui32RefCount 输出:减少后的引用计数(可为NULL)
 * @return PVRSRV_OK 成功,其他值失败
 */
static PVRSRV_ERROR
_Unref(PMR *psPMR, IMG_INT32 uiRefCount, IMG_UINT32 *pui32RefCount)
{
    OS_SPINLOCK_FLAGS uiFlags;

    /* ========== 获取自旋锁 ========== */
    OSSpinLockAcquire(psPMR->hRefCountLock, uiFlags);

    /* ========== 安全检查:防止下溢 ========== */
    /*
     * 如果当前引用计数为0,不能再减少
     * 
     * 可能原因:
     * - 多次Unref同一个引用
     * - Unref次数多于Ref次数
     * - 程序逻辑错误
     */
    if (psPMR->uiRefCount == 0)
    {
        OSSpinLockRelease(psPMR->hRefCountLock, uiFlags);

        PVR_DPF((PVR_DBG_ERROR, 
                "pmr.c: Unref Count = 0 PMR: @0x%p Annot: %s "
                "RefCount: %u",
                psPMR,
                psPMR->szAnnotation,
                uiRefCount));

        OSWarnOn(1);

        return PVRSRV_ERROR_REFCOUNT_OVERFLOW;
    }

    /* ========== 减少引用计数 ========== */
    psPMR->uiRefCount -= uiRefCount;

    /* ========== 返回当前引用计数 ========== */
    /*
     * 如果调用者提供了输出参数,返回减少后的计数
     * 调用者可以根据这个值判断是否需要销毁PMR
     */
    if (pui32RefCount != NULL)
    {
        *pui32RefCount = psPMR->uiRefCount;
    }

    /* ========== 释放自旋锁 ========== */
    OSSpinLockRelease(psPMR->hRefCountLock, uiFlags);

    return PVRSRV_OK;
}

引用计数生命周期管理

/**
 * @brief 减少引用计数,必要时销毁PMR
 * @param psPMR PMR指针
 * @param uiRefCount 减少的数量
 * @return PVRSRV_OK 成功
 * 
 * 核心逻辑:
 * 1. 减少引用计数
 * 2. 如果计数归零,触发销毁流程
 * 3. 如果启用延迟释放,进入僵尸列表
 */
static PVRSRV_ERROR
_UnrefAndMaybeDestroy(PMR *psPMR, IMG_UINT32 uiRefCount)
{
    const PMR_IMPL_FUNCTAB *psFuncTable;
#if defined(SUPPORT_PMR_DEFERRED_FREE)
    PVRSRV_DEVICE_NODE *psDevNode;
    IMG_BOOL bQueuedDeviceImports = IMG_FALSE;
#endif
    PVRSRV_ERROR eError;

    PVR_ASSERT(psPMR != NULL);

    psFuncTable = psPMR->psFuncTab;

    /* ========== 获取Factory锁 ========== */
    /*
     * 为什么需要Factory锁?
     * - PMR销毁时需要调用具体Factory的清理函数
     * - Factory可能在管理多个PMR,需要同步
     * - 避免在销毁过程中创建新的同类型PMR
     */
    _FactoryLock(psFuncTable);

    /* ========== 减少引用计数 ========== */
    eError = _Unref(psPMR, uiRefCount, &uiRefCount);
    PVR_LOG_GOTO_IF_ERROR(eError, "_Unref", ErrFactoryUnlock);

    /* ========== 检查引用计数 ========== */
    if (uiRefCount > 0)
    {
        /* 还有其他引用,不销毁 */
        _FactoryUnlock(psFuncTable);
        return PVRSRV_OK;
    }

    /* ========== 引用计数为0,开始销毁流程 ========== */

#if !defined(SUPPORT_PMR_DEFERRED_FREE)
    /* ========== 不支持延迟释放:立即销毁 ========== */
    /*
     * 用于NoHW和PDump驱动
     * 没有GPU缓存同步问题
     */
    _PMRDestroy(psPMR);
#else
    /* ========== 支持延迟释放:智能销毁策略 ========== */

    psDevNode = PhysHeapDeviceNode(psPMR->psPhysHeap);

    /*
     * 决策树:是否需要延迟释放?
     * 
     * 立即释放条件(任一满足):
     * 1. 未设置DEFER_FREE标志(从未映射到GPU)
     * 2. GPU电源OFF(缓存已失效)
     * 3. PMR已标记为空僵尸
     * 
     * 延迟释放条件:
     * 1. 设置了DEFER_FREE标志
     * 2. GPU电源ON或有设备导入
     * 3. PMR不是空僵尸
     */

    /* ========== 检查1:是否标记为延迟释放 ========== */
    if (!_IntFlagIsSet(psPMR, PMR_FLAG_INTERNAL_DEFER_FREE))
    {
        /*
         * 未标记延迟释放:
         * - 可能是纯CPU内存
         * - 可能从未映射到GPU
         * - 可以安全立即销毁
         */
        _PMRDestroy(psPMR);
        goto exit_;
    }

#if defined(SUPPORT_PMR_DEVICE_IMPORT_DEFERRED_FREE)
    /* ========== 检查2:处理跨设备导入 ========== */
    /*
     * 如果其他设备导入了此PMR,也需要等待它们刷新缓存
     * 将导入信息加入各个设备的僵尸列表
     */
    bQueuedDeviceImports = _DeviceImportsEnqueueZombies(psPMR);
#endif

    /* ========== 检查3:设备状态 ========== */
    if (!bQueuedDeviceImports
        && !_IsDeviceOnAndOperating(psDevNode)
#if defined(SUPPORT_LINUX_OSPAGE_MIGRATION)
        && !PVRSRV_CHECK_OS_LINUX_MOVABLE(psPMR->uiFlags)
#endif
        )
    {
        /*
         * 设备已关闭且没有设备导入:
         * - GPU缓存已失效(电源OFF)
         * - 可以安全立即销毁
         */
        _PMRDestroy(psPMR);
    }
    /* ========== 检查4:空僵尸PMR ========== */
    else if (_IntFlagIsSet(psPMR, PMR_FLAG_INTERNAL_IS_ZOMBIE_PMR_EMPTY))
    {
        /*
         * PMR已被标记为空僵尸:
         * - 所有页面已释放
         * - 只剩下PMR结构本身
         * - 可以安全销毁
         */
        _PMRDestroy(psPMR);
    }
    /* ========== 默认:加入僵尸列表延迟释放 ========== */
    else
    {
        /*
         * 需要延迟释放:
         * 1. 标记为僵尸PMR
         * 2. 加入设备的僵尸列表
         * 3. 等待清理线程处理
         */

        _ZombieListLock(psDevNode);

        /* 设置僵尸标志 */
        _IntFlagSet(psPMR, PMR_FLAG_INTERNAL_IS_ZOMBIE_PMR);

        /* 加入僵尸列表 */
        dllist_add_to_tail(&psDevNode->sPMRZombieList, 
                          &psPMR->sHeader.sZombieNode);
        psDevNode->uiPMRZombieCount++;

        /*
         * 调用Factory的Zombify回调
         * 
         * 目的:
         * - 更新内存统计(将页面从"活跃"转为"僵尸")
         * - 释放可以立即释放的资源
         * - 标记哪些资源需要等待
         * 
         * 需要持锁调用,防止清理线程并发访问
         */
        if (psPMR->psFuncTab->pfnZombify != NULL)
        {
            eError = psPMR->psFuncTab->pfnZombify(psPMR->pvFlavourData, psPMR);
            PVR_LOG_IF_ERROR(eError, "pfnZombify");
        }

        _ZombieListUnlock(psDevNode);
    }
exit_:
#endif /* SUPPORT_PMR_DEFERRED_FREE */

    _FactoryUnlock(psFuncTable);

    return PVRSRV_OK;

ErrFactoryUnlock:
    _FactoryUnlock(psFuncTable);

    return eError;
}

引用计数状态转换图

                    PMR生命周期状态机

┌──────────────────────────────────────────────────────────────┐
│                     初始状态                                  │
│                  uiRefCount = 0                              │
│                  PMR刚创建完成                                │
└──────────────────────────────────────────────────────────────┘
                          │
                          │ PMRLockPhysAddresses()
                          │ PMRExportPMR()
                          │ PMRRefPMR()
                          ↓
┌──────────────────────────────────────────────────────────────┐
│                     活跃状态                                  │
│                  uiRefCount > 0                              │
│  ┌────────────────────────────────────────────────────────┐  │
│  │  可能的引用来源:                                        │ │
│  │  - 物理地址锁定 (iLockCount)                             │ │
│  │  - CPU映射 (iClientCpuMapCount)                         │ │
│  │  - GPU映射 (iAssociatedResCount)                        │ │
│  │  - 导出/导入 (Export/Import)                            │ │
│  │  - 显式引用 (RefPMR)                                    │ │
│  └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
                          │
                          │ PMRUnlockPhysAddresses()
                          │ PMRUnexportPMR()
                          │ PMRUnrefPMR()
                          │ 所有引用释放完毕
                          ↓
┌──────────────────────────────────────────────────────────────┐
│                  引用计数归零                                 │
│                  uiRefCount = 0                              │
│               _UnrefAndMaybeDestroy()                        │
└──────────────────────────────────────────────────────────────┘
                          │
                          ├─────────────┬─────────────┐
                          ↓             ↓             ↓
              ┌─────────────────┐ ┌──────────┐ ┌───────────────┐
              │ 未设DEFER_FREE  │ │ GPU OFF  │ │ 空僵尸PMR     │
              └─────────────────┘ └──────────┘ └───────────────┘
                          │             │             │
                          └─────────────┴─────────────┘
                                      ↓
                          ┌─────────────────────────┐
                          │   立即销毁 _PMRDestroy  │
                          └─────────────────────────┘
                                      │
                                      ↓
                          ┌─────────────────────────┐
                          │     释放物理内存         │
                          │     销毁映射表           │
                          │     销毁锁               │
                          │     释放PMR结构体        │
                          └─────────────────────────┘
                                      │
                                      ↓
                          ┌─────────────────────────┐
                          │        生命周期结束      │
                          └─────────────────────────┘

                          或者 ↓

              ┌─────────────────────────────────────┐
              │  设置DEFER_FREE && GPU ON           │
              └─────────────────────────────────────┘
                              ↓
              ┌─────────────────────────────────────┐
              │         进入僵尸状态                │
              │  设置 IS_ZOMBIE_PMR 标志            │
              │  加入 sPMRZombieList               │
              │  等待清理线程处理                   │
              └─────────────────────────────────────┘
                              │
                              │ 等待GPU缓存刷新
                              │ 或设备电源OFF
                              ↓
              ┌─────────────────────────────────────┐
              │    清理线程 _PmrZombieCleanup       │
              │  检查同步条件满足                   │
              │  从僵尸列表移除                     │
              └─────────────────────────────────────┘
                              │
                              ↓
              ┌─────────────────────────────────────┐
              │     最终销毁 _PMRDestroy            │
              └─────────────────────────────────────┘
                              │
                              ↓
              ┌─────────────────────────────────────┐
              │        生命周期结束                 │
              └─────────────────────────────────────┘

引用计数操作汇总表

操作函数 引用变化 调用场景 注意事项
PMRCreatePMR 0→0 PMR创建 初始为0,需要后续Lock
PMRLockPhysAddresses +1 锁定物理地址 同时增加iLockCount
PMRUnlockPhysAddresses -1 解锁物理地址 同时减少iLockCount
PMRExportPMR +1 导出到其他进程 设置NO_LAYOUT_CHANGE
PMRUnexportPMR -1 取消导出 对应Export操作
PMRImportPMR +1 从其他进程导入 验证密钥
PMRUnimportPMR -1 取消导入 对应Import操作
PMRRefPMR +1 显式增加引用 用于长期持有
PMRUnrefPMR -1 显式减少引用 对应RefPMR
PMRMakeLocalImportHandle +1 创建本地导入句柄 同进程内使用
PMRUnmakeLocalImportHandle -1 销毁本地导入句柄 对应MakeLocal
DevmemIntMapPMR +1 映射到GPU 增加GPU映射计数
DevmemIntUnmapPMR -1 从GPU取消映射 减少GPU映射计数

地址转换引擎

核心转换函数 _PMRLogicalOffsetToPhysicalOffset

/**
 * @brief 逻辑地址到物理地址转换
 * @param psPMR PMR指针
 * @param ui32Log2PageSize 页面大小(log2)
 * @param ui32NumOfPages 要转换的页数
 * @param uiLogicalOffset 逻辑偏移量
 * @param puiPhysicalOffset 输出:物理偏移量数组
 * @param pui32BytesRemain 输出:剩余字节数
 * @param bValid 输出:页面有效性数组
 * @return PVRSRV_OK 成功,其他值失败
 * 
 * 功能说明:
 * - 将PMR内的逻辑地址转换为实际物理地址
 * - 支持单页和多页批量转换
 * - 处理稀疏内存的无效页面
 * - 返回第一个块的剩余字节数
 */
static PVRSRV_ERROR
_PMRLogicalOffsetToPhysicalOffset(const PMR *psPMR,
                                  IMG_UINT32 ui32Log2PageSize,
                                  IMG_UINT32 ui32NumOfPages,
                                  IMG_DEVMEM_OFFSET_T uiLogicalOffset,
                                  IMG_DEVMEM_OFFSET_T *puiPhysicalOffset,
                                  IMG_UINT32 *pui32BytesRemain,
                                  IMG_BOOL *bValid)
{
    PMR_MAPPING_TABLE *psMappingTable = psPMR->psMappingTable;
    IMG_DEVMEM_OFFSET_T uiPageSize = 1ULL << ui32Log2PageSize;
    IMG_DEVMEM_OFFSET_T uiOffset = uiLogicalOffset;
    IMG_UINT64 ui64ChunkIndex;
    IMG_UINT32 ui32Remain;
    IMG_UINT32 idx;

    /* 断言:至少转换一页 */
    PVR_ASSERT(ui32NumOfPages > 0);

    /* ========== 决策:快速路径 vs 慢速路径 ========== */
    /*
     * 快速路径条件:
     * ui32NumPhysChunks == ui32NumLogicalChunks
     * 
     * 含义:
     * - 物理块数等于逻辑块数
     * - 意味着是连续内存或完全backing的稀疏内存
     * - 逻辑地址 == 物理地址(1:1映射)
     * 
     * 优势:
     * - 无需查表
     * - 无需复杂计算
     * - 性能最优
     */
    if (psMappingTable->ui32NumPhysChunks == psMappingTable->ui32NumLogicalChunks)
    {
        /* ========== 快速路径实现 ========== */

        /*
         * 第一页处理
         * 
         * 计算剩余字节:
         * 从当前偏移到PMR末尾的字节数
         */
        *pui32BytesRemain = TRUNCATE_64BITS_TO_32BITS(psPMR->uiLogicalSize - uiOffset);

        /*
         * 直接赋值:逻辑偏移 == 物理偏移
         */
        puiPhysicalOffset[0] = uiOffset;
        bValid[0] = IMG_TRUE;

        /* ========== 多页处理 ========== */
        if (ui32NumOfPages > 1)
        {
            /*
             * 对齐到页边界
             * 
             * 示例:
             * uiOffset = 0x1234 (初始偏移)
             * uiPageSize = 0x1000 (4KB)
             * uiOffset &= ~0xFFF  // 结果:0x1000
             * 
             * 原因:
             * 后续页面都是整页,从页边界开始
             */
            uiOffset &= ~(uiPageSize-1);

            /*
             * 循环处理剩余页面
             */
            for (idx = 1; idx < ui32NumOfPages; idx++)
            {
                uiOffset += uiPageSize;  // 下一页
                puiPhysicalOffset[idx] = uiOffset;
                bValid[idx] = IMG_TRUE;
            }
        }
    }
    /* ========== 慢速路径:稀疏内存 ========== */
    else
    {
        /*
         * 慢速路径需要:
         * 1. 计算逻辑块索引
         * 2. 查询映射表
         * 3. 计算物理偏移
         * 4. 处理无效页面
         */

        for (idx = 0; idx < ui32NumOfPages; idx++)
        {
            IMG_UINT32 uiTranslation;
            const IMG_UINT32 uiChunkSize = psMappingTable->uiChunkSize;

            /* ========== 步骤1:计算逻辑块索引和块内偏移 ========== */
            /*
             * OSDivide64r64 函数:
             * 将64位除法拆分为商和余数
             * 
             * ui64ChunkIndex = uiOffset / uiChunkSize  (商)
             * ui32Remain = uiOffset % uiChunkSize      (余数)
             * 
             * 示例:
             * uiOffset = 0x5234 (21044 bytes)
             * uiChunkSize = 0x1000 (4096 bytes)
             * ui64ChunkIndex = 5 (第5个块)
             * ui32Remain = 0x234 (564 bytes块内偏移)
             */
            ui64ChunkIndex = OSDivide64r64(
                    uiOffset,
                    TRUNCATE_64BITS_TO_32BITS(uiChunkSize),
                    &ui32Remain);

            /* ========== 步骤2:边界检查 ========== */
            /*
             * 防止越界访问
             * 
             * 可能原因:
             * - 用户空间传入的参数错误
             * - 内存布局发生变化但参数未更新
             */
            if (ui64ChunkIndex >= psMappingTable->ui32NumLogicalChunks)
            {
                return PVRSRV_ERROR_BAD_MAPPING;
            }

            /* ========== 步骤3:查询映射表 ========== */
            /*
             * aui32Translation[逻辑块索引] = 物理块索引
             * 
             * 特殊值:
             * TRANSLATION_INVALID (0xFFFFFFFF) 表示该逻辑块未映射
             */
            uiTranslation = psMappingTable->aui32Translation[ui64ChunkIndex];

            /* ========== 步骤4:处理映射结果 ========== */
            if (uiTranslation == TRANSLATION_INVALID)
            {
                /*
                 * 无效映射:
                 * - 该逻辑块没有分配物理内存
                 * - 稀疏内存的"洞"
                 * - 标记为无效,返回特殊值
                 */
                bValid[idx] = IMG_FALSE;
                puiPhysicalOffset[idx] = IMG_UINT64_C(0xffffffffffffffff);
            }
            else
            {
                /*
                 * 有效映射:
                 * 计算物理地址
                 * 
                 * 公式:
                 * 物理偏移 = 物理块索引 × 块大小 + 块内偏移
                 * 
                 * 示例:
                 * uiTranslation = 3 (物理块3)
                 * uiChunkSize = 0x1000
                 * ui32Remain = 0x234
                 * 物理偏移 = 3 * 0x1000 + 0x234 = 0x3234
                 */
                bValid[idx] = IMG_TRUE;
                puiPhysicalOffset[idx] = uiTranslation * uiChunkSize + ui32Remain;
            }

            /* ========== 步骤5:计算第一页剩余字节 ========== */
            if (idx == 0)
            {
                /*
                 * 剩余字节 = 块大小 - 块内偏移
                 * 
                 * 示例:
                 * uiChunkSize = 0x1000 (4096)
                 * ui32Remain = 0x234 (564)
                 * 剩余 = 4096 - 564 = 3532 bytes
                 * 
                 * 用途:
                 * - 告诉调用者在当前块内还能访问多少字节
                 * - 用于IO操作的边界检查
                 */
                *pui32BytesRemain = TRUNCATE_64BITS_TO_32BITS(uiChunkSize - ui32Remain);

                /*
                 * 对齐到页边界
                 * 后续页面从整页边界开始
                 */
                uiOffset &= ~(uiPageSize-1);
            }

            /* ========== 步骤6:移动到下一页 ========== */
            uiOffset += uiPageSize;
        }
    }

    return PVRSRV_OK;
}

地址转换示意图

连续内存(快速路径)

连续内存地址转换

逻辑地址空间:
┌─────────────────────────────────────────────────────────┐
│  0x0000  │  0x1000  │  0x2000  │  0x3000  │  0x4000    │
|────────────────────────────────────────────────────────|
│   Page0  │   Page1  │   Page2  │   Page3  │   Page4    │
└─────────────────────────────────────────────────────────┘
    ↓ (1:1映射)
物理地址空间:
┌─────────────────────────────────────────────────────────┐
│  0x0000  │  0x1000  │  0x2000  │  0x3000  │  0x4000    │
|────────────────────────────────────────────────────────|
│   Page0  │   Page1  │   Page2  │   Page3  │   Page4    │
└─────────────────────────────────────────────────────────┘

转换过程:
1. 检测:NumPhysChunks == NumLogicalChunks
2. 直接赋值:PhysicalOffset = LogicalOffset
3. 无需查表,性能最优

时间复杂度:O(1)

稀疏内存(慢速路径)

稀疏内存地址转换

逻辑地址空间(连续):
┌────────┬────────┬────────┬────────┬────────┬────────┐
│ Chunk0 │ Chunk1 │ Chunk2 │ Chunk3 │ Chunk4 │ Chunk5 │
|─────────────────────────────────────────────────────|
│  0x0   │  0x1   │  0x2   │  0x3   │  0x4   │  0x5   │
└────────┴────────┴────────┴────────┴────────┴────────┘
    ↓         ↓        ↓         ↓        ↓         ↓
映射表 aui32Translation[]:
┌────────┬────────┬────────┬────────┬────────┬────────┐
│   0    │  0xFF  │   2    │  0xFF  │   4    │   5    │
|─────────────────────────────────────────────────────|
│ (有效) │ (无效)  │ (有效) │ (无效) │ (有效)  │ (有效) │
└────────┴────────┴────────┴────────┴────────┴────────┘
    ↓                ↓                 ↓         ↓
物理地址空间(不连续):
┌────────┐      ┌────────┐      ┌────────┬────────┐
│ Block0 │      │ Block2 │      │ Block4 │ Block5 │
│  Phys  │      │  Phys  │      │  Phys  │  Phys  │
└────────┘      └────────┘      └────────┴────────┘

转换示例:

输入:逻辑偏移 0x2500 (在Chunk2中)
    步骤1:计算块索引
        ChunkIndex = 0x2500 / 0x1000 = 2
        Remain = 0x2500 % 0x1000 = 0x500
    步骤2:查询映射表
        Translation = aui32Translation[2] = 2 (有效)
    步骤3:计算物理偏移
        PhysicalOffset = 2 * 0x1000 + 0x500 = 0x2500
    bValid = TRUE
**无效访问示例:**
输入:逻辑偏移 0x1234 (在Chunk1中)
    步骤1:ChunkIndex = 1, Remain = 0x234
    步骤2:Translation = aui32Translation[1] = 0xFF (无效)
    步骤3:PhysicalOffset = 0xFFFFFFFFFFFFFFFF
    bValid = FALSE

时间复杂度:O(n) 其中n为页数

页对齐向下取整

一次性分配内存包含
1. sizeof(*psPMR): PMR结构体大小
2. sizeof(*psMappingTable): 映射表结构体大小
3. IMG_FLEX_ARRAY_SIZE(sizeof(IMG_UINT32), ui32NumLogicalChunks): 翻译数组大小

内存布局:

┌─────────────┬─────────────────┬─────────────────────────┐
│ PMR struct  │ MappingTable    │ Translation Array       │
│             │ struct          │ [ui32NumLogicalChunks]  │
└─────────────┴─────────────────┴─────────────────────────┘

在慢速路径中,代码需要处理多个连续页面的地址转换。关键问题是:用户传入的逻辑偏移量可能不是页对齐的,所以需要页对齐向下取整。

示例说明

假设:
- 页大小 = 4096 字节 (0x1000)
- 用户传入逻辑偏移 = 0x1234 (不是页对齐)
- 需要转换 3 个页面

不进行向下取整的情况:

页面0: 偏移 = 0x1234  (用户指定的起始位置)
页面1: 偏移 = 0x1234 + 0x1000 = 0x2234
页面2: 偏移 = 0x2234 + 0x1000 = 0x3234

问题:这些偏移量都不是页边界对齐的!

进行向下取整后:

uiOffset &= ~(uiPageSize-1);  // 0x1234 & ~0xFFF = 0x1000

页面0: 偏移 = 0x1234  (第一个页面保持用户指定的位置)
页面1: 偏移 = 0x1000 + 0x1000 = 0x2000  (页对齐)
页面2: 偏移 = 0x2000 + 0x1000 = 0x3000  (页对齐)

为什么只对后续页面对齐?

第一个页面 (idx == 0):
- 保持用户指定的确切偏移量puiPhysicalOffset[0] = 转换后的实际地址
- 用户可能需要访问页面中间的某个位置,不能强制对齐

后续页面 (idx > 0):
- 必须页对齐:因为我们是在处理完整的页面
- 从包含用户偏移量的页面边界开始,逐页递增

具体代码流程

// 第一次迭代 (idx == 0)
if (idx == 0) {
    // 1. 计算当前块中剩余的字节数
    *pui32BytesRemain = TRUNCATE_64BITS_TO_32BITS(uiChunkSize - ui32Remain);

    // 2. 为后续页面计算做准备,向下对齐到页边界
    uiOffset &= ~(uiPageSize-1);  // 清除低位,对齐到页边界
}

// 每次迭代结束都会执行
uiOffset += uiPageSize;  // 移动到下一个页面

位运算解释

uiOffset &= ~(uiPageSize-1);

以4KB页为例:
- uiPageSize = 0x1000 (4096)
- uiPageSize-1 = 0xFFF (4095)
- ~(uiPageSize-1) = 0xFFFFFFFFFFFFF000 (掩码)

这个掩码会清除低12位,实现向下对齐到4KB边界。

内存布局示意

原始用户偏移: 0x1234
     │
     ▼
┌────┴─────┬─────────┬─────────┬─────────┐
│   Page 0 │  Page 1 │  Page 2 │  Page 3 │
├──────────┼─────────┼─────────┼─────────┤
│   0x1000 │  0x2000 │  0x3000 │  0x4000 │
└──────────┴─────────┴─────────┴─────────┘
     │         │         │
     │         │         └── 0x3000 (对齐)
     │         └── 0x2000 (对齐) 
     └── 0x1234 (用户指定,不对齐)

向下取整的目的是:
1. 第一个页面:保持用户精确指定的偏移量
2. 后续页面:确保从正确的页边界开始,逐页递增
3. 内存管理正确性:确保页面地址计算的准确性和一致性

这种设计既满足了用户对精确偏移的需求,又保证了内存管理系统的页面对齐要求。

PMR映射表工作原理详解

映射表概念

psMappingTable->aui32Translation[ui64ChunkIndex] 是一个逻辑块到物理块的映射数组

基本概念:
- 逻辑块:用户看到的连续内存块编号
- 物理块:实际物理内存中的块编号
- 映射表:记录逻辑块到物理块映射关系的数组

映射表结构示意

逻辑内存视图 (用户看到的):
┌─────────┬─────────┬─────────┬─────────┐
│ Chunk 0 │ Chunk 1 │ Chunk 2 │ Chunk 3 │
│ (逻辑)  │ (逻辑)   │ (逻辑)  │ (逻辑)  │
└─────────┴─────────┴─────────┴─────────┘

映射表 aui32Translation[]:
Index:  [0]  [1]  [2]  [3]
Value:  [5]  [2]  [7]  [1]
        │    │    │    │
        ▼    ▼    ▼    ▼
实际物理内存:
┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│Chunk 0  │Chunk 1  │Chunk 2  │Chunk 3  │Chunk 4  │Chunk 5  │Chunk 6  │Chunk 7  │
│(物理)   │(物理)    │(物理)   │(物理)    │(物理)   │(物理)    │(物理)   │(物理)   │
└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘

代码详解

获取映射关系

uiTranslation = psMappingTable->aui32Translation[ui64ChunkIndex];
  • ui64ChunkIndex: 当前逻辑偏移所在的逻辑块索引
  • aui32Translation[ui64ChunkIndex]: 返回对应的物理块索引
  • uiTranslation: 存储物理块索引号

    检查映射有效性

if (uiTranslation == TRANSLATION_INVALID)
{
    bValid[idx] = IMG_FALSE;
    puiPhysicalOffset[idx] = IMG_UINT64_C(0xffffffffffffffff);
}

TRANSLATION_INVALID 表示这个逻辑块没有对应的物理内存,可能原因:
- 内存还未分配
- 内存已被释放
- 稀疏内存映射中的"洞"

计算物理偏移量

else
{
    bValid[idx] = IMG_TRUE;
    puiPhysicalOffset[idx] = uiTranslation * uiChunkSize + ui32Remain;
}

物理偏移计算公式详解

puiPhysicalOffset[idx] = uiTranslation * uiChunkSize + ui32Remain;

公式组成:
- uiTranslation: 物理块索引号
- uiChunkSize: 每个块的大小(字节)
- ui32Remain: 在块内的偏移量

计算过程:
1. uiTranslation * uiChunkSize: 计算物理块的起始地址
2. + ui32Remain: 加上块内偏移,得到最终物理地址

具体例子

假设:
- 块大小 uiChunkSize = 0x10000 (64KB)
- 用户访问逻辑偏移 0x25678
- 通过除法得到:ui64ChunkIndex = 2, ui32Remain = 0x5678

映射表查找:

uiTranslation = aui32Translation[2];  // 假设返回 7

物理地址计算:

puiPhysicalOffset[idx] = 7 * 0x10000 + 0x5678 = 0x70000 + 0x5678 = 0x75678

内存布局可视化

用户逻辑地址: 0x25678
         ↓ (通过映射表转换)
实际物理地址: 0x75678

逻辑内存:
┌──────────┬──────────┬──────────┬──────────┐
│ Block 0  │ Block 1  │ Block 2  │ Block 3  │
│ 0x00000  │ 0x10000  │ 0x20000  │ 0x30000  │
└──────────┴──────────┴────┬─────┴──────────┘
                           │
                      0x25678 (逻辑)
                           │
                           ▼ (映射)
物理内存:
┌──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
│ Block 0  │ Block 1  │ Block 2  │ Block 3  │ Block 4  │ Block 5  │ Block 6  │ Block 7  │
│ 0x00000  │ 0x10000  │ 0x20000  │ 0x30000  │ 0x40000  │ 0x50000  │ 0x60000  │ 0x70000  │
└──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴────┬─────┘
                                                                                    │
                                                                               0x75678 (物理)

为什么需要映射表?

内存碎片处理
物理内存可能不连续,映射表允许逻辑上连续的内存映射到分散的物理块。

动态内存管理
- 延迟分配:逻辑空间预留,物理内存按需分配
- 内存回收:释放不用的物理块,映射表标记为无效

稀疏内存
大的虚拟地址空间中只有部分区域有对应的物理内存。

内存重映射
可以重新组织物理内存布局而不影响逻辑地址。

  • 映射表:逻辑块索引 → 物理块索引的查找表
  • 物理地址计算:物理块起始地址 + 块内偏移 = 最终物理地址
  • 无效映射处理:标记为无效并设置特殊值,避免访问错误

这种设计是现代操作系统和GPU驱动中虚拟内存管理的核心机制!

获取设备物理地址 PMR_DevPhysAddr

/**
 * @brief 获取设备物理地址
 * @param psPMR PMR指针
 * @param ui32Log2PageSize 页面大小(log2)
 * @param ui32NumOfPages 页数
 * @param uiLogicalOffset 逻辑偏移,即需求内存大小
 * @param psDevAddrPtr 输出:设备物理地址数组
 * @param pbValid 输出:有效性数组
 * @param uiPMRUsage PMR使用模式(CPU/DEVICE/MAPPING)
 * @return PVRSRV_OK 成功
 * 
 * 必须先调用 PMRLockPhysAddresses()
 */
PVRSRV_ERROR
PMR_DevPhysAddr(const PMR *psPMR,
                IMG_UINT32 ui32Log2PageSize,
                IMG_UINT32 ui32NumOfPages,
                IMG_DEVMEM_OFFSET_T uiLogicalOffset,
                IMG_DEV_PHYADDR *psDevAddrPtr,
                IMG_BOOL *pbValid,
                PMR_PHYSADDRMODE_TYPE uiPMRUsage)
{
    IMG_UINT32 ui32Remain;
    PVRSRV_ERROR eError = PVRSRV_OK;
    IMG_DEVMEM_OFFSET_T auiPhysicalOffset[PMR_MAX_TRANSLATION_STACK_ALLOC];
    IMG_DEVMEM_OFFSET_T *puiPhysicalOffset = auiPhysicalOffset;
#if defined(SUPPORT_STATIC_IPA)
    IMG_UINT32 ui32FlagsIPAPolicy;      /* IPA策略标志 */
    IMG_UINT32 ui32IPAHeapShift;        /* 物理堆位移值 */
    IMG_UINT32 ui32IPAHeapPolicyValue;  /* 物理堆默认策略 */
    IMG_UINT32 ui32IPAHeapClearMask;    /* 物理堆清除掩码 */
    IMG_UINT64 ui64IPAPolicy;           /* 应用到地址的IPA策略 */
    IMG_UINT64 ui64IPAClearMask;        /* 应用到地址的清除掩码 */
#endif

    PVR_ASSERT(psPMR != NULL);
    PVR_ASSERT(ui32NumOfPages > 0);
    PVR_ASSERT(psPMR->psFuncTab->pfnDevPhysAddr != NULL);

    /* ========== 前置检查:物理地址必须已锁定 ========== */
    /*
     * iLockCount 检查:
     * - On-demand分配:必须 > 0
     * - 非On-demand:必须 > 1(初始值为1)
     * 
     * 为什么需要锁定?
     * - 防止物理页面被换出
     * - 防止页面迁移
     * - 保证地址有效性
     */
    if (OSAtomicRead(&psPMR->iLockCount) <= 0)
    {
        PVR_DPF((PVR_DBG_ERROR,"Attempt to obtain DevPhysAddr of non-backed PMR"));
        return PVRSRV_ERROR_PMR_NOT_PERMITTED;
    }

    /* ========== 内存分配优化:栈 vs 堆 ========== */
    /*
     * PMR_MAX_TRANSLATION_STACK_ALLOC 通常为 32 或 64
     * 
     * 策略:
     * - 小量页面:使用栈数组(快速)
     * - 大量页面:动态分配(避免栈溢出)
     */
    if (ui32NumOfPages > PMR_MAX_TRANSLATION_STACK_ALLOC)
    {
        puiPhysicalOffset = OSAllocMem(ui32NumOfPages * sizeof(IMG_DEVMEM_OFFSET_T));
        PVR_RETURN_IF_NOMEM(puiPhysicalOffset);
    }

    /* ========== 步骤1:逻辑地址转物理偏移 ========== */
    eError = _PMRLogicalOffsetToPhysicalOffset(psPMR,
                                               ui32Log2PageSize,
                                               ui32NumOfPages,
                                               uiLogicalOffset,
                                               puiPhysicalOffset,
                                               &ui32Remain,
                                               pbValid);
    PVR_LOG_GOTO_IF_ERROR(eError, "_PMRLogicalOffsetToPhysicalOffset", FreeOffsetArray);

#if defined(SUPPORT_STATIC_IPA)
    /* ========== 步骤2:IPA(中间物理地址)处理 ========== */
    /*
     * IPA (Intermediate Physical Address):
     * 在某些系统中,需要对物理地址进行额外转换
     * 
     * 使用场景:
     * - IOMMU系统
     * - 多级地址转换
     * - 安全内存隔离
     * 
     * 三层地址转换:
     * CPU地址 → IPA → 设备物理地址
     */

    PVR_ASSERT(psPMR->psPhysHeap != NULL);

    /* 获取物理堆的IPA配置 */
    ui32IPAHeapShift = PhysHeapGetIPAShift(psPMR->psPhysHeap);
    ui32IPAHeapPolicyValue = PhysHeapGetIPAValue(psPMR->psPhysHeap);
    ui32IPAHeapClearMask = PhysHeapGetIPAMask(psPMR->psPhysHeap);

#if defined(PVRSRV_INTERNAL_IPA_FEATURE_TESTING)
    /* 测试模式:允许PMR覆盖物理堆的默认IPA策略 */
    ui32FlagsIPAPolicy = PVRSRV_MEMALLOCFLAG_IPA_POLICY(psPMR->uiFlags);
    if (ui32FlagsIPAPolicy == 0U)
    {
        ui32FlagsIPAPolicy = ui32IPAHeapPolicyValue;
    }
#else
    /* 生产模式:使用物理堆的默认策略 */
    ui32FlagsIPAPolicy = ui32IPAHeapPolicyValue;
#endif

    /*
     * CPU使用模式:禁用IPA转换
     * 
     * 原因:
     * - CPU直接访问物理地址
     * - 不需要IPA中间层
     */
    if (unlikely(BITMASK_HAS(uiPMRUsage, CPU_USE)))
    {
        ui32IPAHeapClearMask = 0U;
        ui32FlagsIPAPolicy = 0U;
    }

    /* 计算IPA转换参数 */
    ui64IPAPolicy = (IMG_UINT64)ui32FlagsIPAPolicy << ui32IPAHeapShift;
    ui64IPAClearMask = (IMG_UINT64)ui32IPAHeapClearMask << ui32IPAHeapShift;
#endif /* SUPPORT_STATIC_IPA */

#if defined(SUPPORT_LINUX_OSPAGE_MIGRATION)
    /* ========== 步骤3:处理页面迁移 ========== */
    /*
     * 页面迁移场景:
     * - Linux内核的页面回收
     * - 内存碎片整理
     * - NUMA节点间迁移
     */

    if (BITMASK_HAS(uiPMRUsage, MAPPING_USE) && BITMASK_HAS(uiPMRUsage, CPU_USE))
    {
        /*
         * CPU映射场景:需要等待迁移完成
         * 
         * 策略:
         * - 自旋等待迁移完成
         * - 不设超时(必须完成)
         * - 释放锁让其他线程运行
         */
        PMRLockHeldAssert(psPMR);

        while (1)
        {
            if (psPMR->eState == PMR_STATE_ACTIVE)
            {
                break;  /* 迁移完成,可以获取地址 */
            }

            /*
             * 释放锁,让迁移线程完成工作
             * 避免死锁
             */
            PMRUnlockPMR(psPMR);
            OSReleaseThreadQuanta();  /* 主动让出CPU */
            PMRLockPMR(psPMR);
        }

        /* 迁移完成,获取物理地址 */
        eError = psPMR->psFuncTab->pfnDevPhysAddr(psPMR->pvFlavourData,
                                                  ui32Log2PageSize,
                                                  ui32NumOfPages,
                                                  puiPhysicalOffset,
#if defined(SUPPORT_STATIC_IPA)
                                                  ui64IPAPolicy,
                                                  ui64IPAClearMask,
#endif
                                                  pbValid,
                                                  psDevAddrPtr);
    }
    else if (BITMASK_HAS(uiPMRUsage, MAPPING_USE) && BITMASK_HAS(uiPMRUsage, DEVICE_USE))
    {
        /*
         * GPU映射场景:如果在迁移中,返回重试
         * 
         * 原因:
         * - GPU映射持有更高层的锁
         * - 不能长时间等待
         * - 返回RETRY让上层重试
         */
        PMRLockHeldAssert(psPMR);

        if (psPMR->eState != PMR_STATE_ACTIVE)
        {
            eError = PVRSRV_ERROR_RETRY;
            goto FreeOffsetArray;
        }

        eError = psPMR->psFuncTab->pfnDevPhysAddr(psPMR->pvFlavourData,
                                                  ui32Log2PageSize,
                                                  ui32NumOfPages,
                                                  puiPhysicalOffset,
#if defined(SUPPORT_STATIC_IPA)
                                                  ui64IPAPolicy,
                                                  ui64IPAClearMask,
#endif
                                                  pbValid,
                                                  psDevAddrPtr);
    }
    else
#endif /* SUPPORT_LINUX_OSPAGE_MIGRATION */
    {
        /* ========== 步骤4:标准路径 - 调用Factory函数获取物理地址 ========== */
        /*
         * 调用具体PMR实现的函数
         * 
         * 不同类型PMR的实现:
         * - OS Pages: 查询页面结构体
         * - DMA-BUF: 从sg_table获取
         * - ION: 从ION缓冲区获取
         * - 物理内存: 直接返回物理地址
         */
        eError = psPMR->psFuncTab->pfnDevPhysAddr(psPMR->pvFlavourData,
                                                  ui32Log2PageSize,
                                                  ui32NumOfPages,
                                                  puiPhysicalOffset,
#if defined(SUPPORT_STATIC_IPA)
                                                  ui64IPAPolicy,
                                                  ui64IPAClearMask,
#endif
                                                  pbValid,
                                                  psDevAddrPtr);
        PVR_GOTO_IF_ERROR(eError, FreeOffsetArray);
    }

#if defined(PVR_PMR_TRANSLATE_UMA_ADDRESSES)
    /* ========== 步骤5:UMA地址转换 ========== */
    /*
     * UMA (Unified Memory Architecture):
     * CPU和GPU共享同一物理地址空间
     * 
     * 某些系统需要CPU物理地址→GPU设备地址转换
     * 
     * 注意:默认关闭,因为大多数系统不需要
     */
    if (PhysHeapGetType(psPMR->psPhysHeap) == PHYS_HEAP_TYPE_UMA ||
        PhysHeapGetType(psPMR->psPhysHeap) == PHYS_HEAP_TYPE_DMA)
    {
        IMG_UINT32 i;
        IMG_DEV_PHYADDR sDevPAddrCorrected;

        /* 逐页转换地址 */
        for (i = 0; i < ui32NumOfPages; i++)
        {
            PhysHeapCpuPAddrToDevPAddr(psPMR->psPhysHeap,
                                       1,
                                       &sDevPAddrCorrected,
                                       (IMG_CPU_PHYADDR *) &psDevAddrPtr[i]);
            psDevAddrPtr[i].uiAddr = sDevPAddrCorrected.uiAddr;
        }
    }
#endif

FreeOffsetArray:
    /* ========== 清理:释放动态分配的内存 ========== */
    if (puiPhysicalOffset != auiPhysicalOffset)
    {
        OSFreeMem(puiPhysicalOffset);
    }

    return eError;
}

地址转换完整流程图

┌─────────────────────────────────────────────────────────────┐
│                PMR_DevPhysAddr 完整流程                      │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  前置检查                                                    │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ 1. 检查 iLockCount > 0 (物理地址已锁定)               │ │
│  │ 2. 检查 psFuncTab->pfnDevPhysAddr != NULL              │ │
│  │ 3. 检查 ui32NumOfPages > 0                             │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  内存分配优化                                                │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ if (ui32NumOfPages <= 32)                             │ │
│  │     使用栈数组 auiPhysicalOffset[32]                   │ │
│  │ else                                                  │ │
│  │     动态分配 OSAllocMem()                              │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  第1阶段:逻辑地址 → 物理偏移                                 │
│  _PMRLogicalOffsetToPhysicalOffset()                        │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  连续内存?                                            │ │
│  │  ├─ 是 → 快速路径:PhysOffset = LogicalOffset          │ │
│  │  └─ 否 → 慢速路径:查询映射表                           │ │
│  │                                                       │ │
│  │  输出:                                                │ │
│  │  - puiPhysicalOffset[]: 物理偏移数组                   │ │
│  │  - pbValid[]: 有效性标记                               │ │
│  │  - ui32Remain: 第一块剩余字节                          │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  第2阶段:IPA转换准备 (可选)                                 │
│  #if defined(SUPPORT_STATIC_IPA)                            │
│  ┌───────────────────────────────────────────────────────┐ │
│  │  获取物理堆IPA配置:                                    │ │
│  │  - ui32IPAHeapShift: 位移值                            │ │
│  │  - ui32IPAHeapPolicyValue: 策略值                      │ │
│  │  - ui32IPAHeapClearMask: 清除掩码                      │ │
│  │                                                       │ │
│  │  if (CPU_USE)                                         │ │
│  │      禁用IPA转换                                       │ │
│  │  else                                                 │ │
│  │      计算IPA参数                                       │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  第3阶段:处理页面迁移 (可选)                                 │
│  #if defined(SUPPORT_LINUX_OSPAGE_MIGRATION)                │
│  ┌───────────────────────────────────────────────────────┐ │
│  │  CPU映射 + 迁移中?                                    │ │
│  │  ├─ 是 → 自旋等待迁移完成                               │ │
│  │  │       while (eState != ACTIVE)                     │ │
│  │  │           释放锁 → 让出CPU → 重新获取锁              │ │
│  │  │                                                    │ │
│  │  GPU映射 + 迁移中?                                    │ │
│  │  └─ 是 → 返回 PVRSRV_ERROR_RETRY                       │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  第4阶段:调用Factory获取设备物理地址                          │
│  psFuncTab->pfnDevPhysAddr()                                │
│  ┌───────────────────────────────────────────────────────┐ │
│  │  根据PMR类型调用不同实现:                              │ │
│  │                                                       │ │
│  │  OS Pages PMR:                                        │ │
│  │    从 struct page 获取 PFN                             │ │
│  │    PFN << PAGE_SHIFT → 物理地址                        │ │
│  │                                                       │ │
│  │  DMA-BUF PMR:                                         │ │
│  │    从 sg_table 遍历 scatter-gather 列表                │ │
│  │    获取每个段的物理地址                                 │ │
│  │                                                       │ │
│  │  ION PMR:                                             │ │
│  │    从 ION buffer 的 sg_table 获取                      │ │
│  │                                                       │ │
│  │  物理内存PMR:                                          │ │
│  │    直接返回预先分配的物理地址                           │ │
│  │                                                       │ │
│  │  应用IPA转换(如果启用)                                │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  第5阶段:UMA地址转换 (可选)                                  │
│  #if defined(PVR_PMR_TRANSLATE_UMA_ADDRESSES)               │
│  ┌───────────────────────────────────────────────────────┐ │
│  │  if (PHYS_HEAP_TYPE_UMA || PHYS_HEAP_TYPE_DMA)        │ │
│  │      for each page:                                   │ │
│  │          PhysHeapCpuPAddrToDevPAddr()                 │ │
│  │          转换 CPU物理地址 → GPU设备地址                 │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  清理和返回                                                  │
│  ┌───────────────────────────────────────────────────────┐ │
│  │  if (动态分配的内存)                                   │ │
│  │      OSFreeMem(puiPhysicalOffset)                     │ │
│  │                                                       │ │
│  │  返回:                                               │ │
│  │  - psDevAddrPtr[]: 设备物理地址数组                    │ │
│  │  - pbValid[]: 页面有效性数组                           │ │
│  │  - PVRSRV_OK 或错误码                                  │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

物理地址锁定

锁定物理地址 PMRLockPhysAddressesNested

/**
 * @brief 锁定PMR的物理地址(支持嵌套)
 * @param psPMR PMR指针
 * @param uiLockCount 锁定次数
 * @param ui32NestingLevel 嵌套级别(防止死锁)
 * @return PVRSRV_OK 成功
 * 
 * 作用:
 * 1. 防止物理页面被换出
 * 2. 防止页面迁移
 * 3. 确保物理地址在使用期间有效
 * 4. 增加引用计数
 */
PVRSRV_ERROR 
PMRLockPhysAddressesNested(PMR *psPMR,
                           IMG_UINT32 uiLockCount,
                           IMG_UINT32 ui32NestingLevel)
{
    PVRSRV_ERROR eError;
    IMG_UINT32 uiCallbackValue;

    PVR_ASSERT(psPMR != NULL);

    /*
     * 注意:这个锁不是用来保护引用计数的
     * (引用计数由自旋锁保护)
     * 
     * 这个锁的作用:
     * - 保证物理地址锁定操作的原子性
     * - 确保Lock和Factory回调之间没有其他操作
     */

    /* ========== 步骤1:获取PMR主锁 ========== */
    /*
     * ui32NestingLevel用于防止死锁
     * 
     * 场景:函数A持有锁0,调用函数B需要锁1
     * 如果没有嵌套级别,可能死锁
     * 
     * 嵌套级别越高,优先级越高
     */
    OSLockAcquireNested(psPMR->hLock, ui32NestingLevel);

    /* ========== 步骤2:增加引用计数 ========== */
    /*
     * 为什么在这里增加引用计数?
     * 
     * 1. 锁定物理地址意味着要使用PMR
     * 2. 必须保证PMR在使用期间不被销毁
     * 3. 引用计数是生命周期管理的核心机制
     * 
     * 注意:即使物理地址锁定失败,引用计数也会增加
     * 失败时需要在错误路径中减少引用
     */
    eError = _Ref(psPMR, uiLockCount);
    if (eError != PVRSRV_OK)
    {
        OSLockRelease(psPMR->hLock);
        return eError;
    }

    /* ========== 步骤3:增加锁计数 ========== */
    /*
     * iLockCount作用:
     * 1. 跟踪Lock/Unlock配对
     * 2. 调试检查(防止多unlock或少unlock)
     * 3. 决定何时调用Factory的Lock/Unlock回调
     * 
     * 计数规则:
     * - On-demand PMR: 初始为0,第一次Lock时变为1
     * - 非On-demand PMR: 初始为1(已backing),Lock后变为2
     */

    /*
     * uiCallbackValue: 需要调用Factory回调的阈值
     * 
     * 计算逻辑:
     * - On-demand: 0 → 1时调用(第一次锁定)
     * - 非On-demand: 1 → 2时调用(第一次额外锁定)
     */
    uiCallbackValue = uiLockCount + (PVRSRV_CHECK_ON_DEMAND(psPMR->uiFlags) ? 0 : 1);

    if (OSAtomicAdd(&psPMR->iLockCount, uiLockCount) == uiCallbackValue)
    {
        /*
         * 锁计数刚好达到阈值,调用Factory回调
         * 
         * pfnLockPhysAddresses的作用:
         * 1. 分配物理页面(如果是on-demand)
         * 2. Pin住页面(防止被swap out)
         * 3. 增加页面引用(防止被回收)
         * 4. 刷新TLB(如果需要)
         */
        if (psPMR->psFuncTab->pfnLockPhysAddresses != NULL)
        {
            /* 必须成对实现Lock和Unlock */
            PVR_ASSERT(psPMR->psFuncTab->pfnUnlockPhysAddresses != NULL);

            eError = psPMR->psFuncTab->pfnLockPhysAddresses(psPMR->pvFlavourData);

            PVR_GOTO_IF_ERROR(eError, e1);
        }
    }

    /* ========== 步骤4:释放PMR主锁 ========== */
    OSLockRelease(psPMR->hLock);

    return PVRSRV_OK;

    /* ========== 错误处理 ========== */
e1:
    /*
     * Factory回调失败,需要回滚
     * 1. 减少锁计数
     * 2. 减少引用计数
     * 3. 释放主锁
     */
    OSAtomicSubtract(&psPMR->iLockCount, uiLockCount);
    PVR_ASSERT(_GetRef(psPMR) != 0);
    OSLockRelease(psPMR->hLock);

    PVR_ASSERT(eError != PVRSRV_OK);

    /*
     * 减少引用计数必须在最后
     * 因为可能触发PMR销毁
     */
    (void) _Unref(psPMR, uiLockCount, NULL);

    return eError;
}

解锁物理地址 PMRUnlockPhysAddressesNested

/**
 * @brief 解锁PMR的物理地址(支持嵌套)
 * @param psPMR PMR指针
 * @param uiLockCount 解锁次数
 * @param ui32NestingLevel 嵌套级别
 * @return PVRSRV_OK 成功
 * 
 * 注意:必须与PMRLockPhysAddresses配对使用
 */
PVRSRV_ERROR
PMRUnlockPhysAddressesNested(PMR *psPMR,
                             IMG_UINT32 uiLockCount,
                             IMG_UINT32 ui32NestingLevel)
{
    PVRSRV_ERROR eError = PVRSRV_OK;
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
    PMR_IMPL_ZOMBIEPAGES pvZombiePages = NULL;
    PMR_ZOMBIE_PAGES* psPMRZombiePages = NULL;
#endif
    PVR_ASSERT(psPMR != NULL);

    /* ========== 步骤1:获取PMR主锁 ========== */
    /*
     * 获取锁的原因:
     * 1. 保证锁计数归零和解锁操作的原子性
     * 2. 防止竞态条件(一个线程unlock,另一个线程lock)
     */
    OSLockAcquireNested(psPMR->hLock, ui32NestingLevel);

    /* ========== 步骤2:检查锁计数 ========== */
    /*
     * 调试断言:确保锁定/解锁配对正确
     * 
     * 如果断言失败,说明:
     * - 多次unlock同一个lock
     * - unlock了从未lock的PMR
     * - 程序逻辑错误
     */
    PVR_ASSERT(OSAtomicRead(&psPMR->iLockCount) >= uiLockCount);

    /* ========== 步骤3:减少锁计数并检查是否需要调用Factory回调 ========== */
    /*
     * OSAtomicSubtract 返回减少前的值
     * 
     * 判断条件:
     * - On-demand: 1 → 0 时调用unlock回调
     * - 非On-demand: 2 → 1 时调用unlock回调
     * 
     * 原因:
     * - On-demand: 0表示未backing
     * - 非On-demand: 1表示基础backing状态
     */
    if (OSAtomicSubtract(&psPMR->iLockCount, uiLockCount) == 
        (PVRSRV_CHECK_ON_DEMAND(psPMR->uiFlags) ? 0 : 1))
    {
        if (psPMR->psFuncTab->pfnUnlockPhysAddresses != NULL)
        {
            PVR_ASSERT(psPMR->psFuncTab->pfnLockPhysAddresses != NULL);
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
        /* 带延迟释放的解锁
        *
        * pvZombiePages输出参数:
        * - NULL: 没有页面需要延迟释放
        * - 非NULL: Factory返回需要延迟释放的页面信息
        *
        * 使用场景:
        * - On-demand分配且需要释放部分页面
        * - 稀疏内存布局变更后的旧页面
        */
        eError = psPMR->psFuncTab->pfnUnlockPhysAddresses(psPMR->pvFlavourData, &pvZombiePages);
#else
        /* 标准解锁(不支持页面延迟释放)
        *
        * pfnUnlockPhysAddresses的作用:
        * 1. Unpin页面(允许被swap out)
        * 2. 减少页面引用计数
        * 3. 释放暂时不需要的物理内存
        */
        eError = psPMR->psFuncTab->pfnUnlockPhysAddresses(psPMR->pvFlavourData);
#endif
        PVR_LOG_IF_ERROR(eError, "pfnUnlockPhysAddresses");
        }
    }
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
    /* ========== 步骤4:处理僵尸页面(如果有) ========== /
    /
    * 僵尸页面场景:
    * 1. On-demand PMR释放部分页面
    * 2. 稀疏内存缩减后的旧页面
    * 3. 页面需要等待GPU缓存刷新后再释放
    */
    if (pvZombiePages != NULL)
    {
        /* 预分配僵尸页面包装结构
        *
        * 为什么在这里分配?
        * - 在持锁状态下分配
        * - 简化错误处理
        * - 如果分配失败,可以尝试重新lock
        */
        psPMRZombiePages = OSAllocZMem(sizeof(PMR_ZOMBIE_PAGES));
        PVR_GOTO_IF_NOMEM(psPMRZombiePages, eError, ErrRelockPhysAddresses);
    }
#endif
    /* ========== 步骤5:释放PMR主锁 ========== */
    OSLockRelease(psPMR->hLock);
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
    /* ========== 步骤6:处理僵尸页面(锁外) ========== /
    /
    * _ProcessZombiePages的作用:
    * 1. 检查是否可以立即释放(设备已关闭)
    * 2. 如果不能立即释放,加入僵尸列表
    * 3. 如果没有僵尸页面,释放预分配的包装结构
    */
    eError = _ProcessZombiePages(pvZombiePages, psPMRZombiePages, psPMR);
    PVR_GOTO_IF_ERROR(eError, ErrReturn);
#endif
    /* ========== 步骤7:减少引用计数,可能触发销毁 ========== */
    /*
     * 注意顺序:
     * 1. 先完成unlock操作
     * 2. 再减少引用计数
     * 3. 如果引用计数归零,触发销毁流程
     */
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
    (void) _UnrefAndMaybeDestroy(psPMR, uiLockCount);
    return eError;
#else
    return _UnrefAndMaybeDestroy(psPMR, uiLockCount);
#endif
    /* ========== 错误处理:重新锁定物理地址 ========== */
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
ErrRelockPhysAddresses:
    {
        /* 分配僵尸页面结构失败
        * 需要重新锁定物理地址,保持状态一致
        */
        IMG_UINT32 uiCallbackValue =
        uiLockCount + (PVRSRV_CHECK_ON_DEMAND(psPMR->uiFlags) ? 0 : 1);
        /* 恢复锁计数 */
        if (OSAtomicAdd(&psPMR->iLockCount, uiLockCount) == uiCallbackValue)
        {
            if (psPMR->psFuncTab->pfnLockPhysAddresses != NULL)
            {
                /*
                 * 重新调用Factory的锁定函数
                 * 恢复到unlock之前的状态
                 */
                eError = psPMR->psFuncTab->pfnLockPhysAddresses(psPMR->pvFlavourData);
                PVR_LOG_IF_ERROR(eError, "pfnLockPhysAddresses");
            }
        }
    }

    OSLockRelease(psPMR->hLock);
    ErrReturn:
    return eError;
    #endif
}

锁定/解锁状态转换图

┌─────────────────────────────────────────────────────────────┐
│              PMR物理地址锁定状态机                           │
└─────────────────────────────────────────────────────────────┘

On-Demand PMR:
─────────────
                   创建时
                     │
                     ↓
            ┌─────────────────┐
            │  未backing状态   │
            │  iLockCount = 0  │
            │  物理页面未分配  │
            └─────────────────┘
                     │
                     │ PMRLockPhysAddresses()
                     │ - 分配物理页面
                     │ - Pin页面
                     ↓
            ┌─────────────────┐
            │   已锁定状态     │
            │  iLockCount = 1  │
            │  物理页面已分配  │
            └─────────────────┘
                     │
                     │ 可以多次Lock
                     │ iLockCount++
                     ↓
            ┌─────────────────┐
            │  多次锁定状态    │
            │  iLockCount > 1  │
            │  引用计数增加    │
            └─────────────────┘
                     │
                     │ PMRUnlockPhysAddresses()
                     │ iLockCount--
                     ↓
            ┌─────────────────┐
            │   已锁定状态     │
            │  iLockCount = 1  │
            │  还有1个引用     │
            └─────────────────┘
                     │
                     │ PMRUnlockPhysAddresses()
                     │ - Unpin页面
                     │ - 可能释放物理页面
                     ↓
            ┌─────────────────┐
            │  未backing状态   │
            │  iLockCount = 0  │
            │  物理页面已释放  │
            └─────────────────┘

非On-Demand PMR:
───────────────
                   创建时
                     │
                     ↓
            ┌─────────────────┐
            │  基础backing状态 │
            │  iLockCount = 1  │
            │  物理页面已分配  │
            └─────────────────┘
                     │
                     │ PMRLockPhysAddresses()
                     │ - Pin页面
                     ↓
            ┌─────────────────┐
            │   已锁定状态     │
            │  iLockCount = 2  │
            │  页面已Pin       │
            └─────────────────┘
                     │
                     │ 可以多次Lock
                     │ iLockCount++
                     ↓
            ┌─────────────────┐
            │  多次锁定状态    │
            │  iLockCount > 2  │
            │  多个引用        │
            └─────────────────┘
                     │
                     │ PMRUnlockPhysAddresses()
                     │ iLockCount--
                     ↓
            ┌─────────────────┐
            │   已锁定状态     │
            │  iLockCount = 2  │
            │  还有额外引用    │
            └─────────────────┘
                     │
                     │ PMRUnlockPhysAddresses()
                     │ - Unpin页面
                     ↓
            ┌─────────────────┐
            │  基础backing状态 │
            │  iLockCount = 1  │
            │  回到初始状态    │
            │  物理页面保留    │
            └─────────────────┘

锁定机制关键点汇总

方面 On-Demand PMR 非On-Demand PMR
初始iLockCount 0 1
首次Lock效果 分配物理页面 Pin已存在的页面
iLockCount=0时 无物理backing 不允许(最小为1)
首次Unlock效果 释放物理页面 Unpin页面
页面生命周期 按需分配和释放 创建时分配,销毁时释放
内存效率 高(按需使用) 低(预先分配)
访问延迟 首次访问高 一致性好
典型用途 虚拟内存、大型缓冲区 小型关键缓冲区

内存读写操作

读取内存 PMR_ReadBytes

/**
 * @brief 从PMR读取数据
 * @param psPMR PMR指针
 * @param uiLogicalOffset 逻辑偏移量
 * @param pcBuffer 输出缓冲区
 * @param uiBufSz 缓冲区大小
 * @param puiNumBytes 输出:实际读取字节数
 * @return PVRSRV_OK 成功
 * 
 * 特点:
 * - 自动处理块边界
 * - 支持稀疏内存(无效块填0)
 * - 无需预先lock(函数内部处理)
 */
PVRSRV_ERROR
PMR_ReadBytes(PMR *psPMR,
              IMG_DEVMEM_OFFSET_T uiLogicalOffset,
              IMG_UINT8 *pcBuffer,
              size_t uiBufSz,
              size_t *puiNumBytes)
{
    PVRSRV_ERROR eError = PVRSRV_OK;
    IMG_DEVMEM_OFFSET_T uiPhysicalOffset;
    size_t uiBytesCopied = 0;

    /* ========== 步骤1:参数验证 ========== */
    /*
     * 检查整数溢出
     * 
     * 恶意输入场景:
     * uiLogicalOffset = 0xFFFFFFFFFFFFFFF0
     * uiBufSz = 0x20
     * 溢出后:0xFFFFFFFFFFFFFFF0 + 0x20 = 0x10 (回绕)
     * 
     * 防御:检查加法是否回绕
     */
    if (uiLogicalOffset + uiBufSz < uiLogicalOffset)
    {
        return PVRSRV_ERROR_INVALID_PARAMS;
    }

    /* ========== 步骤2:边界检查和调整 ========== */
    /*
     * 如果读取范围超出PMR大小,截断到PMR末尾
     * 
     * 示例:
     * PMR大小 = 1MB (0x100000)
     * 请求读取:offset=0xFFF00, size=0x200
     * 实际读取:offset=0xFFF00, size=0x100(到PMR末尾)
     */
    if (uiLogicalOffset + uiBufSz > psPMR->uiLogicalSize)
    {
        uiBufSz = TRUNCATE_64BITS_TO_32BITS(psPMR->uiLogicalSize - uiLogicalOffset);
    }
    PVR_ASSERT(uiBufSz > 0);
    PVR_ASSERT(uiBufSz <= psPMR->uiLogicalSize);

    /*
     * PMR必须实现以下之一:
     * 1. pfnReadBytes: 自定义读取函数
     * 2. pfnAcquireKernelMappingData: 提供内核映射
     * 
     * 如果都没有实现,无法读取数据
     */
    PVR_ASSERT(psPMR->psFuncTab->pfnAcquireKernelMappingData != NULL ||
               psPMR->psFuncTab->pfnReadBytes != NULL);

    /* ========== 步骤3:循环读取,处理跨块情况 ========== */
    /*
     * 为什么需要循环?
     * - 读取可能跨越多个块
     * - 每个块可能不连续
     * - 稀疏PMR中某些块可能无效
     */
    while (uiBytesCopied != uiBufSz)
    {
        IMG_UINT32 ui32Remain;      // 当前块剩余字节
        size_t uiBytesToCopy;       // 本次要复制的字节数
        size_t uiRead;              // 实际读取的字节数
        IMG_BOOL bValid;            // 块是否有效

        /* ========== 3.1:获取物理偏移 ========== */
        /*
         * 将当前逻辑偏移转换为物理偏移
         * 
         * 输出:
         * - uiPhysicalOffset: 物理偏移量
         * - ui32Remain: 当前块剩余可读字节
         * - bValid: 该块是否有效(是否有物理backing)
         */
        eError = _PMRLogicalOffsetToPhysicalOffset(psPMR,
                                                   0,    // ui32Log2PageSize=0表示字节级访问
                                                   1,    // 只转换1个"页"
                                                   uiLogicalOffset,
                                                   &uiPhysicalOffset,
                                                   &ui32Remain,
                                                   &bValid);
        PVR_LOG_RETURN_IF_ERROR(eError, "_PMRLogicalOffsetToPhysicalOffset");

        /* ========== 3.2:计算本次读取字节数 ========== */
        /*
         * 取最小值:
         * 1. 剩余要读取的字节数:uiBufSz - uiBytesCopied
         * 2. 当前块剩余字节数:ui32Remain
         * 
         * 示例:
         * 总共要读 1000 bytes
         * 已读 800 bytes
         * 当前块剩余 150 bytes
         * 本次读取 = MIN(1000-800, 150) = 150 bytes
         */
        uiBytesToCopy = MIN(uiBufSz - uiBytesCopied, ui32Remain);

        /* ========== 3.3:读取数据或填充零 ========== */
        if (bValid)
        {
            /*
             * 有效块:调用底层读取函数
             * 
             * _PMR_ReadBytesPhysical会:
             * 1. 锁定物理地址
             * 2. 调用Factory的读取函数或映射内存
             * 3. 复制数据
             * 4. 解锁物理地址
             */
            eError = _PMR_ReadBytesPhysical(psPMR,
                                            uiPhysicalOffset,
                                            &pcBuffer[uiBytesCopied],
                                            uiBytesToCopy,
                                            &uiRead);
            if ((eError != PVRSRV_OK) || (uiRead != uiBytesToCopy))
            {
                PVR_DPF((PVR_DBG_ERROR,
                        "%s: Failed to read chunk (eError = %s, uiRead = %zu, uiBytesToCopy = %zu)",
                        __func__,
                        PVRSRVGetErrorString(eError),
                        uiRead,
                        uiBytesToCopy));
                /* 遇到错误,停止读取 */
                break;
            }
        }
        else
        {
            /*
             * 无效块:填充零
             * 
             * 稀疏内存场景:
             * - 该逻辑块没有分配物理内存
             * - 根据规范,读取应返回零
             * - 类似于读取未初始化的虚拟内存
             */
            PVR_DPF((PVR_DBG_WARNING,
                    "%s: Invalid phys offset at logical offset (0x%llx) logical size (0x%llx)",
                    __func__,
                    uiLogicalOffset,
                    psPMR->uiLogicalSize));

            /* 填充零 */
            OSCachedMemSet(&pcBuffer[uiBytesCopied], 0, uiBytesToCopy);
            uiRead = uiBytesToCopy;
            eError = PVRSRV_ERROR_FAILED_TO_GET_PHYS_ADDR;
        }

        /* ========== 3.4:更新偏移和计数 ========== */
        uiLogicalOffset += uiRead;
        uiBytesCopied += uiRead;
    }

    /* ========== 步骤4:返回实际读取字节数 ========== */
    *puiNumBytes = uiBytesCopied;
    return eError;
}

底层物理读取 _PMR_ReadBytesPhysical

/**
 * @brief 从物理偏移读取数据
 * @param psPMR PMR指针
 * @param uiPhysicalOffset 物理偏移量
 * @param pcBuffer 输出缓冲区
 * @param uiBufSz 要读取的字节数
 * @param puiNumBytes 输出:实际读取字节数
 * @return PVRSRV_OK 成功
 * 
 * 内部函数,由PMR_ReadBytes调用
 */
static PVRSRV_ERROR
_PMR_ReadBytesPhysical(PMR *psPMR,
                       IMG_DEVMEM_OFFSET_T uiPhysicalOffset,
                       IMG_UINT8 *pcBuffer,
                       size_t uiBufSz,
                       size_t *puiNumBytes)
{
    PVRSRV_ERROR eError;

    /* ========== 方式1:使用Factory的pfnReadBytes回调 ========== */
    if (psPMR->psFuncTab->pfnReadBytes != NULL)
    {
        /*
         * 某些PMR类型提供专门的读取函数
         * 
         * 优点:
         * - 可能更高效(避免映射开销)
         * - 可以处理特殊硬件(如GPU寄存器)
         * - 可以实现特殊读取策略
         */

        /* 锁定物理地址 */
        eError = PMRLockPhysAddresses(psPMR);
        PVR_GOTO_IF_ERROR(eError, e0);

        /* 调用Factory的读取函数 */
        eError = psPMR->psFuncTab->pfnReadBytes(psPMR->pvFlavourData,
                                                uiPhysicalOffset,
                                                pcBuffer,
                                                uiBufSz,
                                                puiNumBytes);
        /* 解锁物理地址 */
        PMRUnlockPhysAddresses(psPMR);
        PVR_GOTO_IF_ERROR(eError, e0);
    }
    /* ========== 方式2:默认处理 - 通过内核映射读取 ========== */
    else if (psPMR->psFuncTab->pfnAcquireKernelMappingData)
    {
        /*
         * 默认读取策略:
         * 1. 获取内核虚拟地址映射
         * 2. 通过memcpy复制数据
         * 3. 释放映射
         * 
         * 适用于大多数PMR类型
         */

        IMG_HANDLE hKernelMappingHandle;
        IMG_UINT8 *pcKernelAddress;

        /* 获取内核映射 */
        eError = psPMR->psFuncTab->pfnAcquireKernelMappingData(
                        psPMR->pvFlavourData,
                        (size_t) uiPhysicalOffset,  // 映射起始偏移
                        uiBufSz,                    // 映射大小
                        (void **)&pcKernelAddress,  // 输出:内核虚拟地址
                        &hKernelMappingHandle,      // 输出:映射句柄
                        psPMR->uiFlags);            // PMR标志(缓存策略等)
        PVR_GOTO_IF_ERROR(eError, e0);

        /*
         * 使用保守的DeviceMemCopy
         * 
         * 为什么不用普通memcpy?
         * - PMR可能映射为非缓存内存
         * - 需要特殊的内存屏障
         * - DeviceMemCopy处理这些细节
         */
        OSDeviceMemCopy(&pcBuffer[0], pcKernelAddress, uiBufSz);
        *puiNumBytes = uiBufSz;

        /* 释放内核映射 */
        psPMR->psFuncTab->pfnReleaseKernelMappingData(psPMR->pvFlavourData,
                                                      hKernelMappingHandle);
    }
    else
    {
        /*
         * 既没有pfnReadBytes也没有pfnAcquireKernelMappingData
         * 这个PMR无法被读取
         */
        OSPanic();
#ifndef __CHECKER__
        PVR_LOG_GOTO_WITH_ERROR("psPMR->psFuncTab", eError, PVRSRV_ERROR_INVALID_PARAMS, e0);
#endif
    }

    return PVRSRV_OK;

e0:
    PVR_ASSERT(eError != PVRSRV_OK);
    *puiNumBytes = 0;
    return eError;
}

写入内存 PMR_WriteBytes

/**
 * @brief 向PMR写入数据
 * @param psPMR PMR指针
 * @param uiLogicalOffset 逻辑偏移量
 * @param pcBuffer 输入缓冲区
 * @param uiBufSz 要写入的字节数
 * @param puiNumBytes 输出:实际写入字节数
 * @return PVRSRV_OK 成功
 * 
 * 注意:写入到无效块会被忽略(稀疏PMR)
 */
PVRSRV_ERROR
PMR_WriteBytes(PMR *psPMR,
               IMG_DEVMEM_OFFSET_T uiLogicalOffset,
               IMG_UINT8 *pcBuffer,
               size_t uiBufSz,
               size_t *puiNumBytes)
{
    PVRSRV_ERROR eError = PVRSRV_OK;
    IMG_DEVMEM_OFFSET_T uiPhysicalOffset;
    size_t uiBytesCopied = 0;

    /* ========== 参数验证和边界检查(同读取) ========== */
    if (uiLogicalOffset + uiBufSz < uiLogicalOffset)
    {
        return PVRSRV_ERROR_INVALID_PARAMS;
    }

    if (uiLogicalOffset + uiBufSz > psPMR->uiLogicalSize)
    {
        uiBufSz = TRUNCATE_64BITS_TO_32BITS(psPMR->uiLogicalSize - uiLogicalOffset);
    }
    PVR_ASSERT(uiBufSz > 0);
    PVR_ASSERT(uiBufSz <= psPMR->uiLogicalSize);

    PVR_ASSERT(psPMR->psFuncTab->pfnAcquireKernelMappingData != NULL ||
               psPMR->psFuncTab->pfnWriteBytes != NULL);

    /* ========== 循环写入 ========== */
    while (uiBytesCopied != uiBufSz)
    {
        IMG_UINT32 ui32Remain;
        size_t uiBytesToCopy;
        size_t uiWrite;
        IMG_BOOL bValid;

        /* 获取物理偏移 */
        eError = _PMRLogicalOffsetToPhysicalOffset(psPMR,
                                                   0,
                                                   1,
                                                   uiLogicalOffset,
                                                   &uiPhysicalOffset,
                                                   &ui32Remain,
                                                   &bValid);
        PVR_LOG_RETURN_IF_ERROR(eError, "_PMRLogicalOffsetToPhysicalOffset");

        uiBytesToCopy = MIN(uiBufSz - uiBytesCopied, ui32Remain);

        if (bValid)
        {
            /* 有效块:写入数据 */
            eError = _PMR_WriteBytesPhysical(psPMR,
                                             uiPhysicalOffset,
                                             &pcBuffer[uiBytesCopied],
                                             uiBytesToCopy,
                                             &uiWrite);
            if ((eError != PVRSRV_OK) || (uiWrite != uiBytesToCopy))
            {
                PVR_DPF((PVR_DBG_ERROR,
                        "%s: Failed to write chunk",
                        __func__));
                break;
            }
        }
        else
        {
            /*
             * 无效块:忽略写入
             * 
             * 稀疏内存语义:
             * - 写入到未分配的逻辑地址
             * - 数据被丢弃
             * - 不报错,假装成功
             * 
             * 原因:
             * - 允许应用写入整个逻辑地址空间
             * - 简化应用逻辑(不需要检查每个地址)
             */
            uiWrite = uiBytesToCopy;
        }

        uiLogicalOffset += uiWrite;
        uiBytesCopied += uiWrite;
    }

    *puiNumBytes = uiBytesCopied;
    return eError;
}

读写操作流程图

┌─────────────────────────────────────────────────────────────┐
│                   PMR读写操作完整流程                        │
└─────────────────────────────────────────────────────────────┘
读取操作 (PMR_ReadBytes):
═════════════════════════
┌─────────────────────────┐
│  开始:PMR_ReadBytes    │
│  输入:                 │
│  - 逻辑偏移             │
│  - 缓冲区               │
│  - 大小                 │
└─────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│  第1步:安全检查                                             │
│  ┌───────────────────────────────────────────────────────┐ │
│  │  检查整数溢出:offset + size < offset?                 │ │
│  │  边界检查:offset + size > PMR.size?                   │ │
│  │  调整大小:size = min(size, PMR.size - offset)         │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│  第2步:循环读取(处理跨块)                                 │
│  while (已读取 < 总大小)                                     │
└─────────────────────────────────────────────────────────────┘
↓
┌───────────────┐
│  2.1: 地址转换 │
└───────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│  _PMRLogicalOffsetToPhysicalOffset()                        │
│  ┌───────────────────────────────────────────────────────┐ │
│  │  输入:逻辑偏移                                        │ │
│  │  输出:                                                │ │
│  │  - 物理偏移                                            │ │
│  │  - 块剩余字节                                          │ │
│  │  - 有效性标志                                          │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌───────────────┐
│ 2.2: 计算本次  │
│     读取大小   │
└───────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│  本次读取 = MIN(剩余要读, 块剩余)                           │
└─────────────────────────────────────────────────────────────┘
↓
├──────────────┬──────────────┐
↓              ↓              ↓
┌──────────┐  ┌──────────┐  ┌──────────┐
│ 块有效?  │  │   是     │  │   否     │
└──────────┘  └──────────┘  └──────────┘
│              │
↓              ↓
┌─────────────────┐  ┌─────────────────┐
│ 读取物理数据     │  │ 填充零          │
└─────────────────┘  └─────────────────┘
│              │
↓              ↓
┌──────────────────────────────────┐
│  _PMR_ReadBytesPhysical()        │
│  ┌────────────────────────────┐  │
│  │ 方式1: pfnReadBytes?       │  │
│  │   ├─ 是 → 调用Factory读取  │  │
│  │   └─ 否 → 使用方式2        │  │
│  │                            │  │
│  │ 方式2: 内核映射            │  │
│  │   ├─ 获取内核虚拟地址      │  │
│  │   ├─ memcpy复制数据        │  │
│  │   └─ 释放映射              │  │
│  └────────────────────────────┘  │
└──────────────────────────────────┘
│              │
└──────┬───────┘
↓
┌─────────────────┐
│  更新偏移和计数  │
│  - 逻辑偏移 +=   │
│  - 已读字节 +=   │
└─────────────────┘
↓
┌─────────────────┐
│  检查是否完成    │
└─────────────────┘
│        │
│ 否     │ 是
↓        ↓
(回到循环)  (完成)
↓
┌─────────────────┐
│  返回实际读取数  │
└─────────────────┘
写入操作 (PMR_WriteBytes):
══════════════════════════
┌─────────────────────────┐
│  开始:PMR_WriteBytes   │
│  (流程类似读取)         │
└─────────────────────────┘
↓
(参数检查,同读取)
↓
(循环写入,逐块处理)
↓
┌──────────────┐
│  块有效判断   │
└──────────────┘
│      │
有效    无效
│      │
↓      ↓
┌────────┐ ┌────────────┐
│写入数据│ │忽略写入    │
│        │ │(假装成功)  │
└────────┘ └────────────┘
│      │
└──┬───┘
↓
┌──────────────┐
│  更新偏移     │
└──────────────┘
↓
┌──────────────┐
│  检查完成     │
└──────────────┘
↓
┌──────────────┐
│  返回实际写入 │
└──────────────┘

读写操作对比表

方面 读取 (Read) 写入 (Write)
无效块处理 填充零 忽略写入
返回值 实际读取字节数 实际写入字节数
错误处理 立即停止 立即停止
锁定要求 内部自动锁定 内部自动锁定
Factory回调 pfnReadBytes(可选) pfnWriteBytes(可选)
默认实现 内核映射+memcpy 内核映射+memcpy
缓存一致性 使用DeviceMemCopy 使用DeviceMemCopy
典型用途 调试、数据导出 初始化、数据注入
性能考虑 可能触发页面锁定 可能触发页面锁定

稀疏内存管理

动态稀疏内存变更 PMR_ChangeSparseMemUnlocked

/**
 * @brief 动态修改稀疏PMR的内存布局
 * @param psPMR PMR指针
 * @param ui32AllocPageCount 要分配的页数
 * @param pai32AllocIndices 分配页索引数组
 * @param ui32FreePageCount 要释放的页数
 * @param pai32FreeIndices 释放页索引数组
 * @param uiSparseFlags 稀疏操作标志
 * @return PVRSRV_OK 成功
 * 
 * 注意:调用前必须持有PMR锁
 */
PVRSRV_ERROR 
PMR_ChangeSparseMemUnlocked(PMR *psPMR,
                            IMG_UINT32 ui32AllocPageCount,
                            IMG_UINT32 *pai32AllocIndices,
                            IMG_UINT32 ui32FreePageCount,
                            IMG_UINT32 *pai32FreeIndices,
                            IMG_UINT32 uiSparseFlags)
{
    PVRSRV_ERROR eError;
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
    PMR_IMPL_ZOMBIEPAGES pvZombiePages = NULL;
    PMR_ZOMBIE_PAGES* psPMRZombiePages = NULL;
#endif

    /* ========== 前置检查1:PMR是否允许布局变更 ========== */
    /*
     * 禁止变更的情况:
     * 1. PMR_IsMemLayoutFixed() - 已导出的PMR
     * 2. PMR_IsClientCpuMapped() - 有CPU映射存在
     * 
     * 原因:
     * - 导出后的PMR被其他进程使用,改变布局会导致不一致
     * - CPU映射期间改变布局会导致访问错误
     */
    if (PMR_IsMemLayoutFixed(psPMR) || PMR_IsClientCpuMapped(psPMR))
    {
        PVR_DPF((PVR_DBG_ERROR,
                "%s: This PMR layout cannot be changed - "
                "PMR_IsMemLayoutFixed()=%c, _PMR_IsClientCpuMapped()=%c",
                __func__,
                PMR_IsMemLayoutFixed(psPMR) ? 'Y' : 'n',
                PMR_IsClientCpuMapped(psPMR) ? 'Y' : 'n'));
        return PVRSRV_ERROR_PMR_NOT_PERMITTED;
    }

    /* ========== 前置检查2:PMR是否支持稀疏变更 ========== */
    /*
     * 不是所有PMR类型都支持动态布局变更
     * 
     * 必须实现 pfnChangeSparseMem 回调
     * 
     * 不支持的PMR类型:
     * - 物理内存PMR(预分配)
     * - 某些导入的PMR(如DMA-BUF)
     * - 固定大小的硬件缓冲区
     */
    if (NULL == psPMR->psFuncTab->pfnChangeSparseMem)
    {
        PVR_DPF((PVR_DBG_ERROR,
                "%s: This type of sparse PMR cannot be changed.",
                __func__));
        return PVRSRV_ERROR_NOT_IMPLEMENTED;
    }

#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
    /* ========== 预分配僵尸页面结构 ========== */
    /*
     * 如果要释放页面,预先分配僵尸包装结构
     * 
     * 为什么提前分配?
     * - 简化错误处理
     * - 避免在关键路径中分配失败
     * - 如果不需要(没有僵尸页面),稍后会释放
     */
    if (uiSparseFlags & SPARSE_RESIZE_FREE)
    {
        psPMRZombiePages = OSAllocZMem(sizeof(PMR_ZOMBIE_PAGES));
        PVR_GOTO_IF_NOMEM(psPMRZombiePages, eError, returnErr);
    }
#endif

    /* ========== 核心操作:调用Factory函数修改布局 ========== */
    /*
     * pfnChangeSparseMem 的职责:
     * 1. 分配新页面(根据pai32AllocIndices)
     * 2. 释放旧页面(根据pai32FreeIndices)
     * 3. 更新映射表
     * 4. 返回僵尸页面信息(如果有)
     * 
     * 参数说明:
     * - pvFlavourData: Factory私有数据
     * - psPMR: PMR对象(用于访问映射表等)
     * - ui32AllocPageCount: 要分配的页数
     * - pai32AllocIndices: 逻辑页索引数组(在哪些位置分配)
     * - ui32FreePageCount: 要释放的页数
     * - pai32FreeIndices: 逻辑页索引数组(释放哪些位置)
     * - pvZombiePages: 输出参数,返回需要延迟释放的页面
     * - uiSparseFlags: 操作标志
     */
    eError = psPMR->psFuncTab->pfnChangeSparseMem(
                    psPMR->pvFlavourData,
                    psPMR,
                    ui32AllocPageCount,
                    pai32AllocIndices,
                    ui32FreePageCount,
                    pai32FreeIndices,
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
                    &pvZombiePages,
#endif
                    uiSparseFlags);

    /* ========== 错误处理:内存不足 ========== */
    if (eError != PVRSRV_OK)
    {
#if defined(PVRSRV_ENABLE_PROCESS_STATS)
        /*
         * 如果是内存不足错误,更新OOM统计
         * 
         * 用途:
         * - 系统监控
         * - 调试内存问题
         * - 触发内存回收策略
         */
        if (eError == PVRSRV_ERROR_PMR_FAILED_TO_ALLOC_PAGES)
        {
            PVRSRVStatsUpdateOOMStat(NULL,
                                     PMR_DeviceNode(psPMR),
                                     PVRSRV_DEVICE_STAT_TYPE_OOM_PHYSMEM_COUNT,
                                     OSGetCurrentClientProcessIDKM());
        }
#endif
        PVR_LOG_GOTO_ERROR(eError, "pfnChangeSparseMem", freePmrZombiePages);
    }

#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
    /*
     * 断言检查:
     * - 如果设置了SPARSE_RESIZE_FREE标志,可能有僵尸页面
     * - 如果没有设置,不应该有僵尸页面
     */
    PVR_ASSERT((uiSparseFlags & SPARSE_RESIZE_FREE) ? IMG_TRUE : pvZombiePages == NULL);

    /*
     * 处理僵尸页面:
     * 1. 如果有僵尸页面,加入僵尸列表或立即释放
     * 2. 如果没有僵尸页面,释放预分配的包装结构
     */
    eError = _ProcessZombiePages(pvZombiePages, psPMRZombiePages, psPMR);
    PVR_LOG_GOTO_IF_ERROR(eError, "_ProcessZombiePages", returnErr);
#endif

#if defined(PDUMP)
    /* ========== PDump支持:记录稀疏变更操作 ========== */
    {
        IMG_BOOL bInitialise = IMG_FALSE;
        IMG_UINT8 ui8InitValue = 0;

        /*
         * 检查是否需要初始化新分配的页面
         * 
         * ZERO_ON_ALLOC: 分配时清零
         * POISON_ON_ALLOC: 填充特定值(用于检测未初始化访问)
         */
        if (PVRSRV_CHECK_ZERO_ON_ALLOC(PMR_Flags(psPMR)))
        {
            bInitialise = IMG_TRUE;
        }
        else if (PVRSRV_CHECK_POISON_ON_ALLOC(PMR_Flags(psPMR)))
        {
            ui8InitValue = (IMG_UINT8)PVRSRV_POISON_ON_ALLOC_VALUE;
            bInitialise = IMG_TRUE;
        }

        /*
         * 生成PDump命令
         * 
         * PDump用途:
         * - 记录驱动操作
         * - 回放测试场景
         * - 调试问题
         */
        PDumpPMRChangeSparsePMR(psPMR,
                                IMG_PAGE2BYTES32(psPMR->uiLog2ContiguityGuarantee),
                                ui32AllocPageCount,
                                pai32AllocIndices,
                                ui32FreePageCount,
                                pai32FreeIndices,
                                bInitialise,
                                ui8InitValue,
                                &psPMR->hPDumpAllocHandle);
    }
#endif

    return PVRSRV_OK;

    /* ========== 错误处理路径 ========== */
freePmrZombiePages:
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
    if (uiSparseFlags & SPARSE_RESIZE_FREE)
    {
        OSFreeMem(psPMRZombiePages);
    }
returnErr:
#endif
    return eError;
}

稀疏内存变更示意图

稀疏内存动态变更操作
═══════════════════

场景:16页的稀疏PMR,初始分配了4页

初始状态:
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0  │ 1  │ 2  │ 3  │ 4  │ 5  │ 6  │ 7  │ 8  │ 9  │ 10 │ 11 │ 12 │ 13 │ 14 │ 15 │
├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤
│ ✓  │    │ ✓  │   │    │ ✓  │    │    │    │    │ ✓  │    │    │    │    │    │
│Phys│    │Phys│    │    │Phys│    │    │    │    │Phys│    │    │    │    │    │
│ 0  │    │ 1  │    │    │ 2  │    │    │    │    │ 3  │    │    │    │    │    │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

映射表 aui32Translation:
[0, 0xFF, 1, 0xFF, 0xFF, 2, 0xFF, 0xFF, 0xFF, 0xFF, 3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]

操作请求:
- 分配页面:[4, 7, 13]  (分配3页)
- 释放页面:[2, 10]     (释放2页)

执行 PMR_ChangeSparseMemUnlocked():
════════════════════════════════════

第1步:调用 pfnChangeSparseMem
    ↓
    Factory执行:
    1. 分配物理页面:
       - 为逻辑页4分配物理页4
       - 为逻辑页7分配物理页5
       - 为逻辑页13分配物理页6

    2. 释放物理页面:
       - 释放逻辑页2的物理页1
       - 释放逻辑页10的物理页3

    3. 更新映射表

    4. 返回僵尸页面信息(物理页1和3)

第2步:处理僵尸页面
    ↓
    检查设备状态:
    - 如果GPU OFF → 立即释放
    - 如果GPU ON  → 加入僵尸列表

第3步:PDump记录(如果启用)

结果状态:
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0  │ 1  │ 2  │ 3  │ 4  │ 5  │ 6  │ 7  │ 8  │ 9  │ 10 │ 11 │ 12 │ 13 │ 14 │ 15 │
├────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤
│ ✓  │    │    │    │ ✓  │ ✓ │    │ ✓  │    │    │    │    │    │ ✓  │    │    │
│Phys│    │(已 │    │Phys│Phys│    │Phys│    │    │(已 │    │    │Phys│    │    │
│ 0  │    │释放│    │ 4  │ 2  │    │ 5  │    │    │释放│    │    │ 6  │    │    │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

新映射表 aui32Translation:
[0, 0xFF, 0xFF, 0xFF, 4, 2, 0xFF, 5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 6, 0xFF, 0xFF]

物理内存变化:
- 原有:4页 (页0,1,2,3)
- 新增:3页 (页4,5,6)
- 释放:2页 (页1,3)
- 结果:5页 (页0,2,4,5,6)

内存使用:
- 逻辑大小:16页 (不变)
- 物理大小:5页 (从4页增加到5页)
- 利用率:31.25% (5/16)

稀疏内存操作约束表

约束条件 检查时机 失败原因 错误码
布局固定 函数开始 PMR已导出或锁定 PMR_NOT_PERMITTED
CPU映射存在 函数开始 有活跃的CPU映射 PMR_NOT_PERMITTED
不支持稀疏 函数开始 PMR类型不支持 NOT_IMPLEMENTED
内存不足 Factory调用 物理内存耗尽 FAILED_TO_ALLOC_PAGES
索引越界 Factory内部 页索引超出范围 INVALID_PARAMS
页面冲突 Factory内部 分配已存在的页 INVALID_PARAMS

延迟释放机制

僵尸清理函数 _PmrZombieCleanup

/**
 * @brief 僵尸PMR清理函数(在清理线程中执行)
 * @param pvData 清理项数据(PMR_ZOMBIE_CLEANUP_ITEM*)
 * @return PVRSRV_OK 成功,PVRSRV_ERROR_RETRY 需要重试
 * 
 * 作用:
 * - 等待GPU缓存刷新完成
 * - 释放僵尸PMR和僵尸页面
 * - 处理跨设备导入
 */
static PVRSRV_ERROR 
_PmrZombieCleanup(void *pvData)
{
    PMR_ZOMBIE_CLEANUP_ITEM *psCleanupItem = pvData;
    DLLIST_NODE *psNode;
    DLLIST_NODE sRetryHead;
    IMG_UINT32 uiRetryCount = 0;
    PVRSRV_ERROR eError = PVRSRV_OK;

    /* ========== 步骤1:检查是否可以释放僵尸 ========== */
    /*
     * _CanNotFreeZombies 检查两个条件:
     * 1. GPU MMU缓存同步计数器
     * 2. GPU电源状态计数器
     * 
     * 只有当以下任一条件满足时才能释放:
     * - MMU缓存已刷新(同步计数器已更新)
     * - GPU已关闭电源(缓存已失效)
     */
    if (_CanNotFreeZombies(psCleanupItem))
    {
        /*
         * 条件未满足,返回RETRY
         * 清理线程会稍后重试
         */
        return PVRSRV_ERROR_RETRY;
    }

    /* ========== 步骤2:初始化重试列表 ========== */
    /*
     * 某些僵尸可能无法立即释放
     * 将它们加入重试列表
     */
    dllist_init(&sRetryHead);

    /* ========== 步骤3:遍历僵尸列表 ========== */
    do
    {
        /* 从僵尸列表获取下一个节点 */
        psNode = dllist_get_next_node(&psCleanupItem->sZombieList);
        if (psNode == NULL)
        {
            continue;  /* 列表已空 */
        }

        /* 从列表移除(但还未释放) */
        dllist_remove_node(psNode);

        /* 更新统计 */
        _ZombieListLock(psCleanupItem->psDevNode);
        psCleanupItem->psDevNode->uiPMRZombieCountInCleanup--;
        _ZombieListUnlock(psCleanupItem->psDevNode);

        /* ========== 步骤4:根据僵尸类型处理 ========== */
        switch (PMR_GetZombieTypeFromNode(psNode))
        {
#if defined(SUPPORT_PMR_PAGES_DEFERRED_FREE)
            /* ────────────── 僵尸页面 ────────────── */
            case PMR_ZOMBIE_TYPE_PAGES:
            {
                PMR_ZOMBIE_PAGES* psZombiePages = PMR_GetZombiePagesFromNode(psNode);

                /*
                 * 调用Factory的僵尸页面释放函数
                 * 
                 * pfnFreeZombiePages的作用:
                 * 1. 释放物理页面
                 * 2. 更新内存统计
                 * 3. 返回页面到OS
                 */
                eError = psZombiePages->pfnFactoryFreeZombies(psZombiePages->pvFactoryPages);

                if (eError != PVRSRV_OK)
                {
                    /*
                     * 释放失败(可能是暂时性错误)
                     * 加入重试列表
                     */
                    PVR_DPF((PVR_DBG_ERROR, 
                            "Cannot free zombie pages! Skipping object %p", 
                            psZombiePages));
                    dllist_add_to_tail(&sRetryHead, psNode);
                    uiRetryCount++;
                }
                else
                {
                    /* 成功释放,销毁包装结构 */
                    OSFreeMem(psZombiePages);
                }
                break;
            }
#endif

#if defined(SUPPORT_PMR_DEVICE_IMPORT_DEFERRED_FREE)
            /* ────────────── 设备导入 ────────────── */
            case PMR_ZOMBIE_TYPE_DEVICE_IMPORT:
            {
                PMR_DEVICE_IMPORT *psImport = PMR_GetDeviceImportFromNode(psNode);

                /*
                 * 释放跨设备导入记录
                 * 
                 * 作用:
                 * 1. 清除设备导入位图中的对应位
                 * 2. 从PMR的导入列表移除
                 * 3. 释放导入结构体
                 */
                _DeviceImportFreeImportZombie(psImport);
                break;
            }
#endif

            /* ────────────── PMR本体 ────────────── */
            case PMR_ZOMBIE_TYPE_PMR:
            {
                PMR* psPMR = PMR_GetPMRFromNode(psNode);
                const PMR_IMPL_FUNCTAB *psFuncTable = psPMR->psFuncTab;

                /* 获取Factory锁(防止并发创建/销毁) */
                _FactoryLock(psFuncTable);
#if defined(SUPPORT_PMR_DEVICE_IMPORT_DEFERRED_FREE)
                /*
                * 检查是否还有其他设备在等待缓存刷新
                *
                * uiDevImportBitmap != 0 表示:
                * - 还有其他设备导入了此PMR
                * - 那些设备可能还未刷新缓存
                * - 不能释放PMR
                /
                if (_DeviceImportBitmapGet(psPMR) != 0)
                {
                    /
                    * 还有依赖未解决
                    * 加入重试列表
                    /
                    dllist_add_to_tail(&sRetryHead, psNode);
                    uiRetryCount++;
                    _FactoryUnlock(psFuncTable);
                    continue;  / 继续处理下一个 */
                }
#endif
                /*
                 * 所有条件满足,最终销毁PMR
                 * 
                 * _PMRDestroy的作用:
                 * 1. 调用Factory的Finalize回调
                 * 2. 释放物理内存
                 * 3. 销毁映射表
                 * 4. 销毁锁
                 * 5. 更新统计
                 * 6. 释放PMR结构体
                 */
                _PMRDestroy(psPMR);
                _FactoryUnlock(psFuncTable);
                break;
            }
        }
    } while (psNode != NULL);

    /* ========== 步骤5:处理重试列表 ========== */
    if (uiRetryCount)
    {
        /*
         * 有项目需要重试
         * 
         * 处理:
         * 1. 设置返回值为RETRY
         * 2. 将重试项目移回清理项的列表
         * 3. 更新统计
         * 4. 清理线程会重新调度这个清理项
         */
        eError = PVRSRV_ERROR_RETRY;

        /* 
         * 将重试列表插入到清理项列表头部
         * 这样最老的项目在前面,符合FIFO原则
         */
        dllist_insert_list_at_head(&psCleanupItem->sZombieList, &sRetryHead);

        /* 更新统计 */
        _ZombieListLock(psCleanupItem->psDevNode);
        psCleanupItem->psDevNode->uiPMRZombieCountInCleanup += uiRetryCount;
        _ZombieListUnlock(psCleanupItem->psDevNode);
    }
    else
    {
        /*
         * 所有僵尸都已成功释放
         * 释放清理项本身
         */
        OSFreeMem(psCleanupItem);
    }

return eError;
}

入队僵尸 PMRQueueZombiesForCleanup

/**
 * @brief 将设备的僵尸列表入队到清理线程
 * @param psDevNode 设备节点
 * @return IMG_TRUE 成功入队,IMG_FALSE 失败或无需入队
 * 
 * 调用时机:
 * - GPU MMU缓存刷新后
 * - GPU电源状态变化
 * - 定期清理
 */
IMG_BOOL 
PMRQueueZombiesForCleanup(PPVRSRV_DEVICE_NODE psDevNode)
{
    PVRSRV_DATA *psPVRSRVData = PVRSRVGetPVRSRVData();
    PMR_ZOMBIE_CLEANUP_ITEM *psCleanupItem;

    /* ========== 检查1:驱动是否在卸载 ========== */
    /*
     * 如果驱动正在卸载,不再延迟释放
     * 直接在卸载流程中同步释放所有资源
     */
    if (psPVRSRVData->bUnload || psDevNode->psMMUCacheSyncPrim == NULL)
    {
        return IMG_FALSE;
    }

    /* ========== 检查2:僵尸列表是否为空 ========== */
    _ZombieListLock(psDevNode);

    if (dllist_is_empty(&psDevNode->sPMRZombieList))
    {
        _ZombieListUnlock(psDevNode);
        return IMG_FALSE;  /* 没有僵尸需要处理 */
    }

    /* ========== 步骤1:分配清理项 ========== */
    psCleanupItem = OSAllocMem(sizeof(*psCleanupItem));
    if (psCleanupItem == NULL)
    {
        _ZombieListUnlock(psDevNode);
        return IMG_FALSE;
    }

    /* ========== 步骤2:初始化清理项 ========== */
    /*
     * 清理项结构:
     * - sCleanupThreadFn: 清理线程回调
     * - sZombieList: 僵尸列表(移动自设备节点)
     * - psDevNode: 设备节点引用
     * - psSync: MMU缓存同步原语
     * - uiRequiredSyncValue: 期望的同步值
     * - uiRequiredPowerOffCounter: 期望的电源关闭计数
     */
    psCleanupItem->sCleanupThreadFn.pfnFree = _PmrZombieCleanup;
    psCleanupItem->sCleanupThreadFn.pvData = psCleanupItem;
    psCleanupItem->sCleanupThreadFn.bDependsOnHW = IMG_TRUE;
    psCleanupItem->sCleanupThreadFn.eCleanupType = PVRSRV_CLEANUP_TYPE_PMR;
    CLEANUP_THREAD_SET_RETRY_TIMEOUT(&psCleanupItem->sCleanupThreadFn,
                                     CLEANUP_THREAD_RETRY_TIMEOUT_MS_DEFAULT);

    psCleanupItem->psDevNode = psDevNode;
    psCleanupItem->psSync = psDevNode->psMMUCacheSyncPrim;
    psCleanupItem->uiRequiredSyncValue = psDevNode->ui32NextMMUInvalidateUpdate;
    psCleanupItem->uiRequiredPowerOffCounter = psDevNode->uiPowerOffCounterNext;

    /* ========== 步骤3:移动僵尸列表 ========== */
    /*
     * dllist_replace_head:
     * - 将设备的僵尸列表移动到清理项
     * - 设备的僵尸列表变为空
     * - 原子操作,保证线程安全
     */
    dllist_replace_head(&psDevNode->sPMRZombieList, &psCleanupItem->sZombieList);

    /* 更新统计 */
    psCleanupItem->psDevNode->uiPMRZombieCountInCleanup += psDevNode->uiPMRZombieCount;
    psDevNode->uiPMRZombieCount = 0;

    OSLockRelease(psDevNode->hPMRZombieListLock);

    /* ========== 步骤4:提交到清理线程 ========== */
    /*
     * PVRSRVCleanupThreadAddWork:
     * - 将清理项添加到清理线程队列
     * - 清理线程会异步执行
     * - 如果返回RETRY,会自动重新入队
     */
    PVRSRVCleanupThreadAddWork(psDevNode, &psCleanupItem->sCleanupThreadFn);

    return IMG_TRUE;
}

延迟释放完整流程图

┌─────────────────────────────────────────────────────────────┐
│                延迟释放完整生命周期                          │
└─────────────────────────────────────────────────────────────┘

第1阶段:标记为延迟释放
═════════════════════════

┌─────────────────────┐
│ PMR引用计数归零     │
│ _UnrefAndMaybeDestroy│
└─────────────────────┘
            ↓
┌─────────────────────────────────────────────────────────────┐
│  检查释放条件                                                │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ if (未设置DEFER_FREE)                                  │ │
│  │     → 立即销毁                                         │ │
│  │                                                       │ │
│  │ else if (GPU OFF && 无跨设备导入)                      │ │
│  │     → 立即销毁                                         │ │
│  │                                                       │ │
│  │ else if (标记为空僵尸)                                 │ │
│  │     → 立即销毁                                         │ │
│  │                                                       │ │
│  │ else                                                  │ │
│  │     → 进入僵尸状态(延迟释放)                          │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
            ↓ (进入僵尸状态)
┌─────────────────────────────────────────────────────────────┐
│  标记为僵尸                                                  │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ 1. 设置 IS_ZOMBIE_PMR 标志                             │ │
│  │ 2. 加入设备的 sPMRZombieList                           │ │
│  │ 3. 增加 uiPMRZombieCount                              │ │
│  │ 4. 调用 pfnZombify() 更新内存统计                      │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

第2阶段:等待同步条件
═════════════════════

┌─────────────────────┐
│ 僵尸在设备列表中等待 │
│ sPMRZombieList      │
└─────────────────────┘
            ↓
    等待以下任一事件:

    ┌─────────────────┐
    │ GPU MMU缓存刷新  │
    │ (同步计数器更新) │
    └─────────────────┘
            或
    ┌─────────────────┐
    │ GPU电源关闭      │
    │ (电源计数器更新) │
    └─────────────────┘
            ↓
┌─────────────────────────────────────────────────────────────┐
│  触发入队                                                    │
│  PMRQueueZombiesForCleanup()                                │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ 检查条件:                                             │ │
│  │ 1. 驱动未在卸载                                        │ │
│  │ 2. 僵尸列表非空                                        │ │
│  │ 3. 同步原语有效                                        │ │
│  │                                                       │ │
│  │ 操作:                                                 │ │
│  │ 1. 分配清理项                                          │ │
│  │ 2. 记录同步条件                                        │ │
│  │ 3. 移动僵尸列表到清理项                                 │ │
│  │ 4. 提交到清理线程                                      │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

第3阶段:清理线程处理
═════════════════════

┌─────────────────────────────────────────────────────────────┐
│  清理线程执行                                                │
│  _PmrZombieCleanup()                                        │
└─────────────────────────────────────────────────────────────┘
            ↓
┌─────────────────────────────────────────────────────────────┐
│  检查同步条件                                                │
│  _CanNotFreeZombies()                                       │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ 检查1:MMU同步计数器                                   │ │
│  │   if (当前值 >= 期望值)                                │ │
│  │       → 缓存已刷新,可以释放                            │ │
│  │                                                       │ │
│  │ 检查2:电源关闭计数器                                   │ │
│  │   if (当前值 >= 期望值)                                │ │
│  │       → GPU已关闭,可以释放                             │ │
│  │                                                       │ │
│  │ 结果:                                                 │ │
│  │   任一条件满足 → 继续清理                               │ │
│  │   都不满足 → 返回RETRY,稍后重试                        │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
            ↓ (条件满足)
┌─────────────────────────────────────────────────────────────┐
│  遍历僵尸列表                                                │
│  for each zombie in list                                    │
└─────────────────────────────────────────────────────────────┘
            ↓
    ┌───────────────┬───────────────┬───────────────┐
    │               │               │               │
    ↓               ↓               ↓               ↓
┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐
│僵尸页面  │  │设备导入 │  │ PMR本体  │  │ (其他)  │
└─────────┘  └─────────┘  └─────────┘  └─────────┘
    │               │               │
    ↓               ↓               ↓
┌────────────────────────────────────────────────────┐
│ pfnFreeZombiePages() │ FreeImport() │ _PMRDestroy()│
└────────────────────────────────────────────────────┘
    │               │               │
    ├───────────────┴───────────────┤
    ↓                               ↓
成功释放                        释放失败
    ↓                               ↓
从列表移除                      加入重试列表
减少计数                        保留在清理项中
释放内存                        等待下次重试
    │                               │
    └───────────────┬───────────────┘
                    ↓
┌─────────────────────────────────────────────────────────────┐
│  清理完成                                                    │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ if (所有僵尸已释放)                                    │ │
│  │     释放清理项                                         │ │
│  │     返回 PVRSRV_OK                                    │ │
│  │                                                       │ │
│  │ else (有失败项)                                        │ │
│  │     将失败项移回清理项列表                              │ │
│  │     返回 PVRSRV_ERROR_RETRY                           │ │
│  │     清理线程会重新调度                                  │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

延迟释放时序图

时间轴:从PMR标记为僵尸到最终释放

T0: PMR引用归零
│   └─ _UnrefAndMaybeDestroy()
│      └─ 设置IS_ZOMBIE_PMR
│         └─ 加入sPMRZombieList
│
├─────── (等待中) ───────┐
│                        │
T1: GPU执行渲染命令      │
│   - 访问PMR的物理内存  │
│   - 使用缓存的地址     │
│                        │
├─────── (等待中) ───────┤
│                        │
T2: GPU完成渲染          │
│   - FW发出中断         │
│   - 更新同步计数器     │
│                        │
├─────── (等待中) ───────┤
│                        │
T3: MMU缓存刷新          │
│   - FW更新同步原语     │
│   - ui32NextMMUInvalidate++ │
│                        │
├─────── (触发点) ───────┤
│                        │
T4: 入队清理             │
│   └─ PMRQueueZombiesForCleanup()
│      └─ 创建清理项
│         └─ 提交到清理线程
│
├─────── (排队中) ───────┤
│                        │
T5: 清理线程执行         │
│   └─ _PmrZombieCleanup()
│      ├─ 检查同步条件 ✓
│      ├─ 遍历僵尸列表
│      └─ 释放资源
│
├─────── (完成) ─────────┤
│                        │
T6: 资源完全释放         │
    └─ 物理内存返回OS
       └─ PMR结构体释放

总延迟时间:T6 - T0 = 数十到数百毫秒

完整内存处理流程汇总

PMR生命周期完整流程

┌═══════════════════════════════════════════════════════════════┐
║                  PMR完整生命周期流程图                         ║
╚═══════════════════════════════════════════════════════════════╝

阶段1:创建 (Creation)
══════════════════════

应用层请求
    ↓
DevmemIntAllocatePMR()
    ↓
┌─────────────────────────────────────────┐
│ PMRCreatePMR()                          │
│ ├─ _PMRCreate()                         │
│ │  ├─ 分配PMR结构                       │
│ │  ├─ 初始化映射表                      │
│ │  ├─ 创建锁                            │
│ │  ├─ 生成序列号和密钥                  │
│ │  └─ 设置初始状态                      │
│ │                                       │
│ ├─ 调用Factory创建函数                  │
│ │  └─ 分配物理内存或准备on-demand      │
│ │                                       │
│ ├─ PDump记录(如果启用)               │
│ │                                       │
│ └─ 返回PMR句柄                          │
└─────────────────────────────────────────┘
    ↓
PMR状态:
- uiRefCount = 0
- iLockCount = 0 (on-demand) 或 1 (非on-demand)
- 未映射到GPU/CPU


阶段2:锁定 (Locking)
══════════════════════

DevmemIntMap() 或其他操作
    ↓
┌─────────────────────────────────────────┐
│ PMRLockPhysAddresses()                  │
│ ├─ 获取PMR主锁                          │
│ ├─ _Ref() 增加引用计数                  │
│ ├─ OSAtomicAdd(&iLockCount)             │
│ │                                       │
│ └─ if (首次锁定)                        │
│    └─ pfnLockPhysAddresses()            │
│       ├─ 分配物理页面(on-demand)       │
│       ├─ Pin页面                        │
│       └─ 更新页表                        │
└─────────────────────────────────────────┘
    ↓
PMR状态:
- uiRefCount++
- iLockCount++
- 物理页面已分配并锁定


阶段3:使用 (Usage)
════════════════════

┌─────────────────────┬─────────────────────┬─────────────────────┐
│   GPU映射           │   CPU映射           │   读写操作          │
└─────────────────────┴─────────────────────┴─────────────────────┘
         │                     │                     │
         ↓                     ↓                     ↓
┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
│ DevmemIntMapPMR  │  │ DevmemAcquireCpu │  │ PMR_ReadBytes    │
│ ├─ 获取物理地址   │  │ ├─ PMRAcquireKern│  │ ├─ 地址转换      │
│ ├─ 填充页表       │  │ ├─ 增加映射计数   │  │ ├─ 锁定物理地址  │
│ ├─ 刷新TLB       │  │ └─ 返回虚拟地址    │  │ ├─ 复制数据      │
│ └─ 增加引用       │  └──────────────────┘  │ └─ 解锁物理地址  │
└──────────────────┘                         └──────────────────┘
         │                     │                     │
         └──────────┬──────────┴─────────────────────┘
                    ↓
            PMR状态更新:
            - iAssociatedResCount++ (GPU映射)
            - iClientCpuMapCount++ (CPU映射)
            - 设置DEFER_FREE标志(GPU映射时)


阶段4:解锁 (Unlocking)
════════════════════════

使用完毕后
    ↓
┌─────────────────────────────────────────┐
│ PMRUnlockPhysAddresses()                │
│ ├─ 获取PMR主锁                          │
│ ├─ OSAtomicSubtract(&iLockCount)        │
│ │                                       │
│ └─ if (锁计数归零)                      │
│    └─ pfnUnlockPhysAddresses()          │
│       ├─ Unpin页面                      │
│       ├─ 可能释放页面(on-demand)       │
│       └─ 返回僵尸页面信息(如有)        │
│                                         │
│ ├─ 处理僵尸页面                         │
│ └─ _UnrefAndMaybeDestroy()              │
└─────────────────────────────────────────┘
    ↓
PMR状态:
- uiRefCount--
- iLockCount--
- 如果uiRefCount=0 → 进入销毁流程


阶段5:销毁 (Destruction)
══════════════════════════

引用计数归零
    ↓
┌─────────────────────────────────────────┐
│ _UnrefAndMaybeDestroy()                 │
│ ├─ 检查引用计数 == 0?                   │
│ │                                       │
│ └─ 决策:立即销毁 vs 延迟销毁?           │
└─────────────────────────────────────────┘
         │                    │
    立即销毁              延迟销毁
         │                    │
         ↓                    ↓
┌─────────────────┐  ┌─────────────────────────┐
│ _PMRDestroy()   │  │ 标记为僵尸PMR            │
│ ├─ pfnFinalize()│  │ ├─ 设置IS_ZOMBIE标志     │
│ ├─ 释放物理内存 │  │ ├─ 加入僵尸列表          │
│ ├─ 销毁映射表   │  │ ├─ 调用pfnZombify()     │
│ ├─ 销毁锁       │  │ └─ 等待清理线程          │
│ ├─ 释放PMR结构  │  └─────────────────────────┘
│ └─ 减少LivePMR  │            │
└─────────────────┘            ↓
         │            ┌─────────────────────────┐
         │            │ 等待同步条件             │
         │            │ - GPU缓存刷新            │
         │            │ - GPU电源关闭            │
         │            └─────────────────────────┘
         │                      │
         │                      ↓
         │            ┌─────────────────────────┐
         │            │ PMRQueueZombiesForCleanup│
         │            │ - 创建清理项             │
         │            │ - 提交到清理线程         │
         │            └─────────────────────────┘
         │                      │
         │                      ↓
         │            ┌─────────────────────────┐
         │            │ _PmrZombieCleanup()     │
         │            │ - 检查同步条件           │
         │            │ - 释放僵尸对象           │
         │            │ - 调用_PMRDestroy()     │
         │            └─────────────────────────┘
         │                      │
         └──────────┬───────────┘
                    ↓
            ┌─────────────────┐
            │ 资源完全释放     │
            │ - 物理内存返回OS │
            │ - PMR结构体释放  │
            │ - 统计更新       │
            └─────────────────┘

关键路径性能分析

操作 时间复杂度 关键因素 优化策略
PMR创建 O(n) n=逻辑块数 预分配常用大小
地址转换(连续) O(1) 快速路径 内联函数
地址转换(稀疏) O(m) m=页数 缓存转换结果
物理地址锁定 O(1) 首次O(n)分配 On-demand延迟
读取数据 O(k) k=块数 批量操作
稀疏变更 O(p+q) p=分配,q=释放 合并操作
延迟释放 O(1)入队 异步清理 批处理

内存状态转换表

起始状态 操作 结束状态 副作用
未创建 PMRCreatePMR 已创建,未锁定 分配结构
已创建,未锁定 PMRLockPhysAddresses 已锁定 分配物理内存(on-demand)
已锁定 DevmemIntMapPMR 已映射(GPU) 更新页表,设置DEFER_FREE
已锁定 DevmemAcquireCpu 已映射(CPU) 增加映射计数
已映射 PMRUnlockPhysAddresses 已创建,未锁定 减少引用
已创建 PMRUnrefPMR (ref=0) 僵尸状态 加入僵尸列表
僵尸状态 同步条件满足 清理队列 创建清理项
清理队列 _PmrZombieCleanup 已销毁 释放所有资源

错误处理路径汇总

错误类型 处理策略 恢复可能性
══════════════════════════════════════════════════════════════════════
内存不足
├─ PMR创建失败 返回错误,不创建PMR 可重试
├─ 物理页面分配失败 返回错误,保持未锁定状态 可重试
└─ 僵尸结构分配失败 重新锁定物理地址,保持一致性 自动恢复
参数错误
├─ 逻辑大小不匹配 创建时拒绝 不可恢复
├─ 页索引越界 返回错误,不修改状态 不可恢复
└─ 无效的稀疏操作 返回错误,保持原有布局 不可恢复
权限错误
├─ 布局已固定 拒绝稀疏变更 不可恢复
├─ CPU映射存在 拒绝稀疏变更 需解映射
└─ 不支持的操作 返回NOT_IMPLEMENTED 不可恢复
并发冲突
├─ 页面迁移中 返回RETRY(GPU), 等待(CPU) 自动重试
├─ 引用计数异常 记录警告,拒绝操作 检测错误
└─ 双重释放 检测并拒绝 检测错误
硬件错误
├─ GPU超时 标记设备错误 需重置
├─ MMU错误 触发panic或恢复 部分恢复
└─ DMA错误 返回硬件错误 需重置

典型使用场景流程

场景1:纹理分配和使用

1. 应用分配纹理 (1MB, 稀疏)
   DevmemIntAllocatePMR()
   └─ PMRCreatePMR()
      └─ OSPagesPMR创建 (on-demand)
         - 逻辑大小: 1MB (256页 × 4KB)
         - 物理大小: 0 (未分配)
         - 映射表: 全部无效

2. 上传纹理数据 (实际使用512KB)
   DevmemIntMapPMR() → GPU映射
   └─ PMRLockPhysAddresses()
      └─ pfnLockPhysAddresses()
         - 分配128页物理内存
         - 更新映射表: [0-127]=有效, [128-255]=无效
         - Pin页面防止交换

   DevmemAcquireCpuVirtAddr() → CPU映射
   └─ 获取用户空间虚拟地址
      └─ memcpy纹理数据

3. GPU渲染使用纹理
   GPU访问纹理地址
   └─ MMU查询页表
      └─ 命中: 访问物理内存
      └─ 未命中: 触发页错误

4. 释放纹理
   DevmemReleaseCpuVirtAddr()
   DevmemIntUnmapPMR()
   PMRUnrefPMR()
   └─ 引用计数 → 0
      └─ _UnrefAndMaybeDestroy()
         └─ 延迟释放 (有GPU映射)
            └─ 等待GPU完成渲染
               └─ 等待缓存刷新
                  └─ 清理线程释放

总时间: ~100-500ms (包括延迟)
内存效率: 50% (512KB/1MB)

场景2:大型缓冲区动态调整

1. 初始分配 (4MB, 稀疏)
   PMRCreatePMR()
   - 逻辑: 4MB (1024页)
   - 物理: 256页 (1MB)
   - 利用率: 25%

2. 使用一段时间后扩展
   应用需要2MB
   └─ PMR_ChangeSparseMemUnlocked()
      - 分配: 256页 (索引256-511)
      - 释放: 0页
      - 结果: 2MB物理内存
      - 利用率: 50%

3. 释放不用的部分
   应用只需1.5MB
   └─ PMR_ChangeSparseMemUnlocked()
      - 分配: 0页
      - 释放: 128页 (索引512-639)
      - 僵尸页面: 128页进入延迟释放
      - 结果: 1.5MB物理内存
      - 利用率: 37.5%

4. 最终释放
   PMRUnrefPMR()
   └─ 所有页面进入延迟释放队列
      └─ 分批释放回OS

优势:
- 逻辑地址空间固定 (4MB)
- 物理内存按需调整
- 避免重新映射开销

场景3:跨进程内存共享

进程A (导出方)
═════════════

1. 分配共享内存
   PMRCreatePMR(1MB)

2. 写入数据
   PMR_WriteBytes()

3. 导出PMR
   PMRExportPMR()
   └─ PMR_SetLayoutFixed(TRUE)
      - 锁定布局,禁止变更
      - 生成导出句柄和密钥
      - 引用计数++

4. 传递句柄给进程B
   (通过共享内存或socket)


进程B (导入方)
═════════════

1. 导入PMR
   PMRImportPMR(handle, key)
   └─ 验证密钥
      └─ 引用计数++

2. 映射到GPU
   DevmemIntMapPMR()
   └─ 获取相同的物理地址
      └─ GPU可以访问相同内存

3. 使用完毕
   PMRUnimportPMR()
   └─ 引用计数--


进程A清理
═════════

1. 取消导出
   PMRUnexportPMR()
   └─ 引用计数--

2. 释放PMR
   PMRUnrefPMR()
   └─ 引用计数--
      └─ 如果B也释放了: 计数→0
         └─ 真正释放物理内存

安全保证:
- 密钥验证防止非法访问
- 布局固定保证一致性
- 引用计数防止提前释放

性能优化建议

优化点                          优化前                优化后              提升
═══════════════════════════════════════════════════════════════════════════

1. 栈vs堆分配
   - 小量页面(<32)             堆分配                栈数组              2-3x
   - 大量页面(>=32)            栈溢出风险            堆分配              安全

2. 快速路径检测
   - 连续内存                  O(n)遍历              O(1)直接返回        10-100x
   - 稀疏内存                  必须查表              优化映射表          1.5-2x

3. 批量操作
   - 逐页操作                  n次系统调用           1次批量操作         5-10x
   - 单个PMR多次锁定           多次锁操作            保持锁定            2-3x

4. 延迟释放
   - 同步释放                  阻塞等待刷新          异步清理            消除阻塞
   - 立即释放                  频繁分配/释放         批量延迟            减少碎片

5. 缓存策略
   - 地址转换结果              每次计算              缓存热点            2-5x
   - 映射表访问                每次读取              预取批量            1.5-2x

6. 并发优化
   - 粗粒度锁                  全局锁                Per-PMR锁           线性扩展
   - 自旋锁vs互斥锁            错误选择              场景匹配            避免睡眠

7. 内存对齐
   - 未对齐访问                额外周期              缓存行对齐          1.2-1.5x
   - 碎片化分配                浪费空间              连续分配            节省20%

8. PDump开销
   - 生产环境启用              明显变慢              条件编译            恢复性能
   - 调试环境                  完整记录              按需记录            可控开销

内存使用统计

内存类型 典型大小 生命周期 优化方向
═══════════════════════════════════════════════════════════════
PMR结构体 ~500 bytes 长期 池化分配
映射表 n4 bytes 长期 嵌入PMR
翻译数组 n4 bytes 长期 压缩稀疏
物理页面 4KB-2MB 可变 按需分配
僵尸结构 ~100 bytes 短期 预分配
清理项 ~200 bytes 短期 批量处理
总开销 (1000个PMR):

  • 结构开销: ~500KB
  • 映射表: 取决于稀疏度
  • 物理内存: 实际使用量
  • 管理开销: <1%

总结

这份PMR.c的完整分析涵盖了:

  1. 核心数据结构 - PMR结构体、全局上下文、映射表
  2. 生命周期管理 - 创建、引用计数、锁定、销毁
  3. 地址转换引擎 - 逻辑到物理地址映射,快速/慢速路径
  4. 稀疏内存 - 动态布局变更,映射表管理
  5. 延迟释放 - 僵尸PMR处理,异步清理
  6. 读写操作 - 内核映射,数据传输
  7. 安全机制 - 引用计数溢出保护,密钥验证
  8. 性能优化 - 栈分配优化,批量操作,缓存策略
  9. 调试支持 - PDump集成,内存统计

相关文章

PowerVR Rogue ZS Buffer
PowerVR RGX Parameter Buffer And Ring Buffer
PowerVR Rogue FreeList
PowerVR RGX Firmware Utils
PowerVR RGX BVNC
PowerVR RGX Multicore

发布评论