Files
vt1200/harness/udpstream/vidstream.c
T

714 lines
26 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 added 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 blits an accepted strip frame to the screen: one
* PIXBLT B,XY per source row, repeated down the row `stretch` times by
* SPTCH=0 (vram_b.asm). Task 6 adds the back-channel: the LANCE is taken over
* for transmit and a KIND_STATUS frame is built and sent every 64 accepted
* frames (send_status() below).
*
* 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 LANCE block 4096 bytes (Task 6: init block, TX ring,
* status frame)
*
* The LANCE block is what was slack. Its ring base is aligned at run time
* (lance_start() explains why the offset alone is not enough), and the status
* frame sits right after the 8 descriptors.
*
* 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 reads. Neither slot is ever split across a ring wrap: the two ring
* entries' descriptors are re-armed per slot at frame start.
*
* Screen geometry. VRAM is at bit 0x04000000 and a screen row is 0x800 bits
* (256 bytes), the real unit's pitch (harness/c-hello/README.md). MAME's own
* renderer decodes the same memory at 128 bytes a row; that difference is a
* property of the harness, so the draw test compares VRAM as memory, not the
* rendered picture.
*
* OS repaint canary (spec 8.9). The blit covers screen rows 0..1023 and
* columns 0..1279 only. One 16-bit sentinel word sits in the unused right
* edge of the last screen row (row 1023, column 1280), outside every pixel the
* blit writes. If something else -- the resident OS -- paints over us it also
* disturbs that corner; OS_TOUCH counts how often that was seen. This
* decoder has no partial update: an accepted frame repaints the whole screen
* anyway, so detecting the touch and repainting are the same act here, and
* OS_TOUCH is the record.
*
* 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, ...). Task 5's OS_TOUCH is 0x01810220 (ruling R12),
* i.e. cell 0x44 -- free between the RX_* counters and the injection cells.
* 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 */
/* Screen memory and the strip geometry (see the header comment). */
#define VRAM_BASE 0x04000000 /* video RAM, bit address */
#define VRAM_ROW 0x800 /* 2048 bits = 256 bytes a screen row */
#define SCREEN_ROWS 1024 /* rows in the framebuffer */
#define ROW_PIXELS 1280 /* a strip row is 1280 pixels */
#define ROW_BYTES 160 /* 1280 pixels, 1 bpp */
/*
* The sentinel word: last screen row, first column past the 1280 the blit
* writes. Outside the blitted area by construction, inside mapped VRAM (the
* next 256-byte row would be bit 0x06000000, which the driver maps as the NVR,
* not as video memory).
*/
#define SENT_ADDR (VRAM_BASE + (SCREEN_ROWS - 1) * VRAM_ROW + ROW_PIXELS)
#define SENT_VALUE 0xA5C3
/*
* 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
/*
* Task 6: the terminal's status frame.
*
* The payload takes the LANCE over -- STOP, its own init block and TX ring,
* INIT, IDON, STRT -- and every 64 accepted frames builds a 60-byte Ethernet
* frame in its own buffer and hands it to the chip with a TX descriptor. The
* descriptor setup is lance.c's (harness/udpstream/lance.c): the ring words are
* TMD0/TMD1/TMD2/TMD3 at byte offsets 0/2/4/6, the buffer address's high byte
* lives in TMD1, and the length is (0x10000 - bytes) in TMD2. This is NOT a
* second driver: it is the same init/descriptor sequence, with one buffer
* instead of a loopback probe's.
*
* Where the pieces live: the pool's last 4 KB was slack, so the LANCE block
* goes there -- 24 bytes of init block, 64 bytes of TX ring (8 descriptors) and
* the 60-byte status frame, all aligned at run time in lance_start(). All of
* it is memory this payload owns, which is the point: the firmware's own rings
* are in low memory with 32 descriptors (see lance.c's note on the OS init
* block), and arming a slot in a ring whose position the chip owns is not
* something this payload can do reliably.
*
* The receiver is DISABLED (MODE_DRX) on purpose, and that is a gap rather
* than a design: the receive path in this task is the harness injection
* interface (RX_READY_A/B), the real transport chunks a frame into 15
* datagrams, and a ring of one-descriptor-per-datagram that reassembles them
* does not exist yet (ruling R11 covers why MAME cannot exercise it either).
* Arming an RX ring we have no buffers for would let the chip DMA a datagram
* into address 0, so disabling the receiver is the honest state.
*
* So `drops` in the status body is wired to the chip's own counter -- CSR0's
* MISS bit, one per missed-packet event, cleared after each report -- and with
* the receiver disabled it can only be 0 here. What it must not be is
* invented: the field is the chip's, so an RX ring inherits it unchanged.
*/
#define LANCE_RAP 0x05900000 /* bit address of the register-select port */
#define LANCE_RDP 0x05800000 /* bit address of the data port */
#define CSR0_INIT 0x0001
#define CSR0_STRT 0x0002
#define CSR0_STOP 0x0004
#define CSR0_TDMD 0x0008
#define CSR0_IDON 0x0100
#define CSR0_MISS 0x1000 /* no RX descriptor: a missed-packet event */
#define MODE_DRX 0x0001 /* disable the receiver (see above) */
#define TMD1_OWN 0x8000
#define TMD1_ENP 0x0100
#define TMD1_STP 0x0200
#define L_INIT_OFF 0x3C000 /* 12-word init block (24 bytes) */
#define L_RING_OFF 0x3C040 /* 8 TX descriptors, 8 bytes each */
#define L_RING_LEN 8 /* TLEN code 3 = 2^3 descriptors */
#define STATUS_EVERY 64 /* frames per status report */
#define STATUS_LEN 60 /* Ethernet minimum; the chip adds the FCS */
/* Offset of the VS header inside the Ethernet frame, and of the body. */
#define ETH_HDR_LEN 14
#define STAT_BODY (ETH_HDR_LEN + FP_HEADER_SIZE)
/*
* 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. vram_b.asm (PIXBLT B,XY) shares
* blit_src/blit_daddr/blit_dydx and ignores blit_sptch, because its SPTCH is
* always 0 -- that is what makes one source row fill `stretch` screen rows.
*/
unsigned int vram_pattern;
unsigned int blit_src, blit_sptch, blit_daddr, blit_dydx;
extern void vram_blit_b_(); /* vram_b.asm: PIXBLT B,XY, SPTCH = 0 */
extern void vid_dint_(); /* vram_b.asm: DINT */
extern void vid_eint_(); /* vram_b.asm: EINT */
/*
* 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;
}
/*
* Task 6 state. The toolchain never runs .cinit, so main() clears these
* before they are used (same rule as the markers).
*/
static unsigned int lance_ok; /* 1 once the LANCE is ours */
static unsigned int lance_txring; /* bit address of the TX ring */
static unsigned int status_buf; /* bit address of the status frame */
static unsigned int lance_txslot; /* next TX descriptor to arm */
static unsigned int status_seq; /* reports sent, the status frame_no*/
static unsigned int status_seen; /* frames_rx at the last report */
static unsigned int drops_total; /* CSR0 MISS events, cumulative */
/*
* blit_row(src, daddr_xy, dydx)
*
* One PIXBLT B,XY. The three values are published where vram_b.asm reads
* them (see the note there on the calling form; the assembly entry point takes
* no arguments). `src` is the packed 1-bpp source row as a bit address,
* `daddr_xy` is the screen position as (y << 16) | x, and `dydx` is the
* rectangle as (screen rows << 16) | pixels. The primitive always sets
* SPTCH = 0, so the "screen rows" dimension reads the same source row that
* many times: that is the vertical stretch.
*/
static void blit_row(src, daddr_xy, dydx)
unsigned int src;
unsigned int daddr_xy;
unsigned int dydx;
{
blit_src = src;
blit_daddr = daddr_xy;
blit_dydx = dydx;
vram_blit_b_();
}
/*
* paint_frame(slot, h)
*
* Draw one accepted strip frame: source row r goes down screen rows
* r*stretch .. r*stretch+stretch-1. A frame the screen cannot hold is not
* drawn: zero rows or zero stretch, more than SCREEN_ROWS screen rows, or a
* payload shorter than the rows it claims (fp_parse() has already proved that
* payload_len bytes arrived, so that bound is real). A refused frame never
* touches VRAM, so a malformed header cannot paint garbage over a good one.
*
* The whole burst runs with interrupts masked (spec 8.9) so the resident OS
* cannot repaint between two of our source rows and tear the picture.
*/
static void paint_frame(slot, h)
unsigned int slot;
fp_header *h;
{
unsigned int r, rows, stretch, src, sent;
rows = h->rows;
stretch = h->stretch;
if (h->kind != FP_KIND_STRIP || rows == 0 || stretch == 0) {
return;
}
if (rows * stretch > SCREEN_ROWS) {
return;
}
if (rows * ROW_BYTES > h->payload_len) {
return;
}
/* The canary word lives outside every pixel this frame writes. If it
* is not ours any more, something else painted VRAM: count it and put
* it back. There is no partial update here -- this frame repaints the
* whole screen anyway -- so OS_TOUCH is the record, not a trigger. */
sent = (unsigned int)*(unsigned short *)SENT_ADDR;
if (sent != SENT_VALUE) {
*(unsigned int *)OS_TOUCH = *(unsigned int *)OS_TOUCH + 1;
*(unsigned short *)SENT_ADDR = SENT_VALUE;
}
vid_dint_();
src = slot + h->payload_off * 8;
for (r = 0; r < rows; r++) {
blit_row(src + r * ROW_PIXELS,
(r * stretch) << 16,
(stretch << 16) | ROW_PIXELS);
}
vid_eint_();
}
/*
* The LANCE, transmit only. The sequence is lance.c's, and the comment on
* each step names what makes it necessary (harness/mame/README.md rule 4: a
* step taken from another driver carries its source).
*/
static void w16(bit, v)
unsigned int bit, v;
{
*(unsigned short *)bit = v;
}
static unsigned int r16(bit)
unsigned int bit;
{
return *(unsigned short *)bit;
}
static void wb(bit, v)
unsigned int bit, v;
{
*(unsigned char *)bit = v;
}
/*
* The chip's register file takes a select write (RAP) then a data access
* (RDP), and needs a settle between them: am79c90.cpp's regs_w consumes the
* address register on the next access, and lance.c measured that a store
* immediately after the select lands in the wrong register. Same 30-pass
* settle as lance.c.
*/
static void lance_settle()
{
unsigned int i;
for (i = 0; i < 30; i++) {
}
}
static void lance_csr_w(reg, v)
unsigned int reg, v;
{
*(unsigned short *)LANCE_RAP = reg;
lance_settle();
*(unsigned short *)LANCE_RDP = v;
lance_settle();
}
static unsigned int lance_csr_r(reg)
unsigned int reg;
{
unsigned int v;
*(unsigned short *)LANCE_RAP = reg;
lance_settle();
v = *(unsigned short *)LANCE_RDP;
lance_settle();
return v;
}
/*
* lance_start(ptr) -- take the LANCE over for transmit. 1 on success.
*
* STOP comes first because the init block address (CSR1/CSR2/CSR3) is only
* writable while the chip is stopped -- am79c90.cpp regs_w guards each of them
* with "if (m_csr[0] & CSR0_STOP)" -- and because STOP resets the chip, which
* clears INEA: the network interrupt is off before this payload touches
* anything, so the firmware's ISR cannot act on a half-programmed ring. The
* caller also masks interrupts for the whole sequence.
*
* The init block's words are the C-LANCE's own order: MODE, PADR (three
* words), LADRF (four), RX ring address/length, TX ring address/length. A
* ring word is the address's bits 16-23 in its low byte and the length code
* (2^code descriptors) in bits 13-15; the pool sits above 64 KB, so unlike
* lance.c's low-memory rings the high byte is not zero and has to be written.
*/
static int lance_start(ptr)
unsigned int ptr;
{
unsigned int init, ring, buf, cb, i, idon;
/*
* Alignment is the CHIP's requirement, not a convenience. The Am7990
* masks what it is given: INIT_ADDR_MASK 0xFFFFFE for the init block and
* RING_ADDR_MASK 0xFFFFF8 for a ring (am79c90.h), because a descriptor
* is eight bytes. The pool base is only word-aligned (0x0090C8D0 is
* byte 0x12191A), so a fixed offset does NOT give a ring the chip can
* find: the first run of this code put the ring at byte 0x15D93A, the
* chip masked that to 0x15D938 and read our TMD0 word as TMD1 -- it never
* fetched the status buffer at all (proved from VT1200_LANCE_DMA_LOG:
* no read of the buffer address, and the chip writing 0x599A into our
* TMD0 words). Round up to the alignment the chip will keep.
*/
init = (ptr + L_INIT_OFF * 8 + 0x0F) & 0xFFFFFFF0; /* 16-bit words */
ring = (ptr + L_RING_OFF * 8 + 0x3F) & 0xFFFFFFC0; /* 8-byte */
buf = ring + L_RING_LEN * 64; /* after the ring */
lance_txring = ring;
status_buf = buf;
lance_csr_w(0, CSR0_STOP);
w16(init + 0 * 16, MODE_DRX); /* no loopback, FCS appended */
w16(init + 1 * 16, 0x0008); /* PADR 08:00:2b:16:4b:5a */
w16(init + 2 * 16, 0x162b);
w16(init + 3 * 16, 0x5a4b);
w16(init + 4 * 16, 0); /* LADRF: physical + broadcast */
w16(init + 5 * 16, 0);
w16(init + 6 * 16, 0);
w16(init + 7 * 16, 0);
w16(init + 8 * 16, 0); /* RX ring: none (MODE_DRX) */
w16(init + 9 * 16, 0);
cb = ring >> 3; /* BYTE address of the ring */
w16(init + 10 * 16, cb & 0xFFFF);
w16(init + 11 * 16, ((cb >> 16) & 0xFF) | (3 << 13));
/* Every descriptor points at the one status buffer; TMD1's OWN stays
* clear until a frame is ready, and TMD2 holds the length as a two's
* complement (0x10000 - 60), lance.c's descriptor layout. */
cb = buf >> 3;
for (i = 0; i < L_RING_LEN; i++) {
w16(ring + i * 64 + 0, cb & 0xFFFF);
w16(ring + i * 64 + 16, (cb >> 16) & 0xFF);
w16(ring + i * 64 + 32, (0x10000 - STATUS_LEN) & 0xFFFF);
w16(ring + i * 64 + 48, 0);
}
for (i = 0; i < STATUS_LEN; i++) {
wb(buf + i * 8, 0);
}
lance_csr_w(1, (init >> 3) & 0xFFFF);
lance_csr_w(2, ((init >> 3) >> 16) & 0xFF);
lance_csr_w(3, 0);
lance_csr_w(0, CSR0_INIT);
idon = 0;
for (i = 0; i < 20000; i++) {
idon = lance_csr_r(0);
if (idon & CSR0_IDON) {
break;
}
}
*(unsigned int *)ST_INIT = idon;
if (!(idon & CSR0_IDON)) {
return 0;
}
/* STRT turns the transmitter on; no INEA, so no interrupt reaches the
* firmware's network ISR while this payload owns the chip. */
lance_csr_w(0, CSR0_STRT);
for (i = 0; i < 30; i++) {
}
return 1;
}
/*
* send_status(frames_rx) -- build and transmit one status frame.
*
* The frame is an ordinary Ethernet frame: broadcast destination (the LANCE
* accepts broadcast with an empty LADRF, and the host's capture sees it), the
* terminal's own address from its Ethernet ROM, the stream's ethertype 0x88B7
* (frame.py ETHERTYPE), then the same 16-byte VS header every other frame
* carries, with rows and stretch zero because a status has no geometry. The
* body is 12 bytes, LITTLE-endian as frame.py's pack_status() writes it:
* frames_rx u32, crc_err u16, drops u16, csr0 u16, heap_free_kb u16. (The
* command protocol in lance_cmd.c is big-endian; this one is not, and getting
* it backwards reads 64 frames as 0x40000000 -- which is how the Task 6
* MAME run first found it.)
*
* crc_err is RX_BAD: the frames fp_parse() rejected (header CRC, magic,
* version or length). Since ruling R14 a corrupted *body* cannot appear
* here -- the LANCE validated each datagram's FCS before the bytes were
* stored -- so this is the only CRC error the terminal can count.
*
* Returns 1 when the frame was handed to the chip, 0 when the ring slot was
* still owned and nothing was armed. Overwriting a descriptor the chip still
* owns is what every reference driver refuses to do (Linux stops the queue,
* NetBSD prints "missing buffer"), and the caller must not count a report it
* never sent: it advances status_seen only on 1.
*/
static int send_status(frames_rx)
unsigned int frames_rx;
{
unsigned int i, crc, csr0, tmd1, off, hb, miss, bad, free_kb;
hb = status_buf;
/* Ethernet header. */
for (i = 0; i < 6; i++) {
wb(hb + i * 8, 0xFF);
}
wb(hb + 6 * 8, 0x08); wb(hb + 7 * 8, 0x00);
wb(hb + 8 * 8, 0x2B); wb(hb + 9 * 8, 0x16);
wb(hb + 10 * 8, 0x4B); wb(hb + 11 * 8, 0x5A);
wb(hb + 12 * 8, 0x88); wb(hb + 13 * 8, 0xB7);
/* The VS header, laid out exactly as frame.py's pack_header() does. */
off = hb + ETH_HDR_LEN * 8;
wb(off + 0 * 8, FP_MAGIC0);
wb(off + 1 * 8, FP_MAGIC1);
wb(off + 2 * 8, FP_VERSION);
wb(off + 3 * 8, FP_KIND_STATUS);
wb(off + 4 * 8, status_seq & 0xFF);
wb(off + 5 * 8, (status_seq >> 8) & 0xFF);
wb(off + 6 * 8, FP_STATUS_SIZE);
wb(off + 7 * 8, 0);
wb(off + 8 * 8, 0); /* rows: none */
wb(off + 9 * 8, 0); /* stretch: none */
wb(off + 10 * 8, 0); /* reserved */
wb(off + 11 * 8, 0);
crc = fp_crc((unsigned char *)off, FP_PREFIX_SIZE);
wb(off + 12 * 8, crc & 0xFF);
wb(off + 13 * 8, (crc >> 8) & 0xFF);
wb(off + 14 * 8, 0);
wb(off + 15 * 8, 0);
/* The chip's status word, and the missed-packet count it reports. Read
* first, report what was read, then clear MISS (write-1-to-clear). */
csr0 = lance_csr_r(0);
miss = 0;
if (csr0 & CSR0_MISS) {
drops_total = drops_total + 1;
miss = 1;
}
bad = *(unsigned int *)RX_BAD;
free_kb = heap_bytes() >> 10;
off = hb + STAT_BODY * 8;
wb(off + 0 * 8, frames_rx & 0xFF);
wb(off + 1 * 8, (frames_rx >> 8) & 0xFF);
wb(off + 2 * 8, (frames_rx >> 16) & 0xFF);
wb(off + 3 * 8, (frames_rx >> 24) & 0xFF);
wb(off + 4 * 8, bad & 0xFF);
wb(off + 5 * 8, (bad >> 8) & 0xFF);
wb(off + 6 * 8, drops_total & 0xFF);
wb(off + 7 * 8, (drops_total >> 8) & 0xFF);
wb(off + 8 * 8, csr0 & 0xFF);
wb(off + 9 * 8, (csr0 >> 8) & 0xFF);
wb(off + 10 * 8, free_kb & 0xFF);
wb(off + 11 * 8, (free_kb >> 8) & 0xFF);
/* Arm the next descriptor: TMD3, then the length, then ownership last,
* so the chip cannot take a descriptor whose length is still stale
* (lance_cmd.c does the same, and NetBSD's am7990_rint() says why). */
off = lance_txring + lance_txslot * 64;
for (i = 0; i < 10000; i++) {
if (!(r16(off + 16) & TMD1_OWN)) {
break;
}
}
if (r16(off + 16) & TMD1_OWN) {
return 0; /* still the chip's: arm nothing */
}
w16(off + 48, 0);
w16(off + 32, (0x10000 - STATUS_LEN) & 0xFFFF);
w16(off + 16, TMD1_OWN | TMD1_STP | TMD1_ENP
| ((status_buf >> 3 >> 16) & 0xFF));
/* Where the harness must look to watch the chip finish this frame. */
*(unsigned int *)ST_TMLAST = off + 16;
/* One write: a bare TDMD would clear STRT (CSR0 is a command shadow;
* lance.c's comment and every reference driver agree). */
lance_csr_w(0, CSR0_STRT | CSR0_TDMD);
tmd1 = 0;
for (i = 0; i < 20000; i++) {
tmd1 = r16(off + 16);
if (!(tmd1 & TMD1_OWN)) {
break;
}
}
lance_txslot = (lance_txslot + 1) & (L_RING_LEN - 1);
status_seq = status_seq + 1;
*(unsigned int *)ST_FRAMES = frames_rx;
*(unsigned int *)ST_TMD1 = tmd1;
/* ST_SENT last: a reader that sees it move reads a frame that was
* built, transmitted and recorded, never one in progress. */
*(unsigned int *)ST_SENT = *(unsigned int *)ST_SENT + 1;
if (miss) {
lance_csr_w(0, CSR0_MISS);
}
return 1;
}
/*
* 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;
/* Only an accepted frame reaches VRAM: the bytes just validated
* are the bytes the blit reads, and a corrupt frame can never
* paint over a good one. */
paint_frame(base, &h);
} 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, frames, sent;
*(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;
*(unsigned int *)OS_TOUCH = 0;
/* Arm the VRAM canary before any frame is accepted, so a repaint
* between now and the first blit is counted rather than mistaken for
* our own last frame. */
*(unsigned short *)SENT_ADDR = SENT_VALUE;
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 */
/* Task 6: the status back-channel. The record starts empty, then the
* LANCE is taken over for transmit with interrupts masked -- the OS's
* drivers were using this chip a moment ago, and a network ISR running
* in the middle of the ring setup would see a half-written descriptor. */
*(unsigned int *)RX_EOF = 0;
*(unsigned int *)ST_SENT = 0;
*(unsigned int *)ST_FRAMES = 0;
*(unsigned int *)ST_BUF = 0;
*(unsigned int *)ST_TMD1 = 0;
*(unsigned int *)ST_INIT = 0;
*(unsigned int *)ST_TMLAST = 0;
lance_txslot = 0;
status_seq = 0;
status_seen = 0;
drops_total = 0;
vid_dint_();
lance_ok = lance_start(ptr);
vid_eint_();
*(unsigned int *)ST_BUF = status_buf;
for (;;) {
*(unsigned int *)VID_ALIVE = *(unsigned int *)VID_ALIVE + 1;
rx_poll(0);
rx_poll(1);
/* Report every STATUS_EVERY accepted frames, and -- once the
* harness says the sender has stopped -- for every further
* accepted frame, so a finite run ends with a report whose
* frames_rx is the final count rather than the last multiple
* of 64 (RX_EOF is harness-only; see frame_parse.h). */
frames = *(unsigned int *)RX_OK;
if (!lance_ok) {
continue;
}
if (frames - status_seen >= STATUS_EVERY
|| (*(unsigned int *)RX_EOF != 0 && frames != status_seen)) {
vid_dint_();
sent = send_status(frames);
vid_eint_();
if (sent) {
status_seen = frames;
}
}
}
}