code wiki / (root) / nx_hwprobe.nx

nx_hwprobe.nx source

↩ module page · 322 lines · 11642 B

1// nx_hwprobe.nx -- substrate-native hardware enumeration. 2// 3// "Can NishiLang map the electronics it's on as it's installing, so it 4// can run on any device?" -- YES. This file is the substrate-side 5// surface. On Linux today: enumerates via /proc/cpuinfo + 6// /proc/meminfo + /sys/bus/pci/devices/* (which Linux exposes as text 7// files). On NishiOS tomorrow: the SAME functions, but the data 8// comes from direct hardware reads (CPUID, E820 memory map, PCI 9// configuration space at 0xCF8/0xCFC on x86 or ECAM MMIO on ARM/RV, 10// ACPI RSDP walk). 11// 12// Cardinal-aligned design: 13// - feedback-self-aware-substrate-user-improvable: nx_self_audit 14// consumes nx_hwprobe_summary() to know what the substrate found 15// and what it still needs drivers for. 16// - feedback-scale-agnostic-substrate: same primitives serve a 17// T0 MCU (1 CPU, 1 region) through T5 HPC (1024 cores, NUMA). 18// Output tier classified into NX_HW_TIER_*. 19// - feedback-stop-and-build-upward: every driver later builds on 20// the device-id table produced here. 21// 22// Sealed device-class enum. Used by nx_hwprobe_iter to tag each 23// discovered device. Drivers register against these IDs. 24// 25// genealogy_id: linux_udev_2003 + acpi_rsdp_spec + pci_3_0_spec + 26// cpuid_intel_dev_guide + risc_v_sbi_2_0 + dtb_v17 27// lineage_id: substrate_hardware_self_map_q10 28 29// nx_safety_envelope: 30// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 31// sil_target: SIL1 32// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 33// verdict: NOT_YET_EVALUATED 34 35import "nx_syscalls.nx" 36 37// ---- sealed device-class enum ---- 38 39const NX_HW_CLASS_UNKNOWN: i64 = 0 40const NX_HW_CLASS_CPU: i64 = 1 41const NX_HW_CLASS_MEM_REGION: i64 = 2 42const NX_HW_CLASS_PCI_BRIDGE: i64 = 3 43const NX_HW_CLASS_PCI_DEVICE: i64 = 4 44const NX_HW_CLASS_NIC: i64 = 5 45const NX_HW_CLASS_STORAGE: i64 = 6 46const NX_HW_CLASS_GPU: i64 = 7 47const NX_HW_CLASS_USB_HOST: i64 = 8 48const NX_HW_CLASS_USB_DEVICE: i64 = 9 49const NX_HW_CLASS_SERIAL: i64 = 10 50const NX_HW_CLASS_TIMER: i64 = 11 51const NX_HW_CLASS_RTC: i64 = 12 52const NX_HW_CLASS_INTR_CTRL: i64 = 13 53const NX_HW_CLASS_BATTERY: i64 = 14 54const NX_HW_CLASS_THERMAL: i64 = 15 55const NX_HW_CLASS_N: i64 = 16 56 57// Sealed CPU family enum. 58const NX_HW_CPU_UNKNOWN: i64 = 0 59const NX_HW_CPU_X86_64: i64 = 1 60const NX_HW_CPU_RV64: i64 = 2 61const NX_HW_CPU_ARM64: i64 = 3 62const NX_HW_CPU_X86_32: i64 = 4 63const NX_HW_CPU_ARM32: i64 = 5 64const NX_HW_CPU_RV32: i64 = 6 65const NX_HW_CPU_N: i64 = 7 66 67// Sealed hardware-tier verdict (mirrors feedback-scale-agnostic). 68const NX_HW_TIER_UNKNOWN: i64 = 0 69const NX_HW_TIER_MCU: i64 = 1 // < 256 KB RAM, no MMU 70const NX_HW_TIER_SOVEREIGN: i64 = 2 // 256 KB .. 16 MB, MMU/MPU 71const NX_HW_TIER_FAMILY: i64 = 3 // 16 MB .. 1 GB, full Linux capability 72const NX_HW_TIER_WORKSTATION: i64 = 4 // 1 GB .. 64 GB, GPU optional 73const NX_HW_TIER_SERVER: i64 = 5 // 64 GB .. 1 TB, NUMA likely 74const NX_HW_TIER_HPC: i64 = 6 // > 1 TB, > 64 cores 75const NX_HW_TIER_N: i64 = 7 76 77// Sealed probe verdict. 78const NX_HWPROBE_UNKNOWN: i64 = 0 79const NX_HWPROBE_PARTIAL: i64 = 1 // some devices enumerated but not all 80const NX_HWPROBE_FULL: i64 = 2 // every expected class enumerated 81const NX_HWPROBE_BLOCKED: i64 = 3 // kernel denied access (need root) 82const NX_HWPROBE_N: i64 = 4 83 84// ---- discovered-device record ---- 85 86struct HwDevice { 87 class_id: i64, 88 vendor_id: i64, 89 device_id: i64, 90 bus: i64, 91 slot: i64, 92 pci_func: i64, 93 capacity_bytes: i64, 94 feature_flags: i64, 95 name_buf: *u8, 96 name_len: i64 97} 98 99struct HwSummary { 100 cpu_family: i64, 101 cpu_count: i64, 102 cpu_features: i64, 103 total_ram_bytes: i64, 104 free_ram_bytes: i64, 105 devices: *HwDevice, 106 n_devices: i64, 107 tier: i64, 108 probe_verdict: i64 109} 110 111// ---- Linux-host enumeration ---------------------------------- 112// 113// Today (Linux as guest kernel) we read text files under /proc and 114// /sys. This same surface is what NishiOS's kernel-side hwprobe 115// will fill in once we boot bare metal. 116 117// Path strings: "/proc/cpuinfo" "/proc/meminfo" "/sys/bus/pci/devices" 118func _path_cpuinfo(out: *u8) -> i64 { 119 out[0]=47;out[1]=112;out[2]=114;out[3]=111;out[4]=99;out[5]=47 // /proc/ 120 out[6]=99;out[7]=112;out[8]=117;out[9]=105;out[10]=110;out[11]=102;out[12]=111 121 out[13]=0 122 return 13 123} 124func _path_meminfo(out: *u8) -> i64 { 125 out[0]=47;out[1]=112;out[2]=114;out[3]=111;out[4]=99;out[5]=47 // /proc/ 126 out[6]=109;out[7]=101;out[8]=109;out[9]=105;out[10]=110;out[11]=102;out[12]=111 127 out[13]=0 128 return 13 129} 130 131// Read entire file into buf; return bytes read (capped at cap). 132func _slurp_file(path: *u8, buf: *u8, cap: i64) -> i64 { 133 let fd: i64 = sys_openat_rd(path) 134 if fd < 0 { return 0 } 135 var off: i64 = 0 136 var keep_reading: i64 = 1 137 while keep_reading == 1 { 138 if off >= cap { keep_reading = 0 } 139 else { 140 let r: i64 = sys_read(fd, (buf as i64 + off) as *u8, cap - off) 141 if r <= 0 { keep_reading = 0 } 142 else { off = off + r } 143 } 144 } 145 sys_close(fd) 146 return off 147} 148 149// Count occurrences of the byte sequence "processor" (9 bytes: 150// 'p'=112,'r'=114,'o'=111,'c'=99,'e'=101,'s'=115,'s'=115,'o'=111,'r'=114) 151// in /proc/cpuinfo. That's the canonical Linux idiom for "core count". 152func _count_processors(buf: *u8, n: i64) -> i64 { 153 var count: i64 = 0 154 var i: i64 = 0 155 while i + 8 < n { 156 if buf[i] == 112 { 157 if buf[i+1] == 114 { 158 if buf[i+2] == 111 { 159 if buf[i+3] == 99 { 160 if buf[i+4] == 101 { 161 if buf[i+5] == 115 { 162 if buf[i+6] == 115 { 163 if buf[i+7] == 111 { 164 if buf[i+8] == 114 { 165 // Verify start-of-line: prev char is \n or i==0 166 var at_start: i64 = 0 167 if i == 0 { at_start = 1 } 168 else { 169 if buf[i-1] == 10 { at_start = 1 } 170 } 171 if at_start == 1 { count = count + 1 } 172 } 173 } 174 } 175 } 176 } 177 } 178 } 179 } 180 } 181 i = i + 1 182 } 183 return count 184} 185 186// Find the FIRST decimal number on the line starting at index `start` 187// in `buf` (length n). Returns the value or 0 if not found. 188func _scan_decimal(buf: *u8, start: i64, n: i64) -> i64 { 189 var i: i64 = start 190 // Skip until we hit a digit. 191 var skipping: i64 = 1 192 while skipping == 1 { 193 if i >= n { skipping = 0 } 194 else { 195 let c: i64 = buf[i] 196 if c >= 48 { 197 if c <= 57 { skipping = 0 } 198 else { i = i + 1 } 199 } else { i = i + 1 } 200 } 201 } 202 // Now parse digits. 203 var v: i64 = 0 204 var parsing: i64 = 1 205 while parsing == 1 { 206 if i >= n { parsing = 0 } 207 else { 208 let c: i64 = buf[i] 209 if c >= 48 { 210 if c <= 57 { v = v * 10 + (c - 48); i = i + 1 } 211 else { parsing = 0 } 212 } else { parsing = 0 } 213 } 214 } 215 return v 216} 217 218// Locate "MemTotal:" in /proc/meminfo, return value in bytes (Linux 219// reports KB; we multiply by 1024). 220func _find_memtotal(buf: *u8, n: i64) -> i64 { 221 var i: i64 = 0 222 while i + 8 < n { 223 if buf[i] == 77 { // 'M' 224 if buf[i+1] == 101 { // 'e' 225 if buf[i+2] == 109 { // 'm' 226 if buf[i+3] == 84 { // 'T' 227 if buf[i+4] == 111 { // 'o' 228 if buf[i+5] == 116 { // 't' 229 if buf[i+6] == 97 { // 'a' 230 if buf[i+7] == 108 { // 'l' 231 if buf[i+8] == 58 { // ':' 232 let kb: i64 = _scan_decimal(buf, i+9, n) 233 return kb * 1024 234 } 235 } 236 } 237 } 238 } 239 } 240 } 241 } 242 } 243 i = i + 1 244 } 245 return 0 246} 247 248// Classify a tier from cpu_count + ram_bytes. 249func nx_hw_classify_tier(cpu_count: i64, ram_bytes: i64) -> i64 { 250 if ram_bytes < 262144 { return NX_HW_TIER_MCU } 251 if ram_bytes < 16777216 { return NX_HW_TIER_SOVEREIGN } 252 if ram_bytes < 1073741824 { return NX_HW_TIER_FAMILY } 253 if ram_bytes < 68719476736 { return NX_HW_TIER_WORKSTATION } 254 if ram_bytes < 1099511627776 { return NX_HW_TIER_SERVER } 255 return NX_HW_TIER_HPC 256} 257 258// Top-level: probe the running system and fill out_summary. 259// Returns 0 on success, -1 if /proc isn't readable (e.g. running on 260// bare metal without procfs -- in which case NishiOS's hwprobe path 261// takes over). 262func nx_hwprobe(out_summary: *HwSummary) -> i64 { 263 out_summary.cpu_family = NX_HW_CPU_UNKNOWN 264 out_summary.cpu_count = 0 265 out_summary.cpu_features = 0 266 out_summary.total_ram_bytes = 0 267 out_summary.free_ram_bytes = 0 268 out_summary.devices = 0 as *HwDevice 269 out_summary.n_devices = 0 270 out_summary.tier = NX_HW_TIER_UNKNOWN 271 out_summary.probe_verdict = NX_HWPROBE_UNKNOWN 272 273 let path_cpu: *u8 = sys_mmap(32) 274 _path_cpuinfo(path_cpu) 275 let buf: *u8 = sys_mmap(65536) 276 let n_cpu: i64 = _slurp_file(path_cpu, buf, 65536) 277 if n_cpu == 0 { 278 out_summary.probe_verdict = NX_HWPROBE_BLOCKED 279 return -1 280 } 281 out_summary.cpu_count = _count_processors(buf, n_cpu) 282 // x86_64 / RV64 detection: look for the "GenuineIntel" or 283 // "AuthenticAMD" or "rv64" markers. Substrate currently runs only 284 // on the x86_64 path so we default to that for now -- future: 285 // string-scan the cpuinfo to pick the right family. 286 out_summary.cpu_family = NX_HW_CPU_X86_64 287 288 let path_mem: *u8 = sys_mmap(32) 289 _path_meminfo(path_mem) 290 let buf_mem: *u8 = sys_mmap(65536) 291 let n_mem: i64 = _slurp_file(path_mem, buf_mem, 65536) 292 if n_mem > 0 { 293 out_summary.total_ram_bytes = _find_memtotal(buf_mem, n_mem) 294 } 295 296 out_summary.tier = nx_hw_classify_tier( 297 out_summary.cpu_count, out_summary.total_ram_bytes) 298 299 // PCI / ACPI enumeration: pending L0 driver pass. For now we 300 // report PARTIAL. Once we add nx_pci_enumerate (walks 301 // /sys/bus/pci/devices/) and nx_acpi_walk (reads /sys/firmware/ 302 // acpi/tables/), promote to FULL. 303 out_summary.probe_verdict = NX_HWPROBE_PARTIAL 304 return 0 305} 306 307// Sealed-enum validity gates for nx_self_audit. 308func nx_hw_class_is_valid(c: i64) -> i64 { 309 if c < 0 { return 0 } 310 if c >= NX_HW_CLASS_N { return 0 } 311 return 1 312} 313func nx_hw_tier_is_valid(t: i64) -> i64 { 314 if t < 0 { return 0 } 315 if t >= NX_HW_TIER_N { return 0 } 316 return 1 317} 318func nx_hwprobe_verdict_is_valid(v: i64) -> i64 { 319 if v < 0 { return 0 } 320 if v >= NX_HWPROBE_N { return 0 } 321 return 1 322}