# LOADER_CROSS — the boundary between the TFTP transport and the COFF loader Append-only. Each entry starts with a timestamp header. --- ## 2026-10-18 — COFF loader interface (loader half complete) The loader half is implemented and tested with debugger-poked images. This entry is the contract the transport half must satisfy; it is written by the loader owner for the transport owner. ### What the transport hands over 1. **The complete linked COFF image in RAM.** Not the `.obj`; the GSPLNK `-a` output (`*.out`), byte-for-byte, 510–774 bytes for the current test apps. The transport owns the receive buffer and passes its **bit address** and **byte length**. The loader does not allocate or receive the image itself. 2. **The loader resident at bit `0x01880000`.** The raw `dwt/loader/build/loader.bin` (1220 bytes, `.text` size `0x2620` bits) is position-dependent; it must be placed exactly there. A transport that includes the loader in its own payload must copy it to that address first (and must not overwrite it). 3. **A descriptor** at bit `0x01882C00` (or at an address passed in `A8`). Offsets are **bytes**; in the loader's assembly they are bits (+0, +0x10, +0x20, +0x40, +0x60): ``` DESC + 0x00 u16 0x4C44 "LD" ) both magic words must match DESC + 0x02 u16 0x5231 "R1" ) DESC + 0x04 u32 buf bit address of the COFF image DESC + 0x08 u32 len image length in bytes DESC + 0x0C u32 mode 1 = return to caller ``` `mode = 1` is the transport mode. `mode = 0` is the debugger mode: the loader restores the interrupted context and jumps to the PC the harness saved; a transport must not use it. 4. **A call**, using the firmware convention the loader itself uses for `create_process`: ``` move a14, -*sp, 1 movi DESC_ADDR, a14, L ; no descriptor args to push, but keep A14 calla 01880000h ; A8 = 0 (fixed descriptor) or DESC_ADDR ``` `A8` may be 0 (use the fixed descriptor at `0x01882C00`) or the descriptor's bit address. Any other value falls back to the fixed descriptor if it does not carry the magic. ### What the loader returns * `A8 = RESULT_STATUS`: **0 = success**, otherwise an error code (table below). * The **result block** at bit `0x01882D00` (32-bit cells at +0x20 intervals): | offset | meaning | |---:|---| | `+0x000` | status (same value as `A8`) | | `+0x020` | entry passed to `create_process` (bits) | | `+0x040` | `.text` paddr / `+0x060` size (bits) | | `+0x080` | `.data` paddr / `+0x0A0` size | | `+0x0C0` | `.bss` paddr / `+0x0E0` size | | `+0x100` | relocation records seen | | `+0x120` | PCB returned by `create_process` | | `+0x140` | section count | | `+0x160` | descriptor length (bytes) | | `+0x180` | loader base `0x01880000` | * On success the app is already a **first-class process** (a PCB exists and the child is scheduled when the caller yields). The loader does not wait for the app and does not return its exit status. ### Address policy: the loader picks, the transport does not The transport passes **buffer and length only**. The load address of every section comes from the COFF section headers (bit addresses); the entry comes from the optional header (`entry`, else `textstart`). There is no "load-address" argument. The linked image is position-dependent (GSPLNK resolves all references and emits `nreloc = 0`), so the transport must not move the sections after the fact and must not send a `.obj`. The loader checks, before copying: * section size is a whole number of 16-bit words; * the section bytes are inside `[buf, buf+len)`; * `0x100 <= paddr` and `paddr + size <= 0x2000000` bits (4 MiB RAM); * no overlap with `[0x01880000,0x01883200)` (loader page) or the COFF buffer; * the range lies inside one block of the **global** free list (head bit `0x1E8AC0`; block `{u32 size_bits; …; u32 next@+0x20}`, user at `+0x40`). The free-list check is a **check, not a reservation**. The transport should choose app link addresses away from the allocator's growth path (the large free block starts at byte `0x12191A` and grows upward; the current apps live at byte `0x318000`+). The transport's own code, stack and receive buffer must also stay out of the target sections; the loader cannot see them. ### Status codes | code | meaning | |---:|---| | 0 | success, child created | | 1 | bad descriptor (magic) | | 2 | truncated COFF / section headers past `len` | | 3 | bad COFF magic (not `0x0090`) | | 4 | section size not a whole number of 16-bit words | | 5 | target outside RAM | | 6 | target overlaps the loader page or the COFF buffer | | 7 | relocation records present (unsupported; the input is probably a `.obj`) | | 8 | target not inside a global free-list block | | 9 | no entry point | | 10 | `create_process` returned 0 | ### Currently proven * Debugger-poked `marker.coff` (`buf=0x01900000`, `len=510`, mode 0 and mode 1, `A8=0` and `A8=DESC`): status 0, PCB `0x003FFBD0`, markers `start=018C0000 data=DEADBEEF bss=0`, counter live. * Debugger-poked `xhello.coff` (`len=774`, mode 0): status 0, entry `0x01840000`, `stage=6`, window visible in `loader_frame2650.png`. * `rela.obj` (`len=342`): status 7, `relocs=2`, nothing copied. ### Files the transport needs * `dwt/loader/build/loader.bin` — raw loader to place at `0x01880000`. * `dwt/loader/coff.py` — the host-side reference parser; the loader's field offsets match it. * `dwt/notes/LOADER.md` — the full COFF layout, the position-dependence evidence and the loader algorithm.