nx_hal.nx source
↩ module page · 554 lines · 25006 B
1// nx_hal.nx -- substrate Hardware Abstraction Layer.
2//
3// Cardinal 2026-05-21 ("bits up eliminate and for the rest we want to
4// be hardware agnostic as in the future nishi silicon will run all
5// of this"): every substrate primitive above this layer expresses
6// hardware-level operations in HAL terms, NOT Linux-syscall terms.
7// The HAL is the substrate-defined contract that ANY backing
8// implementation (Linux today, bare-metal Nishi kernel tomorrow,
9// future Nishi silicon eventually) must provide.
10//
11// Substrate's bottom-up sovereignty story:
12// - Top of stack: Elder AI services + UI -- can run on whatever OS
13// the substrate currently runs on (Win-native + bash-launched
14// today; future substrate-hosted on a NishiLang container)
15// - Middle: substrate primitives (HAL clients) -- hardware-agnostic;
16// compile + run unchanged on any HAL implementation
17// - Bottom: HAL implementation (per-target shim) -- the thin
18// adapter that turns hardware-level HAL ops into actual machine
19// behavior. Today: Linux syscall ABI. Tomorrow: bare-metal
20// MMIO + page-table walks. Eventually: future Nishi silicon.
21//
22// The win: as we eliminate third parties layer by layer (Docker,
23// Linux, Win32, NVIDIA), the substrate primitives DO NOT CHANGE.
24// Only the HAL shim changes. The investment in shipped substrate
25// is preserved.
26//
27// V1 operations (the hardware-level concepts any silicon must
28// provide; bare-metal Nishi kernel + future Nishi silicon both
29// implement these):
30// nx_hal_clock_now_ns monotonic nanosecond timestamp
31// nx_hal_alloc_pages contiguous page allocation
32// nx_hal_release_pages return pages to substrate
33// nx_hal_console_write debug-channel byte output
34// nx_hal_random_bytes hardware-quality entropy
35// nx_hal_yield cooperative scheduling hint
36// nx_hal_exit process termination
37// nx_hal_spawn fork + exec; substrate becomes parent
38// nx_hal_wait reap child + read exit code
39// nx_hal_kill send signal to child
40//
41// V1.5 dispatcher (2026-05-26): each public op is a one-branch
42// dispatcher over NX_HAL_ACTIVE_SHIM_KIND. Each shim has its own
43// implementation function (nx_hal_<op>_<shim>); shims with no real
44// backing return NO_BACKING honestly rather than silently doing the
45// wrong thing. Today only NX_HAL_SHIM_LINUX has real implementations;
46// NX_HAL_SHIM_BARE_METAL and NX_HAL_SHIM_NISHI_SILICON are TODO_SILICON
47// stubs that document what the real implementation must do.
48//
49// Switching the active shim is a one-line constant edit at the top
50// of this file (NX_HAL_ACTIVE_SHIM_KIND). A bare-metal or silicon
51// build flips that constant; until each stub is filled in, calls
52// route through NO_BACKING which is loud at runtime.
53//
54// (Future: when the NishiLang preprocessor grows general @ifdef
55// support beyond the hardcoded TARGET_X86_64 dimension, the runtime
56// dispatcher gets replaced with compile-time @ifdef so that wrong-
57// target builds fail at compile rather than runtime. Today the
58// preprocessor only knows ONE macro so the runtime dispatcher is
59// the honest mechanism.)
60//
61// V2+ scope (for the next layers down the stack):
62// - nx_hal_mmio_read/write (bare-metal direct register access)
63// - nx_hal_intr_register (interrupt vector hookup)
64// - nx_hal_dma_buffer (DMA-coherent allocator)
65// - nx_hal_cpu_pin (affinity for the Nishi-silicon clusters)
66//
67// genealogy_id: linux_syscall_abi_1991 + microkernel_minimality_1969 +
68// cardinal_2026-05-21_bits_up_silicon_to_top +
69// cardinal_2026-05-21_hardware_agnostic_future_nishi_silicon
70// lineage_id: substrate_hal_v1_dispatcher
71//
72// nx_capability_manifest:
73// variant_class: hal
74// variant_id: hal_v1_5_dispatcher
75// requires_isa: [rv32i, rv64imac, x86_64, cortex_m, armv7a, aarch64, wasm32]
76// requires_syscalls: [shim_dependent]
77// requires_ram_min_b: 4096
78// tier_floor: NX_TIER_INF_EDGE
79// tier_ceiling: NX_TIER_INF_HPC
80// adversary_class: THREAT_AI_ADVERSARY
81//
82// nx_safety_envelope:
83// intended_use: "Hardware Abstraction Layer; substrate primitives
84// call public HAL ops; dispatcher routes to active
85// shim's implementation; shims provide per-target
86// backing or honest NO_BACKING"
87// sil_target: SIL2
88// evidence: [sealed_enum_op_kinds, sealed_enum_shim_kinds,
89// dispatcher_branch_per_shim, no_backing_loud_runtime,
90// audit_lint_for_direct_sys_calls]
91// verdict: NOT_YET_EVALUATED
92
93import "nx_syscalls.nx"
94const NX_MAGIC_1000000000: i64 = 1000000000
95
96// ===== HAL operation kinds (sealed enum) =================================================
97//
98// Every HAL operation has a kind code. The enum is the SUBSTRATE
99// contract -- every shim (Linux today, Nishi-silicon tomorrow) must
100// implement all kinds < NX_HAL_N_KINDS.
101const NX_HAL_OP_CLOCK_NOW_NS: i64 = 0
102const NX_HAL_OP_ALLOC_PAGES: i64 = 1
103const NX_HAL_OP_RELEASE_PAGES: i64 = 2
104const NX_HAL_OP_CONSOLE_WRITE: i64 = 3
105const NX_HAL_OP_RANDOM_BYTES: i64 = 4
106const NX_HAL_OP_YIELD: i64 = 5
107const NX_HAL_OP_EXIT: i64 = 6
108const NX_HAL_OP_SPAWN: i64 = 7 // fork+exec; substrate becomes parent
109const NX_HAL_OP_WAIT: i64 = 8 // reap child + read exit code
110const NX_HAL_OP_KILL: i64 = 9 // send signal to child
111const NX_HAL_OP_N_KINDS: i64 = 10
112
113func nx_hal_op_is_valid(op: i64) -> i64 {
114 if op < 0 { return 0 }
115 if op >= NX_HAL_OP_N_KINDS { return 0 }
116 return 1
117}
118
119// ===== Backing-shim kinds (sealed enum) =================================================
120const NX_HAL_SHIM_LINUX: i64 = 0
121const NX_HAL_SHIM_BARE_METAL: i64 = 1 // future: substrate kernel on bare hw
122const NX_HAL_SHIM_NISHI_SILICON: i64 = 2 // future: custom Nishi ASIC
123const NX_HAL_SHIM_N_KINDS: i64 = 3
124
125func nx_hal_shim_is_valid(s: i64) -> i64 {
126 if s < 0 { return 0 }
127 if s >= NX_HAL_SHIM_N_KINDS { return 0 }
128 return 1
129}
130
131// ===== Verdicts =================================================
132const NX_HAL_OK: i64 = 0
133const NX_HAL_BAD_INPUT: i64 = 1
134const NX_HAL_NO_BACKING: i64 = 2 // shim not implemented for this op
135const NX_HAL_FAULT: i64 = 3 // hardware/syscall returned error
136const NX_HAL_N_VERDICTS: i64 = 4
137
138func nx_hal_verdict_is_valid(v: i64) -> i64 {
139 if v < 0 { return 0 }
140 if v >= NX_HAL_N_VERDICTS { return 0 }
141 return 1
142}
143
144// ===== Active shim selector =================================================
145// THE seam between substrate and silicon target. Edit this single
146// constant to switch the build's backing shim; the dispatchers below
147// route every op through the matching implementation function.
148//
149// LINUX -- today; real implementations via Linux syscall ABI.
150// BARE_METAL -- future; substrate runs on Nishi-OS bare-metal kernel.
151// Stubs return NO_BACKING until the kernel HAL shim
152// lands per task K-7+.
153// NISHI_SILICON -- future; substrate runs on Nishi-fabbed silicon
154// with no general-purpose OS. Stubs return NO_BACKING
155// until the silicon RTL exposes the documented MMIO
156// contract (see TODO_SILICON comments per op).
157const NX_HAL_ACTIVE_SHIM_KIND: i64 = NX_HAL_SHIM_LINUX
158
159func nx_hal_active_shim() -> i64 {
160 return NX_HAL_ACTIVE_SHIM_KIND
161}
162
163// ===== Per-target syscall numbers needed by the Linux shim =================================================
164// (Some primitives below need syscalls that aren't already in
165// nx_syscalls.nx; we declare them here so the substrate stays
166// out of nx_syscalls.nx's stable surface.)
167@ifdef TARGET_X86_64
168const NX_HAL_SYS_GETRANDOM: i64 = 318
169const NX_HAL_SYS_SCHED_YIELD: i64 = 24
170const NX_HAL_SYS_KILL: i64 = 62
171@endif
172
173@ifndef TARGET_X86_64
174const NX_HAL_SYS_GETRANDOM: i64 = 278
175const NX_HAL_SYS_SCHED_YIELD: i64 = 124
176const NX_HAL_SYS_KILL: i64 = 129
177@endif
178
179// ===== nx_hal_clock_now_ns =================================================
180// Returns nanoseconds since some monotonic epoch. Substrate primitives
181// that need durations or scheduling timestamps call THIS, not
182// sys_clock_gettime directly.
183func nx_hal_clock_now_ns_linux() -> i64 {
184 // CLOCK_MONOTONIC is id 1; timespec is two i64s.
185 let ts: *i64 = (sys_mmap(16)) as *i64
186 ts[0] = 0
187 ts[1] = 0
188 let rc: i64 = __syscall(SYS_CLOCK_GETTIME, 1, ts as i64, 0, 0, 0, 0)
189 if rc != 0 { return 0 - NX_HAL_FAULT }
190 return ts[0] * NX_MAGIC_1000000000 + ts[1]
191}
192
193func nx_hal_clock_now_ns_bare_metal() -> i64 {
194 // TODO_SILICON: on Nishi-OS bare metal, read CLINT MTIME
195 // (RV64 virt: 0x0200BFF8) and scale by fabric clock period.
196 // Kernel's clint.nx already exposes this; HAL shim plugs into
197 // sched_now_ns once K-7 (bare-metal HAL shim task) lands.
198 return 0 - NX_HAL_NO_BACKING
199}
200
201func nx_hal_clock_now_ns_nishi_silicon() -> i64 {
202 // TODO_SILICON: dedicated time-stamp counter. For Nishi RV64
203 // silicon: mcycle CSR scaled by fabric clock period (10 MHz on
204 // FPGA Tier A; per-process for MPW Tier B). See
205 // nishi-silicon/hdl/README.md once Tier A bring-up writes it.
206 return 0 - NX_HAL_NO_BACKING
207}
208
209func nx_hal_clock_now_ns() -> i64 {
210 let s: i64 = nx_hal_active_shim()
211 if s == NX_HAL_SHIM_LINUX { return nx_hal_clock_now_ns_linux() }
212 if s == NX_HAL_SHIM_BARE_METAL { return nx_hal_clock_now_ns_bare_metal() }
213 if s == NX_HAL_SHIM_NISHI_SILICON { return nx_hal_clock_now_ns_nishi_silicon() }
214 return 0 - NX_HAL_NO_BACKING
215}
216
217// ===== nx_hal_alloc_pages =================================================
218// Returns pointer to n_bytes of zero-initialised anonymous memory.
219// Page-rounded by the shim. Substrate primitives that today call
220// sys_mmap(size) should migrate to this.
221func nx_hal_alloc_pages_linux(n_bytes: i64) -> *u8 {
222 return (sys_mmap(n_bytes)) as *u8
223}
224
225func nx_hal_alloc_pages_bare_metal(n_bytes: i64) -> *u8 {
226 // TODO_SILICON: bare-metal page allocator. Kernel vm.nx already
227 // implements a bump allocator over the physical RAM range starting
228 // at kernel_end. HAL shim calls into that allocator once K-7
229 // exposes it as kvm_alloc_pages(n_bytes).
230 return (0 as i64) as *u8
231}
232
233func nx_hal_alloc_pages_nishi_silicon(n_bytes: i64) -> *u8 {
234 // TODO_SILICON: silicon-fabbed page allocator. On Nishi RV64
235 // (Tier A FPGA: no MMU; Tier B MPW: optional MMU), pages come
236 // from the static RAM region documented by silicon's memory-map
237 // header. Bump allocator suffices for Tier A; ring allocator
238 // when MMU lands in Tier B+.
239 return (0 as i64) as *u8
240}
241
242func nx_hal_alloc_pages(n_bytes: i64) -> *u8 {
243 if n_bytes <= 0 { return (0 as i64) as *u8 }
244 let s: i64 = nx_hal_active_shim()
245 if s == NX_HAL_SHIM_LINUX { return nx_hal_alloc_pages_linux(n_bytes) }
246 if s == NX_HAL_SHIM_BARE_METAL { return nx_hal_alloc_pages_bare_metal(n_bytes) }
247 if s == NX_HAL_SHIM_NISHI_SILICON { return nx_hal_alloc_pages_nishi_silicon(n_bytes) }
248 return (0 as i64) as *u8
249}
250
251// ===== nx_hal_release_pages =================================================
252// Releases pages obtained via nx_hal_alloc_pages. V1 Linux shim is
253// a no-op (the substrate hasn't shipped munmap wrappers yet; sys_mmap
254// allocations are released only at process exit). Stub here so the
255// CONTRACT is published; future shims can implement actual freeing.
256func nx_hal_release_pages_linux(ptr: *u8, n_bytes: i64) -> i64 {
257 // No-op until sys_munmap wrapper ships. Return NO_BACKING so
258 // callers know to expect leak at process boundary.
259 return 0 - NX_HAL_NO_BACKING
260}
261
262func nx_hal_release_pages_bare_metal(ptr: *u8, n_bytes: i64) -> i64 {
263 // TODO_SILICON: bare-metal page release. Kernel vm.nx bump
264 // allocator does not free today; ring allocator would on K-8+.
265 return 0 - NX_HAL_NO_BACKING
266}
267
268func nx_hal_release_pages_nishi_silicon(ptr: *u8, n_bytes: i64) -> i64 {
269 // TODO_SILICON: silicon-fabbed page release. Tier A bump
270 // allocator does not free; Tier B+ ring allocator does.
271 return 0 - NX_HAL_NO_BACKING
272}
273
274func nx_hal_release_pages(ptr: *u8, n_bytes: i64) -> i64 {
275 if (ptr as i64) == 0 { return 0 - NX_HAL_BAD_INPUT }
276 if n_bytes <= 0 { return 0 - NX_HAL_BAD_INPUT }
277 let s: i64 = nx_hal_active_shim()
278 if s == NX_HAL_SHIM_LINUX { return nx_hal_release_pages_linux(ptr, n_bytes) }
279 if s == NX_HAL_SHIM_BARE_METAL { return nx_hal_release_pages_bare_metal(ptr, n_bytes) }
280 if s == NX_HAL_SHIM_NISHI_SILICON { return nx_hal_release_pages_nishi_silicon(ptr, n_bytes) }
281 return 0 - NX_HAL_NO_BACKING
282}
283
284// ===== nx_hal_console_write =================================================
285// Writes bytes to the debug console. V1 Linux shim writes to fd 2
286// (stderr). Future bare-metal shim writes to UART or framebuffer
287// console; future Nishi-silicon shim writes to dedicated debug MMIO.
288func nx_hal_console_write_linux(buf: *u8, len: i64) -> i64 {
289 return sys_write(2, buf, len)
290}
291
292func nx_hal_console_write_bare_metal(buf: *u8, len: i64) -> i64 {
293 // TODO_SILICON: bare-metal UART write. Kernel uart.nx implements
294 // the 16550 polled-THRE protocol at 0x10000000 with MIE masking
295 // for atomicity. HAL shim calls uart_print_bytes(buf, len) once
296 // K-7 (bare-metal HAL shim task) lands.
297 return 0 - NX_HAL_NO_BACKING
298}
299
300func nx_hal_console_write_nishi_silicon(buf: *u8, len: i64) -> i64 {
301 // TODO_SILICON: silicon-fabbed debug MMIO write. Tier A FPGA:
302 // 16550-compatible UART at 0x10000000 (kernel-compatible).
303 // Tier B MPW: same plus optional second console for printf-debug
304 // separation. Tier C home-fab: TBD per fab/README.md.
305 return 0 - NX_HAL_NO_BACKING
306}
307
308func nx_hal_console_write(buf: *u8, len: i64) -> i64 {
309 if (buf as i64) == 0 { return 0 - NX_HAL_BAD_INPUT }
310 if len < 0 { return 0 - NX_HAL_BAD_INPUT }
311 if len == 0 { return NX_HAL_OK }
312 let s: i64 = nx_hal_active_shim()
313 if s == NX_HAL_SHIM_LINUX { return nx_hal_console_write_linux(buf, len) }
314 if s == NX_HAL_SHIM_BARE_METAL { return nx_hal_console_write_bare_metal(buf, len) }
315 if s == NX_HAL_SHIM_NISHI_SILICON { return nx_hal_console_write_nishi_silicon(buf, len) }
316 return 0 - NX_HAL_NO_BACKING
317}
318
319// ===== nx_hal_random_bytes =================================================
320// Fills buf with len bytes of hardware-quality entropy. V1 Linux
321// shim uses getrandom(). Future bare-metal shim taps the silicon
322// RNG. Future Nishi-silicon shim taps the dedicated entropy source.
323func nx_hal_random_bytes_linux(buf: *u8, len: i64) -> i64 {
324 let rc: i64 = __syscall(NX_HAL_SYS_GETRANDOM, buf as i64, len, 0, 0, 0, 0)
325 if rc < 0 { return 0 - NX_HAL_FAULT }
326 if rc != len { return 0 - NX_HAL_FAULT }
327 return NX_HAL_OK
328}
329
330func nx_hal_random_bytes_bare_metal(buf: *u8, len: i64) -> i64 {
331 // TODO_SILICON: bare-metal entropy source. No silicon RNG yet
332 // on QEMU virt; bare-metal HAL shim must either (a) seed from
333 // boot-time entropy + chacha20 stretch, or (b) wait for silicon
334 // RNG block. Stub returns NO_BACKING so callers cannot accept
335 // weak entropy by accident.
336 return 0 - NX_HAL_NO_BACKING
337}
338
339func nx_hal_random_bytes_nishi_silicon(buf: *u8, len: i64) -> i64 {
340 // TODO_SILICON: silicon entropy block. Tier A FPGA: ring-
341 // oscillator TRNG (well-studied design, ~100 Kbps). Tier B MPW:
342 // dedicated CMOS thermal-noise TRNG (~1 Mbps). Tier C home-fab:
343 // TBD. HAL shim reads from MMIO entropy FIFO once silicon
344 // exposes one.
345 return 0 - NX_HAL_NO_BACKING
346}
347
348func nx_hal_random_bytes(buf: *u8, len: i64) -> i64 {
349 if (buf as i64) == 0 { return 0 - NX_HAL_BAD_INPUT }
350 if len < 0 { return 0 - NX_HAL_BAD_INPUT }
351 if len == 0 { return NX_HAL_OK }
352 let s: i64 = nx_hal_active_shim()
353 if s == NX_HAL_SHIM_LINUX { return nx_hal_random_bytes_linux(buf, len) }
354 if s == NX_HAL_SHIM_BARE_METAL { return nx_hal_random_bytes_bare_metal(buf, len) }
355 if s == NX_HAL_SHIM_NISHI_SILICON { return nx_hal_random_bytes_nishi_silicon(buf, len) }
356 return 0 - NX_HAL_NO_BACKING
357}
358
359// ===== nx_hal_yield =================================================
360// Cooperative scheduler hint -- substrate releases its time slice.
361// Used by busy-wait loops + actor cardinals.
362func nx_hal_yield_linux() -> i64 {
363 let rc: i64 = __syscall(NX_HAL_SYS_SCHED_YIELD, 0, 0, 0, 0, 0, 0)
364 if rc != 0 { return 0 - NX_HAL_FAULT }
365 return NX_HAL_OK
366}
367
368func nx_hal_yield_bare_metal() -> i64 {
369 // TODO_SILICON: bare-metal cooperative yield. Kernel sched.nx
370 // sched_yield_now() is exactly this; HAL shim calls it once
371 // K-7 exposes the kernel ABI to substrate.
372 return 0 - NX_HAL_NO_BACKING
373}
374
375func nx_hal_yield_nishi_silicon() -> i64 {
376 // TODO_SILICON: on a single-tasking Tier A FPGA bring-up there
377 // is nothing to yield to (busy-wait suffices). Tier B MPW with
378 // multi-tasking kernel routes through the same sched_yield_now()
379 // path as bare-metal once kernel HAL shim lands.
380 return 0 - NX_HAL_NO_BACKING
381}
382
383func nx_hal_yield() -> i64 {
384 let s: i64 = nx_hal_active_shim()
385 if s == NX_HAL_SHIM_LINUX { return nx_hal_yield_linux() }
386 if s == NX_HAL_SHIM_BARE_METAL { return nx_hal_yield_bare_metal() }
387 if s == NX_HAL_SHIM_NISHI_SILICON { return nx_hal_yield_nishi_silicon() }
388 return 0 - NX_HAL_NO_BACKING
389}
390
391// ===== nx_hal_exit =================================================
392// Terminates the substrate process with the given code. All shims
393// implement this. Linux: exit_group syscall. Bare metal: jump to
394// reset vector. Nishi silicon: assert HALT line.
395func nx_hal_exit_linux(code: i64) -> i64 {
396 __syscall(SYS_EXIT, code, 0, 0, 0, 0, 0)
397 return 0 - NX_HAL_FAULT // unreachable
398}
399
400func nx_hal_exit_bare_metal(code: i64) -> i64 {
401 // TODO_SILICON: bare-metal exit. On QEMU virt this writes
402 // 0x5555 to SiFive finisher at 0x100000 for clean poweroff
403 // (kernel tasks.nx already does this). HAL shim calls into
404 // kernel finisher path once K-7 exposes it.
405 return 0 - NX_HAL_NO_BACKING
406}
407
408func nx_hal_exit_nishi_silicon(code: i64) -> i64 {
409 // TODO_SILICON: silicon HALT line assertion. Tier A FPGA: write
410 // 1 to a HALT MMIO register that the test-harness FPGA bitstream
411 // exposes (loops back via JTAG). Tier B MPW: dedicated HALT pin
412 // + optional exit-code MMIO read-back. Tier C home-fab: TBD.
413 return 0 - NX_HAL_NO_BACKING
414}
415
416func nx_hal_exit(code: i64) -> i64 {
417 let s: i64 = nx_hal_active_shim()
418 if s == NX_HAL_SHIM_LINUX { return nx_hal_exit_linux(code) }
419 if s == NX_HAL_SHIM_BARE_METAL { return nx_hal_exit_bare_metal(code) }
420 if s == NX_HAL_SHIM_NISHI_SILICON { return nx_hal_exit_nishi_silicon(code) }
421 return 0 - NX_HAL_NO_BACKING
422}
423
424// ===== nx_hal_spawn =================================================
425// Substrate becomes parent of a new child process. Linux shim:
426// sys_fork + sys_execve. Bare-metal/Nishi-silicon shims: schedule
427// a new task on the substrate kernel scheduler.
428//
429// Returns:
430// > 0 child PID (parent path)
431// == 0 CHILD PATH -- only reached if execve fails; child should
432// immediately nx_hal_exit(). V1 substrate does this dance
433// internally so the caller always sees parent-path return
434// values.
435// < 0 negated verdict on parent-side failure
436//
437// argv must be a *i64 array where each slot is a *u8 string pointer;
438// the array is null-terminated (the terminator slot holds the
439// integer 0, not a string). envp same shape, or null to inherit.
440func nx_hal_spawn_linux(exec_path: *u8, argv: *i64, envp: *i64) -> i64 {
441 let pid: i64 = sys_fork()
442 if pid < 0 { return 0 - NX_HAL_FAULT }
443 if pid == 0 {
444 // CHILD path -- execve replaces image. If it returns,
445 // execve failed; the child exits non-zero so the parent
446 // sees the error via wait.
447 sys_execve(exec_path, argv, envp)
448 nx_hal_exit(127)
449 return 0 // unreachable
450 }
451 // PARENT path.
452 return pid
453}
454
455func nx_hal_spawn_bare_metal(exec_path: *u8, argv: *i64, envp: *i64) -> i64 {
456 // TODO_SILICON: bare-metal spawn = kernel sched_spawn(entry_addr,
457 // stack_top, brane_caps). No filesystem on bare metal yet, so
458 // exec_path resolves via an embedded module registry (kernel
459 // tasks.nx already enumerates task_entry_addr_a/_perf/_vm).
460 // HAL shim calls into kernel once K-7 exposes the spawn ABI.
461 return 0 - NX_HAL_NO_BACKING
462}
463
464func nx_hal_spawn_nishi_silicon(exec_path: *u8, argv: *i64, envp: *i64) -> i64 {
465 // TODO_SILICON: Tier A FPGA single-tasking has no spawn (return
466 // BAD_INPUT-style honest verdict). Tier B MPW with kernel routes
467 // through bare-metal path. Tier C home-fab: TBD.
468 return 0 - NX_HAL_NO_BACKING
469}
470
471func nx_hal_spawn(exec_path: *u8, argv: *i64, envp: *i64) -> i64 {
472 if (exec_path as i64) == 0 { return 0 - NX_HAL_BAD_INPUT }
473 if (argv as i64) == 0 { return 0 - NX_HAL_BAD_INPUT }
474 let s: i64 = nx_hal_active_shim()
475 if s == NX_HAL_SHIM_LINUX { return nx_hal_spawn_linux(exec_path, argv, envp) }
476 if s == NX_HAL_SHIM_BARE_METAL { return nx_hal_spawn_bare_metal(exec_path, argv, envp) }
477 if s == NX_HAL_SHIM_NISHI_SILICON { return nx_hal_spawn_nishi_silicon(exec_path, argv, envp) }
478 return 0 - NX_HAL_NO_BACKING
479}
480
481// ===== nx_hal_wait =================================================
482// Wait for the named child to exit. Returns exit code via the
483// out-pointer, returns OK / verdict on the call itself.
484// pid: > 0 means wait for that specific child; -1 means ANY child.
485// exit_code_out: *i64; on OK, this is filled with the child's
486// exit code (0..255 typical). Must be caller-allocated.
487func nx_hal_wait_linux(pid: i64, exit_code_out: *i64) -> i64 {
488 let status: *i64 = (sys_mmap(8)) as *i64
489 status[0] = 0
490 let reaped: i64 = sys_wait4(pid, status, 0)
491 if reaped < 0 { return 0 - NX_HAL_FAULT }
492 exit_code_out[0] = wait_exit_code(status[0])
493 return NX_HAL_OK
494}
495
496func nx_hal_wait_bare_metal(pid: i64, exit_code_out: *i64) -> i64 {
497 // TODO_SILICON: bare-metal wait = kernel sched_wait(pid, &exit).
498 // Kernel sched.nx already has task-exit notification via the
499 // IPC mailbox; HAL shim wraps it once K-7 exposes the ABI.
500 return 0 - NX_HAL_NO_BACKING
501}
502
503func nx_hal_wait_nishi_silicon(pid: i64, exit_code_out: *i64) -> i64 {
504 // TODO_SILICON: Tier A FPGA single-tasking has no wait. Tier B
505 // MPW routes through bare-metal path. Tier C home-fab: TBD.
506 return 0 - NX_HAL_NO_BACKING
507}
508
509func nx_hal_wait(pid: i64, exit_code_out: *i64) -> i64 {
510 if (exit_code_out as i64) == 0 { return 0 - NX_HAL_BAD_INPUT }
511 let s: i64 = nx_hal_active_shim()
512 if s == NX_HAL_SHIM_LINUX { return nx_hal_wait_linux(pid, exit_code_out) }
513 if s == NX_HAL_SHIM_BARE_METAL { return nx_hal_wait_bare_metal(pid, exit_code_out) }
514 if s == NX_HAL_SHIM_NISHI_SILICON { return nx_hal_wait_nishi_silicon(pid, exit_code_out) }
515 return 0 - NX_HAL_NO_BACKING
516}
517
518// ===== nx_hal_kill =================================================
519// Send a signal to a child. Linux shim: sys_kill. Substrate
520// supervisor uses this for the BACKING_OFF -> TERMINATED transition
521// when escalate is required.
522// pid: child PID (must be > 0; substrate refuses to signal-broadcast)
523// signal: SIGTERM=15 / SIGKILL=9 etc.
524func nx_hal_kill_linux(pid: i64, signal: i64) -> i64 {
525 let rc: i64 = __syscall(NX_HAL_SYS_KILL, pid, signal, 0, 0, 0, 0)
526 if rc != 0 { return 0 - NX_HAL_FAULT }
527 return NX_HAL_OK
528}
529
530func nx_hal_kill_bare_metal(pid: i64, signal: i64) -> i64 {
531 // TODO_SILICON: bare-metal signal delivery = kernel
532 // sched_signal(pid, signal). Kernel sched.nx does not yet have
533 // a signal abstraction; SIGTERM-equivalent is "set exit-pending
534 // flag, next preempt drops the task." HAL shim wraps that path
535 // once K-7 exposes it.
536 return 0 - NX_HAL_NO_BACKING
537}
538
539func nx_hal_kill_nishi_silicon(pid: i64, signal: i64) -> i64 {
540 // TODO_SILICON: Tier A FPGA single-tasking has no kill. Tier B
541 // MPW routes through bare-metal path. Tier C home-fab: TBD.
542 return 0 - NX_HAL_NO_BACKING
543}
544
545func nx_hal_kill(pid: i64, signal: i64) -> i64 {
546 if pid <= 0 { return 0 - NX_HAL_BAD_INPUT }
547 if signal <= 0 { return 0 - NX_HAL_BAD_INPUT }
548 if signal > 64 { return 0 - NX_HAL_BAD_INPUT }
549 let s: i64 = nx_hal_active_shim()
550 if s == NX_HAL_SHIM_LINUX { return nx_hal_kill_linux(pid, signal) }
551 if s == NX_HAL_SHIM_BARE_METAL { return nx_hal_kill_bare_metal(pid, signal) }
552 if s == NX_HAL_SHIM_NISHI_SILICON { return nx_hal_kill_nishi_silicon(pid, signal) }
553 return 0 - NX_HAL_NO_BACKING
554}