code wiki / _hdl_build / _gpu_dxg_submit_gate.nx
_gpu_dxg_submit_gate.nx source
↩ module page · 243 lines · 14605 B
1// _gpu_dxg_submit_gate.nx -- SOVEREIGN-GPU ladder R5 (a REAL command submission ACCEPTED by the 5080).
2//
3// R4f pinned allocations resident. This rung drives LX_DXSUBMITCOMMAND itself: a well-formed submission
4// carrying a mapped, resident pushbuffer GPU-VA and a resolved context, accepted by dxgkrnl on the real
5// discrete RTX 5080 -- wrapper-free, no libcuda / libvulkan / libd3d12 / libdxcore / libc.
6//
7// THE ABI WAS MEASURED, NOT GUESSED. Two recon sweeps established it against the live device:
8// * ioctl SIZE: sweeping the size field with broadcast_context_count=0 (which cannot start GPU work, so
9// it cannot TDR), exactly ONE size answers -EINVAL (recognised, contents rejected) while every other
10// size answers -ENOTTY (unknown ioctl). That size is 384 => SUBMIT_CODE 0xC180470F.
11// * FIELD OFFSETS: with command_length=0 (a no-op submit: well-formed, the GPU executes 0 bytes), only
12// the layout whose count and context fields are both correct lets the context RESOLVE and returns 0;
13// every wrong layout leaves the count zero or the context unresolved and returns -EINVAL. Exactly one
14// of five candidate layouts answered 0: broadcast_context_count@0x18, broadcast_context[0]@0x1c.
15//
16// GREEN iff (author=organ, every value from a REAL device return):
17// A) the full chain resolves: enum -> discrete adapter -> device -> context -> paging queue ->
18// EXISTINGHEAP allocation -> GPU VA -> resident;
19// B) SUBMIT ret == 0 (dxgkrnl ACCEPTED the submission);
20// C) the accept is REPEATABLE -- the mechanism, not a one-shot;
21// D) TAMPER matrix, each from a DIFFERENT real rule, every one differing from the real ret==0:
22// T1 count at the WRONG offset (0x14) -> context never resolves;
23// T2 bogus context handle -> real validation rejects;
24// T3 correct struct but the WRONG ioctl size (392) -> -ENOTTY from the size-encoded command number;
25// T4 the same well-formed submit on a NON-dxg fd (/dev/null) -> -ENOTTY.
26// T3 and T4 together are what stop "any ioctl returns 0" from passing as an accept.
27// E) a POSITIVE CONTROL after the tampers: the channel still accepts, so this is not a guard that
28// refuses everything once poked.
29//
30// SCOPE -- READ THIS BEFORE QUOTING THE GREEN. command_length is 0, so THE GPU EXECUTES ZERO BYTES. This
31// rung proves the SUBMISSION CHANNEL accepts a well-formed command with a resolved context and a resident
32// pushbuffer. It proves NO throughput, NO kernel execution, and NO speedup, because a zero-length buffer
33// contains no instructions. Running actual work needs NVIDIA Blackwell ISA (SASS) encoded into that
34// pushbuffer -- that is the next rung and it is NOT claimed here. A green here that was read as "we run
35// compute on the 5080" would be exactly the false-green this gate is shaped to prevent.
36//
37// ⚠RUN IT ON THE HOST THAT HOLDS THE GPU. The build root has no /dev/dxg; the gate reports UNOBSERVABLE
38// and exits CLEAN there, so a RED from this gate always means the GPU path actually broke.
39// license_tier: ORIGINAL expect_exit: 0
40import "nx_syscalls.nx"
41import "nx_gate_verdict.nx"
42
43const ENUM2_CODE: i64 = 0xC0104714
44const QAI_CODE: i64 = 0xC0184709
45const OAFL_CODE: i64 = 0xC00C4701
46const CDEV_CODE: i64 = 0xC0404702
47const CCV_CODE: i64 = 0xC0284704
48const CPQ_CODE: i64 = 0xC0204707
49const CALLOC_CODE: i64 = 0xC0484706
50const MAPVA_CODE: i64 = 0xC068470C
51const MKRES_CODE: i64 = 0xC030470B
52// nr=0x0f with the struct size encoded in the command number. 384 is the ONLY size dxgkrnl recognises.
53const SUBMIT_CODE: i64 = 0xC180470F
54// the same nr with size=392 -- a real command number that dxgkrnl does NOT know, so it answers -ENOTTY.
55const SUBMIT_BADSIZE_CODE: i64 = 0xC188470F
56// measured field offsets inside d3dkmt_submitcommand
57const SUB_COUNT_OFF: i64 = 0x18
58const SUB_CTX_OFF: i64 = 0x1c
59const SUB_BAD_COUNT_OFF: i64 = 0x14
60const EXISTINGHEAP_FLAGS: i64 = 0x10020
61const HEAP_BYTES: i64 = 65536
62const GPU_VA_BASE: i64 = 0x10000000000
63
64func rd32(buf: *u8, off: i64) -> i64 { return (buf[off] as i64)|((buf[off+1] as i64)<<8)|((buf[off+2] as i64)<<16)|((buf[off+3] as i64)<<24) }
65func rd64(buf: *u8, off: i64) -> i64 { let q: *i64 = (buf as i64 + off) as *i64; return q[0] }
66func setu32(buf: *u8, off: i64, val: i64) -> i64 { let w: *u8 = (buf as i64 + off) as *u8; w[0]=(val&0xff) as u8; w[1]=((val>>8)&0xff) as u8; w[2]=((val>>16)&0xff) as u8; w[3]=((val>>24)&0xff) as u8; return 0 }
67func setu64(buf: *u8, off: i64, val: i64) -> i64 { let q: *i64 = (buf as i64 + off) as *i64; q[0]=val; return 0 }
68func zerobuf(n: i64) -> *u8 { let b: *u8 = sys_mmap(n); var i: i64 = 0; while i < n { b[i] = 0 as u8; i = i + 1 } return b }
69
70func query_type(fd: i64, handle: i64, qtype: i64, psize: i64, outv: *i64) -> i64 {
71 let priv: *u8 = zerobuf(64)
72 let req: *u8 = zerobuf(64)
73 let h: *i64 = (req as i64 + 0) as *i64; h[0] = (handle & 0xffffffff) | ((qtype & 0xffffffff) << 32)
74 let pd: *i64 = (req as i64 + 8) as *i64; pd[0] = priv as i64
75 let pds: *i64 = (req as i64 + 16) as *i64; pds[0] = psize & 0xffffffff
76 let ret: i64 = sys_ioctl(fd, QAI_CODE, req as i64); outv[0] = rd32(priv, 0); return ret
77}
78func open_from_luid(fd: i64, luid_lo: i64, luid_hi: i64, outh: *i64) -> i64 {
79 let req: *u8 = zerobuf(64)
80 let lo: *i64 = (req as i64 + 0) as *i64; lo[0] = (luid_lo & 0xffffffff) | ((luid_hi & 0xffffffff) << 32)
81 let ret: i64 = sys_ioctl(fd, OAFL_CODE, req as i64); outh[0] = rd32(req, 8); return ret
82}
83func create_device(fd: i64, adapter: i64, outd: *i64) -> i64 {
84 let buf: *u8 = zerobuf(512)
85 setu32(buf, 0, adapter & 0xffffffff)
86 let ret: i64 = sys_ioctl(fd, CDEV_CODE, buf as i64); outd[0] = rd32(buf, 12); return ret
87}
88func create_ctx(fd: i64, device: i64, outc: *i64) -> i64 {
89 let buf: *u8 = zerobuf(512)
90 setu32(buf, 0, device & 0xffffffff)
91 let ret: i64 = sys_ioctl(fd, CCV_CODE, buf as i64); outc[0] = rd32(buf, 32); return ret
92}
93func create_pq(fd: i64, device: i64, outpq: *i64, outfva: *i64) -> i64 {
94 let buf: *u8 = zerobuf(512)
95 setu32(buf, 0, device & 0xffffffff)
96 let ret: i64 = sys_ioctl(fd, CPQ_CODE, buf as i64); outpq[0] = rd32(buf, 8); outfva[0] = rd64(buf, 16); return ret
97}
98func alloc_eh(fd: i64, device: i64, sysmem: *u8, outh: *i64) -> i64 {
99 let desc: *u8 = zerobuf(64)
100 setu32(desc, 0, 1); setu64(desc, 8, HEAP_BYTES)
101 let entry: *u8 = zerobuf(256)
102 setu64(entry, 8, sysmem as i64)
103 let buf: *u8 = zerobuf(512)
104 setu32(buf, 0, device & 0xffffffff); setu64(buf, 32, desc as i64); setu32(buf, 40, 0)
105 setu32(buf, 44, 1); setu64(buf, 48, entry as i64); setu32(buf, 56, EXISTINGHEAP_FLAGS)
106 let ret: i64 = sys_ioctl(fd, CALLOC_CODE, buf as i64); outh[0] = rd32(entry, 0); return ret
107}
108func map_va(fd: i64, pq: i64, alloc: i64, outva: *i64) -> i64 {
109 let buf: *u8 = zerobuf(256)
110 setu32(buf, 0, pq & 0xffffffff); setu64(buf, 24, GPU_VA_BASE)
111 setu32(buf, 32, alloc & 0xffffffff); setu64(buf, 48, 16); setu64(buf, 56, 1)
112 let ret: i64 = sys_ioctl(fd, MAPVA_CODE, buf as i64); outva[0] = rd64(buf, 88); return ret
113}
114func makeresident(fd: i64, pq: i64, alloc_handle: i64) -> i64 {
115 let alist: *u8 = zerobuf(64); setu32(alist, 0, alloc_handle & 0xffffffff)
116 let plist: *u8 = zerobuf(64)
117 let buf: *u8 = zerobuf(256)
118 setu32(buf, 0, pq & 0xffffffff); setu32(buf, 4, 1); setu64(buf, 8, alist as i64); setu64(buf, 16, plist as i64)
119 return sys_ioctl(fd, MKRES_CODE, buf as i64)
120}
121// one submission. count_off/ctx_off/code are parameters so the tamper matrix drives the SAME code path
122// the real call uses -- a tamper that took a different path would prove nothing about this one.
123func submit_at(fd: i64, code: i64, cmdbuf: i64, len: i64, count_off: i64, ctx_off: i64, context: i64) -> i64 {
124 let sb: *u8 = zerobuf(1024)
125 setu64(sb, 0, cmdbuf); setu32(sb, 8, len)
126 setu32(sb, count_off, 1); setu32(sb, ctx_off, context & 0xffffffff)
127 return sys_ioctl(fd, code, sb as i64)
128}
129
130func main() -> i64 {
131 let ctr: *i64 = gv_ctr()
132 gv_head("_gpu_dxg_submit_gate -- R5: a real command submission ACCEPTED by the discrete RTX 5080 via raw /dev/dxg" as *u8)
133
134 // ★THE THIRD STATE. This gate's subject is a PHYSICAL DEVICE, and most hosts in the fleet do not have
135 // one -- the NAS that builds this has no /dev/dxg at all. Counting that as a failed tooth would make
136 // the gate permanently RED everywhere except one laptop, and a detector that is permanently red is one
137 // everyone learns to ignore. "I could not look" is not "it is broken": absent hardware exits CLEAN and
138 // says UNOBSERVABLE, so a RED from this gate always means the GPU path actually broke.
139 let fd: i64 = sys_openat_rd("/dev/dxg" as *u8)
140 if fd < 0 {
141 gv_puts(" UNOBSERVABLE: no /dev/dxg on this host -- there is no GPU paravirt device to submit to.\n" as *u8)
142 gv_puts(" This is NOT a failure: the subject is absent, so the rung is unjudged rather than broken.\n" as *u8)
143 gv_puts("NX-GPU-DXG-SUBMIT-GATE UNOBSERVABLE verdict=GREEN (subject absent: no /dev/dxg; run this on the host that holds the discrete GPU)\n" as *u8)
144 sys_exit(0)
145 return 0
146 }
147 gv_check("T1 /dev/dxg opens (the WSL dxgkrnl last-mile is present)" as *u8, 1, ctr)
148
149 let ainfo: *u8 = zerobuf(4096)
150 let ereq: *u8 = zerobuf(64); ereq[0] = 8 as u8
151 let r8: *i64 = (ereq as i64 + 8) as *i64; r8[0] = ainfo as i64
152 let eret: i64 = sys_ioctl(fd, ENUM2_CODE, ereq as i64)
153 let nc: i64 = rd32(ereq, 0)
154 let v: *i64 = sys_mmap(16)
155 var n_discrete: i64 = 0
156 var disc_lo: i64 = 0
157 var disc_hi: i64 = 0
158 var ai: i64 = 0
159 while ai < nc {
160 let base: i64 = ai * 20
161 let eh: i64 = rd32(ainfo, base)
162 let qret: i64 = query_type(fd, eh, 15, 4, v)
163 let t: i64 = v[0]
164 if qret == 0 { if ((t>>4)&1) == 1 { if ((t>>2)&1) == 0 {
165 n_discrete = n_discrete + 1
166 disc_lo = rd32(ainfo, base+4)
167 disc_hi = rd32(ainfo, base+8)
168 } } }
169 ai = ai + 1
170 }
171 let ah: *i64 = sys_mmap(16); let aor: i64 = open_from_luid(fd, disc_lo, disc_hi, ah)
172 let dh: *i64 = sys_mmap(16); let dr: i64 = create_device(fd, ah[0], dh)
173 let device: i64 = dh[0]
174 let chh: *i64 = sys_mmap(16); let cr: i64 = create_ctx(fd, device, chh)
175 let context: i64 = chh[0]
176 let pqh: *i64 = sys_mmap(16); let fvah: *i64 = sys_mmap(16)
177 let pqr: i64 = create_pq(fd, device, pqh, fvah)
178 let pq: i64 = pqh[0]
179 let sm_pb: *u8 = zerobuf(HEAP_BYTES)
180 let pbh: *i64 = sys_mmap(16); let ar: i64 = alloc_eh(fd, device, sm_pb, pbh)
181 let pb_alloc: i64 = pbh[0]
182 let pbva: *i64 = sys_mmap(16); let mr: i64 = map_va(fd, pq, pb_alloc, pbva)
183 let pb_va: i64 = pbva[0]
184 let rr: i64 = makeresident(fd, pq, pb_alloc)
185
186 gv_puts(" chain: enum=" as *u8); gv_num(eret)
187 gv_puts(" discrete=" as *u8); gv_num(n_discrete)
188 gv_puts(" adapter_open=" as *u8); gv_num(aor)
189 gv_puts(" device=" as *u8); gv_num(dr)
190 gv_puts(" context=" as *u8); gv_num(cr)
191 gv_puts(" paging_queue=" as *u8); gv_num(pqr)
192 gv_puts(" alloc=" as *u8); gv_num(ar)
193 gv_puts(" map_va=" as *u8); gv_num(mr)
194 gv_puts(" resident=" as *u8); gv_num(rr); gv_puts("\n" as *u8)
195
196 var chain: i64 = 0
197 if eret == 0 { if n_discrete == 1 { if aor == 0 { if dr == 0 { if cr == 0 { if pqr == 0 {
198 if ar == 0 { if device != 0 { if context != 0 { if pq != 0 { if pb_alloc != 0 { if pb_va != 0 {
199 chain = 1
200 } } } } } } } } } } } }
201 gv_check("T2 the full chain resolves to a mapped, resident pushbuffer on the discrete 5080" as *u8, chain, ctr)
202
203 // ---- the real submissions ----
204 let s1: i64 = submit_at(fd, SUBMIT_CODE, pb_va, 0, SUB_COUNT_OFF, SUB_CTX_OFF, context)
205 let s2: i64 = submit_at(fd, SUBMIT_CODE, pb_va, 0, SUB_COUNT_OFF, SUB_CTX_OFF, context)
206 gv_puts(" SUBMIT 1 ret=" as *u8); gv_num(s1)
207 gv_puts(" SUBMIT 2 ret=" as *u8); gv_num(s2)
208 gv_puts(" (command_length=0 -- the GPU executes ZERO bytes; this is a channel accept, not work)\n" as *u8)
209 var accepted: i64 = 0
210 if s1 == 0 { accepted = 1 }
211 gv_check("T3 dxgkrnl ACCEPTS a well-formed submission with a resolved context (ret 0)" as *u8, accepted, ctr)
212 var repeatable: i64 = 0
213 if s1 == 0 { if s2 == 0 { repeatable = 1 } }
214 gv_check("T4 the accept is REPEATABLE -- a mechanism, not a one-shot" as *u8, repeatable, ctr)
215
216 // ---- tamper matrix: four different real rules, all must differ from the accept ----
217 let t1: i64 = submit_at(fd, SUBMIT_CODE, pb_va, 0, SUB_BAD_COUNT_OFF, SUB_CTX_OFF, context)
218 let t2: i64 = submit_at(fd, SUBMIT_CODE, pb_va, 0, SUB_COUNT_OFF, SUB_CTX_OFF, 0xdeadbeef)
219 let t3: i64 = submit_at(fd, SUBMIT_BADSIZE_CODE, pb_va, 0, SUB_COUNT_OFF, SUB_CTX_OFF, context)
220 let nfd: i64 = sys_openat_rd("/dev/null" as *u8)
221 let t4: i64 = submit_at(nfd, SUBMIT_CODE, pb_va, 0, SUB_COUNT_OFF, SUB_CTX_OFF, context)
222 if nfd >= 0 { sys_close(nfd) }
223 gv_puts(" [tamper] wrong-count-offset=" as *u8); gv_num(t1)
224 gv_puts(" | bogus-context=" as *u8); gv_num(t2)
225 gv_puts(" | wrong-ioctl-size=" as *u8); gv_num(t3)
226 gv_puts(" | non-dxg-fd=" as *u8); gv_num(t4); gv_puts("\n" as *u8)
227 var tamper: i64 = 0
228 if t1 != 0 { if t2 != 0 { if t3 != 0 { if t4 != 0 { tamper = 1 } } } }
229 gv_check("T5 all four tampers are REJECTED -- the ret 0 is a real device accept, not any-ioctl-returns-0" as *u8, tamper, ctr)
230
231 // A positive control on the tamper axis: the SAME code path, correct again, must still accept. Without
232 // it, a guard that refused everything after the first submit would pass T5 and mean nothing.
233 let s3: i64 = submit_at(fd, SUBMIT_CODE, pb_va, 0, SUB_COUNT_OFF, SUB_CTX_OFF, context)
234 gv_puts(" POSITIVE CONTROL after the tampers: submit ret=" as *u8); gv_num(s3); gv_puts("\n" as *u8)
235 var recovered: i64 = 0
236 if s3 == 0 { recovered = 1 }
237 gv_check("T6 positive control: the channel still ACCEPTS after the rejections (not a guard that refuses everything)" as *u8, recovered, ctr)
238
239 sys_close(fd)
240 let rc: i64 = gv_verdict("GPU-DXG-SUBMIT-GATE" as *u8, ctr, "raw /dev/dxg LX_DXSUBMITCOMMAND (0xC180470F, size 384, count@0x18 ctx@0x1c) is ACCEPTED on the discrete RTX 5080 with a mapped+resident pushbuffer, wrapper-free -- SCOPE: command_length=0 so the GPU executes ZERO bytes; this is a submission-channel accept and claims NO throughput, NO kernel execution and NO speedup. Running work needs Blackwell ISA in the pushbuffer, which is the next rung and is NOT claimed here" as *u8)
241 sys_exit(rc)
242 return rc
243}