本章目标:上一章说"硬件只认命令流",本章就来看下命令(packet)到底长什么样。不需要记忆全部字段,我们抓住header = opcode + sub-opcode + 参数这个统一骨架,然后用本仓库里几个真实的 emit 函数,看懂
WRITE/INDIRECT/FENCE/TRAP/POLL_REGMEM这几个最常用的命令是怎么拼出来的。
3.1 packet 的统一骨架
SDMA 的每条命令都是若干个dword(32 位)拼起来的。第一个 dword 是header,其余是这条命令的参数。header 里最关键的两个字段:
- OP(opcode):主操作码,决定"这是哪一类命令"(写内存?拷贝?发 fence?)。
- SUB_OP(sub-opcode):子操作码,在同一大类里再细分(比如 WRITE 里再分"线性写 / tiled 写")。
在代码里,header 用一组宏拼装,最典型的就是SDMA_PKT_HEADER_OP(...):
// 一个 header 的通用形态SDMA_PKT_HEADER_OP(op)|SDMA_PKT_HEADER_SUB_OP(sub_op)|<其它标志位>驱动侧用amdgpu_ring_write(ring, dword)把一个个 dword 依次写进 ring buffer,硬件就按顺序读出来解释执行。理解 packet,本质就是理解"每个 dword 放了什么"。
3.2 最小可验证例子:WRITE(写一个魔数)
理解 packet 的最佳入口,是 ring 自检函数sdma_v4_0_ring_test_ring()。它干的事极简单:用 SDMA 往一块内存写一个已知值0xDEADBEEF,然后 CPU 去读,读到了就说明引擎活着。
// sdma_v4_0.c: sdma_v4_0_ring_test_ring()(节选)tmp=0xCAFEDEAD;adev->wb.wb[index]=cpu_to_le32(tmp);// 先把目标内存填成 0xCAFEDEADamdgpu_ring_alloc(ring,5);// 这条命令占 5 个 dwordamdgpu_ring_write(ring,SDMA_PKT_HEADER_OP(SDMA_OP_WRITE)|// dw0: headerSDMA_PKT_HEADER_SUB_OP(SDMA_SUBOP_WRITE_LINEAR));amdgpu_ring_write(ring,lower_32_bits(gpu_addr));// dw1: 目标地址低 32amdgpu_ring_write(ring,upper_32_bits(gpu_addr));// dw2: 目标地址高 32amdgpu_ring_write(ring,SDMA_PKT_WRITE_UNTILED_DW_3_COUNT(0));// dw3: 写多少(count)amdgpu_ring_write(ring,0xDEADBEEF);// dw4: 要写入的数据amdgpu_ring_commit(ring);// 提交(推 WPTR + 敲门)把这 5 个 dword 排开看,WRITE (LINEAR)packet 的结构一目了然:
| dword | 内容 | 说明 |
|---|---|---|
| dw0 | header | OP=WRITE,SUB_OP=WRITE_LINEAR |
| dw1 | dst addr lo | 目标 GPU 地址低 32 位 |
| dw2 | dst addr hi | 目标 GPU 地址高 32 位 |
| dw3 | count | 写入的 dword 数(0 表示 1 个) |
| dw4 | data | 要写的值0xDEADBEEF |
执行后 CPU 轮询那块内存,从0xCAFEDEAD变成0xDEADBEEF就算通过。这就是一条完整 SDMA 命令的最小闭环,把它记牢,后面再复杂的 packet 也是同一套路。
3.3INDIRECT:执行一个 IB(间接缓冲区)
ring buffer 本身不大,不适合放很长的命令流。真正干活的大命令流通常放在单独的IB(Indirect Buffer)里,然后在 ring 里放一条INDIRECTpacket,告诉硬件"去那个地址执行一段命令"。
// sdma_v4_0.c: sdma_v4_0_ring_emit_ib()(节选)amdgpu_ring_write(ring,SDMA_PKT_HEADER_OP(SDMA_OP_INDIRECT)|// dw0: header + vmidSDMA_PKT_INDIRECT_HEADER_VMID(vmid&0xf));amdgpu_ring_write(ring,lower_32_bits(ib->gpu_addr)&0xffffffe0);// dw1: IB 地址低(32B 对齐)amdgpu_ring_write(ring,upper_32_bits(ib->gpu_addr));// dw2: IB 地址高amdgpu_ring_write(ring,ib->length_dw);// dw3: IB 长度(dword)amdgpu_ring_write(ring,0);// dw4/5: csa 等amdgpu_ring_write(ring,0);要点:
- header 里带了VMID——说明这段 IB 在哪个虚拟地址空间里执行,这是 GPU 虚拟内存隔离的一部分。
- IB 地址要求32 字节对齐(
& 0xffffffe0)。 - 这就是"ring → IB"两级命令结构的硬件基础,第 6 章会从软件角度再讲一遍。
3.4FENCE+TRAP:宣告"我干完了"
一批命令执行完,需要两件事:① 写一个递增的序号(seq)到某个地址(fence),让别人能查询进度;② 触发一个中断(trap),主动通知驱动。SDMA 把这两步分别用FENCE和TRAP两条 packet 完成:
// sdma_v4_0.c: sdma_v4_0_ring_emit_fence()(节选)/* ① 写 fence:把 seq 写到 addr */amdgpu_ring_write(ring,SDMA_PKT_HEADER_OP(SDMA_OP_FENCE));amdgpu_ring_write(ring,lower_32_bits(addr));// fence 地址低amdgpu_ring_write(ring,upper_32_bits(addr));// fence 地址高amdgpu_ring_write(ring,lower_32_bits(seq));// 序号/* (64 位 fence 时再写一遍高 32 位,略)*//* ② 触发中断:TRAP */amdgpu_ring_write(ring,SDMA_PKT_HEADER_OP(SDMA_OP_TRAP));amdgpu_ring_write(ring,SDMA_PKT_TRAP_INT_CONTEXT_INT_CONTEXT(0));回顾第 2 章的硬件模型:TRAP 触发的正是trap_irq,驱动在中断里推进 fence、唤醒等待者。FENCE(可查询)+TRAP(主动通知)就是 GPU 任务完成通知的标准组合拳。
3.5POLL_REGMEM:等待某个条件成立
有时命令流需要"等到某寄存器/内存等于某值再继续",比如 HDP cache flush。这用POLL_REGMEM(轮询寄存器或内存)实现:
// sdma_v4_0.c: sdma_v4_0_wait_reg_mem()(节选)amdgpu_ring_write(ring,SDMA_PKT_HEADER_OP(SDMA_OP_POLL_REGMEM)|SDMA_PKT_POLL_REGMEM_HEADER_HDP_FLUSH(hdp)|SDMA_PKT_POLL_REGMEM_HEADER_MEM_POLL(mem_space)|SDMA_PKT_POLL_REGMEM_HEADER_FUNC(3));/* 3 == "等于" */// ... 地址、reference(期望值)、mask、retry/interval ...它内建"比较函数"(这里FUNC(3)表示==)、期望值、掩码和重试间隔,硬件会自旋等待直到条件满足。emit_hdp_flush、emit_vm_flush(TLB 失效等待)都用它做同步。
3.6 与页表相关的 opcode:COPY与PTEPDE(预告)
除了上面这些通用命令,SDMA 还有专门服务页表更新的 opcode,本章先点名,第 12 章展开:
COPY:批量拷贝,vm_copy_pte用它把一批 PTE 从暂存区拷进页表。WRITE:逐条写 PTE(就是 3.2 那个 WRITE,vm_write_pte会用到,支持递增地址)。PTEPDE:页表专用,vm_set_pte_pde用它对连续页表项做批量、带掩码的更新,一条命令顶很多条 WRITE。
这三条正是 SDMA 作为"页表更新执行者"的指令基础。
3.7 常用 opcode 速查(本章出现的)
| opcode | 作用 | 出现在 |
|---|---|---|
SDMA_OP_WRITE | 往内存写数据 | test_ring、vm_write_pte |
SDMA_OP_COPY | 内存拷贝 | emit_copy_buffer、vm_copy_pte |
SDMA_OP_INDIRECT | 执行一个 IB | emit_ib |
SDMA_OP_FENCE | 写 fence 序号 | emit_fence |
SDMA_OP_TRAP | 触发中断 | emit_fence |
SDMA_OP_POLL_REGMEM | 轮询等待条件 | hdp_flush、vm_flush |
SDMA_OP_PTEPDE | 批量更新连续页表项 | vm_set_pte_pde |
完整版见 附录 B 常用 SDMA opcode 速查表。
3.8 本章小结
- SDMA 每条命令 =header(OP + SUB_OP + 标志)+ 若干参数 dword,用
amdgpu_ring_write逐 dword 写入 ring。 WRITE(3.2 的写魔数)是理解 packet 的最小闭环,务必吃透。INDIRECT把 ring 引到更大的IB去执行,并携带 VMID。FENCE + TRAP是任务完成通知的标准组合:前者可查询、后者发中断。POLL_REGMEM用于命令流内的等待/同步。COPY/WRITE/PTEPDE是页表更新的指令基础,为第 12 章埋下伏笔。
下一章预告:硬件三章到此结束。从第 4 章起进入软件层,我们先总览 SDMA 在驱动里的数据结构——amdgpu_sdma_instance与amdgpu_sdma——看这些 packet、队列、指针在 C 结构体里是如何被组织起来的。