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.
7.1 KiB
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
-
The complete linked COFF image in RAM. Not the
.obj; the GSPLNK-aoutput (*.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. -
The loader resident at bit
0x01880000. The rawdwt/loader/build/loader.bin(1220 bytes,.textsize0x2620bits) 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). -
A descriptor at bit
0x01882C00(or at an address passed inA8). 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 callermode = 1is the transport mode.mode = 0is the debugger mode: the loader restores the interrupted context and jumps to the PC the harness saved; a transport must not use it. -
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_ADDRA8may be 0 (use the fixed descriptor at0x01882C00) 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 +0x000status (same value as A8)+0x020entry passed to create_process(bits)+0x040.textpaddr /+0x060size (bits)+0x080.datapaddr /+0x0A0size+0x0C0.bsspaddr /+0x0E0size+0x100relocation records seen +0x120PCB returned by create_process+0x140section count +0x160descriptor length (bytes) +0x180loader 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 <= paddrandpaddr + size <= 0x2000000bits (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=0andA8=DESC): status 0, PCB0x003FFBD0, markersstart=018C0000 data=DEADBEEF bss=0, counter live. - Debugger-poked
xhello.coff(len=774, mode 0): status 0, entry0x01840000,stage=6, window visible inloader_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 at0x01880000.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_openis unusable (hangs in its resolver, no RRQ). Use the minimal client indwt/tcpip/tftpc.asm(bit01870000, 694 B):malloc(0x10000)at3F701230,socket(2,2,0)at3F5C9A00,sendto(fd,buf,len,0,to,16)at3F5D3D70,recvfrom(fd,buf,2048,0,&from,&fromlen)at3F5D7D50with*fromlen=16,close(fd)at3F5E8930. Stack arguments are pushed last first; the sixthrecvfromargument is a pointer to the socklen. - Buffer/process: run in the rdebug process (ordinary
jump); nocreate_processand no registration. A child returned-1fromsocket()in the one attempt.mallocreturns a bit address; advance the destination byn*8, notn. Keep the receive buffer (2048 B) and thefrom/fromlen/ACK cells disjoint — a0x800bit offset is only 256 bytes. - Verified: 600 B fetched into the malloc buffer at
0x0095bbd0, server RRQ15:23:48 ... requested 'dwt/tcpip/probe.bin', andpeek/cmpmatch byte-for-byte (sha2569662e62b…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), thencalla 01880000hwithA8=0or the descriptor address. The transport scratch is below0x01880000(recv01876000,from0187A000,fromlen0187A080, ACK0187A100), so it does not collide with the loader page01880000–01883200.