Files
vt1200/harness/udpstream/vidstream.c
T

191 lines
6.6 KiB
C

/*
* vidstream.c -- terminal side of the VT1200 video stream.
*
* Task 3 claimed the working set with ONE malloc at startup and published
* where it landed. Task 4 adds the receive side: two 24 KB frame slots at the
* head of that pool, and a poll loop that hands each completed slot to
* fp_parse() (frame_parse.c, shared with the host unit test) and counts what
* it accepted. Task 5 replaces the accept path's placeholder with the blit.
*
* Pool layout inside the single allocation (POOL_BYTES = 249856 = 244 KB):
*
* +0x00000 frame slot A 24576 bytes
* +0x06000 frame slot B 24576 bytes
* +0x0C000 framebuffer 163840 bytes (scheme A, Task 5)
* +0x34000 dictionary 32768 bytes
* +0x3C000 slack 4096 bytes
*
* A slot holds one complete frame -- 16-byte header and payload -- exactly as
* it arrives on the wire, so the bytes fp_parse() validates are the bytes the
* blit will read. Neither slot is ever split across a ring wrap: the two ring
* entries' descriptors are re-armed per slot at frame start.
*
* Marker layout: see the table in frame_parse.h. Markers are 32-bit cells on
* the byte-aligned page at bit 0x01810000, 0x20 bits (4 bytes) apart. The
* design plan names them 0x01810020, 0x01810024, ... but the TMS34010 is
* bit-addressed, so 0x01810024 is four BITS after 0x01810020 -- inside the
* same cell. The plan's numbers are byte offsets into the page; the rule is
*
* marker named 0x018100NN -> bit 0x01810000 + NN * 8
*
* (POOL_PTR = 0x01810100, ...). vidstream.lua uses the same rule.
*
* K&R C only: no prototypes, no '//', declarations before statements.
*/
#include "romapi.h"
#include "poolcfg.h" /* POOL_BYTES, written by build-stream.sh */
#include "frmpsr.h" /* frame_parse.h under an 8.3 name; see that file */
#define HEAP_FREE 0x1E8BA0 /* firmware free-byte counter, IN BITS */
#define MARK 0x01A00000 /* .text must end before this bit address */
#define STATIC_BYTES 249856 /* the designed working set, 244 KB */
/*
* The two frame slots. SLOT_BYTES is the plan's 24 KB: a 128-row strip frame
* (128 x 160 B + 16 B header = 20,496 B = 15 packets) fits, and so does any
* smaller strip. Slot addresses are bit addresses, because that is what a
* pointer holds in this toolchain (see harness/c-hello/README.md: "pointers
* are bit addresses").
*/
#define SLOT_BYTES 24576
/*
* Static fallback. pool_static is global so the linker map names it and the
* MAME check can assert the exact address a failed malloc falls back to.
* stack_cushion and the five VRAM words below sit between it and ENTRY.ASM's
* _cstack: TMS34010 pushes grow downward, so the 4 KB stack spills into those
* 4 KB, never into the pool.
*/
unsigned int pool_static[STATIC_BYTES / 4];
unsigned int stack_cushion[1024];
/*
* VRAM.ASM's externals. vram.asm is linked into every payload in this
* directory; the C side owns the values. Task 5 fills the blit_* words.
*/
unsigned int vram_pattern;
unsigned int blit_src, blit_sptch, blit_daddr, blit_dydx;
/*
* The firmware's free-space counter is a 32-bit count IN BITS at
* 0x1E8BA0 (dwt/notes/HEAP.md). POOL_FREE reports bytes, because the
* marker is for humans and the check compares against POOL_BYTES.
*/
static unsigned int heap_bytes()
{
return *(unsigned int *)HEAP_FREE / 8;
}
/*
* Handle one slot if the receive path has raised its ready flag.
*
* In the real transport the LANCE receive interrupt fills the slot and sets
* the flag; MAME cannot hand this emulated LANCE an Ethernet frame (the
* driver attaches no network interface -- ruling R11), so in the harness
* vidstream.lua writes the slot and sets the flag instead. The payload does
* not care which of the two did it.
*
* `len` is the number of bytes the transport actually delivered. fp_parse()
* rejects a frame whose payload_len wants more than that, which is the rule
* that makes payload_len plus the received length authoritative rather than a
* descriptor field.
*/
static void rx_poll(slot)
unsigned int slot;
{
unsigned int readyp, lenp, basep, base, len, ready;
fp_header h;
int ok;
if (slot == 0) {
readyp = RX_READY_A;
lenp = RX_LEN_A;
basep = RX_SLOT_A;
} else {
readyp = RX_READY_B;
lenp = RX_LEN_B;
basep = RX_SLOT_B;
}
ready = *(unsigned int *)readyp;
if (ready == 0) {
return;
}
base = *(unsigned int *)basep;
len = *(unsigned int *)lenp;
ok = 0;
if (base != 0) {
ok = fp_parse((unsigned char *)base, len, &h);
}
if (ok) {
*(unsigned int *)RX_OK = *(unsigned int *)RX_OK + 1;
*(unsigned int *)RX_LAST_LEN = h.payload_len;
*(unsigned int *)RX_SLOT = slot;
/* Task 5 blits the accepted frame from here: h.payload_off
* bytes into the slot, h.rows rows of 160 bytes (1280 pixels,
* 1 bpp, MSB first), h.stretch scanlines each. Nothing is
* drawn for a rejected frame, so a corrupt frame can never
* reach VRAM. */
} else {
*(unsigned int *)RX_BAD = *(unsigned int *)RX_BAD + 1;
}
/* Release the slot last: the checks read the counters, and a slot
* freed before its counter moved would look like a dropped frame. */
*(unsigned int *)readyp = 0;
}
int main()
{
unsigned int before, after, ptr;
*(unsigned int *)VID_ALIVE = 0;
*(unsigned int *)POOL_PTR = 0;
*(unsigned int *)POOL_FREEB = 0;
*(unsigned int *)POOL_FREEA = 0;
/* Receive record: counters and the two slots are cleared before anything
* is published, so a reader never sees a stale ready flag or a slot
* address that has no allocation behind it. */
*(unsigned int *)RX_OK = 0;
*(unsigned int *)RX_BAD = 0;
*(unsigned int *)RX_LAST_LEN = 0;
*(unsigned int *)RX_SLOT = 0;
*(unsigned int *)RX_SLOT_A = 0;
*(unsigned int *)RX_SLOT_B = 0;
*(unsigned int *)RX_READY_A = 0;
*(unsigned int *)RX_LEN_A = 0;
*(unsigned int *)RX_READY_B = 0;
*(unsigned int *)RX_LEN_B = 0;
before = heap_bytes();
ptr = malloc_(POOL_BYTES); /* malloc_ takes BYTES, returns BITS */
after = heap_bytes();
if (ptr == 0) {
/* The fallback is the 244 KB static pool by definition; a larger
* request still reports the failure through the low-bit flag. */
ptr = (unsigned int)pool_static;
*(unsigned int *)POOL_FREEB = before | 1;
} else {
*(unsigned int *)POOL_FREEB = before;
}
*(unsigned int *)POOL_FREEA = after;
/* Slot B follows slot A in the same pool. Both are published before
* POOL_PTR so the harness can find them from either one. */
*(unsigned int *)RX_SLOT_A = ptr;
*(unsigned int *)RX_SLOT_B = ptr + SLOT_BYTES * 8;
*(unsigned int *)POOL_PTR = ptr; /* publish last: a complete record */
for (;;) {
*(unsigned int *)VID_ALIVE = *(unsigned int *)VID_ALIVE + 1;
rx_poll(0);
rx_poll(1);
}
}