code wiki / _hdl_build / nx_trap_syscall_emit.nx
nx_trap_syscall_emit.nx source
↩ module page · 356 lines · 16551 B
1// nx_trap_syscall_emit.nx -- the TRAP+SYSCALL emitter (K-R1 of the kernel-up ladder).
2// A NEW kernel syscall surface IS A SPEC FILE: reads (banner, 8x `sys <name> <a7>
3// <emit>`, out) DATA and AUTHORS a bare-metal rv64 flat image for qemu-virt whose trap
4// vector dispatches an 8-syscall mini-surface. The trap dispatch IS a STATE_MACHINE
5// (shape 18 / pe13) instance: read a7, branch to the matching per-syscall handler row,
6// emit that syscall's transcript byte to the UART, advance mepc+4, mret. exit (a7=93)
7// writes the SiFive finisher = clean halt. Zero hand-written machine code per surface;
8// the emitter is a tiny rv64 encoder (the few forms a trap surface needs:
9// auipc/lui/addi/sb/sw + B-type branch + jal + csrrw/csrrs + ecall + mret) and the
10// IMAGE + the GOLDEN transcript are TABLE-COMPUTED from the spec, byte-reproducibly.
11// nx_trap_syscall_emit <specpath> -> writes the flat image to the spec's `out`
12// and the table-computed golden transcript to <out>.golden
13// VERDICT log -> knowledge/status/trap_syscall.log (TRAPEMIT rows, the gate's evidence).
14// Sovereign: syscalls only, no gcc/.sh. license_tier: ORIGINAL
15import "nx_syscalls.nx"
16const TS_MAGIC_8192: i64 = 8192
17
18// ---- qemu-virt platform map (device tree as data, not magic) ----
19const TS_UART: i64 = 0x10000000 // NS16550A THR (write a byte = transmit)
20const TS_FIN: i64 = 0x100000 // SiFive test finisher (write to exit)
21const TS_PASS: i64 = 0x5555 // FINISHER_PASS low half -> clean halt
22// CSR addresses
23const TS_MTVEC: i64 = 0x305
24const TS_MEPC: i64 = 0x341
25// rv64 register numbers used
26const RV_X0: i64 = 0
27const RV_T0: i64 = 5
28const RV_T1: i64 = 6
29const RV_T2: i64 = 7
30const RV_A7: i64 = 17
31const RV_T3: i64 = 28
32const RV_T4: i64 = 29
33// the syscall surface width (data-driven by the spec; 8 = the K-R1 acceptance surface)
34const TS_NSYS: i64 = 8
35
36func ts_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
37func ts_fp(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 }
38func ts_fn(fd: i64, v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(fd,"-" as *u8,1)}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;k=1}; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(fd,bb,k); return 0 }
39
40// ---- the rv64 mini-encoder (one form per function; integer-only, struct-free) ----
41func ts_lui(rd: i64, imm20: i64) -> i64 { return ((imm20 & 0xFFFFF) << 12) | (rd << 7) | 0x37 }
42func ts_auipc(rd: i64, imm20: i64) -> i64 { return ((imm20 & 0xFFFFF) << 12) | (rd << 7) | 0x17 }
43func ts_addi(rd: i64, rs1: i64, imm: i64) -> i64 { return ((imm & 0xFFF) << 20) | (rs1 << 15) | (rd << 7) | 0x13 }
44// store; f3=0 -> SB, f3=2 -> SW. imm split S-type (we only use imm=0 here, full split for generality)
45func ts_store(rs2: i64, rs1: i64, f3: i64, imm: i64) -> i64 {
46 let hi: i64 = ((imm >> 5) & 0x7f) << 25
47 let lo: i64 = (imm & 0x1f) << 7
48 return hi | (rs2 << 20) | (rs1 << 15) | (f3 << 12) | lo | 0x23
49}
50// B-type branch; f3=0 -> BEQ, f3=1 -> BNE. imm in bytes (signed, multiple of 2).
51func ts_branch(rs1: i64, rs2: i64, f3: i64, imm: i64) -> i64 {
52 let b12: i64 = ((imm >> 12) & 0x1) << 31
53 let b11: i64 = ((imm >> 11) & 0x1) << 7
54 let b10_5: i64 = ((imm >> 5) & 0x3f) << 25
55 let b4_1: i64 = ((imm >> 1) & 0xf) << 8
56 return b12 | b10_5 | (rs2 << 20) | (rs1 << 15) | (f3 << 12) | b4_1 | b11 | 0x63
57}
58// J-type jal; imm in bytes (signed, multiple of 2).
59func ts_jal(rd: i64, imm: i64) -> i64 {
60 let b20: i64 = ((imm >> 20) & 0x1) << 31
61 let b19_12: i64 = ((imm >> 12) & 0xff) << 12
62 let b11: i64 = ((imm >> 11) & 0x1) << 20
63 let b10_1: i64 = ((imm >> 1) & 0x3ff) << 21
64 return b20 | b10_1 | b11 | b19_12 | (rd << 7) | 0x6f
65}
66// SYSTEM: csr in inst[31:20]; f3=1 -> csrrw, f3=2 -> csrrs.
67func ts_csrrw(rd: i64, csr: i64, rs1: i64) -> i64 { return ((csr & 0xFFF) << 20) | (rs1 << 15) | (1 << 12) | (rd << 7) | 0x73 }
68func ts_csrrs(rd: i64, csr: i64, rs1: i64) -> i64 { return ((csr & 0xFFF) << 20) | (rs1 << 15) | (2 << 12) | (rd << 7) | 0x73 }
69
70func ts_w32(buf: *u8, off: i64, w: i64) -> i64 {
71 buf[off] = (w & 0xff) as u8
72 buf[off+1] = ((w >> 8) & 0xff) as u8
73 buf[off+2] = ((w >> 16) & 0xff) as u8
74 buf[off+3] = ((w >> 24) & 0xff) as u8
75 return off + 4
76}
77
78// ---- label-offset planner (single-pass structural arithmetic; offsets DERIVED from
79// the syscall table, never hand-typed -- author=emitter). Returns BYTE offsets via
80// out-params. blen = banner length. trap_entry / trap_ret / handler-base computed.
81// Handler layout: each non-exit handler = 3 words; exit handler = 7 words; exit is the
82// LAST row, so handler_base(i) = handler0 + 3*i for i<NSYS-1 (all earlier handlers are
83// size 3). trap_ret follows after handler0 + 3*(NSYS-1) + 7.
84func ts_trap_entry_byte(blen: i64) -> i64 {
85 // boot: auipc+addi+csrrw(3) + lui t0(1) + banner(2*blen) + driver(2*NSYS) + guard(1)
86 let w: i64 = 3 + 1 + (2 * blen) + (2 * TS_NSYS) + 1
87 return w * 4
88}
89func ts_handler0_byte(blen: i64) -> i64 {
90 // trap_entry: dispatch(2*NSYS words) + fallthrough jal(1)
91 let te: i64 = ts_trap_entry_byte(blen)
92 return te + ((2 * TS_NSYS) + 1) * 4
93}
94func ts_handler_byte(blen: i64, idx: i64) -> i64 {
95 // each earlier handler is 3 words; idx-th handler base
96 return ts_handler0_byte(blen) + (idx * 3) * 4
97}
98func ts_trap_ret_byte(blen: i64) -> i64 {
99 // after handler0 + 3*(NSYS-1) words (the first NSYS-1 handlers) + 7 words (exit)
100 let h0: i64 = ts_handler0_byte(blen)
101 return h0 + (((TS_NSYS - 1) * 3) + 7) * 4
102}
103
104// ---- emit the BOOT section: install trap vector, set UART base, print the banner ----
105func ts_emit_boot(buf: *u8, off: i64, banner: *u8, blen: i64) -> i64 {
106 var o: i64 = off
107 let te: i64 = ts_trap_entry_byte(blen)
108 // auipc t3,0 (t3 = pc here = byte off=0); addi t3,t3,(te-0); csrrw x0,mtvec,t3
109 o = ts_w32(buf, o, ts_auipc(RV_T3, 0))
110 o = ts_w32(buf, o, ts_addi(RV_T3, RV_T3, te)) // te - 0 (auipc at byte 0)
111 o = ts_w32(buf, o, ts_csrrw(RV_X0, TS_MTVEC, RV_T3))
112 o = ts_w32(buf, o, ts_lui(RV_T0, TS_UART >> 12)) // t0 = UART base
113 var i: i64 = 0
114 while i < blen {
115 o = ts_w32(buf, o, ts_addi(RV_T1, RV_X0, banner[i] as i64))
116 o = ts_w32(buf, o, ts_store(RV_T1, RV_T0, 0, 0)) // sb t1,0(t0)
117 i = i + 1
118 }
119 return o
120}
121
122// ---- emit the KAT DRIVER: for each syscall set a7=num then ecall (+ guard spin) ----
123func ts_emit_driver(buf: *u8, off: i64, nums: *i64) -> i64 {
124 var o: i64 = off
125 var i: i64 = 0
126 while i < TS_NSYS {
127 o = ts_w32(buf, o, ts_addi(RV_A7, RV_X0, nums[i]))
128 o = ts_w32(buf, o, 0x00000073) // ecall
129 i = i + 1
130 }
131 o = ts_w32(buf, o, ts_jal(RV_X0, 0)) // guard spin (never reached)
132 return o
133}
134
135// ---- emit the TRAP DISPATCH (STATE_MACHINE): a7 == nums[i] -> beq handler(i) ----
136func ts_emit_dispatch(buf: *u8, off: i64, nums: *i64, blen: i64) -> i64 {
137 var o: i64 = off // o == trap_entry byte
138 var i: i64 = 0
139 while i < TS_NSYS {
140 o = ts_w32(buf, o, ts_addi(RV_T4, RV_X0, nums[i]))
141 // beq a7,t4,handler(i) : target = handler_byte - (current pc after the addi)
142 let pc: i64 = o
143 let tgt: i64 = ts_handler_byte(blen, i)
144 o = ts_w32(buf, o, ts_branch(RV_A7, RV_T4, 0, tgt - pc))
145 i = i + 1
146 }
147 // fallthrough: jal x0, trap_ret
148 let pc2: i64 = o
149 o = ts_w32(buf, o, ts_jal(RV_X0, ts_trap_ret_byte(blen) - pc2))
150 return o
151}
152
153// ---- emit ONE non-exit handler: print emit char, jal trap_ret ----
154func ts_emit_handler(buf: *u8, off: i64, ch: i64, blen: i64) -> i64 {
155 var o: i64 = off
156 o = ts_w32(buf, o, ts_addi(RV_T1, RV_X0, ch))
157 o = ts_w32(buf, o, ts_store(RV_T1, RV_T0, 0, 0)) // sb t1,0(t0)
158 let pc: i64 = o
159 o = ts_w32(buf, o, ts_jal(RV_X0, ts_trap_ret_byte(blen) - pc))
160 return o
161}
162
163// ---- emit the EXIT handler: print emit char, write finisher PASS = clean halt ----
164func ts_emit_exit(buf: *u8, off: i64, ch: i64) -> i64 {
165 var o: i64 = off
166 o = ts_w32(buf, o, ts_addi(RV_T1, RV_X0, ch))
167 o = ts_w32(buf, o, ts_store(RV_T1, RV_T0, 0, 0)) // sb emit char
168 o = ts_w32(buf, o, ts_lui(RV_T2, TS_FIN >> 12)) // t2 = finisher base 0x100000
169 var hi: i64 = TS_PASS >> 12
170 var lo: i64 = TS_PASS & 0xFFF
171 if lo >= 0x800 { lo = lo - 0x1000; hi = hi + 1 }
172 o = ts_w32(buf, o, ts_lui(RV_T1, hi))
173 o = ts_w32(buf, o, ts_addi(RV_T1, RV_T1, lo)) // t1 = 0x5555
174 o = ts_w32(buf, o, ts_store(RV_T1, RV_T2, 2, 0)) // sw t1,0(t2) -> halt
175 o = ts_w32(buf, o, ts_jal(RV_X0, 0)) // spin (halt already taken)
176 return o
177}
178
179// ---- emit the TRAP RETURN: mepc+4 ; mret ----
180func ts_emit_ret(buf: *u8, off: i64) -> i64 {
181 var o: i64 = off
182 o = ts_w32(buf, o, ts_csrrs(RV_T3, TS_MEPC, RV_X0)) // t3 = mepc (csrr t3,mepc)
183 o = ts_w32(buf, o, ts_addi(RV_T3, RV_T3, 4))
184 o = ts_w32(buf, o, ts_csrrw(RV_X0, TS_MEPC, RV_T3)) // csrw mepc,t3
185 o = ts_w32(buf, o, 0x30200073) // mret
186 return o
187}
188
189// ---- author the whole image into buf from the parsed table; return byte length ----
190func ts_emit_image(buf: *u8, banner: *u8, blen: i64, nums: *i64, emits: *u8) -> i64 {
191 var o: i64 = ts_emit_boot(buf, 0, banner, blen)
192 o = ts_emit_driver(buf, o, nums)
193 o = ts_emit_dispatch(buf, o, nums, blen)
194 // handlers in row order; the LAST (exit) is the finisher handler
195 var i: i64 = 0
196 while i < TS_NSYS {
197 if i == TS_NSYS - 1 {
198 o = ts_emit_exit(buf, o, emits[i] as i64)
199 } else {
200 o = ts_emit_handler(buf, o, emits[i] as i64, blen)
201 }
202 i = i + 1
203 }
204 o = ts_emit_ret(buf, o)
205 return o
206}
207
208// ---- spec field parse: `key value` lines (banner / out) ----
209func ts_field(buf: *u8, ls: i64, le: i64, key: *u8, out: *u8) -> i64 {
210 var k: i64 = 0
211 while key[k] != (0 as u8) {
212 if ls + k >= le { return 0 - 1 }
213 if buf[ls + k] != key[k] { return 0 - 1 }
214 k = k + 1
215 }
216 var o: i64 = 0
217 var q: i64 = ls + k
218 while q < le { if buf[q] == (13 as u8) { q = le } else { out[o] = buf[q]; o = o + 1; q = q + 1 } }
219 out[o] = 0 as u8
220 return o
221}
222
223// skip non-space chars from p in [.,le); return index of first space or le
224func ts_skip_tok(buf: *u8, p: i64, le: i64) -> i64 {
225 var q: i64 = p
226 var go: i64 = 1
227 while go == 1 { if q >= le { go = 0 } else { if buf[q] == (32 as u8) { go = 0 } else { q = q + 1 } } }
228 return q
229}
230// skip space chars from p in [.,le); return index of first non-space or le
231func ts_skip_sp(buf: *u8, p: i64, le: i64) -> i64 {
232 var q: i64 = p
233 var go: i64 = 1
234 while go == 1 { if q >= le { go = 0 } else { if buf[q] == (32 as u8) { q = q + 1 } else { go = 0 } } }
235 return q
236}
237// parse one `sys <name> <a7> <emit>` row in [ls,le): fill *outnum,*outemit; 1 ok / 0 no.
238// Tokenize on spaces: tok0="sys", tok1=name, tok2=decimal a7, tok3 starts with emit char.
239func ts_parse_sys(buf: *u8, ls: i64, le: i64, outnum: *i64, outemit: *u8) -> i64 {
240 if ls + 4 > le { return 0 }
241 if buf[ls] != (115 as u8) { return 0 } // s
242 if buf[ls+1] != (121 as u8) { return 0 } // y
243 if buf[ls+2] != (115 as u8) { return 0 } // s
244 if buf[ls+3] != (32 as u8) { return 0 } // space
245 var p: i64 = ts_skip_sp(buf, ls + 3, le) // -> start of name
246 p = ts_skip_tok(buf, p, le) // -> after name
247 p = ts_skip_sp(buf, p, le) // -> start of number
248 // parse decimal a7 number
249 var num: i64 = 0
250 var any: i64 = 0
251 var go: i64 = 1
252 while go == 1 {
253 if p >= le { go = 0 } else {
254 let c: i64 = buf[p] as i64
255 if c >= 48 { if c <= 57 { num = (num * 10) + (c - 48); any = 1; p = p + 1 } else { go = 0 } } else { go = 0 }
256 }
257 }
258 if any == 0 { return 0 }
259 p = ts_skip_sp(buf, p, le) // -> emit char
260 if p >= le { return 0 }
261 outnum[0] = num
262 outemit[0] = buf[p]
263 return 1
264}
265
266func ts_log(name: *u8, bytes: i64, golden: *u8, verdict: *u8) -> i64 {
267 let lfd: i64 = sys_openat_append("knowledge/status/trap_syscall.log" as *u8, 0x1a4)
268 if lfd < 0 { return 0 - 1 }
269 ts_fp(lfd, "TRAPEMIT name=" as *u8); ts_fp(lfd, name)
270 ts_fp(lfd, " machine=virt nsys=" as *u8); ts_fn(lfd, TS_NSYS)
271 ts_fp(lfd, " bytes=" as *u8); ts_fn(lfd, bytes)
272 ts_fp(lfd, " golden=" as *u8); ts_fp(lfd, golden)
273 ts_fp(lfd, " verdict=" as *u8); ts_fp(lfd, verdict); ts_fp(lfd, "\n" as *u8)
274 sys_close(lfd)
275 return 0
276}
277
278func main(argc: i64, argv: *i64) -> i64 {
279 if argc < 2 { ts_p("usage: nx_trap_syscall_emit <specpath>\n" as *u8); sys_exit(2); return 2 }
280 let sp: *u8 = argv[1] as *u8
281 let lenp: *i64 = sys_mmap(16) as *i64
282 let spec: *u8 = sys_read_file(sp, lenp)
283 let sn: i64 = lenp[0]
284 if sn <= 0 { ts_p("TRAP-EMIT REFUSED: spec missing\n" as *u8); ts_log("(missing)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
285 let banner: *u8 = sys_mmap(256)
286 let outp: *u8 = sys_mmap(256)
287 let nums: *i64 = sys_mmap(8 * 64) as *i64
288 let emits: *u8 = sys_mmap(64)
289 banner[0] = 0 as u8
290 outp[0] = 0 as u8
291 var nsys: i64 = 0
292 let onum: *i64 = sys_mmap(16) as *i64
293 let oemit: *u8 = sys_mmap(8)
294 var ls: i64 = 0
295 while ls < sn {
296 var le: i64 = ls
297 var scan: i64 = 1
298 while scan == 1 { if le >= sn { scan = 0 } else { if spec[le] == (10 as u8) { scan = 0 } else { le = le + 1 } } }
299 if spec[ls] != (35 as u8) {
300 ts_field(spec, ls, le, "banner " as *u8, banner)
301 ts_field(spec, ls, le, "out " as *u8, outp)
302 if ts_parse_sys(spec, ls, le, onum, oemit) == 1 {
303 if nsys < TS_NSYS { nums[nsys] = onum[0]; emits[nsys] = oemit[0]; nsys = nsys + 1 }
304 }
305 }
306 ls = le + 1
307 }
308 var blen: i64 = 0
309 while banner[blen] != (0 as u8) { blen = blen + 1 }
310 if blen <= 0 { ts_p("TRAP-EMIT REFUSED: no banner row\n" as *u8); ts_log("(no-banner)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
311 if outp[0] == (0 as u8) { ts_p("TRAP-EMIT REFUSED: no out row\n" as *u8); ts_log("(no-out)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
312 if nsys != TS_NSYS { ts_p("TRAP-EMIT REFUSED: need 8 sys rows, got " as *u8); ts_fn(1, nsys); ts_p("\n" as *u8); ts_log("(bad-nsys)" as *u8, 0, "-" as *u8, "REFUSED" as *u8); sys_exit(2); return 2 }
313
314 // ---- table-compute the GOLDEN transcript: banner + emit chars in row order ----
315 let golden: *u8 = sys_mmap(256)
316 var gi: i64 = 0
317 var bi: i64 = 0
318 while bi < blen { golden[gi] = banner[bi]; gi = gi + 1; bi = bi + 1 }
319 golden[gi] = 10 as u8; gi = gi + 1 // banner trailing newline (driver prints it)
320 var si: i64 = 0
321 while si < nsys { golden[gi] = emits[si]; gi = gi + 1; si = si + 1 }
322 golden[gi] = 0 as u8
323
324 // banner-with-newline that the BOOT code prints: banner bytes + '\n'
325 let bnl: *u8 = sys_mmap(256)
326 var bj: i64 = 0
327 while bj < blen { bnl[bj] = banner[bj]; bj = bj + 1 }
328 bnl[bj] = 10 as u8; bj = bj + 1
329 bnl[bj] = 0 as u8
330
331 let bin: *u8 = sys_mmap(TS_MAGIC_8192)
332 let sz: i64 = ts_emit_image(bin, bnl, bj, nums, emits)
333 let ofd: i64 = sys_openat_wr(outp, 0x1a4)
334 if ofd < 0 { ts_p("TRAP-EMIT RED: cannot open out\n" as *u8); ts_log(outp, sz, golden, "RED" as *u8); sys_exit(1); return 1 }
335 sys_write(ofd, bin, sz)
336 sys_close(ofd)
337
338 // write the golden transcript next to the image (<out>.golden) for the gate to read
339 let gp: *u8 = sys_mmap(512)
340 var gpi: i64 = 0
341 while outp[gpi] != (0 as u8) { gp[gpi] = outp[gpi]; gpi = gpi + 1 }
342 gp[gpi] = 46 as u8; gpi = gpi + 1 // '.'
343 gp[gpi] = 103 as u8; gpi = gpi + 1 // 'g'
344 gp[gpi] = 111 as u8; gpi = gpi + 1 // 'o'
345 gp[gpi] = 108 as u8; gpi = gpi + 1 // 'l'
346 gp[gpi] = 100 as u8; gpi = gpi + 1 // 'd'
347 gp[gpi] = 0 as u8
348 let gfd: i64 = sys_openat_wr(gp, 0x1a4)
349 if gfd >= 0 { sys_write(gfd, golden, gi); sys_close(gfd) }
350
351 ts_p("TRAP-EMIT GREEN: authored " as *u8); ts_p(outp); ts_p(" bytes=" as *u8); ts_fn(1, sz)
352 ts_p(" golden=" as *u8); ts_p(golden); ts_p(" (spec in, bootable rv64 trap+syscall image out)\n" as *u8)
353 ts_log(outp, sz, golden, "GREEN" as *u8)
354 sys_exit(0)
355 return 0
356}