/* * 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 */ } } }