mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-30 12:03:54 +02:00
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.
156 lines
7.1 KiB
Markdown
156 lines
7.1 KiB
Markdown
# 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`.
|