DF-4924 / df4924_trigger.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | /* * DF-4924 harness โ gfx_v9_0 gfx-ring frame-reservation undercount for the * TMZ FRAME_CONTROL emission consumed by amdgpu_ib_schedule(). * * Arithmetic/write-pattern replica (same method as the DF-4864 evidence * pack): it reproduces, with the EXACT constants from the guest's kernel * source, the reservation math of amdgpu_ib_schedule() * (sys/dev/drm/amd/amdgpu/amdgpu_ib.c:159-166) and the ring-write * discipline of amdgpu_ring_write() (amdgpu_ring.h:276-283), and shows that * for gfx-ring submissions with num_ibs == 24 (mod 64) the emission stream * exceeds the ring allocation by exactly 2 dwords (the unaccounted * gfx_v9_0_ring_emit_tmz FRAME_CONTROL packet: gfx_v9_0.c:4780 + * amdgpu_ib.c:231-232). * * Constants transcribed from the tree (path:line in comments): * emit_frame_size (gfx_v9_0.c:4745-4763) = 160 dw (worst case, all terms) * emit_ib_size (gfx_v9_0.c:4765) = 4 dw * align_mask (gfx_v9_0.c:4739) = 0xff -> ring_alloc aligns to 256 dw * emit_tmz writes (gfx_v9_0.c gfx_v9_0_ring_emit_tmz) = 2 dw * SOC15_FLUSH_GPU_TLB_NUM_WREG = 4 (soc15.h:31) * SOC15_FLUSH_GPU_TLB_NUM_REG_WAIT = 1 (soc15.h:32) * * Build: cc -O2 -o df4924_trigger df4924_trigger.c * Run: ./df4924_trigger * Success criterion: baseline destroys exactly 2 canary dwords iff * num_ibs % 64 == 24; fixed accounting destroys none for any num_ibs. */ #include <stdio.h> #include <string.h> #include <stdint.h> /* --- transcribed kernel constants ------------------------------------ */ /* gfx_v9_0.c:4745-4763 emit_frame_size, term by term */ #define T_COND_EXEC_1 5 /* COND_EXEC */ #define T_PIPELINE_SYNC 7 /* PIPELINE_SYNC */ #define SOC15_NUM_WREG 4 /* soc15.h:31 */ #define SOC15_NUM_REG_WAIT 1 /* soc15.h:32 */ #define T_WREG_PER 5 #define T_REGWAIT_PER 7 #define T_VM_FLUSH 2 #define T_FENCE_VM_FLUSH 8 #define T_GDS_SWITCH 20 #define T_DOUBLE_SB 4 #define T_COND_EXEC_2 5 #define T_HDP_FLUSH 7 #define T_VGT_FLUSH 4 #define T_CE_META 14 #define T_DE_META 31 #define T_CNTX_CTRL 3 #define T_HDP_INVL 5 #define T_FENCE_X2 (8 + 8) /* uf fence + real fence */ #define T_SWITCH_BUFFER 2 #define T_TMZ_UNACCOUNTED 2 /* gfx_v9_0_ring_emit_tmz: NOT in sum */ #define EMIT_IB_SIZE 4 /* gfx_v9_0.c:4765 */ #define ALIGN_MASK 0xff /* gfx_v9_0.c:4739 */ #define TMZ_ENABLED 1 /* gfx_v9_0.c:4780 .emit_tmz is set */ static unsigned frame_size(int with_tmz_fix) { unsigned f = T_COND_EXEC_1 + T_PIPELINE_SYNC + SOC15_NUM_WREG * T_WREG_PER + SOC15_NUM_REG_WAIT * T_REGWAIT_PER + T_VM_FLUSH + T_FENCE_VM_FLUSH + T_GDS_SWITCH + T_DOUBLE_SB + T_COND_EXEC_2 + T_HDP_FLUSH + T_VGT_FLUSH + T_CE_META + T_DE_META + T_CNTX_CTRL + T_HDP_INVL + T_FENCE_X2 + T_SWITCH_BUFFER; if (with_tmz_fix) f += T_TMZ_UNACCOUNTED; return f; } /* amdgpu_ring_alloc (amdgpu_ring.c:64-76): align up; result is count_dw */ static unsigned ring_alloc(unsigned frame, unsigned num_ibs) { unsigned ndw = frame + num_ibs * EMIT_IB_SIZE; return (ndw + ALIGN_MASK) & ~ALIGN_MASK; } /* * amdgpu_ring_write (amdgpu_ring.h:276-283): logs an error when count_dw * <= 0 but writes the dword anyway. Emulated against a canaried buffer * exactly alloc_dw dwords long, canary zone at/after alloc_dw. */ #define MAXBUF 4096 static uint32_t ringbuf[MAXBUF]; static int count_dw, wptr, writes_past_end; static void ring_write(unsigned v, unsigned alloc_dw) { if (count_dw <= 0) writes_past_end++; /* the DRM_ERROR path โ write happens */ ringbuf[wptr++] = v; count_dw--; } /* one worst-case gfx CS submission; emission totals per amdgpu_ib_schedule */ static int submit(unsigned num_ibs, int with_tmz_fix) { unsigned frame = frame_size(with_tmz_fix); unsigned alloc_dw = ring_alloc(frame, num_ibs); unsigned emitted = 0, i, destroyed = 0; if (alloc_dw + 8 > MAXBUF) return -1; memset(ringbuf, 0xCA, sizeof(ringbuf)); /* 0xCACACACA canaries */ count_dw = (int)alloc_dw; wptr = 0; writes_past_end = 0; emitted += T_COND_EXEC_1; /* ib.c:194-195 init_cond_exec */ emitted += T_PIPELINE_SYNC; /* ib.c:169-181 */ emitted += SOC15_NUM_WREG * T_WREG_PER + SOC15_NUM_REG_WAIT * T_REGWAIT_PER + T_VM_FLUSH + T_FENCE_VM_FLUSH + T_VGT_FLUSH; /* vm_flush */ emitted += T_DOUBLE_SB; /* vm_flush double SB */ emitted += T_COND_EXEC_2; /* vm.c cond_exec */ emitted += T_HDP_FLUSH; /* ib.c:201-204 */ emitted += T_CNTX_CTRL + T_CE_META + T_DE_META; /* ib.c:208-214 */ for (i = 0; i < num_ibs; i++) /* ib.c:216-229 */ emitted += EMIT_IB_SIZE; if (TMZ_ENABLED) emitted += T_TMZ_UNACCOUNTED; /* ib.c:231-232 โ the bug */ emitted += T_HDP_INVL; /* ib.c:234-237 */ emitted += 8; /* uf fence ib.c:243-246 */ emitted += 8; /* real fence ib.c:248 */ emitted += T_GDS_SWITCH + T_SWITCH_BUFFER; /* gds + SB tail */ for (i = 0; i < emitted; i++) ring_write(0xDEADBEEF, alloc_dw); for (i = alloc_dw; i < alloc_dw + 8; i++) /* canary check */ if (ringbuf[i] != 0xCACACACA) destroyed++; if (destroyed || writes_past_end) printf("num_ibs=%3u reserved=%4u emitted=%4u writes_past_count=%d canary_dwords_destroyed=%d\n", num_ibs, alloc_dw, emitted, writes_past_end, destroyed); return destroyed ? 1 : 0; } int main(void) { int n, hits = 0, with_fix = 0; int hit_n[64]; int nhit = 0; printf("== baseline accounting (tree as-is: TMZ NOT in emit_frame_size) ==\n"); printf("frame_size=%u emit_ib_size=%u align_mask=0x%x tmz=+%u dw\n", frame_size(0), EMIT_IB_SIZE, ALIGN_MASK, T_TMZ_UNACCOUNTED); for (n = 1; n <= 200; n++) if (submit(n, 0) == 1) { hits++; if (nhit < 64) hit_n[nhit++] = n; } printf("baseline: %d overrun submission(s) in num_ibs 1..200: ", hits); for (n = 0; n < nhit; n++) printf("%d ", hit_n[n]); printf("\nrule: reserved = ALIGN(160 + 4*n, 256); emitted = 160 + 4*n + 2\n"); printf(" overrun iff (160 + 4*n) %% 256 == 0 iff n %% 64 == 24\n"); for (n = 1; n <= 200; n++) if (submit(n, 1) == 1) with_fix++; printf("== fixed accounting (emit_frame_size += 2 for TMZ) ==\n"); printf("fixed: %d overrun submission(s) in num_ibs 1..200 (expect 0)\n", with_fix); if (hits > 0 && with_fix == 0 && nhit >= 3 && hit_n[0] == 24 && hit_n[1] == 88 && hit_n[2] == 152) { printf("VERDICT: baseline overruns by exactly 2 dwords iff " "num_ibs %% 64 == 24; fix eliminates all. REPLICATED.\n"); return 0; } printf("VERDICT: unexpected result.\n"); return 1; } |