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}