463 lines · plain
1.. SPDX-License-Identifier: GPL-2.02 3================================================================================4arch/wasm32 boot protocol (wasm_boot_info v2)5================================================================================6 7:Status: K2 (setup_arch v1) → K6-B1 (v2 + initramfs). Defines the8 HardwareJS → kernel handoff at module instantiation time.9 v1 was the initial K2..K5 surface; v2 lands at K6-B1 to10 carry HardwareJS-supplied initramfs bytes11 (``initrd_offset`` / ``initrd_size``). The struct stays at12 96 bytes; the v1 reserved tail shrinks from u32[4] to13 u32[2] to make room.14 15This document is the source of truth for *what HardwareJS hands the16kernel at boot time* and *how the kernel reads it*. It plays the same17role for the boot interface that ``memory-model.rst`` plays for the18memory interface: spec-first, code-second. Treat any divergence19between this document and either ``arch/wasm32/include/asm/boot.h`` or20``hardwarejs/src/bootKernel.ts`` as a bug in the code, not the doc.21 22Why a boot protocol at all23~~~~~~~~~~~~~~~~~~~~~~~~~~24 25Every Linux arch port has a bootloader → kernel handoff:26 27 - x86 receives ``boot_params`` (offset 0x1f1 in the bzImage real-mode28 stub).29 - arm64 / riscv receive a flattened devicetree pointer (in ``x0`` or30 ``a1`` respectively).31 - powerpc receives a `prom_init`-built tree pointer.32 - multiboot receives ``mb_info`` in ``%ebx``.33 34The handoff is small but load-bearing: it carries the RAM map, the35command line, optionally an initramfs pointer, and a magic so the36kernel can refuse to boot if the bootloader's idea of "this kernel" is37wrong. wasm32 is no different — it just runs in a JS engine instead38of on metal. HardwareJS is the bootloader.39 40For K2, only a small subset of what a real bootloader provides is41needed (RAM map + command line + boot-CPU id). The struct is42versioned so K3+ can grow it (initramfs, multi-bank RAM map, host43clock identity) without breaking K2 binaries.44 45--------------------------------------------------------------------------------461. Entry-point ABI47--------------------------------------------------------------------------------48 49The kernel exports a single boot entry-point::50 51 (export "wasm_start_kernel" (func (param i32)))52 53The single ``i32`` parameter is the byte offset, into54``env.kernel_memory``, of a ``struct wasm_boot_info`` populated by55HardwareJS before the call. The kernel reads (never writes) the56struct, validates it (see §3), copies out the fields it needs, and57proceeds with ``start_kernel()``.58 59Rationale for "offset into ``env.kernel_memory``", not a JS pointer60type: wasm32 pointers are 32-bit offsets into the active linear61memory. HardwareJS writes the struct directly into the SAB-backed62``env.kernel_memory`` (which it owns the allocation of, §2) and hands63the kernel the address. The kernel can then dereference fields via64ordinary loads against memory 0 (= ``env.kernel_memory`` in its own65local view) — no per-field copy required, no bounce buffer, and the66struct is unambiguously inside the same address space the kernel67already operates on.68 69K1 used a zero-argument signature; K2's bump to ``(i32)`` is the70first ABI break in the kernel module's export shape. We tag this71explicitly: a v1 HardwareJS cannot instantiate a v0 kernel and vice72versa (the wasm engine throws a type-mismatch ``LinkError`` at73instantiate, which is the correct behaviour).74 75--------------------------------------------------------------------------------762. Where the struct lives77--------------------------------------------------------------------------------78 79HardwareJS allocates the struct out of ``env.kernel_memory`` itself,80at the address immediately following the kernel image. Concretely:81 82* ``__heap_base`` (a wasm global exported by ``wasm-ld``) marks the83 first byte past the kernel's ``.text`` / ``.rodata`` / ``.data`` /84 ``.bss``.85* HardwareJS rounds ``__heap_base`` up to 16-byte alignment, places86 the ``struct wasm_boot_info`` there, copies the command-line bytes87 immediately after the struct (also 16-byte aligned), and exposes88 ``usable_start`` (see §4) as "first byte ``memblock_add`` may take".89 90Layout of the prologue inside ``env.kernel_memory``::91 92 +----------------------+ 093 | __wasm_call_ctors / |94 | .text / .rodata |95 | .data / .bss | ┐96 | __heap_base ─────── | ─┘ end-of-image marker emitted by wasm-ld97 +----------------------+98 | 16B align padding |99 +----------------------+ <-- boot_info_offset (passed as arg to wasm_start_kernel)100 | struct |101 | wasm_boot_info |102 +----------------------+103 | 16B align padding |104 +----------------------+ <-- boot_info.cmdline_offset105 | command-line bytes |106 | (NUL-terminated) |107 +----------------------+108 | 16B align padding |109 +----------------------+ <-- boot_info.usable_start110 | |111 | memblock's domain |112 | (free RAM) |113 | |114 +----------------------+ <-- boot_info.usable_end (== total_memory in v1)115 116HardwareJS knows ``__heap_base`` because it reads it back from the117freshly-instantiated module's exports just before calling118``wasm_start_kernel``. The placement is **not** hardcoded in either119the C or the TypeScript; both sides compute it from ``__heap_base``.120 121If HardwareJS ever needs to reserve a trailing region for itself —122e.g. an in-RAM debug ring, a virtio MMIO doorbell page — it shrinks123``usable_end`` accordingly. K2 has no such reservation;124``usable_end == total_memory`` and the kernel sees one contiguous125RAM bank.126 127--------------------------------------------------------------------------------1283. Struct ``wasm_boot_info`` (v2; v1 → v2 diff inline)129--------------------------------------------------------------------------------130 131::132 133 /* All fields little-endian, naturally aligned, no padding bytes.134 * Total size: 96 bytes in BOTH v1 and v2 (sizeof(struct135 * wasm_boot_info) MUST equal the struct_size field's value).136 * v2 (K6-B1) reclaims 8 bytes from v1's reserved[4] for137 * initrd_offset + initrd_size.138 */139 struct wasm_boot_info {140 __le32 magic; /* 'W' 'B' 'I' '1' == 0x31494257 */141 __le32 abi_version; /* 1 for v1; 2 for v2 (K6-B1+) */142 __le32 struct_size; /* 96 in v1 and v2 */143 __le32 flags; /* reserved, MUST be 0 */144 145 __le64 total_memory; /* total bytes in env.kernel_memory */146 __le64 usable_start; /* first byte memblock may take */147 __le64 usable_end; /* last+1 byte memblock may take */148 149 __le64 kernel_image_start; /* 0 in v1 and v2 */150 __le64 kernel_image_end; /* __heap_base */151 152 __le32 cmdline_offset; /* offset into env.kernel_memory */153 __le32 cmdline_length; /* bytes, excluding trailing NUL */154 155 __le32 num_cpus; /* HardwareJS's intended max */156 __le32 boot_cpu_id; /* 0 in v1 and v2 */157 158 __le64 host_timer_freq_hz; /* for jiffies math at K5+ */159 160 /* --- v2 additions (K6-B1) --- */161 __le32 initrd_offset; /* offset into env.kernel_memory162 * of HardwareJS-supplied cpio,163 * or 0 if no initramfs */164 __le32 initrd_size; /* byte length of initrd, or 0 */165 166 __le32 reserved[2]; /* zero in v2; nonzero rejects.167 * Shrunk from u32[4] in v1. */168 };169 170Magic and version fields171~~~~~~~~~~~~~~~~~~~~~~~~172 173* ``magic`` is the ASCII string ``WBI1`` interpreted as little-endian174 u32 (i.e. ``0x31494257``). The kernel rejects any other value with175 ``panic("wasm_boot_info: bad magic 0x%08x")`` BEFORE touching176 memblock or printk's ring buffer (the early console is already up177 from K1 so this panic surfaces via ``linux.dmesg``).178 179* ``abi_version`` is the protocol version. The build pins the180 currently-supported version in181 ``arch/wasm32/include/asm/boot.h::WASM_BOOT_INFO_ABI_VERSION``182 (v1 == 1 in K2..K5; v2 == 2 in K6-B1+). Other values cause183 ``panic("wasm_boot_info: unsupported abi_version %u")``. Versions184 are not back-compat: kernel + HardwareJS must agree exactly. If185 we need to extend the struct, we bump this number; we do not186 try to extend in-place by relying on ``struct_size``.187 188* ``struct_size`` must equal ``sizeof(struct wasm_boot_info)`` for189 the given ``abi_version``. The kernel rejects any other size with190 ``panic("wasm_boot_info: struct_size mismatch")``. This guards191 against the case where HardwareJS and kernel are nominally on the192 same version but somehow have drifted (e.g. unrelated padding193 changes in a struct member type).194 195* ``flags`` is reserved in v1 and v2 and must be zero. Nonzero196 rejects.197 198* ``reserved[]`` is reserved in v1 (u32[4]) and v2 (u32[2]); every199 element must be zero. Any nonzero element rejects. This means200 HardwareJS can NEVER emit-and-ignore unknown fields — the kernel201 reads every byte and the spec is monolithic per version.202 203Memory map fields204~~~~~~~~~~~~~~~~~205 206* ``total_memory`` is the total byte-length of ``env.kernel_memory``207 *at instantiation time*. The kernel uses this as an upper bound;208 HardwareJS is responsible for sizing the Memory's ``initial`` and209 ``maximum`` pages such that this value lies in the closed210 ``[initial * 64KiB, maximum * 64KiB]`` interval. The kernel does211 not currently call ``memory.grow``; that is K3+ territory.212 213* ``usable_start`` is the first byte of RAM that ``memblock_add``214 may claim. It MUST be ``>= kernel_image_end`` and 16-byte aligned.215 HardwareJS computes it as216 ``(boot_info_offset + struct_size + cmdline_length + 1)`` rounded217 up to the next 16-byte boundary, after rounding ``boot_info_offset``218 itself to 16 (so the struct, cmdline, and ``usable_start`` are all219 aligned).220 221* ``usable_end`` is one past the last byte of usable RAM. In v1 this222 is always ``total_memory``. If HardwareJS reserves a trailing223 region (post-K2), ``usable_end`` shrinks; the kernel never sees224 HardwareJS-reserved RAM as part of its allocator domain.225 226* ``kernel_image_start`` is always 0 in v1 (the kernel module's227 load address is byte 0 of its memory-0 view).228 229* ``kernel_image_end`` is the value of the ``__heap_base`` wasm230 global as observed by HardwareJS just before the boot call. The231 kernel verifies this is ``>= 0x1000`` (paranoia floor; a kernel232 smaller than 4 KiB would be a build bug worth panicking on) and233 ``< total_memory``. ``memblock_reserve(0, kernel_image_end)`` is234 the first thing setup_arch does, so the kernel image's static235 data is never handed back to the buddy allocator.236 237Command line238~~~~~~~~~~~~239 240* ``cmdline_offset`` is the byte offset into ``env.kernel_memory``241 of the command-line string. The bytes are valid for at least242 ``cmdline_length`` bytes; HardwareJS additionally writes a NUL243 byte at ``cmdline_offset + cmdline_length`` (the kernel relies244 on this for ``strlen`` paths). It MUST satisfy ``cmdline_offset245 + cmdline_length < usable_start`` (i.e. the cmdline lives within246 the prologue, not in memblock-managed RAM).247 248* ``cmdline_length`` is the byte length excluding the trailing249 NUL. Maximum is 4095; the kernel rejects ``cmdline_length >250 COMMAND_LINE_SIZE - 1`` with a panic. Strings shorter than 4095251 are fine and arrive as-is (no per-arch ``""`` substitution).252 253* If ``cmdline_length == 0`` the kernel treats the command line as254 empty (``""``). ``cmdline_offset`` is then ignored, but255 HardwareJS SHOULD still set it to ``boot_info_offset +256 struct_size`` rounded to 16 so a memory dump shows the prologue257 in its expected shape.258 259CPU topology260~~~~~~~~~~~~261 262* ``num_cpus`` is the maximum number of logical CPUs HardwareJS263 intends to support in this boot. K2 ignores this (we hardcode264 ``num_online_cpus() == 1``); K4 onwards reads it to size265 per-CPU areas and CPU-id allocation. Must be in266 ``[1, CONFIG_NR_CPUS]``; the kernel rejects out-of-range with267 ``panic("wasm_boot_info: num_cpus out of range")``.268 269* ``boot_cpu_id`` is the logical id of the CPU running270 ``wasm_start_kernel``. Always 0 in v1; the kernel rejects any271 other value to keep K2's setup_arch monomorphic.272 273Host timer274~~~~~~~~~~275 276* ``host_timer_freq_hz`` is the host clock frequency in Hz at277 which ``linux.now_ns`` will tick. In practice this is always278 10^9 (nanoseconds) on Node and the browser, but the field279 exists so the kernel does not have to assume that — K5's280 hrtimer integration reads this to set the clocksource shift /281 mult fields. K2 stores the value but does not act on it; if282 HardwareJS reports anything other than 10^9 in K2 we log it283 via pr_info and continue.284 285Initramfs (v2 additions, K6-B1)286~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~287 288* ``initrd_offset`` is the byte offset into ``env.kernel_memory``289 of a HardwareJS-supplied initramfs (newc/crc cpio archive). 0290 means "no initramfs supplied"; in that case ``initrd_size``291 must also be 0 (the kernel rejects the mismatched pair with292 ``panic("wasm_boot_info: initrd_offset=%u initrd_size=%u (one293 zero, one not)")``).294 295* ``initrd_size`` is the byte length of the initramfs. 0 paired296 with ``initrd_offset == 0``. When nonzero, the window297 ``[initrd_offset, initrd_offset + initrd_size)`` MUST:298 299 - lie entirely inside ``[0, total_memory)``,300 - start at or past ``kernel_image_end`` (not overlap the301 kernel image), and302 - not overlap the memblock window303 ``[usable_start, usable_end)``.304 305 Violations of any of these three are rejected with a precise306 panic line naming the overlap. HardwareJS' canonical layout307 places the initrd in the boot prologue between the cmdline308 buffer and ``usable_start``; the kernel does not require that309 exact placement, only the disjointness invariants.310 311* On the kernel side, ``setup_arch`` reads these two fields and312 sets ``initrd_start = initrd_offset`` and ``initrd_end =313 initrd_start + initrd_size`` (the upstream globals from314 ``init/do_mounts_initrd.c``). It also sets315 ``initrd_below_start_ok = 1`` because the canonical placement316 is below ``min_low_pfn = PFN_UP(usable_start)`` and the317 init/main.c sanity check would otherwise reject the initrd.318 319* wasm32's ``__initcall*_start/end`` section symbols are 0-imports320 (see ``arch/wasm32/kernel/sections.c``) so the upstream321 ``rootfs_initcall(populate_rootfs)`` never runs. Instead,322 ``arch_kernel_init_pre_run_init`` (the K5-C2 arch hook) calls323 ``unpack_to_rootfs(initrd_start, initrd_end - initrd_start)``324 directly. This is the "wasm32 doesn't dispatch initcalls" §15325 row's K6-B1-scoped workaround; a generic initcall-dispatch326 shim is a future K-phase concern.327 328--------------------------------------------------------------------------------3294. Strict validation330--------------------------------------------------------------------------------331 332The kernel's first action in ``wasm_start_kernel(boot_info_offset)``333is to read 4 bytes at ``boot_info_offset + offsetof(magic)``,334compare to the v1 magic, and panic on mismatch BEFORE doing anything335else — including before calling ``start_kernel`` or even336``__wasm32_set_current``. The boot console registered by K1 is still337available because the wasm module's static initializers already ran338during ``__wasm_call_ctors`` (run by HardwareJS before339``wasm_start_kernel``); the early dmesg path therefore works.340 341Rejection cases (each panics with a distinct message so the test342harness can assert the specific failure mode):343 344============================================================ =================================================345Cause panic() reason346============================================================ =================================================347``magic`` ≠ ``WBI1`` ``wasm_boot_info: bad magic 0x%08x``348``abi_version`` ≠ supported ``wasm_boot_info: unsupported abi_version %u``349``struct_size`` ≠ ``sizeof(struct wasm_boot_info)`` ``wasm_boot_info: struct_size %u, expected %zu``350``flags`` ≠ 0 ``wasm_boot_info: flags must be 0, got 0x%x``351any ``reserved[i]`` ≠ 0 ``wasm_boot_info: reserved[%u] must be 0``352``kernel_image_end`` < 0x1000 ``wasm_boot_info: kernel_image_end < 4 KiB``353``kernel_image_end`` ≥ ``total_memory`` ``wasm_boot_info: kernel_image_end >= total_memory``354``usable_start`` < ``kernel_image_end`` ``wasm_boot_info: usable_start < kernel_image_end``355``usable_end`` > ``total_memory`` ``wasm_boot_info: usable_end > total_memory``356``usable_start`` ≥ ``usable_end`` ``wasm_boot_info: empty memblock window``357``cmdline_length`` ≥ ``COMMAND_LINE_SIZE`` ``wasm_boot_info: cmdline_length too large``358``cmdline_offset + cmdline_length`` ≥ ``usable_start`` ``wasm_boot_info: cmdline overlaps memblock``359``num_cpus`` < 1 or > ``CONFIG_NR_CPUS`` ``wasm_boot_info: num_cpus out of range``360``boot_cpu_id`` ≠ 0 ``wasm_boot_info: boot_cpu_id != 0``361(v2) one of ``initrd_offset``/``initrd_size`` zero, other not ``wasm_boot_info: initrd_offset=%u initrd_size=%u``362(v2) initrd window past ``total_memory`` ``wasm_boot_info: initrd window ... extends past total_memory``363(v2) ``initrd_offset`` < ``kernel_image_end`` ``wasm_boot_info: initrd_offset ... < kernel_image_end``364(v2) initrd overlaps ``[usable_start, usable_end)`` ``wasm_boot_info: initrd window ... overlaps memblock``365============================================================ =================================================366 367These checks live in ``arch/wasm32/kernel/setup.c::wasm_validate_boot_info``,368which is called exactly once from ``wasm_start_kernel`` before369``start_kernel()``. Adding validation rules requires a doc-side370addition to this table in the same commit.371 372--------------------------------------------------------------------------------3735. HardwareJS-side construction374--------------------------------------------------------------------------------375 376The reference implementation lives in377``hardwarejs/src/kernelWorker.mjs`` (struct writer) with the378field-offset / version constants mirrored in379``hardwarejs/src/boot-info-abi.ts`` (TypeScript module exported380from ``hardwarejs`` so callers can validate boot_info bytes381without depending on private offsets). It MUST satisfy all of §3382and §4. In particular:383 3841. Read ``__heap_base`` from ``instance.exports``.3852. Align that value to the next 16-byte boundary; call this386 ``boot_info_offset``.3873. Write the magic/version/size fields first (atomic vs. the388 rest of the struct is unimportant — the kernel runs in a389 single Worker that is not yet awake), then the memory map390 fields, then the cmdline_offset/length, then the391 cpu/timer fields, then the v2 initrd_offset/initrd_size,392 then reserved.3934. Copy the command-line string at394 ``boot_info_offset + struct_size`` rounded to 16, with a NUL395 byte one past the end.3965. If ``bootKernel({ initramfs })`` was supplied, copy the cpio397 bytes at ``(cmdline_offset + cmdline_length + 1)`` rounded to398 16; record that offset and length in399 ``initrd_offset`` / ``initrd_size``. Otherwise leave both400 fields zero.4016. Set ``usable_start`` to the next 16-byte boundary past either402 the cmdline NUL (no initramfs) or the initrd region (with403 initramfs).4047. Set ``usable_end = total_memory``.4058. Call ``instance.exports.wasm_start_kernel(boot_info_offset)``.406 407If HardwareJS itself wants to verify before calling the kernel —408useful for fuzz-test harnesses that construct malformed boot_info on409purpose — it MAY parse the struct back and re-check; the kernel's410validation is the authoritative one.411 412--------------------------------------------------------------------------------4136. ABI stability promise414--------------------------------------------------------------------------------415 416The v1 protocol shipped at K2 and was the stable surface through417K5. K6-B1 bumps to v2 to add ``initrd_offset`` /418``initrd_size`` for HardwareJS-supplied initramfs delivery. Field419additions, renames, type changes, semantic changes: all require420a ``abi_version`` bump.421 422The ``reserved[]`` array exists to let us experiment in423non-stable branches (e.g. an out-of-tree K-experiment that wants424to plumb a new field) without burning a version number on every425attempt; production branches between v0.1.0 and v1.0.0 must426either land their new field as a new versioned ABI, or stay427within ``reserved[]``.428 429After v1.0.0, any new ABI version is a breaking change of the430``wackywasm`` userspace too (since userspace binaries depend on a431kernel that depends on a HardwareJS) and is gated on the next432major version of the project.433 434--------------------------------------------------------------------------------4357. v1 → v2 historical note (K6-B1)436--------------------------------------------------------------------------------437 438K6-B1 bumped the ABI from v1 to v2. The changes were439intentionally minimal:440 441* ``abi_version`` 1 → 2.442* ``initrd_offset`` (u32) and ``initrd_size`` (u32) added at443 byte offsets 80 and 84.444* ``reserved[4]`` shrunk to ``reserved[2]`` (offsets 88..95) to445 keep ``sizeof(struct wasm_boot_info)`` at 96.446* Validation gained four new rejection cases (see §4 table) that447 enforce disjointness of the initrd window from the kernel448 image and the memblock window.449 450A C ``static_assert`` in ``arch/wasm32/include/asm/boot.h`` pins451each new field's byte offset; the TypeScript mirror in452``hardwarejs/src/boot-info-abi.ts`` exports the same offsets as453named constants and is cross-checked at runtime by454``hardwarejs/test/k6-b1-bootinfo-v2.test.ts``.455 456A v1 caller hitting a v2 kernel (or vice versa) is rejected at457``wasm_boot_panic("wasm_boot_info: unsupported abi_version458%u")``. We intentionally did NOT add forward/backward-compat459handling at K6-B1; the K6-B1 commit bumps both sides in lockstep.460Forward-compat support (e.g. v2 kernel accepting v1 callers with461no initrd) is a §15 future row to revisit only if a real demo462client cannot be updated in lockstep with kernel builds.463