Files
vt1200/dwt/notes/LOADER_CROSS.md
SDD 01923ecb69 dwt/tcpip: map the socket API and fetch a file over TFTP into RAM
The firmware's tftp_open hangs in its resolver from the rdebug process, so
the payload speaks RFC 783 over the firmware socket API: socket(2,2,0),
sendto(RRQ), recvfrom(DATA)/ACK into a malloc'd buffer, close. Verified
live: 600 B fetched from 192.168.99.1 into the malloc buffer, server RRQ
logged, and rdebug peek matches probe.bin byte-for-byte.

Notes: dwt/notes/TCPIP.md (function table, ABI, limits) and the
loader/transport hand-off in dwt/notes/LOADER_CROSS.md.
2026-10-18 15:24:34 +03:00

156 lines
7.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
## 2026-10-18 15:25 EEST — TFTP fetch works from the rdebug process
* **Exact fetch call:** the firmware's `tftp_open` is unusable (hangs in its
resolver, no RRQ). Use the minimal client in `dwt/tcpip/tftpc.asm`
(bit `01870000`, 694 B): `malloc(0x10000)` at `3F701230`, `socket(2,2,0)` at
`3F5C9A00`, `sendto(fd,buf,len,0,to,16)` at `3F5D3D70`,
`recvfrom(fd,buf,2048,0,&from,&fromlen)` at `3F5D7D50` with `*fromlen=16`,
`close(fd)` at `3F5E8930`. Stack arguments are pushed **last first**; the
sixth `recvfrom` argument is a **pointer to the socklen**.
* **Buffer/process:** run in the **rdebug process** (ordinary `jump`); no
`create_process` and no registration. A child returned `-1` from `socket()`
in the one attempt. `malloc` returns a **bit** address; advance the
destination by `n*8`, not `n`. Keep the receive buffer (2048 B) and the
`from`/`fromlen`/ACK cells disjoint — a `0x800` bit offset is only 256 bytes.
* **Verified:** 600 B fetched into the malloc buffer at `0x0095bbd0`, server
RRQ `15:23:48 ... requested 'dwt/tcpip/probe.bin'`, and `peek`/`cmp` match
byte-for-byte (sha256 `9662e62b…333359`). The payload returns 0; rdebug stays
live.
* **Hand-off:** the fetched buffer's bit address and byte length go straight
into the descriptor at `0x01882C00` (`magic 4C44/5231, buf, len, mode=1`),
then `calla 01880000h` with `A8=0` or the descriptor address. The transport
scratch is below `0x01880000` (recv `01876000`, `from` `0187A000`, `fromlen`
`0187A080`, ACK `0187A100`), so it does not collide with the loader page
`01880000`–`01883200`.