code wiki / _hdl_build / nx_paging_emit.nx
nx_paging_emit.nx source
↩ module page · 191 lines · 11038 B
1// nx_paging_emit.nx -- CPU-DATAPATH PAGING test (virtual-memory-paging-mmu, integration rung).
2//
3// Table-computes a bare-metal rv64 image that proves the CPU TRANSPARENTLY translates virtual
4// addresses through the page table once satp.MODE=Sv39 -- no explicit walk, just a normal load:
5// 1. lay a Sv39 gigapage PTE (0x20000007 = PPN 0x80000<<10 | V|R|W) at root[VPN2=3]=0x80008018
6// 2. store a sentinel (0x5ECA1234) to PHYSICAL 0x80009000 (satp still Bare = identity)
7// 3. build satp = (Sv39<<60) | root_PPN(0x80008) and csrrw it -> translation ON
8// 4. LOAD from VIRTUAL 0xC0009000 -- the CPU walks the page table and reads PA 0x80009000
9// 5. verify the loaded word == the sentinel -> emit "VOK" (else "X"); finisher.
10// "VOK" proves transparent CPU-datapath VA translation (the gigapage maps VA 0xC0000000.. -> PA
11// 0x80000000.., so VA 0xC0009000 != its PA 0x80009000 -- a real translation, not identity).
12// Zero hand-written machine code: a tiny rv64 encoder + two-pass forward-branch resolution.
13// nx_paging_emit -> runtime/_hdl_build/_paging_virt.bin + .gold
14// Sovereign, no gcc/.sh. license_tier: ORIGINAL
15import "nx_syscalls.nx"
16const PE_MAGIC_8192: i64 = 8192
17
18const PE_OUT: *u8 = "runtime/_hdl_build/_paging_virt.bin"
19const PE_GOLD: *u8 = "runtime/_hdl_build/_paging_virt.bin.gold"
20const PE_LOG: *u8 = "knowledge/status/paging.log"
21
22const PE_UART: i64 = 0x10000000
23const PE_FIN: i64 = 0x100000
24const PE_PASS: i64 = 0x5555
25const PE_MEM_BASE: i64 = 0x80000000
26const PE_CSR_SATP: i64 = 0x180
27const PE_CSR_MSTATUS: i64 = 0x300
28const PE_CSR_MEPC: i64 = 0x341
29const PE_MRET: i64 = 0x30200073 // mret instruction word
30const PE_MPP_S: i64 = 0x800 // mstatus with MPP=01 (Supervisor)
31
32const RV_X0: i64 = 0
33const RV_T0: i64 = 5 // UART
34const RV_T1: i64 = 6 // value / satp
35const RV_T2: i64 = 7 // mode scratch
36const RV_T3: i64 = 28 // loaded
37const RV_T4: i64 = 29 // expected
38const RV_T5: i64 = 30 // base scratch
39
40const PE_PTE_ADDR: i64 = 0x80008000 // root page table
41const PE_PTE_OFF: i64 = 0x18 // root[VPN2=3] = +3*8
42const PE_PTE_VAL: i64 = 0x20000007 // gigapage PPN 0x80000, V|R|W
43const PE_SENT_PA: i64 = 0x80009000 // sentinel physical address
44const PE_SENT_VAL: i64 = 0x5ECA1234
45const PE_VA: i64 = 0xC0009000 // virtual address mapping -> PE_SENT_PA
46const PE_SATP_PPN: i64 = 0x80008 // root >> 12
47const PE_SV39: i64 = 8
48
49func pe_lui(rd: i64, imm20: i64) -> i64 { return ((imm20 & 0xFFFFF) << 12) | (rd << 7) | 0x37 }
50func pe_addi(rd: i64, rs1: i64, imm: i64) -> i64 { return ((imm & 0xFFF) << 20) | (rs1 << 15) | (rd << 7) | 0x13 }
51func pe_load(rd: i64, rs1: i64, f3: i64, imm: i64) -> i64 { return ((imm & 0xFFF) << 20) | (rs1 << 15) | (f3 << 12) | (rd << 7) | 0x03 }
52func pe_store(rs2: i64, rs1: i64, f3: i64, imm: i64) -> i64 {
53 let hi: i64 = ((imm >> 5) & 0x7f) << 25
54 let lo: i64 = (imm & 0x1f) << 7
55 return hi | (rs2 << 20) | (rs1 << 15) | (f3 << 12) | lo | 0x23
56}
57func pe_branch(rs1: i64, rs2: i64, f3: i64, imm: i64) -> i64 {
58 let b12: i64 = ((imm >> 12) & 0x1) << 31
59 let b11: i64 = ((imm >> 11) & 0x1) << 7
60 let b10_5: i64 = ((imm >> 5) & 0x3f) << 25
61 let b4_1: i64 = ((imm >> 1) & 0xf) << 8
62 return b12 | b10_5 | (rs2 << 20) | (rs1 << 15) | (f3 << 12) | b4_1 | b11 | 0x63
63}
64func pe_jal(rd: i64, imm: i64) -> i64 {
65 let b20: i64 = ((imm >> 20) & 0x1) << 31
66 let b19_12: i64 = ((imm >> 12) & 0xff) << 12
67 let b11: i64 = ((imm >> 11) & 0x1) << 20
68 let b10_1: i64 = ((imm >> 1) & 0x3ff) << 21
69 return b20 | b10_1 | b11 | b19_12 | (rd << 7) | 0x6f
70}
71func pe_slli(rd: i64, rs1: i64, shamt: i64) -> i64 { return ((shamt & 0x3f) << 20) | (rs1 << 15) | (1 << 12) | (rd << 7) | 0x13 }
72func pe_srli(rd: i64, rs1: i64, shamt: i64) -> i64 { return ((shamt & 0x3f) << 20) | (rs1 << 15) | (5 << 12) | (rd << 7) | 0x13 }
73func pe_or(rd: i64, rs1: i64, rs2: i64) -> i64 { return (rs2 << 20) | (rs1 << 15) | (6 << 12) | (rd << 7) | 0x33 }
74func pe_csrrw(rd: i64, csr: i64, rs1: i64) -> i64 { return ((csr & 0xfff) << 20) | (rs1 << 15) | (1 << 12) | (rd << 7) | 0x73 }
75
76func pe_w32(buf: *u8, off: i64, w: i64) -> i64 {
77 buf[off]=(w&0xff) as u8; buf[off+1]=((w>>8)&0xff) as u8; buf[off+2]=((w>>16)&0xff) as u8; buf[off+3]=((w>>24)&0xff) as u8
78 return off + 4
79}
80// li 32-bit constant (lui+addi, sign-corrected). On the sovereign emu LUI does NOT sign-extend, so
81// a bit-31-set value materialises positive (correct for our addresses/values). 2 words.
82func pe_li32(buf: *u8, off: i64, rd: i64, val: i64) -> i64 {
83 var hi: i64 = (val >> 12) & 0xFFFFF
84 var lo: i64 = val & 0xFFF
85 if lo >= 0x800 { lo = lo - 0x1000; hi = (hi + 1) & 0xFFFFF }
86 var o: i64 = pe_w32(buf, off, pe_lui(rd, hi))
87 o = pe_w32(buf, o, pe_addi(rd, rd, lo))
88 return o
89}
90// li zero-extended 32-bit address (li32 + slli32 + srli32). 4 words.
91func pe_li32u(buf: *u8, off: i64, rd: i64, val: i64) -> i64 {
92 var o: i64 = pe_li32(buf, off, rd, val)
93 o = pe_w32(buf, o, pe_slli(rd, rd, 32))
94 o = pe_w32(buf, o, pe_srli(rd, rd, 32))
95 return o
96}
97func pe_emit_str(buf: *u8, off: i64, s: *u8, n: i64) -> i64 {
98 var o: i64 = off
99 var i: i64 = 0
100 while i < n { o = pe_w32(buf, o, pe_addi(RV_T1, RV_X0, s[i] as i64)); o = pe_w32(buf, o, pe_store(RV_T1, RV_T0, 0, 0)); i = i + 1 }
101 return o
102}
103func pe_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
104func pe_fn(fd: i64, v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m}; 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 }
105
106// emit the image with the given fail-branch + halt absolute offsets; returns size. pos_out[0]=FAIL
107// offset, pos_out[1]=HALT offset (recorded as emitted -- structure-derived, value-independent).
108func pe_emit_image(buf: *u8, fail_off: i64, halt_off: i64, pos_out: *i64, pte_val: i64, cont_off: i64) -> i64 {
109 var o: i64 = 0
110 o = pe_w32(buf, o, pe_lui(RV_T0, PE_UART >> 12)) // t0 = UART
111 // enter S-mode (paging only applies in S/U): mepc = &cont; mstatus.MPP = S; mret
112 o = pe_li32u(buf, o, RV_T1, PE_MEM_BASE + cont_off)
113 o = pe_w32(buf, o, pe_csrrw(RV_X0, PE_CSR_MEPC, RV_T1))
114 o = pe_li32(buf, o, RV_T1, PE_MPP_S)
115 o = pe_w32(buf, o, pe_csrrw(RV_X0, PE_CSR_MSTATUS, RV_T1))
116 o = pe_w32(buf, o, PE_MRET)
117 pos_out[2] = o // cont: runs in S-mode
118 // lay PTEs. root[VPN2=2] (offset 0x10) = an ALWAYS-VALID gigapage identity map for the kernel
119 // text (VA 0x80000000.. -> PA 0x80000000..) so the CPU can FETCH this code once Sv39 is on; the
120 // data leaf root[VPN2=3] (offset 0x18) maps VA 0xC0000000.. and stays tamper-able for the gate.
121 o = pe_li32u(buf, o, RV_T5, PE_PTE_ADDR)
122 o = pe_li32(buf, o, RV_T1, 0x2000000F) // identity code-map gigapage (PPN 0x80000, V|R|W|X -- X required to FETCH under perm enforcement)
123 o = pe_w32(buf, o, pe_store(RV_T1, RV_T5, 2, 0x10)) // sw code-map, 0x10(t5) = root[VPN2=2]
124 o = pe_li32(buf, o, RV_T1, pte_val)
125 o = pe_w32(buf, o, pe_store(RV_T1, RV_T5, 2, PE_PTE_OFF)) // sw data-leaf, 0x18(t5) = root[VPN2=3]
126 // store sentinel at PA
127 o = pe_li32u(buf, o, RV_T5, PE_SENT_PA)
128 o = pe_li32(buf, o, RV_T1, PE_SENT_VAL)
129 o = pe_w32(buf, o, pe_store(RV_T1, RV_T5, 2, 0)) // sw sentinel, 0(t5)
130 // build satp = (8<<60)|PPN and write it
131 o = pe_li32(buf, o, RV_T1, PE_SATP_PPN) // t1 = PPN
132 o = pe_w32(buf, o, pe_addi(RV_T2, RV_X0, PE_SV39)) // t2 = 8
133 o = pe_w32(buf, o, pe_slli(RV_T2, RV_T2, 60)) // t2 <<= 60
134 o = pe_w32(buf, o, pe_or(RV_T1, RV_T1, RV_T2)) // t1 |= t2
135 o = pe_w32(buf, o, pe_csrrw(RV_X0, PE_CSR_SATP, RV_T1)) // satp = t1 (Sv39 ON)
136 // load via VA (transparently translated) + verify
137 o = pe_li32u(buf, o, RV_T5, PE_VA)
138 o = pe_w32(buf, o, pe_load(RV_T3, RV_T5, 2, 0)) // lw t3, 0(t5) -> translate
139 o = pe_li32(buf, o, RV_T4, PE_SENT_VAL)
140 let pc_b: i64 = o
141 o = pe_w32(buf, o, pe_branch(RV_T3, RV_T4, 1, fail_off - pc_b)) // bne t3,t4, FAIL
142 o = pe_emit_str(buf, o, "VOK" as *u8, 3)
143 o = pe_w32(buf, o, pe_jal(RV_X0, halt_off - o)) // jal HALT
144 pos_out[0] = o // FAIL block
145 o = pe_emit_str(buf, o, "X" as *u8, 1)
146 pos_out[1] = o // HALT block
147 o = pe_li32(buf, o, RV_T5, PE_FIN)
148 o = pe_li32(buf, o, RV_T1, PE_PASS)
149 o = pe_w32(buf, o, pe_store(RV_T1, RV_T5, 2, 0))
150 o = pe_w32(buf, o, pe_jal(RV_X0, 0))
151 return o
152}
153
154func pe_parse_num(s: *u8) -> i64 {
155 var q: i64 = 0; var val: i64 = 0
156 if s[0] == (48 as u8) { if s[1] == (120 as u8) {
157 q = 2
158 var go: i64 = 1
159 while go == 1 { let c: i64 = s[q] as i64; var d: i64 = 0-1; if c>=48 { if c<=57 { d=c-48 } } if c>=97 { if c<=102 { d=c-87 } } if c>=65 { if c<=70 { d=c-55 } } if d<0 { go=0 } else { val=val*16+d; q=q+1 } }
160 return val
161 }}
162 var go2: i64 = 1
163 while go2 == 1 { let c: i64 = s[q] as i64; if c>=48 { if c<=57 { val=val*10+(c-48); q=q+1 } else { go2=0 } } else { go2=0 } }
164 return val
165}
166
167func main(argc: i64, argv: *i64) -> i64 {
168 var pte_val: i64 = PE_PTE_VAL
169 var outp: *u8 = PE_OUT
170 if argc >= 2 { pte_val = pe_parse_num(argv[1] as *u8) } // PTE override (gate's bad-PTE tamper)
171 if argc >= 3 { outp = argv[2] as *u8 } // out-path override
172 let scratch: *u8 = sys_mmap(PE_MAGIC_8192)
173 let pos: *i64 = sys_mmap(32) as *i64
174 pe_emit_image(scratch, 0, 0, pos, pte_val, 0) // measure pass -> fixes FAIL/HALT/CONT offsets
175 let fail_off: i64 = pos[0]
176 let halt_off: i64 = pos[1]
177 let cont_off: i64 = pos[2]
178 let buf: *u8 = sys_mmap(PE_MAGIC_8192)
179 let sz: i64 = pe_emit_image(buf, fail_off, halt_off, pos, pte_val, cont_off)
180 let fd: i64 = sys_openat_wr(outp, 420)
181 if fd < 0 { pe_p("PAGINGEMIT verdict=RED reason=out-unwritable\n" as *u8); return 1 }
182 sys_write(fd, buf, sz); sys_close(fd)
183 let gold: *u8 = sys_mmap(8)
184 gold[0]=86 as u8; gold[1]=79 as u8; gold[2]=75 as u8 // "VOK"
185 let gfd: i64 = sys_openat_wr(PE_GOLD, 420)
186 if gfd >= 0 { sys_write(gfd, gold, 3); sys_close(gfd) }
187 pe_p("PAGINGEMIT name=" as *u8); pe_p(outp); pe_p(" bytes=" as *u8); pe_fn(1, sz); pe_p(" golden=VOK (CPU-datapath Sv39 VA translation)\n" as *u8)
188 let lf: i64 = sys_openat_append(PE_LOG, 420)
189 if lf >= 0 { var n: i64=0; let m: *u8="PAGINGEMIT authored _paging_virt.bin golden=VOK\n" as *u8; while m[n]!=(0 as u8){n=n+1} sys_write(lf,m,n); sys_close(lf) }
190 return 0
191}