6.2 ASIC 无关抽象:amd_ip_blocks

上一篇(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_gpu fork 子进程,让每个进程各持一份副本来回避冲突。
  • struct amdgpu_ip_block_version:描述一个引擎的一个版本,字段是 type(GFX / COMPUTE / DMA...)、major/minor/rev 和一个指向操作表的 funcs
  • struct amdgpu_ip_funcs:真正干活的函数指针表。前几个字段是运行期填入的硬件身份(family_idchip_external_revchip_rev),后面是一整排回调:write_linearcopy_linearconst_fillget_reg_offsetgfx_program_computegfx_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_idamd_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_LORSRC1/2USER_DATANUM_THREAD 等 SH 寄存器),为一次裸 PM4 的 shader 启动做准备
gfx_dispatch_direct DISPATCH_DIRECT,带 grid 尺寸 + 代次相关的 initiator flags(就是 §五 那个 gfx9=0x0 / 其余=0x45 的坑)
gfx_write_confirm 发带 WR_CONFIRMWRITE_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_length2securepriority 本次要写/拷多少字节、是否安全提交、队列优先级
PM4 缓冲 pm4 / pm4_size / pm4_dw(及 *2 回调把包写进 pm4,用 pm4_dw 回填实际 dword 数(out 参数约定)
Buffer 句柄与地址 bo...bo4bo_mc...bo_mc4bo_cpu...、resources[]va_handle* 参与本次操作的 BO、其 GPU 地址与 CPU 映射,resources[] 供 bo_list 提交
提交上下文 context_handleib_infoibs_requesthw_ip_infoerr_codes 传统 CS 提交所需的 context / IB / 请求,以及回填的错误码
用户队列资源 queue/shadow/doorbell/rptr/wptr/csa/eopqueue_cpuwptr_cpudb_handlequeue_idtimeline_syncobj_handle/pointsubmit_modeuserq_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
};

注意两点:

  1. computegfx 共用同一张 gfx_v8_x_ip_funcs
  2. .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_EN0x45 = 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_offsetamd_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_idip_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 调用,不接触任何代次魔数------这正是这层抽象存在的意义。