code wiki / _hdl_build / nx_fw_robust_gate.nx
nx_fw_robust_gate.nx source
↩ module page · 126 lines · 7505 B
1// nx_fw_robust_gate.nx -- proves S-class never-brick holds in SUBOPTIMAL conditions.
2//
3// Fault-injects a crash at EVERY interruption point of the A/B + factory + atomic-commit flash and
4// asserts rf_select() still returns a BOOTABLE image -- i.e. the device is never bricked no matter
5// where power is lost; plus a teeth neg-control proving rf_select is honest when truly hopeless:
6//
7// T0 clean-commit : a clean flash commits the inactive bank -> FW_OK + selectable + active flipped
8// F1 powerloss-mid-write : crash while writing the inactive bank -> HELD, active retained + selectable
9// F2 powerloss-pre-commit: crash after verify, before the selector flip-> old bank selected + selectable
10// F3 powerloss-at-commit : crash at/after the atomic selector flip -> new bank selected + selectable
11// F4 double-fault : BOTH banks dead (e.g. bad flash + crash) -> immutable FACTORY + selectable
12// F5 selector-lost : the selector itself is corrupt/missing -> default bank + selectable
13// N1 truly-hopeless (teeth): banks AND factory all gone -> rf_select == null (NOT selectable)
14//
15// GREEN only if T0,F1,F2,F3,F4,F5 are selectable AND N1 is not. Evidence -> knowledge/status/fw_robust_gate.log.
16// Sovereign: imports the robust-flash organ (-> nx_fw_safeflash -> nx_sha256) + nx_framed_append + nx_syscalls.
17// license_tier: ORIGINAL
18import "nx_fw_robust_flash.nx"
19import "nx_framed_append.nx"
20import "nx_syscalls.nx"
21
22const RG_LOG: *u8 = "knowledge/status/fw_robust_gate.log"
23const RG_FAC: *u8 = "FACTORY-immutable-recovery-anchor"
24const RG_GOOD: *u8 = "NISHI-fw-current-good-image"
25const RG_NEW: *u8 = "NISHI-fw-new-candidate-image"
26
27func rg_len(s: *u8) -> i64 { var i: i64 = 0; while s[i] != 0 as u8 { i = i + 1 } return i }
28
29// write a non-bootable blob (32x 0xFF: bad magic + too short) -> fw_bootable == 0
30func rg_garbage(path: *u8) -> i64 {
31 let g: *u8 = sys_mmap(64); var i: i64 = 0; while i < 32 { g[i] = 0xff as u8; i = i + 1 }
32 fw_write_bytes(path, g, 32)
33 return 0
34}
35
36func rg_row(name: *u8, pass: i64) -> i64 {
37 let buf: *u8 = sys_mmap(528)
38 var o: i64 = 0
39 o = fw_cat(buf, o, "FWRG row=\x00" as *u8)
40 o = fw_cat(buf, o, name)
41 if pass == 1 { o = fw_cat(buf, o, " verdict=PASS\x00" as *u8) } else { o = fw_cat(buf, o, " verdict=FAIL\x00" as *u8) }
42 buf[o] = 0 as u8
43 fa_appendz(RG_LOG, buf, 512)
44 fw_puts(" "); fw_puts(name)
45 if pass == 1 { fw_puts(" PASS\n") } else { fw_puts(" FAIL\n") }
46 return 0
47}
48
49func main() -> i64 {
50 fw_puts("fw-robust gate (S-class never-brick: fault injection at every step)\n")
51 let e: i64 = sys_now_realtime_sec()
52 let p: i64 = __syscall(39, 0, 0, 0, 0, 0, 0)
53 let gl: i64 = rg_len(RG_GOOD); let nl: i64 = rg_len(RG_NEW); let fl: i64 = rg_len(RG_FAC)
54 let fac: *u8 = sys_mmap(256); let a: *u8 = sys_mmap(256); let b: *u8 = sys_mmap(256)
55 let sel: *u8 = sys_mmap(256); let nw: *u8 = sys_mmap(256)
56
57 // ---- T0 clean flash commits the inactive bank ----
58 fw_path(fac, "/tmp/rg_t0f." as *u8, e, p); fw_path(a, "/tmp/rg_t0a." as *u8, e, p); fw_path(b, "/tmp/rg_t0b." as *u8, e, p); fw_path(sel, "/tmp/rg_t0s." as *u8, e, p); fw_path(nw, "/tmp/rg_t0n." as *u8, e, p)
59 fw_make(fac, RG_FAC, fl); fw_make(a, RG_GOOD, gl); fw_make(b, RG_GOOD, gl); fw_make(nw, RG_NEW, nl); rf_write_sel_atomic(sel, RF_BANK_A)
60 let r0: i64 = rf_safe_flash(sel, a, b, fac, nw, 0)
61 var t0: i64 = 0
62 if r0 == FW_OK { if rf_selectable(sel, a, b, fac) == 1 { if rf_read_sel(sel) == RF_BANK_B { t0 = 1 } } }
63
64 // ---- F1 power loss MID inactive write (active retained, not committed) ----
65 fw_path(fac, "/tmp/rg_f1f." as *u8, e, p); fw_path(a, "/tmp/rg_f1a." as *u8, e, p); fw_path(b, "/tmp/rg_f1b." as *u8, e, p); fw_path(sel, "/tmp/rg_f1s." as *u8, e, p); fw_path(nw, "/tmp/rg_f1n." as *u8, e, p)
66 fw_make(fac, RG_FAC, fl); fw_make(a, RG_GOOD, gl); fw_make(b, RG_GOOD, gl); fw_make(nw, RG_NEW, nl); rf_write_sel_atomic(sel, RF_BANK_A)
67 let r1: i64 = rf_safe_flash(sel, a, b, fac, nw, 1)
68 var f1: i64 = 0
69 if r1 == RF_HELD { if rf_selectable(sel, a, b, fac) == 1 { if rf_read_sel(sel) == RF_BANK_A { f1 = 1 } } }
70
71 // ---- F2 power loss AFTER verify, BEFORE commit (old bank still selected) ----
72 fw_path(fac, "/tmp/rg_f2f." as *u8, e, p); fw_path(a, "/tmp/rg_f2a." as *u8, e, p); fw_path(b, "/tmp/rg_f2b." as *u8, e, p); fw_path(sel, "/tmp/rg_f2s." as *u8, e, p)
73 fw_make(fac, RG_FAC, fl); fw_make(a, RG_GOOD, gl); fw_make(b, RG_NEW, nl); rf_write_sel_atomic(sel, RF_BANK_A)
74 var f2: i64 = 0; if rf_selectable(sel, a, b, fac) == 1 { f2 = 1 }
75
76 // ---- F3 power loss AT/AFTER the atomic commit (new bank selected; selector is never torn) ----
77 fw_path(fac, "/tmp/rg_f3f." as *u8, e, p); fw_path(a, "/tmp/rg_f3a." as *u8, e, p); fw_path(b, "/tmp/rg_f3b." as *u8, e, p); fw_path(sel, "/tmp/rg_f3s." as *u8, e, p)
78 fw_make(fac, RG_FAC, fl); fw_make(a, RG_GOOD, gl); fw_make(b, RG_NEW, nl); rf_write_sel_atomic(sel, RF_BANK_B)
79 var f3: i64 = 0; if rf_selectable(sel, a, b, fac) == 1 { f3 = 1 }
80
81 // ---- F4 DOUBLE FAULT: both banks dead -> immutable factory ----
82 fw_path(fac, "/tmp/rg_f4f." as *u8, e, p); fw_path(a, "/tmp/rg_f4a." as *u8, e, p); fw_path(b, "/tmp/rg_f4b." as *u8, e, p); fw_path(sel, "/tmp/rg_f4s." as *u8, e, p)
83 fw_make(fac, RG_FAC, fl); rg_garbage(a); rg_garbage(b); rf_write_sel_atomic(sel, RF_BANK_A)
84 var f4: i64 = 0; if rf_selectable(sel, a, b, fac) == 1 { f4 = 1 }
85
86 // ---- F5 selector lost/corrupt -> default bank ----
87 fw_path(fac, "/tmp/rg_f5f." as *u8, e, p); fw_path(a, "/tmp/rg_f5a." as *u8, e, p); fw_path(b, "/tmp/rg_f5b." as *u8, e, p); fw_path(sel, "/tmp/rg_f5s." as *u8, e, p)
88 fw_make(fac, RG_FAC, fl); fw_make(a, RG_GOOD, gl); rg_garbage(b); rg_garbage(sel)
89 var f5: i64 = 0; if rf_selectable(sel, a, b, fac) == 1 { f5 = 1 }
90
91 // ---- N1 truly hopeless: banks AND factory all gone -> NOT selectable (teeth) ----
92 fw_path(fac, "/tmp/rg_n1f." as *u8, e, p); fw_path(a, "/tmp/rg_n1a." as *u8, e, p); fw_path(b, "/tmp/rg_n1b." as *u8, e, p); fw_path(sel, "/tmp/rg_n1s." as *u8, e, p)
93 rg_garbage(fac); rg_garbage(a); rg_garbage(b); rf_write_sel_atomic(sel, RF_BANK_A)
94 var n1: i64 = 0; if rf_selectable(sel, a, b, fac) == 0 { n1 = 1 }
95
96 var passes: i64 = 0
97 if t0 == 1 { passes = passes + 1 }
98 if f1 == 1 { passes = passes + 1 }
99 if f2 == 1 { passes = passes + 1 }
100 if f3 == 1 { passes = passes + 1 }
101 if f4 == 1 { passes = passes + 1 }
102 if f5 == 1 { passes = passes + 1 }
103 if n1 == 1 { passes = passes + 1 }
104 var green: i64 = 0
105 if passes == 7 { green = 1 }
106
107 rg_row("T0-clean-commit \x00" as *u8, t0)
108 rg_row("F1-powerloss-mid-write \x00" as *u8, f1)
109 rg_row("F2-powerloss-pre-commit \x00" as *u8, f2)
110 rg_row("F3-powerloss-at-commit \x00" as *u8, f3)
111 rg_row("F4-double-fault-factory \x00" as *u8, f4)
112 rg_row("F5-selector-lost-default\x00" as *u8, f5)
113 rg_row("N1-truly-hopeless-teeth \x00" as *u8, n1)
114
115 let vb: *u8 = sys_mmap(528)
116 var o: i64 = 0
117 o = fw_cat(vb, o, "FW-ROBUST verdict=\x00" as *u8)
118 if green == 1 { o = fw_cat(vb, o, "GREEN\x00" as *u8) } else { o = fw_cat(vb, o, "RED\x00" as *u8) }
119 o = fw_cat(vb, o, " passes=\x00" as *u8); o = fw_catn(vb, o, passes); o = fw_cat(vb, o, "/7 END\x00" as *u8)
120 vb[o] = 0 as u8
121 fa_appendz(RG_LOG, vb, 512)
122 fw_puts(vb); fw_puts("\n")
123
124 if green == 1 { return 0 }
125 return 1
126}