上一篇(6.1)提到 lib/amdgpu 要回答的第二个难题:寄存器布局、dispatch 细节、compute pipeline 编程方式,全都随 ASIC(GFX9/10/11/12)变化。如果每个测试都写死某代硬件的魔数,加一款新芯片就得改一大片。这一篇把 amd_ip_blocks.{c,h} + amd_ip_blocks_ex.c 这套抽象讲透:它长什么样、初始化时发生了什么、测试代码为什么只需要一句 get_ip_block() 就能拿到对的实现。
一、核心数据结构
整套抽象有三层结构体,理解它们的包含关系是理解一切的前提。
#mermaid-svg-MBdHWtc8yS43lnmD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MBdHWtc8yS43lnmD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MBdHWtc8yS43lnmD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MBdHWtc8yS43lnmD .error-icon{fill:#552222;}#mermaid-svg-MBdHWtc8yS43lnmD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MBdHWtc8yS43lnmD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MBdHWtc8yS43lnmD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MBdHWtc8yS43lnmD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MBdHWtc8yS43lnmD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MBdHWtc8yS43lnmD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MBdHWtc8yS43lnmD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MBdHWtc8yS43lnmD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MBdHWtc8yS43lnmD .marker.cross{stroke:#333333;}#mermaid-svg-MBdHWtc8yS43lnmD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MBdHWtc8yS43lnmD p{margin:0;}#mermaid-svg-MBdHWtc8yS43lnmD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MBdHWtc8yS43lnmD .cluster-label text{fill:#333;}#mermaid-svg-MBdHWtc8yS43lnmD .cluster-label span{color:#333;}#mermaid-svg-MBdHWtc8yS43lnmD .cluster-label span p{background-color:transparent;}#mermaid-svg-MBdHWtc8yS43lnmD .label text,#mermaid-svg-MBdHWtc8yS43lnmD span{fill:#333;color:#333;}#mermaid-svg-MBdHWtc8yS43lnmD .node rect,#mermaid-svg-MBdHWtc8yS43lnmD .node circle,#mermaid-svg-MBdHWtc8yS43lnmD .node ellipse,#mermaid-svg-MBdHWtc8yS43lnmD .node polygon,#mermaid-svg-MBdHWtc8yS43lnmD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MBdHWtc8yS43lnmD .rough-node .label text,#mermaid-svg-MBdHWtc8yS43lnmD .node .label text,#mermaid-svg-MBdHWtc8yS43lnmD .image-shape .label,#mermaid-svg-MBdHWtc8yS43lnmD .icon-shape .label{text-anchor:middle;}#mermaid-svg-MBdHWtc8yS43lnmD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MBdHWtc8yS43lnmD .rough-node .label,#mermaid-svg-MBdHWtc8yS43lnmD .node .label,#mermaid-svg-MBdHWtc8yS43lnmD .image-shape .label,#mermaid-svg-MBdHWtc8yS43lnmD .icon-shape .label{text-align:center;}#mermaid-svg-MBdHWtc8yS43lnmD .node.clickable{cursor:pointer;}#mermaid-svg-MBdHWtc8yS43lnmD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MBdHWtc8yS43lnmD .arrowheadPath{fill:#333333;}#mermaid-svg-MBdHWtc8yS43lnmD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MBdHWtc8yS43lnmD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MBdHWtc8yS43lnmD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MBdHWtc8yS43lnmD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MBdHWtc8yS43lnmD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MBdHWtc8yS43lnmD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MBdHWtc8yS43lnmD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MBdHWtc8yS43lnmD .cluster text{fill:#333;}#mermaid-svg-MBdHWtc8yS43lnmD .cluster span{color:#333;}#mermaid-svg-MBdHWtc8yS43lnmD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MBdHWtc8yS43lnmD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MBdHWtc8yS43lnmD rect.text{fill:none;stroke-width:0;}#mermaid-svg-MBdHWtc8yS43lnmD .icon-shape,#mermaid-svg-MBdHWtc8yS43lnmD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MBdHWtc8yS43lnmD .icon-shape p,#mermaid-svg-MBdHWtc8yS43lnmD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MBdHWtc8yS43lnmD .icon-shape .label rect,#mermaid-svg-MBdHWtc8yS43lnmD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MBdHWtc8yS43lnmD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MBdHWtc8yS43lnmD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MBdHWtc8yS43lnmD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ip_blocks\[\] 数组
funcs
amdgpu_ips: amdgpu_ip_blocks_device
(全局单例,最多 AMD_IP_MAX 个槽)
amdgpu_ip_block_version
type / major / minor / rev / *funcs
amdgpu_ip_funcs
family_id + 一大张函数指针表
struct amdgpu_ip_blocks_device:一个进程级全局单例amdgpu_ips,内部是ip_blocks[AMD_IP_MAX]数组 +num_ip_blocks计数。它记录「当前注册了哪些引擎」,但要强调:这是一份全局单例、单设备语义 的账本,不是 per-device 状态。同一进程里对多台设备各调一次setup_amdgpu_ip_blocks,会把块重复追加 进这同一个数组、并让共享 funcs 的硬件身份互相覆盖 (结构体旁/* we may improve later */就是在说这个限制)。多 GPU 测试因此走igt_multi_fork_foreach_gpufork 子进程,让每个进程各持一份副本来回避冲突。struct amdgpu_ip_block_version:描述一个引擎的一个版本,字段是type(GFX / COMPUTE / DMA...)、major/minor/rev和一个指向操作表的funcs。struct amdgpu_ip_funcs:真正干活的函数指针表。前几个字段是运行期填入的硬件身份(family_id、chip_external_rev、chip_rev),后面是一整排回调:write_linear、copy_linear、const_fill、get_reg_offset、gfx_program_compute、gfx_dispatch_direct,以及一堆 hang/reset 测试专用的钩子。
type 用的枚举是 enum amd_ip_block_type:
c
enum amd_ip_block_type {
AMD_IP_GFX = 0,
AMD_IP_COMPUTE,
AMD_IP_DMA,
AMD_IP_UVD,
AMD_IP_VCE,
AMD_IP_UVD_ENC,
AMD_IP_VCN_DEC,
AMD_IP_VCN_ENC,
AMD_IP_VCN_UNIFIED = AMD_IP_VCN_ENC,
AMD_IP_VCN_JPEG,
AMD_IP_VPE,
AMD_IP_MAX,
};
二、amdgpu_ip_funcs:硬件原语回调表
amdgpu_ip_funcs 是整套抽象里真正「干活」的一层------它不描述「是什么硬件」,而是回答「在这块引擎上,某个操作该发什么 PM4 包」。字段分两类:开头几个标量是运行期身份和常量,后面一大排是按用途分组的函数指针。
先看变量(setup 时回填或静态给定):
| 变量 | 作用 |
|---|---|
family_id / chip_external_rev / chip_rev |
硬件身份,family_id 是 amd_ip_blocks_ex_init 分流的唯一依据(见 §五) |
align_mask |
ring / buffer 的对齐掩码 |
nop |
本引擎的 NOP 包字(GFX 与 SDMA 不同) |
deadbeaf / pattern |
测试用的哨兵值与填充模式------写进去再读回来比对 |
回调按业务分四组。
1. PM4 原语组:一次内存操作 = 一段命令流
这组签名都是 (func, ring_context, uint32_t *pm4_dw)------把要发的 PM4 dword 追加进 ring_context 的命令缓冲,并回填本次写了多少 dword。它们是所有 ring 测试的地基:
| 回调 | 作用 |
|---|---|
write_linear |
往一段线性地址写一串值------最基础的「提交能跑通、写得对」验证 |
bad_write_linear |
故意发一个畸形/越权的 write(多带 cmd_error 参数),用于错误注入 / 负路径测试 |
write_linear_atomic |
原子写变体,验证 atomic 包 |
const_fill |
按常量填一段内存 |
copy_linear |
内存拷贝 |
compare / compare_pattern |
CPU 侧把结果读回来跟期望值 / pattern 比对(不是发包,是校验 helper) |
wait_reg_mem |
发 WAIT_REG_MEM,轮询某地址直到满足条件------引擎内同步原语 |
get_reg_offset |
语义枚举 → 真实寄存器偏移(详见 §六) |
为什么要抽这一组:write / copy / fill / compare / wait 就是测试需要的最小操作集,而同一个操作在 GFX ring 和 SDMA ring 上 PM4 编码完全不同 。抽成回调后,同一个测试只要换 ip_block(GFX ↔ DMA)就能在两条引擎上各跑一遍,测试体一行不用改。
2. 用户队列生命周期组
| 回调 | 作用 |
|---|---|
userq_create / userq_submit / userq_destroy |
用户态队列(user-mode queue)的建 / 提 / 销 |
业务动机:内核同时支持「传统 CS ioctl 提交」和「用户态队列直提」两套模型,测试要覆盖两条路径。把队列生命周期抽成回调,上层提交逻辑就能在两种模型间复用。
3. compute 流水线组:启动一个 shader
| 回调 | 作用 |
|---|---|
gfx_program_compute |
编排最小 compute 流水线(写 PGM_LO、RSRC1/2、USER_DATA、NUM_THREAD 等 SH 寄存器),为一次裸 PM4 的 shader 启动做准备 |
gfx_dispatch_direct |
发 DISPATCH_DIRECT,带 grid 尺寸 + 代次相关的 initiator flags(就是 §五 那个 gfx9=0x0 / 其余=0x45 的坑) |
gfx_write_confirm |
发带 WR_CONFIRM 的 WRITE_DATA------等写真正落地,常用来给 shader 完成打标记 |
gfx_emit_nops |
往 ring 里补 count 个 NOP(对齐 / 占位) |
gfx_write_data_mem |
更通用的 WRITE_DATA,可指定 engine_sel 和是否 WR_CONFIRM |
业务动机:要在测试里真跑一段 compute shader,就得「配流水线 + dispatch」,而这套编码随代次变化最剧烈 ------所以这组正是 amd_ip_blocks_ex_init 里按 family_id 替换的重点对象(§五)。
4. hang/reset 钩子组:定向制造故障,考验驱动恢复
| 回调 | 制造的故障 / 恢复路径 |
|---|---|
wait_reg_mem_hang |
轮询一个永远不会到来的值 → 队列「干净地」挂住,用于 per-queue reset 测试 |
priv_fault_hang |
先发非法 opcode 触发 CP_BAD_OPCODE_ERROR,再挂住 → 驱动走 per-queue reset 恢复 |
priv_inst_hang |
从非特权用户队列发特权 INDIRECT_BUFFER(PRIV 位)→ 触发特权指令故障 |
priv_fault_badcount_hang |
发一个 count 字段错误、又没有包体的坏包 → 流水线找不到包尾中途卡死,vmid / per-queue reset 救不回来,只能靠 gfx pipe reset |
业务动机:GPU reset / 恢复测试需要确定性地制造出不同「类别」的故障,每一类走的恢复路径不同(per-queue reset vs pipe reset)。把「如何制造某类 hang」抽成回调,既能按代次给出正确的坏包,又让 reset 测试主体保持通用。
5. 回调共享的载体:amdgpu_ring_context
上面 PM4 原语组的签名都是 (func, ring_context, uint32_t *pm4_dw)------回调本身无状态 ,所以「这次提交的具体参数」必须由一个上下文对象携带。amdgpu_ring_context 就是测试主体与回调之间的**共享参数袋 **:测试填好 buffer 和传输长度,回调把 PM4 写进去并回填产出的 dword 数,再由 amdgpu_test_exec_cs_helper() 提交。字段按用途分几组:
| 分组 | 代表字段 | 作用 |
|---|---|---|
| 传输参数 | write_length / write_length2、secure、priority |
本次要写/拷多少字节、是否安全提交、队列优先级 |
| PM4 缓冲 | pm4 / pm4_size / pm4_dw(及 *2) |
回调把包写进 pm4,用 pm4_dw 回填实际 dword 数(out 参数约定) |
| Buffer 句柄与地址 | bo...bo4、bo_mc...bo_mc4、bo_cpu...、resources[]、va_handle* |
参与本次操作的 BO、其 GPU 地址与 CPU 映射,resources[] 供 bo_list 提交 |
| 提交上下文 | context_handle、ib_info、ibs_request、hw_ip_info、err_codes |
传统 CS 提交所需的 context / IB / 请求,以及回填的错误码 |
| 用户队列资源 | queue/shadow/doorbell/rptr/wptr/csa/eop、queue_cpu、wptr_cpu、db_handle、queue_id、timeline_syncobj_handle/point、submit_mode、userq_params |
user-mode queue 提交路径用到的一整套 ring/doorbell/fence 资源 |
一个 context 同时容纳「传统 CS」和「用户队列」两套字段,正是为了让同一个测试主体在两种提交模型间切换时,只换 submit 回调、不动数据组织。amd_basic.c 里几乎每个测试都先 calloc 一个 ring_context、填参数、调 ip_block->funcs->xxx(...)、最后 free,就是这个用法的典型。
三、三个静态 ip_block:版本号是幌子,family_id 才是真身
第一个反直觉的点在这里:无论你的卡是 GFX9 还是 GFX12,注册进 amdgpu_ips 的永远是这三个「v8」块:
c
struct amdgpu_ip_block_version gfx_v8_x_ip_block = {
.type = AMD_IP_GFX, .major = 8, .minor = 0, .rev = 0,
.funcs = &gfx_v8_x_ip_funcs
};
struct amdgpu_ip_block_version compute_v8_x_ip_block = {
.type = AMD_IP_COMPUTE, .major = 8, .minor = 0, .rev = 0,
.funcs = &gfx_v8_x_ip_funcs
};
struct amdgpu_ip_block_version sdma_v3_x_ip_block = {
.type = AMD_IP_DMA, .major = 3, .minor = 0, .rev = 0,
.funcs = &sdma_v3_x_ip_funcs
};
注意两点:
compute和gfx共用同一张gfx_v8_x_ip_funcs。.major = 8是硬编码常量 ,跟真实 GFX 版本无关。setup_amdgpu_ip_blocks()里甚至有断言守着它:igt_assert_eq(compute_v8_x_ip_block.major, 8)。
所以这里的 major = 8 是「这套抽象的版本号」,不是「硬件的 GFX 版本」。要判断真实硬件代次,唯一可靠来源是 funcs->family_id ,不是 ip_block->major。这个区别很关键------如果你误把 ip_block->major 当成 GFX 版本去做 dispatch 分支,在所有卡上都会拿到 8,直接回归。
那「v8 的操作表」怎么在 GFX12 上还能正确工作?答案是初始化时会把随 ASIC 变化的那几个回调替换掉。
四、初始化主线:setup_amdgpu_ip_blocks
setup_amdgpu_ip_blocks(major, minor, amdinfo, device) 每打开一个设备就调用一次。但注意它不是 为该设备建一套私有 ip blocks------它把同一批全局静态块追加进全局 amdgpu_ips、并把这台设备的身份覆盖写进那张共享 funcs。流程如下:
#mermaid-svg-rj9rPAIGif9geX2j{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rj9rPAIGif9geX2j .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rj9rPAIGif9geX2j .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rj9rPAIGif9geX2j .error-icon{fill:#552222;}#mermaid-svg-rj9rPAIGif9geX2j .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rj9rPAIGif9geX2j .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rj9rPAIGif9geX2j .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rj9rPAIGif9geX2j .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rj9rPAIGif9geX2j .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rj9rPAIGif9geX2j .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rj9rPAIGif9geX2j .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rj9rPAIGif9geX2j .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rj9rPAIGif9geX2j .marker.cross{stroke:#333333;}#mermaid-svg-rj9rPAIGif9geX2j svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rj9rPAIGif9geX2j p{margin:0;}#mermaid-svg-rj9rPAIGif9geX2j .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rj9rPAIGif9geX2j .cluster-label text{fill:#333;}#mermaid-svg-rj9rPAIGif9geX2j .cluster-label span{color:#333;}#mermaid-svg-rj9rPAIGif9geX2j .cluster-label span p{background-color:transparent;}#mermaid-svg-rj9rPAIGif9geX2j .label text,#mermaid-svg-rj9rPAIGif9geX2j span{fill:#333;color:#333;}#mermaid-svg-rj9rPAIGif9geX2j .node rect,#mermaid-svg-rj9rPAIGif9geX2j .node circle,#mermaid-svg-rj9rPAIGif9geX2j .node ellipse,#mermaid-svg-rj9rPAIGif9geX2j .node polygon,#mermaid-svg-rj9rPAIGif9geX2j .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rj9rPAIGif9geX2j .rough-node .label text,#mermaid-svg-rj9rPAIGif9geX2j .node .label text,#mermaid-svg-rj9rPAIGif9geX2j .image-shape .label,#mermaid-svg-rj9rPAIGif9geX2j .icon-shape .label{text-anchor:middle;}#mermaid-svg-rj9rPAIGif9geX2j .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rj9rPAIGif9geX2j .rough-node .label,#mermaid-svg-rj9rPAIGif9geX2j .node .label,#mermaid-svg-rj9rPAIGif9geX2j .image-shape .label,#mermaid-svg-rj9rPAIGif9geX2j .icon-shape .label{text-align:center;}#mermaid-svg-rj9rPAIGif9geX2j .node.clickable{cursor:pointer;}#mermaid-svg-rj9rPAIGif9geX2j .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rj9rPAIGif9geX2j .arrowheadPath{fill:#333333;}#mermaid-svg-rj9rPAIGif9geX2j .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rj9rPAIGif9geX2j .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rj9rPAIGif9geX2j .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rj9rPAIGif9geX2j .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rj9rPAIGif9geX2j .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rj9rPAIGif9geX2j .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rj9rPAIGif9geX2j .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rj9rPAIGif9geX2j .cluster text{fill:#333;}#mermaid-svg-rj9rPAIGif9geX2j .cluster span{color:#333;}#mermaid-svg-rj9rPAIGif9geX2j div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rj9rPAIGif9geX2j .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rj9rPAIGif9geX2j rect.text{fill:none;stroke-width:0;}#mermaid-svg-rj9rPAIGif9geX2j .icon-shape,#mermaid-svg-rj9rPAIGif9geX2j .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rj9rPAIGif9geX2j .icon-shape p,#mermaid-svg-rj9rPAIGif9geX2j .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rj9rPAIGif9geX2j .icon-shape .label rect,#mermaid-svg-rj9rPAIGif9geX2j .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rj9rPAIGif9geX2j .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rj9rPAIGif9geX2j .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rj9rPAIGif9geX2j :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 识别芯片
family_id + chip_external_rev → CHIP_xxx
推导 chip_class
CHIP_xxx → GFX6..GFX12
注册三个静态块
ip_block_add(gfx/compute/sdma)
回填硬件身份
每个 funcs->family_id = amdinfo->family_id
按 family_id 装 ASIC 相关回调
ip_block_ex_setup → amd_ip_blocks_ex_init
断言校验
family_id 与 amdinfo 一致
- 识别芯片 :用
identify_chip宏,把amdinfo->family_id+chip_external_rev映射到具体CHIP_NAVI10/CHIP_GFX1200之类。 - 推导 chip_class :再把
CHIP_xxx归到GFX6...GFX12。这决定后面走哪条初始化分支------GFX7 到 GFX12 都走同一条「注册三块 + 装回调」的路径。 - 回填身份 :注册后,遍历所有块把内核查到的
family_id/chip_external_rev/chip_rev写进各自的funcs。这一步之后funcs->family_id才有值 ,amd_ip_blocks_ex_init靠它分流。 - 装 ASIC 相关回调 :对每个块调
amdgpu_device_ip_block_ex_setup(),进而调amd_ip_blocks_ex_init()。
其中「注册」本身很朴素,就是往数组里塞:
c
static int
amdgpu_device_ip_block_add(struct amdgpu_ip_block_version *ip_block_version)
{
if (amdgpu_ips.num_ip_blocks >= AMD_IP_MAX)
return -1;
amdgpu_ips.ip_blocks[amdgpu_ips.num_ip_blocks++] = ip_block_version;
return 0;
}
五、amd_ip_blocks_ex_init:按 family_id 打补丁
amd_ip_blocks_ex_init() 是「v8 操作表」能适配所有代的秘密。它先装一套 default 实现,再按 family_id 用更专的实现覆盖会变的那几个回调:
c
void amd_ip_blocks_ex_init(struct amdgpu_ip_funcs *funcs)
{
funcs->gfx_program_compute = gfx_program_compute_default;
funcs->gfx_dispatch_direct = gfx_dispatch_direct_default;
funcs->gfx_write_confirm = gfx_write_confirm_default;
/* ... hang/reset 钩子 ... */
switch (funcs->family_id) {
case AMDGPU_FAMILY_RV:
case AMDGPU_FAMILY_NV:
case AMDGPU_FAMILY_VGH:
funcs->gfx_dispatch_direct = gfx_dispatch_direct_gfx9;
break;
case AMDGPU_FAMILY_YC:
case AMDGPU_FAMILY_GC_10_3_6:
case AMDGPU_FAMILY_GC_10_3_7:
funcs->gfx_dispatch_direct = gfx_dispatch_direct_gfx10;
break;
case AMDGPU_FAMILY_GC_11_0_0:
case AMDGPU_FAMILY_GC_11_0_1:
case AMDGPU_FAMILY_GC_11_5_0:
funcs->gfx_program_compute = gfx_program_compute_gfx11;
funcs->gfx_dispatch_direct = gfx_dispatch_direct_gfx11;
break;
case AMDGPU_FAMILY_GC_12_0_0:
funcs->gfx_program_compute = gfx_program_compute_gfx12;
funcs->gfx_dispatch_direct = gfx_dispatch_direct_gfx11;
break;
default:
...
}
}
一个具体的坑:gfx_dispatch_direct_gfx9 发的是 flags = 0x00000000,而 gfx10/11/12 用的是 0x00000045。差异就在 bit0 COMPUTE_SHADER_EN(0x45 = 0b1000101)------gfx9 这一档故意清掉 它。对普通 dispatch 测试无碍,但要真正启动 compute shader 时,0x0 会让 shader 起不来。这正是 amd_svm.c 跑 copy shader 时绕开 gfx_dispatch_direct()、改为手写 DISPATCH_DIRECT 并保持 COMPUTE_SHADER_EN 置位的原因(见其注释)。换句话说:这层抽象覆盖了「不同代 initiator 值不同」,但 shader 启动是它的一个已知边界,并非所有场景都能完全兜住。
注意触发替换的条件(在 amdgpu_device_ip_block_ex_setup 里):只有当某个块缺少 gfx_program_compute/gfx_dispatch_direct/gfx_write_confirm 时才调 amd_ip_blocks_ex_init。静态表里这几个指针初始为空,所以第一次一定会触发。
六、寄存器偏移:get_reg_offset 的另一条分流
除了 dispatch,寄存器偏移也随代变化。这条走的是 funcs->get_reg_offset,参数是一个语义化枚举而不是裸地址:
c
enum general_reg {
COMPUTE_PGM_LO,
COMPUTE_PGM_RSRC1,
COMPUTE_TMPRING_SIZE,
COMPUTE_USER_DATA_0,
COMPUTE_USER_DATA_1,
COMPUTE_RESOURCE_LIMITS,
COMPUTE_NUM_THREAD_X,
};
调用方写 get_reg_offset(COMPUTE_USER_DATA_0),由 funcs 上的 get_reg_offset 回调查表返回真实偏移,测试里不会出现 0x240 这种写死某代的魔数。
但这里有个容易误解的点,得说清楚:和 dispatch 不同,get_reg_offset 并没有按代次分流。 两张静态 funcs 表都把 .get_reg_offset 写死成 gfx_v8_0_get_reg_offset(amd_ip_blocks.c:1087、:1107),而 amd_ip_blocks_ex_init 从不覆盖它 (只换 dispatch/program)。也就是说,GFX8--GFX12 拿到的永远是 v8 那张表 。它能通用,是因为这几个 compute SH 寄存器的偏移在 GFX8--GFX12 上架构稳定 ,而不是「换代自动查到不同偏移」。gfx_v9_0_get_reg_offset 虽然定义了,但从未挂进任何 funcs 表,且内部只是 return gfx_v8_0_get_reg_offset(...)(amd_gfx_v9_0.c:28),属于预留位。所以这层抽象在 reg offset 上的真正价值是语义化枚举避免魔数,而不是 per-family swap。
七、消费侧:测试代码只需get_ip_block
对测试代码来说,上面这些初始化全是幕后。用起来只有一句:
c
const struct amdgpu_ip_block_version *
get_ip_block(amdgpu_device_handle device, enum amd_ip_block_type type)
{
int i;
if (g_chip.dev != device)
return NULL;
for (i = 0; i < amdgpu_ips.num_ip_blocks; i++)
if (amdgpu_ips.ip_blocks[i]->type == type)
return amdgpu_ips.ip_blocks[i];
return NULL;
}
拿到块之后,典型用法是 ip_block->funcs->write_linear(...) 或 ip_block->funcs->get_reg_offset(...)。因为 funcs 已经在初始化时按当前 ASIC 打好了补丁,测试代码写一次就能在所有支持的卡上跑。
八、关键结论
- 注册进系统的永远是
gfx/compute/sdma三个静态 v8/v3 块,major是抽象版本号常量,不是 GFX 硬件版本。 - 判断硬件代次唯一可靠来源是
funcs->family_id;ip_block->major恒为 8/3,拿它做分支必然出错。 amdgpu_ip_funcs一张回调表按业务分四组:PM4 原语 (write/copy/fill/compare/wait,换ip_block即可 GFX↔SDMA 复跑)、用户队列生命周期 (覆盖 CS ioctl 与用户态队列两套提交模型)、compute 流水线 (program+dispatch,随代次变化最剧烈,是 ex_init 重点替换对象)、hang/reset 钩子(定向制造不同故障类别,对应 per-queue vs pipe reset)。- ASIC 差异主要收敛在
amd_ip_blocks_ex_init:它按family_id替换 dispatch/program 回调。而get_reg_offset并未按代次分流------它恒为gfx_v8_0_get_reg_offset,靠的是 compute SH 寄存器偏移在 GFX8--GFX12 上的架构稳定,语义化枚举只是为了避免魔数。 - 测试代码只依赖
get_ip_block()+ 语义化的 funcs 调用,不接触任何代次魔数------这正是这层抽象存在的意义。