第 5 章 通用层与 per-IP 层:函数指针解耦硬件差异
发布时间:2026/8/21 20:32:40 作者:尧图编辑部 阅读量:1,286

本章目标先讲清 amdgpu 应对十几代硬件的核心武器——分层 函数指针再以这套机制为尺标横向扫一遍 v4 → v5 → v6 → v7 各代 SDMA 的实际差异。前半是机制怎么解耦“后半是实证差在哪”一体两面。本章源码amdgpu_sdma.c通用层、sdma_v4_0.cper-IP 主线以及sdma_v5_0.c/v6_0.c/v7_0.c、lsdma_v6_0.c等对比参照。5.1 问题一份代码十几代硬件回顾第 1 章的版本表SDMA 从 v2_4、v3_0、v4_0一路到 v6_0、v7_1还有 LSDMA。每代硬件的寄存器布局、packet 细节、doorbell 方式都可能不同。如果每处都写if (是 Vega) ... else if (是 Navi) ...代码会彻底失控。amdgpu 的解法是经典的面向接口编程思想在 C 里用**函数指针表ops table**实现通用层只定义要做什么接口和通用流程每代硬件提供一张函数指针表填入具体怎么做实现。运行时通过指针调用自动分发到当前硬件的实现。5.2 两层分工┌───────────────────────────────────────────────────────────┐ │ 通用层generic与硬件无关 │ │ amdgpu_sdma.c / amdgpu_ring.c / amdgpu_vm_sdma.c ... │ │ - 定义接口结构体amdgpu_ring_funcs / buffer_funcs ... │ │ - 提供通用 helperget_instance_from_ring、init_microcode │ │ - 编排通用流程提交、调度、fence 等待 │ └──────────────────────────┬────────────────────────────────┘ │ 通过函数指针调用 ┌──────────────────────────┴──────────────────────────────┐ │ per-IP 层每代硬件一份实现 │ │ sdma_v4_0.c / sdma_v5_2.c / sdma_v6_0.c ... │ │ - 填充函数指针表sdma_v4_0_ring_funcs { ... } │ │ - 实现具体动作get_wptr / emit_ib / emit_fence ... │ └─────────────────────────────────────────────────────────┘一句话“通用层定义 whatper-IP 层实现 how。”5.3 四张关键的函数指针表每个sdma_v*.c本质上就是在填四张表。以 v4.0 为例函数指针表定义在通用层作用v4.0 里的实例amd_ip_funcs通用IP 生命周期init/fini/suspend…sdma_v4_0_ip_funcsamdgpu_ring_funcs通用一条 ring 的全部操作sdma_v4_0_ring_funcs/..._page_ring_funcsamdgpu_buffer_funcs通用拷贝/填充能力sdma_v4_0_buffer_funcsamdgpu_vm_pte_funcs通用页表更新能力sdma_v4_0_vm_pte_funcs后续章节各有对应ip_funcs第 7 章、ring_funcs第 8 章、buffer_funcs第 11 章、vm_pte_funcs第 12 章。本章先看它们如何挂上去。5.4ring_funcs一条 ring 的操作接口amdgpu_ring_funcs是最核心的一张表——它定义了对一条 ring 能做的所有操作。看 v4.0的填法节选// sdma_v4_0.cstaticconststructamdgpu_ring_funcssdma_v4_0_ring_funcs{.typeAMDGPU_RING_TYPE_SDMA,// 这是一条 SDMA 类型的 ring.align_mask0xff,.nopSDMA_PKT_NOP_HEADER_OP(SDMA_OP_NOP),.support_64bit_ptrstrue,.get_rptrsdma_v4_0_ring_get_rptr,// ← 第 2 章见过.get_wptrsdma_v4_0_ring_get_wptr,// ← 第 2 章见过.set_wptrsdma_v4_0_ring_set_wptr,// ← 第 2 章见过.emit_ibsdma_v4_0_ring_emit_ib,// ← 第 3 章见过.emit_fencesdma_v4_0_ring_emit_fence,// ← 第 3 章见过.emit_vm_flushsdma_v4_0_ring_emit_vm_flush,.emit_hdp_flushsdma_v4_0_ring_emit_hdp_flush,.test_ringsdma_v4_0_ring_test_ring,// ← 第 3 章见过.test_ibsdma_v4_0_ring_test_ib,.insert_nopsdma_v4_0_ring_insert_nop,.pad_ibsdma_v4_0_ring_pad_ib,...};注意一个重要细节GFX queue 和 Page queue 用的是两张不同的表staticconststructamdgpu_ring_funcssdma_v4_0_ring_funcs{...};// GFXstaticconststructamdgpu_ring_funcssdma_v4_0_page_ring_funcs{...};// Page对比会发现两张表绝大部分一样唯独set_wptr/get_wptr不同——GFX 用sdma_v4_0_ring_get_wptrPage 用sdma_v4_0_page_ring_get_wptr。原因正是第 2 章说的两条队列走不同的寄存器组SDMA0_GFX_*vsSDMA0_PAGE_*。数据结构、硬件模型、函数指针在这里完美对上了。5.5 表是怎么挂到实例上的set_ring_funcs光定义表还不够要把表指针赋给每个实例的ring.funcs/page.funcs。这由set_ring_funcs完成// sdma_v4_0.c: sdma_v4_0_set_ring_funcs()for(i0;iadev-sdma.num_instances;i){adev-sdma.instance[i].ring.funcssdma_v4_0_ring_funcs;// GFX 挂 GFX 表adev-sdma.instance[i].ring.mei;if(adev-sdma.has_page_queue){// 有 page 队列才挂adev-sdma.instance[i].page.funcssdma_v4_0_page_ring_funcs;adev-sdma.instance[i].page.mei;}}看到has_page_queue了吗第 4 章那个是否启用 page 队列的总开关在这里第一次发挥作用——只有为真时才给page.funcs赋值。类似地还有sdma_v4_0_set_buffer_funcs()→ 挂adev-mman.buffer_funcssdma_v4_0_set_vm_pte_funcs()→ 挂adev-vm_manager.vm_pte_funcssdma_v4_0_set_irq_funcs()→ 挂各中断源的处理函数这些set_*_funcs全部在early_init阶段被调用第 7 章。“early_init 挂表之后全程通过指针调用”是 amdgpu 每个 IP 的统一套路。5.5.1 表里也能按版本再分叉函数指针不是一代一张死板对应同一份 per-IP 文件里也能按细分版本选不同表// sdma_v4_0.c: sdma_v4_0_set_buffer_funcs()if(amdgpu_ip_version(adev,SDMA0_HWIP,0)IP_VERSION(4,4,0))amdgpu_sdma_set_buffer_funcs_scheds(adev,sdma_v4_4_buffer_funcs);// 4.4elseamdgpu_sdma_set_buffer_funcs_scheds(adev,sdma_v4_0_buffer_funcs);// 4.0两张 buffer_funcs 的差异仅在单次拷贝上限122vs130却能用同一套emit_copy_buffer实现。这就是函数指针的弹性共享大部分实现只在必要处分叉。5.6 通用层的 helper与硬件无关的公共逻辑amdgpu_sdma.c里则是不依赖任何具体硬件的公共函数谁都能用。几个代表从 ring 反查实例双队列都认// amdgpu_sdma.cstructamdgpu_sdma_instance*amdgpu_sdma_get_instance_from_ring(structamdgpu_ring*ring){structamdgpu_device*adevring-adev;for(inti0;iadev-sdma.num_instances;i)if(ringadev-sdma.instance[i].ring||// 匹配 GFX queueringadev-sdma.instance[i].page)// 也匹配 Page queuereturnadev-sdma.instance[i];returnNULL;}这个函数是双 ring 设计最好的注脚给任意一条 ring都能找回它属于哪个实例无论它是 GFX 还是 Page 队列。中断处理、复位等场景大量用到它。上下文保存区地址抢占用// amdgpu_sdma.cuint64_tamdgpu_sdma_get_csa_mc_addr(structamdgpu_ring*ring,unsignedintvmid){// SR-IOV / vmid0 / 未开启 mcbp 时返回 0不启用抢占 CSA// 否则按实例 index 算出 CSA 在显存里的地址}CSAContext Save Area用于队列被抢占时保存/恢复上下文。这类逻辑与具体寄存器无关所以放通用层。其它通用 helper后续章节展开amdgpu_sdma_init_microcode()/amdgpu_sdma_destroy_inst_ctx()固件加载/清理第 9 章amdgpu_sdma_ras_late_init()/amdgpu_sdma_process_ecc_irq()RAS/ECC第 9 章amdgpu_sdma_reset_engine()引擎复位第 9、14 章5.7 一次调用的分发过程把机制串起来以发一个 IB为例看通用层如何分发到 v4.0 实现上层代码 amdgpu_ring_emit_ib(ring, ...) // 通用层amdgpu_ring.h 宏 │ 展开为 ring-funcs-emit_ib(...) ▼ ring-funcs sdma_v4_0_ring_funcs // early_init 时挂上的 │ .emit_ib sdma_v4_0_ring_emit_ib ▼ sdma_v4_0_ring_emit_ib(ring, ...) // per-IP 层真正写 INDIRECT packet上层永远调用统一的ring-funcs-emit_ib运行时自动落到当前硬件的实现。换一代硬件只需换一张表上层代码一行不动。这就是 amdgpu 能优雅支撑十几代硬件的秘密。5.8 横向一份代码十几代硬件版本一览上面讲的是机制现在换个视角看实证正因为有这套函数指针分层本仓库drivers/gpu/drm/amd/amdgpu/才能用一致的骨架容纳十几代 SDMA。快速对照文件硬件代对应产品示例si_dma.c/cik_sdma.cSI / CIKlegacyTahiti / Hawaiisdma_v2_4.c/v3_0.cGFX8Carrizo / Fiji、Polarissdma_v4_0.cGFX9本系列主线Vega、Ravensdma_v4_4_2.cGFX9.4MI 系Aldebaran、Arcturussdma_v5_0.c/v5_2.cGFX10 / 10.3Navi1x / Navi2xsdma_v6_0.c/v7_0.c/v7_1.cGFX11 / GFX12RDNA3 / RDNA4lsdma_v6_0.c等LSDMA配套 GFX11/12万变不离其宗每个文件都在填 5.3 那四张表。而且 opcode 语义INDIRECT/FENCE/TRAP/…跨代稳定——emit_ib永远发SDMA_OP_INDIRECTemit_fence永远是FENCE TRAP。这正是 SDMA 抽象能长期复用的根基。5.9 各代差异的三个观察点带着四张表哪里不一样的问题去读差异集中在三处寄存器命名与 packet 头宏GFX9 用mmSDMA0_GFX_RB_*/mmSDMA0_PAGE_RB_*和SDMA_PKT_HEADER_OP(...)GFX11 改成regSDMA0_QUEUE0_RB_*这类统一队列命名和SDMA_PKT_COPY_LINEAR_HEADER_OP(...)新宏。只是命名与布局的演进指针管理语义RPTR 归硬件、WPTR 归驱动 doorbell完全一致第 2 章。page queue 的去留最值得注意的架构差异本树里只有sdma_v4_0.cGFX9和sdma_v4_4_2.cGFX9.4置has_page_queue true即一实例两队列的经典形态GFX10v5/v6/v7不再依赖常驻内核 page ring随MES / user queue架构演进走不同调度路径。但 SDMA拷贝 页表更新两大职责没变直接影响第 12 章页表更新走哪条 ring。多实例 分区消费级Navi/RDNA通常 1~2 个实例数据中心 GPUsdma_v4_4_2.c则num_instances可达十几个实例与AID/XCC 分区绑定aid_id/xcc_id这是它比 v4.0 复杂的主要来源第 4 章。另外两个边角知道定位即可LSDMAlsdma_v6_0.c起是轻量、同步、寄存器编程PIO的辅助拷贝引擎用于复位等底层场景与主 SDMA 不是一回事legacy 的si_dma.c/cik_sdma.c结构思想一脉相承理解 v4.0 后再看会觉得就是简化版。5.10 速读陌生sdma_vX.c的五问套路拿到一个没读过的版本按这五问就能十几分钟摸清骨架对应回调细节由后续章节展开搜_ip_funcs生命周期回调有无新增/改名第 7 章。搜_ring_funcs重点比get/set_wptr寄存器变了没和emit_*packet 宏变了没第 8 章。搜has_page_queue判断是不是双 ring影响第 12 章。搜_buffer_funcs的copy_max_bytes单次拷贝上限影响 TTM 迁移分片第 11 章。搜_vm_pte_funcs确认页表操作实现第 12 章。5.11 本章小结amdgpu 用分层 函数指针应对多代硬件通用层定 whatper-IP 层实现 how。每个sdma_v*.c主要就是填四张表ip_funcs/ring_funcs/buffer_funcs/vm_pte_funcs。GFX 和 Page 队列用两张ring_funcs差异集中在get_wptr/set_wptr根源是两套寄存器——硬件、结构、函数指针三者一致。set_*_funcs在early_init调用负责把表挂到实例/设备上has_page_queue决定是否挂 page 表。amdgpu_sdma.c提供与硬件无关的通用 helper如get_instance_from_ring双队列都认。各代 SDMA共享同一套四张表骨架opcode 语义跨代稳定主要差异在寄存器/宏命名GFX9mmSDMA0_GFX/PAGE_*→ GFX11regSDMA0_QUEUE0_*。page queue 是 GFX9v4.x的经典形态本树仅 v4_0、v4_4_2 置has_page_queuetrueGFX10 随 MES/user queue 演进。多实例 AID/XCC 分区是 MI 系复杂度的主来源LSDMA是轻量、同步、PIO 的辅助引擎。下一章预告既然 SDMA 对外是一条条 ring那它是怎么接入 amdgpu 通用的ring / IB / GPU scheduler / fence体系的第 6 章我们理清amdgpu_ring、amdgpu_ib、amdgpu_job三者的关系看一次提交从调度器到硬件的完整数据流。