✅ FreeList 是什么?为什么存在?
在 PowerVR GPU 中,所有的渲染命令会被最终“打包”到一个叫 Parameter Buffer(PB) 的区域,用于 Tiling 阶段对场景进行 binning。 但是 PB 需要反复分配小块空间,不能每次 malloc/free,因此 PowerVR 设计了一个高效的 FreeList 对应的 CCB/Kick 流程图 来管理 PB 内存。
FreeList 的特点:
| 特点 | 说明 |
|---|---|
| 管理的是 PB 内存块(Parameter Buffer Blocks) | PB 被分成固定大小的 Block(通常 256B 或 512B) |
| 使用 链表结构(FreeList)跟踪可用 Block | 分配就是 pop,释放就是 push |
| Firmware 和 Host 共享 FreeList | Host 初始化,FW 动态分配和回收 |
| 是 场景级别(per-TA/3D workload)的关键资源 | 如果耗尽 → GPU hang / OOM / Kick 重试 |
| 提供 自动 grow / shrink 机制 | Host 动态增加/减少 block pool |
✅ FreeList 的用途:Parameter Buffer 的 Block 分配器
Parameter Buffer 中记录:
- Geometry lists
- ISPs state
- Varying + attribute stream 数据
- Tile lists
- Tiling commands
每条渲染指令都会写入 PB 中,而 PB 又被 分成多个 block —— FreeList 管理的就是这些 block。
+-------------------------------+
| Parameter Buffer (PB) |
| +--------+--------+--------+ |
| | Block0 | Block1 | Block2 | |
| +--------+--------+--------+ |
| ↑ ↑ ↑ |
| free blocks |
+-------------------------------+
|
v
FreeList 管理
GPU FW 会从 FreeList 中请求:
Block = FreeList_AllocBlock()
工作完成后:
FreeList_FreeBlock(Block)
✅ FreeList 的核心数据结构(Host 侧)
在 DDK(drivers/gpu/drm/imagination/)中对应:
struct PVR_FW_FREE_LIST
{
u32 ui32ListSize; // 当前 block 数量
u32 ui32MaxListSize; // 最大 block 数量
u32 ui32GrowSize; // 自动扩容块数
u32 ui32GrowThreshold; // 阈值
u32 ui32CurrentFree; // 当前剩余 block 数
...
phys_addr_t paBase; // PB 物理地址基址
};
FreeList 实际上是一段连续的 PB pool + 一个可用块链表。
✅ FreeList 分配机制(Firmware 视角)
★ 分配过程(Alloc)
-
GPU FW 请求 block
-
FreeList 检查链表是否为空
-
若有 → pop 一个 block
-
若没有 → 通知 Host 进行 Grow
-
若 Host 无法扩容 → Kick 失败 → Fence error → GPU hang
伪代码:
block = freelist->head;
if (!block) {
FWSignalGrow();
return ERROR_NO_BLOCK;
}
freelist->head = block->next;
return block;
✅ FreeList 回收机制(Free)
Pipeline(TA/3D/Compute)完成一个段落后,FW 将使用完毕的 block 通过 MTS(Memory Transfer Service)通知 Host 回收:
freelist->head = freed_block;
因为 PB 是环形结构,FW 会在 fence 到达后批量回收。
✅ FreeList Grow / Shrink 机制(关键)
PowerVR PB 是可自动扩容的(与其他 GPU 不同)。
Grow 条件:
当 FW 分配时可用量 < GrowThreshold:
current_free < grow_threshold → Host 分配“GrowSize”个 block
Host 执行:
-
分配新的物理 memory
-
添加到 FreeList
-
更新 FW 表
Shrink 条件:
在场景切换 OR GPU 空闲时:
current_free > high_water_mark → Host 回收部分 block
这样避免长期占用大量 PB 内存。
✅ FreeList Overflow / Underflow 的常见问题
✔ underflow(最常见)
症状:
No free PB blocks → TA stalled → Timeout → GPU RESET
原因:
-
批次太多 geometry
-
ParameterBufferSize 过小
-
FreeList Grow 阈值配置太低
-
驱动缺少 Grow 回调
✔ overflow
实际上 FreeList 不会 overflow(因为 block 数是固定池),但可能:
-
Host shrink 太多
-
导致 FW 缺块
-
again → hang
✅ DDK 中 FreeList 主要函数
Host 侧关键代码(DDK):
pvr_freelist_create()
pvr_freelist_grow()
pvr_freelist_shrink()
pvr_freelist_destroy()
pvr_pb_allocate()
pvr_pb_free()
Firmware 侧(Rogue FW 中):
FreeListAlloc()
FreeListFree()
FreeListGrowRequest()
✅ FreeList 与 TA/3D 的关系(非常重要)
一个 FreeList 可以被多个 TA/3D Job 共享:
TA1 ───┐
TA2 ───┼──► 同一个 FreeList
3D1 ───┘
因此:
-
大量并发 Kick → PB 需求激增
-
Grow 失败 → GPU 卡死
✅ 【总览流程图】
CCB Job → Firmware → FreeList 分配 → TA/3D 执行 → FreeList 回收 这是内部 GPU pipeline 的真实流程,不是概念化图。同时会标注:
H- ost 驱动(内核)执行点
-
Firmware(Microkernel / usc FW)执行点
-
TA / 3D pipeline
-
FreeList Grow / Free / Underflow
-
CCB 命令产生的 PB Block 使用点
┌────────────────────────────────────────────────────────────────────┐
│ Host (Kernel Driver) │
└────────────────────────────────────────────────────────────────────┘
[1] 用户层提交绘制任务
│
▼
[2] pvr_submit_job() 生成 TA/3D CCB 命令
│
▼
[3] 将命令写入 CCB(Client Control Buffer)
│
▼
[4] 写 Doorbell → 通知 Firmware 有新的 Kick
│
▼
───────────────────────────────────────────────────────────────────────────
Firmware (Rogue Microkernel)
───────────────────────────────────────────────────────────────────────────
▼
[5] FW 读取 CCB 命令
│
▼
[6] 解析 TA/3D Kick 命令
│
▼
[7] 准备参数缓冲区(Parameter Buffer)
│
▼
[8] FreeList_AllocBlock() ← 请求 PB Block
│
├─ 有可用 block → 分配
│
└─ 无可用 block → FreeListGrowRequest → Host
│
▼
───────────────────────────────────────────────────────────────────────────
Host FreeList Grow 处理
───────────────────────────────────────────────────────────────────────────
│
[9] pvr_freelist_grow() 分配更多 block
│
▼
[10] 更新 FW Shared FreeList Table
│
▼
───────────────────────────────────────────────────────────────────────────
回到 Firmware
───────────────────────────────────────────────────────────────────────────
│
▼
[11] FW 继续消耗 PB block,执行 Tiling
│
▼
[12] TA 结束 → 释放 TA Blocks 到 FreeList(batched)
│
▼
[13] 启动 3D → 再次使用 FreeList 分配 block
│
▼
[14] 3D 完成 → FreeList_FreeBlock()
│
▼
[15] 如果达到 shrink 条件 → shrink request → Host
│
▼
───────────────────────────────────────────────────────────────────────────
Host(FreeList Shrink)
───────────────────────────────────────────────────────────────────────────
│
▼
[16] 回收多余 PB block → 更新 FW FreeList Table
│
▼
───────────────────────────────────────────────────────────────────────────
Firmware → Signal Fence
───────────────────────────────────────────────────────────────────────────
│
▼
[17] FW 发送 Fence → Host 完成 job
graph TD
CPU[CPU Driver] --> CCB[Write TA Kick to CCB]
CCB --> FW1[FW Detects CCB update]
FW1 --> FW2[FW Parses TA Kick]
FW2 --> TA_Start[FW Schedules TA Pass]
TA_Start --> FL_Req["TA Requests PB Pages<br/>FreeList Alloc"]
FL_Req -->|FreeList OK| PMR_Alloc[PMR Alloc Physical Pages]
PMR_Alloc --> SMMU_Map["SMMU Maps Pages<br/>Update PageTables"]
SMMU_Map --> TLB_Update["TLB Updates/Invalidate"]
TLB_Update --> TA_Run["TA Executes Pass<br/>Write PB"]
FL_Req -->|"FreeList Empty / Fail"| TA_OOM["TA OOM / Kick Failed"]
TA_OOM --> FW_OOM[FW Returns Error]
FW_OOM --> CPU_OOM[CPU Receives TA_oom]
TA_Run --> TA_Done[TA Pass Complete]
TA_Done --> FW3[FW Schedule 3D Pass]
FW3 --> D3_Run["3D Reads PB via SMMU/TLB<br/>and Executes"]
D3_Run --> D3_Done[3D Pass Complete]
D3_Done --> FL_Reclaim[FW Marks Pages Reclaimable]
FL_Reclaim --> PMR_Ref[PMR Refcount-- per page]
PMR_Ref -->|All refs done| FL_Free[FreeList Returns Pages]
PMR_Ref -->|Still referenced| FL_Delay[Delayed Free]
FL_Free --> FW_Comp[FW Completes Kick]
FL_Delay --> FW_Comp
FW_Comp --> CPU_Done["CPU Receives Fence / Complete Signal"]
🔥 深度细化:每一步的实际驱动函数调用
🔷 【Host → CCB → Kick】
pvr_submit_job()
↓
pvr_ccb_send_cmd()
↓
ccb_write_packet()
↓
pvr_sync_kick() / pvr_3dctx_kick()
↓
doorbell_write() → 通知 Firmware
🔷 【Firmware → FreeList block 分配】
ProcessCCB()
↓
ProcessTAKick() / Process3DKick()
↓
FW_ParameterManager_AllocPBBlock()
↓
FreeList_AllocBlock()
↓
├─ block available → return
└─ no free block → FreeListGrowRequest → Host
🔷 【Host → Grow FreeList】
pvr_freelist_grow()
↓
Alloc new PB chunks
↓
Add to freelist->free_stack
↓
Update FW freelist table via FWCCB
🔷 【3D / TA 使用 PB】
FW writes parameter buffer
↓
binner / uTile Scheduler consumes PB blocks
🔷 【回收 block】
FW_ParameterManager_FreePBBlock()
FW_FreeListReturnBlock()
回收不会立即 push,而是通过 “freeBlockList” 批处理,由ProcessFreeListReturns()完成。
✅ 结合硬件架构的最终总结
FreeList 解决的问题:
| 问题 | FreeList 方案 |
|---|---|
| PB 是固定 block,需要反复分配 | 提供可复用的 Block 链表 |
| GPU 固件需快速 alloc/free | 固件本地链表结构 |
| 场景复杂度会变化 | 支持自动 grow/shrink |
| 多 Job 并发 | FreeList 支持共享 |
| Block 耗尽导致 GPU 锁死 | Grow + 回调机制 |
FreeList 是 PowerVR 参数缓冲区的核心生命线,一旦耗尽或创建错误,整个 GPU pipeline 会立即停滞。
FreeList Underflow 时真正发生的事情(调试 GPU hang 必看)
FW AllocBlock → freelist->freecount == 0 → underflow
↓
Send Grow request to Host
↓
Host busy → grow pending
↓
FW cannot continue PB writes
↓
TA stall → TA timeout
↓
GPU Watchdog triggers EoC (End of Context)
↓
Device Reset
📌 FAQ:常见 FreeList 问题与 GPU 卡死场景
| 场景 | 结果 |
|---|---|
| Grow 请求 Host 未响应 | TA/3D job hang |
| PB block 物理映射 SMMU 出错 | FW 无法写入 PB → Fence 不到达 |
| FreeList 被多个 context 争抢 | 快速耗尽 |
| grow_threshold 太低 | 高频 underflow |
JINHU