mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-31 12:05:05 +02:00
harness/mame/NETWORK_REPORT.md: how the VT1200 firmware drives the LANCE, from ROM disassembly and MAME traces. 50 sections, each claim tied to a bit address, a trace count or a quoted source line. Covers the driver device struct (0x23CBF0), init (0x3F9D4BC0), descriptor/DMA encoding, the live handler (0x3F9D5B40) with per-status-bit dispatch, the RX poll (0x3F9D62B0), ring arming (0x3F9D36C0), TX completion (0x3F9D8AF0) and the single transmit path every protocol uses (0x3F6084E0 -> 0x3F9DC890 -> 0x3F9D4BC0), plus the IP protocol dispatch (0x3F6270F0), ICMP (343-count chain), TCP state tables and live TIME_WAIT, the ARP responder (0x3F6119A0) and the socket 4-tuple demux confirmed by a memory dump. It also records what was withdrawn: five successive explanations for the real-silicon CSR0=0x1BB / TMD1=0x8338 result, and the descriptor-length encoding bug found by comparing Linux 7990.c and NetBSD am7990.c (length must be 0xF000 | -len; MAME masks the difference in get_buf_length). harness/udpstream: the LANCE loopback/takeover payload and the variants used to test the init/kick orderings in MAME (bare TDMD, STOP before INIT, double INIT, long wait with CSR0 sampling, IDON clear, corrected descriptor length). build-lance.sh takes LANCE_SRC/LANCE_OUT so the variants build from one script.
162 lines
4.5 KiB
C
162 lines
4.5 KiB
C
/*
|
|
* udpstream.c -- spike: receive full frames over UDP and blit them into VRAM.
|
|
*
|
|
* The terminal is the client; the host sends self-describing datagrams:
|
|
*
|
|
* +0 u16 start row (little endian)
|
|
* +2 u16 row count
|
|
* +4 row_count * 160 bytes of 1-bpp pixels (1280 px per row)
|
|
*
|
|
* Each row lands at bit 0x04000000 + row * 2048 (the real unit's 256-byte row
|
|
* pitch). Only the 160 visible bytes of a row are sent; the remaining 96 mask
|
|
* bytes are left as they are. 8 rows per datagram = 1284 bytes, under the
|
|
* 1500-byte Ethernet MTU.
|
|
*
|
|
* Markers at 0x018C8000 (32-bit cells):
|
|
* +0x000 stage 1 entered main, 2 socket ok, 3 bound, 4 receiving
|
|
* 0xEE socket failed, 0xEF bind failed
|
|
* +0x020 fd
|
|
* +0x040 bind() result
|
|
* +0x060 datagrams received
|
|
* +0x080 last datagram length
|
|
* +0x0A0 rows written
|
|
* +0x0C0 bytes blitted
|
|
*
|
|
* K&R C only; the ROM entry points and the sockaddr layout are in romapi.h.
|
|
*/
|
|
#include "romapi.h"
|
|
|
|
#define VRAM_BASE 0x04000000
|
|
#define ROW_BITS 2048 /* 256-byte row pitch on the real unit */
|
|
#define ROW_BYTES 160 /* 1280 visible pixels per row */
|
|
#define MAX_ROWS 8
|
|
#define PORT 7000
|
|
#define MARK 0x018C8000
|
|
#define LIMIT_CELL 0x018C9000 /* host-written datagram limit, 0 = unlimited */
|
|
#define PORT_CELL 0x018C9040 /* host-written UDP port, 0 = 7000 */
|
|
|
|
static unsigned char sa[16];
|
|
static unsigned char from[16];
|
|
static int fromlen;
|
|
static unsigned char buf[4 + MAX_ROWS * ROW_BYTES];
|
|
static unsigned int rset[8];
|
|
static unsigned int tv[2];
|
|
|
|
static void cell(off, v)
|
|
unsigned int off, v;
|
|
{
|
|
*(unsigned int *)(MARK + off) = v;
|
|
}
|
|
|
|
static unsigned int getcell(off)
|
|
unsigned int off;
|
|
{
|
|
return *(unsigned int *)(MARK + off);
|
|
}
|
|
|
|
int main()
|
|
{
|
|
int fd, r, n;
|
|
unsigned int limit, count, port;
|
|
unsigned int start, rows, row, i, dst, len;
|
|
|
|
cell(0x000, 1);
|
|
limit = *(unsigned int *)LIMIT_CELL;
|
|
count = 0;
|
|
port = *(unsigned int *)PORT_CELL;
|
|
if (port == 0) {
|
|
port = PORT;
|
|
}
|
|
|
|
/* The per-process user area (process+0x100 bits) is not initialised by
|
|
* create_process: on both the emulator and real hardware it reads as the
|
|
* heap fill 0x55. The syscall layer keeps its file table and syscall
|
|
* state there, so clear it before the first call. */
|
|
{
|
|
unsigned int proc, user, k;
|
|
proc = *(unsigned int *)0x1B3900;
|
|
cell(0x140, proc);
|
|
user = *(unsigned int *)(proc + 0x100);
|
|
cell(0x160, user);
|
|
for (k = 0; k < 0x100; k++) {
|
|
*(unsigned char *)(user + k * 8) = 0;
|
|
}
|
|
}
|
|
|
|
fd = socket_(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
cell(0x020, fd);
|
|
if (fd < 0) {
|
|
cell(0x000, 0xEE);
|
|
return 1;
|
|
}
|
|
cell(0x000, 2);
|
|
|
|
sa_any(sa, port);
|
|
cell(0x120, port);
|
|
r = bind_(fd, sa, SOCKADDR_LEN);
|
|
cell(0x040, r);
|
|
if (r < 0) {
|
|
cell(0x000, 0xEF);
|
|
close_(fd);
|
|
return 2;
|
|
}
|
|
cell(0x000, 3);
|
|
|
|
/* limit == 0xDEAD: bind and return without reading, so the host can test
|
|
* whether traffic to an unread socket is what kills the machine. */
|
|
if (limit == 0xDEAD) {
|
|
cell(0x000, 0xDD);
|
|
return 0;
|
|
}
|
|
|
|
cell(0x060, 0);
|
|
cell(0x0A0, 0);
|
|
cell(0x0C0, 0);
|
|
|
|
for (;;) {
|
|
fromlen = SOCKADDR_LEN;
|
|
n = recvfrom_(fd, buf, sizeof(buf), 0, from, &fromlen);
|
|
cell(0x0E0, (unsigned int)n);
|
|
if (n <= 0) {
|
|
close_(fd);
|
|
return 3;
|
|
}
|
|
if (n < 4) {
|
|
close_(fd);
|
|
return 4;
|
|
}
|
|
len = (unsigned int)n;
|
|
start = (unsigned int)buf[0] | ((unsigned int)buf[1] << 8);
|
|
rows = (unsigned int)buf[2] | ((unsigned int)buf[3] << 8);
|
|
if (rows > MAX_ROWS || start + rows > 1024) {
|
|
close_(fd);
|
|
return 5;
|
|
}
|
|
if (len < 4 + rows * ROW_BYTES) {
|
|
close_(fd);
|
|
return 6;
|
|
}
|
|
|
|
for (row = 0; row < rows; row++) {
|
|
dst = VRAM_BASE + (start + row) * ROW_BITS;
|
|
for (i = 0; i < ROW_BYTES; i++) {
|
|
*(unsigned char *)(dst + i * 8) =
|
|
buf[4 + row * ROW_BYTES + i];
|
|
}
|
|
}
|
|
|
|
cell(0x080, len);
|
|
cell(0x060, getcell(0x060) + 1);
|
|
cell(0x0A0, getcell(0x0A0) + rows);
|
|
cell(0x0C0, getcell(0x0C0) + rows * ROW_BYTES);
|
|
cell(0x000, 4);
|
|
|
|
count = count + 1;
|
|
if (limit != 0 && count >= limit) {
|
|
cell(0x100, count);
|
|
close_(fd);
|
|
return 0; /* bounded run: leave the OS to it */
|
|
}
|
|
}
|
|
}
|