6.5 命令提交:amd_command_submission 把 IB 送进内核并等回结果
前面几篇准备好了内存6.3、拼好了 PM46.4。这一篇解决 6.1 的第三个难题一次提交有大量固定流程——分配 IB BO、拷贝 PM4、建 BO list、创建请求、submit、等 fence、清理。amd_command_submission.{c,h}把这套流程封装成一个核心函数amdgpu_test_exec_cs_helper外加一层更结构化的cmd_context_tAPI。这一篇讲清楚提交的完整链路、amdgpu_ring_context里哪些字段是输入/输出、以及为什么错误处理要容忍一堆 reset 相关的 errno。一、一次命令提交包含的信息amdgpu_cs_submit需要三样东西一个 context一个描述 IB 的ib_infoGPU 地址 dword 数一个列出本次引用了哪些 BO 的 BO list。提交后拿到一个seq_no再用它查 fence 状态等完成。整条链路是分配 IB BOamdgpu_bo_alloc_and_map_sync (GTT)拷贝调用方的 PM4memcpy(ib_cpu, ring_context-pm4, pm4_dw)填 ib_infoib_mc_address sizepm4_dw建 BO listresources[] IB BOamdgpu_cs_submit→ seq_noamdgpu_cs_query_fence_status等 fence释放 IB BO / 销毁 BO list注意一个设计PM4 是调用方拼好的IB BO 是提交时才现分配的。ring_context-pm4只是一段普通内存里的命令helper 分配一块 GTT 的 IB BO把 PM4 拷进去再用这块 BO 的 GPU 地址提交。这样调用方不必自己管 IB BO 的生命周期问题就是多一次拷贝。二、核心函数amdgpu_test_exec_cs_helper签名很简单一切都从ring_context里读结果也写回ring_contextintamdgpu_test_exec_cs_helper(amdgpu_device_handle device,unsignedintip_type,structamdgpu_ring_context*ring_context,intexpect_failure);它的普通队列非 user-queue路径核心是/* 分配 IB把调用方 PM4 拷进去 */ramdgpu_bo_alloc_and_map_sync(device,ring_context-write_length,4096,AMDGPU_GEM_DOMAIN_GTT,0,AMDGPU_VM_MTYPE_UC,...);ring_ptrib_result_cpu;memcpy(ring_ptr,ring_context-pm4,ring_context-pm4_dw*sizeof(*ring_context-pm4));/* 填 IB 描述 */ring_context-ib_info.ib_mc_addressib_result_mc_address;ring_context-ib_info.sizering_context-pm4_dw;ring_context-ibs_request.ip_typeip_type;ring_context-ibs_request.ringring_context-ring_id;ring_context-ibs_request.number_of_ibs1;ring_context-ibs_request.ibsring_context-ib_info;/* BO list 调用方 resources[] 这块 IB BO */memcpy(all_res,ring_context-resources,sizeof(ring_context-resources[0])*ring_context-res_cnt);all_res[ring_context-res_cnt]ib_result_handle;ramdgpu_bo_list_create(device,ring_context-res_cnt1,all_res,NULL,ring_context-ibs_request.resources);/* 提交 */ramdgpu_cs_submit(ring_context-context_handle,0,ring_context-ibs_request,1);.../* 等 fence */ramdgpu_cs_query_fence_status(fence_status,AMDGPU_TIMEOUT_INFINITE,0,expired);all_res是用alloca现开的临时数组大小是res_cnt 1——把调用方登记的资源和这块新 IB BO 合到一起。所以调用方只要在resources[]/res_cnt里放它的数据 BOIB BO 由 helper 自动补上。三、ring_context一个大杂烩输入/输出结构struct amdgpu_ring_context是提交路径的「参数包」。理解它就理解了 helper 的输入契约。关键字段分三类类别字段说明输入命令pm4/pm4_dw调用方拼好的 PM4 及其实际 dword 数write_length这里被复用为 IB BO 的分配字节数输入资源与目标resources[4]/res_cnt本次提交要登记的数据 BOIB BO 由 helper 追加context_handle/ring_id提交用的 context 与 ringip_type参数GFX / COMPUTE / DMA输出ibs_request.seq_no提交后内核回填的序列号err_codes.err_code_cs_submitsubmit 返回值err_codes.err_code_wait_for_fencefence 查询返回值它还内嵌了 user-queue 相关的一大堆资源queue/doorbell/wptr/rptr…那是另一条提交路径UQ用的普通提交用不到。四、两条提交路径普通队列 vs user queuering_context-user_queue决定走哪条路。helper 内部据此分叉falsetruering_context-user_queue?内核队列路径bo_list_create cs_submit query_fence_status用户队列路径ip_block-funcs-userq_submit(...)timeline syncobj 等待内核队列传统就是上面那段amdgpu_cs_submitamdgpu_cs_query_fence_status。用户队列UQIB BO 用_sync变体分配带 timeline syncobj提交交给ip_block-funcs-userq_submit完成靠amdgpu_timeline_syncobj_wait。expect_failure时会切到UQ_SUBMIT_NO_SYNC供错误注入测试用。五、为什么错误处理要「容忍」一堆 errno这段很反直觉但很重要。正常提交的成功判断不是简单igt_assert_eq(r, 0)/* a good job may be collateral-cancelled by a concurrent * queue reset; tolerate every reset-induced error */if(r!-ECANCELEDr!-ENODATAr!-EHWPOISONr!-ETIME)igt_assert_eq(r,0);原因在并发跑 reset/hang 测试的环境里一个本来没问题的 job 可能被另一条队列的 reset「连带取消」。这时 submit 或 fence 会返回-ECANCELED/-ENODATA/-EHWPOISON/-ETIME。把这些当致命错误会造成大量假失败所以这里显式放行——只有出现这四种之外的错误才断言。写并发/reset 相关测试时要记住这个约定。六、更结构化的一层cmd_context_t API除了「一发入魂」的amdgpu_test_exec_cs_helper头文件还提供了一套面向对象味道更浓的 API适合需要反复提交、或复用外部 BO 的场景cmd_context_t*cmd_context_create(amdgpu_device_handle device,enumamd_ip_block_typeip_type,uint32_tring_id,bool user_queue,uint32_twrite_length,amdgpu_bo_handle external_bo,uint64_texternal_bo_mc,volatileuint32_t*external_bo_cpu);intcmd_place_packet(cmd_context_t*ctx,constcmd_packet_params_t*params);intcmd_submit_packet(cmd_context_t*ctx);intcmd_place_and_submit_packet(cmd_context_t*ctx,constcmd_packet_params_t*params);intcmd_wait_completion(cmd_context_t*ctx);/* 便捷封装 */intcmd_submit_write_linear(cmd_context_t*ctx,uint64_tdst,uint32_tsize,uint32_tdata);intcmd_submit_copy_linear(cmd_context_t*ctx,uint64_tsrc,uint64_tdst,uint32_tsize);intcmd_submit_atomic(cmd_context_t*ctx,uint64_tdst,uint32_tdata);voidcmd_context_destroy(cmd_context_t*ctx,bool destroy_external_bo);cmd_packet_params_t里用cmd_packet_type_tWRITE_LINEAR/COPY_LINEAR/ATOMIC/FENCE/TIMESTAMP描述要发什么包。这套 API 把「拼包 提交 等待」拆成可分别调用的步骤还能通过external_bo复用调用方已分配的 BOuses_external_bo标记谁负责释放。七、还提供了 write/copy/fill 的高层 helper再往上还有一批直接「建 context 拼包 提交 校验」一条龙的测试级 helper测试用例常常直接调它们amdgpu_command_submission_write_linear_helper/..._helper2amdgpu_command_submission_const_fill_helperamdgpu_command_submission_copy_linear_helper它们内部会用ip_block-funcs-write_linear/const_fill/copy_linear就是 6.2 那张操作表拼 PM4再走amdgpu_test_exec_cs_helper提交。所以一个write_linear测试从上到下正好穿过helper → ip_funcs 拼包 → exec_cs_helper 提交。八、关键结论提交需要三样context、描述 IB 的ib_info、BO listamdgpu_test_exec_cs_helper把「分配 IB → 拷 PM4 → 建 list → submit → 等 fence → 清理」全包了。PM4 由调用方拼好放在ring_context-pm4IB BO 是提交时现分配的调用方只需在resources[]/res_cnt里放数据 BOIB BO 由 helper 追加进 list。user_queue字段决定走内核队列还是 UQ 路径UQ 用 timeline syncobj 完成同步。成功判断要容忍-ECANCELED/-ENODATA/-EHWPOISON/-ETIME——并发 reset 会连带取消本来正常的 job否则会造成假失败。需要反复提交或复用外部 BO 时用cmd_context_t那套分步 API简单一次性提交用amdgpu_test_exec_cs_helper或更高层的 write/copy/fill helper。