dwt/tcpip: LAN TFTP client (100.103.255.254) and a verified self-contained fetch + load on the real unit

This commit is contained in:
SDD committed 2026-10-18 17:12:00 +03:00
1 parent efef682b63
commit 09f8607466
3 files changed
+278

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

+22
View File
@@ -282,3 +282,25 @@ The LANCE address ROM at bit `0x6800000` reads (low bytes of the words):
byte-identical to the MAME model. The `Ethernet Address ROM Test` passing is
consistent with this.
### Self-contained TFTP loader on the real unit
Retargeted `tftpc.asm` to the LAN (`100.103.255.254:69`) as
`dwt/tcpip/tftpl.asm`; the RRQ name stays `dwt/tcpip/probe.bin` because the
server serves the chosen COFF for any requested name. The DOS 8.3 build name
must be <= 8 characters: `tftpc_lan` silently truncates and GSPLNK produces no
`TFTPC_LAN.OUT`; `tftpl` is safe.
```
server: dwt/tcpip/serve_probe.py 100.103.255.254 --file dwt/loader/build/xhello.coff
RRQ: 100.103.100.103:1036 requested 'dwt/tcpip/probe.bin' (774 bytes)
fetch: sha256 a8b3014d27deb97adcebb418a5d14da51fc3c11559e84923ede72e260d3e7271
loader: status 0, entry 0x01840000, pcb 0x00849a10
app: xhello stage=6 strlen=4
```
Evidence: `dwt/loader/evidence/real-unit-tftp/hostload_tftp_xhello.png`.
This is the first fully self-contained delivery on the real unit: the terminal
fetched the COFF over TFTP into `malloc` memory and ran it in a created
process, with only the rdebug jump used to start the fetch.
+256
View File
@@ -0,0 +1,256 @@
; tftpc.asm -- minimal TFTP read client built on the firmware's socket API.
;
; The firmware's own tftp_open() calls a resolver that does not return when
; called from the rdebug process (dwt/notes/TCPIP.md), so this payload speaks
; RFC 783 directly: RRQ, DATA/ACK, 512-byte blocks, one file.
;
; socket = 3F5C9A00h socket(2,2,0) -> fd
; sendto = 3F5D3D70h sendto(fd, buf, len, flags, to, tolen) -> n
; recvfrom = 3F5D7D50h recvfrom(fd, buf, len, flags, from, fromlen) -> n
; close = 3F5E8930h close(fd)
; malloc = 3F701230h malloc(bytes) -> bit pointer
;
; The transfer is 600 bytes, so DATA 1 is 512 (full) and DATA 2 is 88
; (short); the short block ends the transfer. ACKs go to the sockaddr that
; recvfrom reports, not to a hard-coded port.
;
; Evidence cells (32-bit, bit addresses):
; 0187C000 stage (1 malloc, 2 socket, 3 receiving, 4 closed, 0FFh failed)
; 0187C020 fd
; 0187C040 destination buffer (malloc)
; 0187C060 total bytes fetched
; 0187C080 last recvfrom length
; 0187C0A0 last DATA block word as read from the wire
; 0187C0C0 marker 05A5A5A5h when the fetch completed
; 0187C200 entry SP
; 0187C220 entry A14
;
; Scratch (fixed bit addresses, above this image):
; 01875000 argument area
; 01876000 receive buffer (2048 bytes)
; 0187A000 recvfrom source sockaddr
; 0187A080 recvfrom sockaddr length
; 0187A100 ACK packet (4 bytes)
.text
_start:
setf 16, 0, 0
setf 32, 0, 1
mmtm sp, a0,a1,a2,a3,a4,a5,a6,a7
move sp, @0187C200h, 1
move a14, @0187C220h, 1
movi 1, a0, L
move a0, @0187C000h, 1
; ---- buf = malloc(010000h) ------------------------------------
move a14, -*sp, 1
movi 01875000h, a14, L
movi 010000h, a0, L
move a0, *a14+, 1
calla 3F701230h
move @0187C200h, sp, 1
move @0187C220h, a14, 1
setf 16, 0, 0
setf 32, 0, 1
move a8, @0187C040h, 1
move a8, a11 ; dest cursor (callee-saved)
move a8, a8
jreq fail
movi 2, a0, L
move a0, @0187C000h, 1
; ---- fd = socket(2,2,0) --------------------------------------
movi 01875000h, a14, L
movi 0, a0, L
move a0, *a14+, 1
movi 2, a0, L
move a0, *a14+, 1
movi 2, a0, L
move a0, *a14+, 1
calla 3F5C9A00h
move @0187C200h, sp, 1
move @0187C220h, a14, 1
setf 16, 0, 0
setf 32, 0, 1
move a8, @0187C020h, 1
move a8, a9 ; fd (callee-saved)
move a8, a8
jrlt fail
; ---- sendto(fd, RRQ, 28, 0, SADDR, 16) ------------------------
movi 01875000h, a14, L
movi 16, a0, L
move a0, *a14+, 1
movi SADDR, a0, L
move a0, *a14+, 1
movi 0, a0, L
move a0, *a14+, 1
movi 28, a0, L
move a0, *a14+, 1
movi RRQ, a0, L
move a0, *a14+, 1
move a9, *a14+, 1
calla 3F5D3D70h
move @0187C200h, sp, 1
move @0187C220h, a14, 1
setf 16, 0, 0
setf 32, 0, 1
move a8, a8
jrlt fail
movi 3, a0, L
move a0, @0187C000h, 1
sub a0, a0
move a0, @0187C060h, 1 ; total = 0
recv_loop:
; ---- recvfrom(fd, BUF, 2048, 0, &FROM, &FROMLEN) -------------
; arg5 is a POINTER to the socklen (as tftp_read passes it); the
; value is initialised to 16 first.
movi 16, a0, L
move a0, @0187A080h, 1 ; fromlen = 16
movi 01875000h, a14, L
movi 0187A080h, a0, L
move a0, *a14+, 1 ; arg5 = &fromlen
movi 0187A000h, a0, L
move a0, *a14+, 1 ; arg4 = &from
movi 0, a0, L
move a0, *a14+, 1 ; arg3 = flags
movi 00800h, a0, L
move a0, *a14+, 1 ; arg2 = 2048
movi 01876000h, a0, L
move a0, *a14+, 1 ; arg1 = receive buffer
move a9, *a14+, 1 ; arg0 = fd
calla 3F5D7D50h
move @0187C200h, sp, 1
move @0187C220h, a14, 1
setf 16, 0, 0
setf 32, 0, 1
move a8, @0187C080h, 1 ; last length
move a8, a8
jrle fail
; opcode: wire 00 03 -> little-endian word 0300h
move @01876000h, a0, 0
cmpi 0300h, a0
jrne fail ; only DATA is accepted
move @01876010h, a4, 0 ; block number, wire order
move a4, @0187C0A0h, 1
move a8, a3
subk 4, a3 ; payload length
; ---- total += payload, dest += payload -----------------------
move @0187C060h, a1, 1
add a3, a1
move a1, @0187C060h, 1 ; total += payload (bytes)
move a11, a6 ; copy destination
move a3, a2
sll 3, a2 ; bytes -> bits: pointers are bit addresses
add a2, a11 ; dest cursor advances now (do NOT add bytes)
; ---- copy payload from BUF+4 to a6, one byte at a time ------
; Interrupts are disabled for the copy so a 512-byte copy is atomic
; with respect to the firmware's ISRs.
dint
movi 01876020h, a5, L
move a3, a2
move a3, @0187C0E0h, 1 ; payload length
move a2, @0187C140h, 1 ; copy count
move a6, @0187C120h, 1 ; write start
move a11, @0187C100h, 1 ; cursor after advance
move a5, @0187C160h, 1 ; source
jreq copy_done
copy_bytes:
movb *a5, a0
movb a0, *a6
addk 8, a5 ; one byte
addk 8, a6
dsjs a2, copy_bytes
copy_done:
eint
; ---- ACK: 00 04 <block as received> --------------------------
movi 00400h, a0, L
move a0, @0187A100h, 0
move a4, @0187A110h, 0
movi 01875000h, a14, L
movi 16, a0, L
move a0, *a14+, 1
movi 0187A000h, a0, L
move a0, *a14+, 1
movi 0, a0, L
move a0, *a14+, 1
movi 4, a0, L
move a0, *a14+, 1
movi 0187A100h, a0, L
move a0, *a14+, 1
move a9, *a14+, 1
calla 3F5D3D70h
move @0187C200h, sp, 1
move @0187C220h, a14, 1
setf 16, 0, 0
setf 32, 0, 1
move a8, a8
jrlt fail
; short block ends the transfer; full block means read again
move @0187C080h, a2, 1
subk 4, a2
cmpi 512, a2
jrlt close_it
jruc recv_loop
close_it:
movi 4, a0, L
move a0, @0187C000h, 1
movi 01875000h, a14, L
move a9, *a14+, 1
calla 3F5E8930h ; close(fd)
move @0187C200h, sp, 1
move @0187C220h, a14, 1
setf 16, 0, 0
setf 32, 0, 1
movi 05A5A5A5h, a0, L
move a0, @0187C0C0h, 1
sub a8, a8
jruc epilogue
fail:
movi 0FFh, a0, L
move a0, @0187C000h, 1
movi 0FFFFFFFFh, a8, L
epilogue:
mmfm sp, a0,a1,a2,a3,a4,a5,a6,a7
rets
RRQ:
.word 00100h ; opcode 1 (00 01)
.word 07764h ; "dw"
.word 02F74h ; "t/"
.word 06374h ; "tc"
.word 06970h ; "pi"
.word 02F70h ; "p/"
.word 07270h ; "pr"
.word 0626Fh ; "ob"
.word 02E65h ; "e."
.word 06962h ; "bi"
.word 0006Eh ; "n\0"
.word 0636Fh ; "oc"
.word 06574h ; "te"
.word 00074h ; "t\0"
SADDR:
.word 00002h ; sin_family = AF_INET
.word 04500h ; sin_port = htons(69)
.word 06764h ; 100.103
.word 0FEFFh ; .255.254
.word 0
.word 0
.word 0
.word 0
.end