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}