Files
vt1200/harness/udpstream/vidstream.c
T
arti 8de6d94e84 feat(rx): mask interrupts for good -- the OS timer was halving the frame rate
Spec 8.9 decides the payload may kill the resident OS; this is the first half of
that, and it is a rate fix as much as a robustness one.

With the OS still ticking, the payload's loop is preempted and the machine
accepts ~15 frames/s **whatever the payload size**: a 5-byte one-tile delta
measured the same as a 140-tile one (195 frames in 13.4 s, MAME at 100% speed,
frames_rx from the payload's own status frames).  That is scheduling, not
decode -- the loop itself runs ~20k iterations/s.

VID_OWN_CPU (default 1) takes one DINT before the main loop and makes vid_eint_()
a no-op, so nothing preempts the LANCE poll.  Measured on the same one-tile
stream:

  OS ticking (before)   15.0 fps accepted, 400 frames in 15.4 s
  DINT at startup       24.0 fps accepted, 385/400 frames, drops=0
  and, paced differently, 31.5 fps with 387/400 -- the sender, not the terminal,
  is now the throttle (its socket blocks once the tap's drain rate is exceeded)

Consequence stated where it is made: the OS's drivers stop being serviced, so
its console and network stack are gone for good.  The payload owns the machine
once it takes the LANCE either way.  The harness reaches the payload through the
debugger, not the OS, so the replay modes are unaffected -- verified: modes
tiles, delta and replay all RESULT PASS with their pixel checkers after the
change.

Set VID_OWN_CPU=0 at build time to reproduce the old burst-EINT behaviour for a
comparison run.
2026-10-31 22:33:07 +02:00

1859 lines
74 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 representation: two 24 KB frame
* slots at the head of that pool and a poll loop that hands a completed frame
* 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). Task 7 replaces Task 4's debugger-injected
* buffer with the payload's own LANCE RX ring: sixteen descriptors whose
* buffers tile a slot, armed per slot, consumed in ring order, so the chip
* DMAs the wire bytes into the memory fp_parse() and the blit read.
* Plan 2's Task 3 (this one) adds the tile dictionary: KIND_DICT_CHUNK frames
* are assembled into a 32 KB block after the two slots, and the dispatcher
* learns the TILES_FULL/TILES_DELTA kinds so a frame that arrives before the
* dictionary is complete is refused and counted instead of being mistaken for
* a strip. Plan 2's Task 4 paints TILES_FULL: one PIXBLT B,XY per 32x32 tile
* from the dictionary (vram_tile.asm), 1,280 of them per frame inside one
* dint/eint pair. Plan 2's Task 5 decodes TILES_DELTA: a validated changed-tile
* list painted by the same per-tile primitive, only the tiles it names.
*
* Pool layout inside the single allocation (POOL_BYTES = 249856 = 244 KB):
*
* +0x00000 frame slot A 49152 bytes (32 x 1526 B RX buffers)
* +x0C000 frame slot B 49152 bytes (32 x 1526 B RX buffers)
* +0x18000 dictionary 32768 bytes (256 x 128 B, Task 3)
* +0x20000 slack 114688 bytes (unused; the LANCE block's
* fixed 0x3C000 tail follows)
* +0x3C000 LANCE block 4096 bytes (init block, TX ring, status
* frame, RX ring)
*
* The dictionary sits at POOL_PTR + 2 * SLOT_BYTES, which is the placement the
* plan fixes and plan 2's Task 4 consumes. The 160 KB framebuffer reserve
* follows it: this decoder paints straight from the RX fragments (there is no
* per-frame CPU copy, spec 8.1) and never touches that region, so it is slack
* today, but the layout adds up to exactly POOL_BYTES -- 49152 + 49152 + 32768
* + 114688 + 4096 = 249856. The pool size did NOT have to grow for the deeper
* ring: the 160 KB framebuffer reserve was unused slack, and 114,688 B of it
* is still free between the dictionary and the LANCE block. dict_slack_check below pins the part Task 3
* depends on (the slot pair and the 32 KB dictionary); the LANCE block's
* offsets at 0x3C000 were fixed before it and are not re-checked here. Had
* the dictionary gone after the framebuffer, the plan's base would not be
* POOL_PTR + 2 * SLOT_BYTES and Task 4 would read 160 KB of zeroes.
*
* The LANCE block is the 4 KB tail. Its ring bases are aligned at run time
* (lance_start() explains why the offset alone is not enough), and the status
* frame sits right after the 8 TX descriptors, the RX ring after that.
*
* A slot is RX_DESC packet buffers of RXBUFLEN = 1526 bytes (32 x 1526 =
* 48832, so it fits 49152 with 320 bytes to spare). The chip writes ONE Ethernet frame
* per buffer -- 14-byte header, the VS frame's chunk, and the FCS it appends --
* so a VS frame arrives as a series of datagrams in ring order, and the frame's
* bytes are NOT contiguous in the slot: they are 14 bytes into each buffer,
* 1526 bytes apart. This payload therefore keeps a fragment table (offset,
* buffer, length per datagram) and `paint()` reads each source row across the
* fragments that hold it; there is no per-frame CPU copy of the picture (the
* design's "DMA, no CPU copy" -- spec 8.1). fp_parse() runs over the first
* fragment's body, which holds the VS header, with the accumulated length as
* its bound.
*
* Descriptors are re-armed per slot: when a frame completes, all 16 are pointed
* at the OTHER slot BEFORE the picture is painted, so the chip can fill N+1
* while we draw N and can never write into the slot being painted. A frame
* that needs more than 16 datagrams cannot be received at all: the ring is
* exhausted, the partial frame is dropped and counted (RX_RING_DROPS). 128
* rows of 160 bytes is 20,496 bytes = 15 datagrams, so the design's strip frame
* fits with one descriptor to spare.
*
* 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). The harness
* now models the real unit's VR319 monitor by default, so MAME's renderer uses
* the same 256-byte pitch; the draw test still compares VRAM as memory, not the
* rendered picture, because that is what the payload actually writes.
*
* 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 Task 4's retired
* injection cells. Task 7's ring record takes cells 0x10, 0x14, 0x18, 0x1c,
* 0x40 and 0x48 (the free page prefix R12 left for the RX work, at the same
* four-byte spacing every other cell uses); vidstream.lua uses the same rule.
* Plan 2's Task 3 takes cells 0x50 (DICT_READY), 0x54 (DICT_BYTES), 0x58
* (DICT_BAD) and 0x5c (TILES_DROP) -- the gap between RX_FCS_DROP and
* RX_SLOT_A, still at four-byte spacing.
*
* 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 */
/*
* The decode-time instrument (plan 2, the 12 risk: B,XY at 0.32 us/word is
* MAME-only). REFCNT is the TMS34010's own refresh counter, an I/O register
* at bit 0xC00001F0 -- the same one the firmware's POST reads twice and
* requires to have moved (harness/mame/BRINGUP_LOG.md, the REFCNT fix). It is
* a real register, not a harness invention, and reading it costs a MOVE.
*
* What it is NOT: microseconds. MAME returns (total_cycles()/16) & 0xfffc,
* i.e. a tick every 64 CPU cycles with the low two bits masked, and the real
* part's DRAM-refresh rate has not been measured on the unit. The status
* field therefore carries REFCNT ticks, and the unit conversion is still the
* M2 measurement the plan asks for. The counter is 16 bits and wraps; every
* sample below extends the difference modulo 65,536. Stock MAME advances it
* by 4 every 64 cycles (a 21 ms wrap at 50 MHz); the documented local REFCNT
* patch advances it every cycle (a 1.3 ms wrap). A tile row is well under one
* period in either build, so sampling once per row -- and every 64 entries in
* the delta validation pass -- keeps the extension unambiguous. If a future
* payload cannot read REFCNT, it must report that instead of substituting a
* made-up clock.
*/
#define REFCNT_REG 0xC00001F0 /* TMS34010 refresh counter, 16 bits */
static unsigned int decode_mark; /* REFCNT at the last sample */
static unsigned int decode_ticks; /* this frame so far, wrap-extended */
static unsigned int decode_worst; /* worst since the last status sent */
#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 holds a whole RX ring: 32 descriptors x
* RXBUFLEN (1526) = 48,832 B, rounded up. It also still holds a 128-row strip
* frame (128 x 160 B + 16 B header = 20,496 B = 15 packets).
*
* 32, not 16, because 16 could not absorb the tile dictionary: it arrives as
* 23 back-to-back datagrams (one TILES_DICT_CHUNK each), and with a 16-deep
* ring the chip dropped the tail while the consumer was still copying -- the
* measured zero-gap upload stalled at 24,616 of 32,768 B. The heap has
* megabytes free; the ring, not the memory, was the limit. 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 49152
/*
* Plan 2's Task 3: the tile dictionary.
*
* 256 entries of 32x32 1-bpp tiles, 128 bytes each = 32 KB, held in the
* startup pool right after the slot pair -- POOL_PTR + 2 * SLOT_BYTES, the
* placement the plan fixes and the tile blit (Task 4) consumes. The chunks
* arrive as KIND_DICT_CHUNK frames whose payload is a u16 little-endian
* offset plus up to 1448 dictionary bytes (tiledict.py); dict_chunk() is the
* assembler and states the rules it enforces.
*
* The pool layout in the header comment is exactly POOL_BYTES, so a build with
* a smaller request cannot hold the dictionary: dict_slack_check below refuses
* it at build time, and the run-time guard in main() refuses to point the
* assembler at memory outside the allocation. (POOL_BYTES is generated into
* poolcfg.h by build-stream.sh; the fallback pool is STATIC_BYTES, the same
* size.)
*/
#define DICT_OFF (2 * SLOT_BYTES) /* 0x0C000: after the two slots */
#define DICT_ENTRY 128 /* one 32x32 1-bpp tile, in bytes */
#define DICT_ENTRIES 256 /* entries: an index is one byte */
#define DICT_SIZE (DICT_ENTRY * DICT_ENTRIES) /* 256 entries = 32 KB */
/*
* Task 4: a TILES_FULL frame is 1,280 dictionary indices, row-major, one byte
* per tile: 40 tiles across the 1280-pixel screen, 32 tile bands down the 1024
* rows. Tile (tx, ty) is dictionary entry `index[ty * 40 + tx]`, painted by
* one PIXBLT B,XY from dict_base + index*1024 bits (128 bytes = 1024 bits;
* ruling R4 corrected the spec's old index << 7) with SPTCH = 32 bits,
* DYDX = (32, 32) and destination XY (ty*32, tx*32) -- vram_tile.asm owns the
* operand setup, tile_blit() below is its only caller.
*/
#define TILE_PIXEL 32 /* a tile is 32x32 pixels */
#define TILE_COLS 40 /* 1280 px / 32 */
#define TILE_ROWS 32 /* 1024 px / 32 */
#define TILES_SCREEN (TILE_COLS * TILE_ROWS) /* 1,280 indices a frame */
#define TILE_SHIFT 10 /* 128 bytes = 1024 bits an entry */
/*
* The arithmetic check the brief asks for, as a compile-time assertion.
*
* A #if here does NOT work: this preprocessor compiled the file with
* POOL_BYTES = 50000 and no complaint, so it does not fold the macro
* arithmetic in #if. An `extern' array with a negative length is accepted
* too (measured). A DEFINED array whose length is 1 or -1 does fail the
* build (measured: POOL_BYTES=50000 -> "1 Error, No Warnings" and no
* VIDSTRM.OUT), at the cost of one byte of .bss on a good build. The
* dictionary must fit after the slot pair, which is the arithmetic below; the
* full layout is exactly POOL_BYTES (header comment).
*/
char dict_slack_check[(POOL_BYTES >= (DICT_OFF + DICT_SIZE)) ? 1 : -1];
/*
* Task 5: the wire's tile count and the decoder's geometry must be the same
* grid. frame_parse.h pins it (FP_TILES_SCREEN, the layout block) and this is
* the build-time half: a disagreement is a compile error rather than a paint
* that lands one tile off. Same trick as dict_slack_check above.
*/
char tile_grid_check[(TILES_SCREEN == FP_TILES_SCREEN) ? 1 : -1];
/*
* Task 6: the terminal's status frame.
*
* The payload takes the LANCE over -- STOP, its own init block, RX and TX
* rings, 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), the
* 60-byte status frame and the RX ring (16 descriptors), 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.
*
* Task 7 arms the receiver (MODE = 0, so CSR0's STRT sets RXON) against those
* 16 descriptors. `drops` in the status body is three counts, all meaning "a
* frame that arrived and did not reach the screen": the chip's CSR0 MISS bit,
* counted once per report window in which it was latched (the bit cannot
* distinguish two misses in one window, and send_status() says so), plus the
* payload's own count of frames the ring was too small to finish
* (RX_RING_DROPS), plus the datagrams the chip flagged bad (RX_FCS_DROP) --
* those deliver no bytes at all, so without the third count a body-corrupting
* packet would be invisible. The chip's field is not replaced, and the
* payload's own counts are added to it rather than invented.
*/
#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 TMD1_OWN 0x8000
#define TMD1_ENP 0x0100
#define TMD1_STP 0x0200
/*
* The RX ring (Task 7). RX_DESC descriptors of 8 bytes each, each pointing at
* its own RXBUFLEN-byte buffer inside the armed slot. RXBUFLEN is one full
* 1514-byte Ethernet frame plus margin, the figure lance_cmd.c uses on the
* unit: a datagram from vidsrc.py is 14 + 1450 = 1464 bytes, the chip appends
* the 4-byte FCS, writes it after the frame and reports the total in RMD3.
* RX_DESC * RXBUFLEN = 48832 bytes, which fits a 49152-byte slot.
*
* The depth is the burst size the protocol actually uses: a whole 32 KB
* dictionary upload is 23 datagrams (MAX_BODY 1448), so 32 descriptors let the
* chip take the entire upload before the consumer has copied the first chunk.
* It is not a substitute for the sender pacing -- any ring can be outrun by a
* longer burst -- but it removes the accidental failure of the one burst the
* protocol defines.
*/
#define RX_DESC 32
#define RXBUFLEN 1526
#define RMD1_OWN 0x8000 /* the chip owns the descriptor */
#define RMD1_ENP 0x0100 /* end of packet */
#define RMD1_STP 0x0200 /* start of packet */
#define RMD1_BUFF 0x0400 /* buffer (descriptor) error */
#define RMD1_CRC 0x0800 /* CRC/FCS error */
#define RMD1_OFLO 0x1000 /* overflow error */
#define RMD1_FRAM 0x2000 /* framing error */
#define RMD1_ERR 0x4000 /* any of the above: the datagram is bad */
#define ETH_FCS 4 /* the chip stores this after the frame */
#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;
/* Task 4's tile primitive (vram_tile.asm): the source bit address and the
* destination XY word. DYDX and SPTCH are the constant 32x32 tile's, and are
* set in the assembly; only these two vary per call. */
unsigned int tile_src, tile_daddr;
extern void vram_blit_b_(); /* vram_b.asm: PIXBLT B,XY, SPTCH = 0 */
extern void tile_blit_(); /* vram_tile.asm: PIXBLT B,XY, 32x32 tile */
extern void vid_dint_(); /* vram_b.asm: DINT */
extern void vid_eint_(); /* vram_b.asm: EINT */
/*
* VID_OWN_CPU -- mask interrupts once at startup and never re-enable them.
*
* This is spec 8.9's "the resident OS may die", and it is also a *rate* fix,
* not just a robustness one. With the OS's timer still running, this loop is
* preempted and the machine accepts ~15 frames/s whatever the payload size: a
* 5-byte one-tile delta measured the same as a 140-tile one (195 frames in
* 13.4 s, MAME at 100% speed). That is scheduling, not decode. With IE held
* clear nothing preempts the loop and the LANCE is polled as fast as the chip
* delivers.
*
* Consequences, stated because they are real: the OS's drivers stop being
* serviced, so its console and network stack are gone for good -- the payload
* owns the machine either way once it takes the LANCE. The harness reaches
* the payload through the debugger, not through the OS, so the replay modes
* are unaffected.
*
* Set VID_OWN_CPU=0 to build the old behaviour (bursts of EINT around each
* transfer) for a comparison run.
*/
#ifndef VID_OWN_CPU
#define VID_OWN_CPU 1
#endif
#if VID_OWN_CPU
#define vid_eint_() ((void)0) /* masked for good; see main() */
#endif
/*
* 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 lance_rxring; /* bit address of the RX 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 windows, lower bound on misses */
/*
* Task 7 receive state.
*
* rx_slot_of[] which slot each descriptor points into (0 = A, 1 = B).
* Per descriptor, so descriptors can be recycled one at a time
* and nothing the chip has already delivered is re-pointed
* unread (see rx_release()).
* rx_pos the next descriptor the consumer expects. The chip fills
* descriptors strictly in ring order (am79c90.cpp's
* recv_complete_cb advances m_rx_ring_pos by one per packet and
* INIT resets it to 0), and this payload consumes them in that
* same order, so rx_pos *is* the chip's position -- there is no
* second, shadow copy of it that could drift. It is published as
* RX_RING_POS so the MAME check can inject at the right descriptor
* without guessing.
* rx_acc bytes of the current VS frame accumulated so far, i.e. the sum
* of the delivered datagram bodies. It doubles as the "still
* receiving" flag: 0 means no frame is in progress.
* rx_nfrag fragments recorded for the frame being received.
* rx_frag_* one entry per datagram: where its body sits in the slot, which
* frame byte it starts at, and how many body bytes it delivered.
* paint() walks this instead of copying the picture into a
* contiguous buffer.
* RX_RING_DROPS counts frames abandoned because the ring ran out of descriptors
* before the frame completed: a frame larger than 16 datagrams cannot be
* received at all, so it is dropped and reported rather than half-painted.
* RX_FCS_DROP counts datagrams the chip flagged bad (RMD1_ERR) whose ethertype
* says they are ours: they deliver no bytes at all -- the chip writes RMD3 = 0
* for them -- so without this cell a body-corrupting packet would be invisible
* to both crc_err and drops. Each one abandons the frame it belongs to.
*/
static unsigned int rx_pos;
static unsigned int rx_acc;
static unsigned int rx_nfrag;
/* Which slot each descriptor currently points into. Per descriptor, not one
* global "armed" slot: a burst is handled by releasing descriptors one at a
* time as their data is consumed, so a descriptor the chip has already filled
* is never re-pointed behind the consumer's back. */
static unsigned int rx_slot_of[RX_DESC];
/* Descriptors consumed for the frame being assembled -- their data must stay
* put until the frame is parsed or abandoned, so they are released together
* at the end. */
static unsigned int rx_hold[RX_DESC];
static unsigned int rx_nhold;
/* The slot the first fragment of the current frame landed in (RX_SLOT). */
static unsigned int rx_frame_slot;
static unsigned int rx_frag_off[RX_DESC];
static unsigned int rx_frag_ptr[RX_DESC];
static unsigned int rx_frag_len[RX_DESC];
/*
* Plan 2's Task 3 state. Like every other static here it is cleared by main()
* before use: the toolchain never runs .cinit.
*
* dict_base bit address of the dictionary in the pool (POOL_PTR + DICT_OFF)
* dict_bytes bytes accepted, always a contiguous prefix from entry 0
* dict_bad chunks refused (gap/overlap/duplicate, overrun, after ready)
* dict_ready 1 once dict_bytes is exactly DICT_SIZE
* tiles_drop tile frames that could not become pixels
*
* dict_bytes is the assembler's only cursor. It is not a copy of anything the
* sender says: the offset in a chunk is checked against it, never trusted, so a
* missing or replayed chunk is visible as an offset mismatch rather than a hole
* in the dictionary.
*/
static unsigned int dict_base;
static unsigned int dict_bytes;
static unsigned int dict_bad;
static unsigned int dict_ready;
static unsigned int tiles_drop;
/* Set by dict_refuse(); main() turns it into an out-of-cadence status frame.
* That early report is the difference between the host seeing "dictionary
* incomplete" and seeing nothing at all: after one lost chunk every later
* chunk is refused, no frame is ever accepted, and the 64-frame cadence never
* fires. See the note on the report loop in main(). */
static unsigned int status_now;
/*
* 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_();
}
/*
* The decode-time instrument around a paint (REFCNT_REG above).
*
* decode_begin() marks the start of one frame's decode, decode_sample()
* extends the elapsed count by however many REFCNT ticks have passed since the
* last sample (mod 65,536 -- the counter is 16 bits), and decode_end() folds
* the frame's total into decode_worst, which send_status() reports and main()
* clears on a successful send. The per-row sample is what makes the wrapped
* counter unambiguous over a full repaint.
*/
static void decode_begin()
{
decode_mark = *(unsigned short *)REFCNT_REG;
decode_ticks = 0;
}
static void decode_sample()
{
unsigned int now;
now = *(unsigned short *)REFCNT_REG;
decode_ticks = decode_ticks + ((now - decode_mark) & 0xFFFF);
decode_mark = now;
}
static void decode_end()
{
decode_sample();
if (decode_ticks > decode_worst) {
decode_worst = decode_ticks;
}
}
/*
* tile_blit(src, tx, ty)
*
* One PIXBLT B,XY of a 32x32 dictionary tile. `src` is the entry's bit
* address (dict_base + index * TILE_BITS), (tx, ty) the tile's position on the
* 40x32 grid. The assembly entry takes no arguments (ruling R13); the two
* values are published where vram_tile.asm reads them, and the destination XY
* is formed here because that is the only per-call arithmetic: x = tx*32,
* y = ty*32, packed as (y << 16) | x.
*/
static void tile_blit(src, tx, ty)
unsigned int src;
unsigned int tx;
unsigned int ty;
{
tile_src = src;
tile_daddr = ((ty * TILE_PIXEL) << 16) | (tx * TILE_PIXEL);
tile_blit_();
}
/*
* paint(h) -- draw one accepted strip frame from the fragment table.
*
* Source row r goes down screen rows r*stretch .. r*stretch+stretch-1. The
* frame's bytes do not sit contiguously in the slot (each datagram's body is 14
* bytes into its own buffer), so a row is cut into the pieces the fragments
* hold it in: the row starts at frame byte payload_off + r*ROW_BYTES, and each
* piece is one PIXBLT from the fragment that holds it, with the destination x
* advanced by the bytes already drawn. A 1450-byte datagram holds 9 rows and a
* fraction, so only about one row in nine is split at all.
*
* 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() and the accumulator have already proved that payload_len body
* 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(h)
fp_header *h;
{
unsigned int r, rows, stretch, sent, pos, done, k, f, room, n;
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;
}
if (rx_nfrag == 0) {
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_();
pos = h->payload_off; /* the frame's first body byte */
k = 0;
for (r = 0; r < rows; r++) {
done = 0;
while (done < ROW_BYTES) {
while (k < rx_nfrag
&& pos >= rx_frag_off[k] + rx_frag_len[k]) {
k = k + 1;
}
if (k >= rx_nfrag) {
vid_eint_();
return; /* truncated: stop drawing */
}
if (pos < rx_frag_off[k]) {
vid_eint_();
return; /* a gap: never paint across it */
}
f = pos - rx_frag_off[k];
room = rx_frag_len[k] - f;
n = ROW_BYTES - done;
if (room < n) {
n = room;
}
blit_row(rx_frag_ptr[k] + f * 8,
((r * stretch) << 16) | (done * 8),
(stretch << 16) | (n * 8));
done = done + n;
pos = pos + n;
}
}
vid_eint_();
}
/*
* paint_tiles(h) -- draw one accepted TILES_FULL frame, 1,280 tiles. 1 when
* the picture was painted, 0 when the frame could not be decoded (the caller
* counts the refusal in TILES_DROP).
*
* The payload is 1,280 dictionary indices in a single byte, row-major: index
* i is tile (tx, ty) with ty = i / 40, tx = i % 40. The nested loop avoids
* the division, which the TMS34010 has no instruction for. Each tile is one
* PIXBLT B,XY of its 128-byte dictionary entry from dict_base + index*1024
* (the shift by TILE_SHIFT, because a shift by a named constant is emitted
* without a multiply).
*
* A frame is refused, and nothing is painted, when:
* - the dictionary is not complete (DICT_READY 0 or no base): there is
* nothing to decode with -- this is the case Task 3's dict mode pins;
* - the payload is not exactly 1,280 bytes: the TILES_FULL format is one
* byte per tile and nothing else, so a short frame does not name every
* tile and a longer one is not this kind's wire form. Painting a partial
* screen over a good one would be worse than leaving the old picture, and
* the arbitrary-length tolerance would be a rule nobody wrote down.
* A gap in the fragment table in the middle of the burst is impossible for a
* completed frame (fp_parse() accepted it and rx_acc is the accumulated length,
* so every payload byte is covered); the check is there so such a frame stops
* drawing instead of painting from a stale pointer, and it is deliberately not
* counted as a drop because the frame was not refused up front. That branch
* leaves a partial picture, exactly as paint()'s truncated case does.
*
* The whole burst runs with interrupts masked (spec 8.9): 1,280 blits take
* long enough that a resident OS repaint between two tiles would tear the
* picture. The loop is kept small on purpose -- it is the code the
* instruction cache holds while the blits run: no call per tile except the
* primitive, no division, no per-tile function.
*
* The OS-repaint canary is the same one paint() uses; a frame that sees it
* disturbed counts OS_TOUCH and puts it back.
*/
static int paint_tiles(h)
fp_header *h;
{
unsigned int tx, ty, k, pos, idx, sent;
if (!dict_ready || dict_base == 0) {
return 0;
}
if (h->payload_len != TILES_SCREEN) {
return 0;
}
if (rx_nfrag == 0) {
return 0;
}
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_();
decode_begin();
k = 0;
pos = h->payload_off; /* the frame's first index byte */
for (ty = 0; ty < TILE_ROWS; ty++) {
for (tx = 0; tx < TILE_COLS; tx++) {
while (k < rx_nfrag
&& pos >= rx_frag_off[k] + rx_frag_len[k]) {
k = k + 1;
}
if (k >= rx_nfrag) {
vid_eint_();
decode_end();
return 1; /* truncated: stop drawing */
}
if (pos < rx_frag_off[k]) {
vid_eint_();
decode_end();
return 1; /* a gap: never paint across it */
}
idx = (unsigned int)*(unsigned char *)
(rx_frag_ptr[k] + (pos - rx_frag_off[k]) * 8);
tile_blit(dict_base + (idx << TILE_SHIFT), tx, ty);
pos = pos + 1;
}
decode_sample();
}
vid_eint_();
decode_end();
return 1;
}
/*
* frag_get(pos, vp) -- one frame byte at frame offset `pos`. 1 on success, 0
* when the fragment table does not hold it (a gap, or a frame shorter than the
* position asked for).
*
* paint_delta() reads single bytes at offsets its own arithmetic derives -- the
* count, each position and each value -- where frag_copy() moves one contiguous
* run. Same table, same rules: no copy across a gap and nothing past the last
* delivered byte.
*/
static int frag_get(pos, vp)
unsigned int pos;
unsigned int *vp;
{
unsigned int k;
k = 0;
while (k < rx_nfrag && pos >= rx_frag_off[k] + rx_frag_len[k]) {
k = k + 1;
}
if (k >= rx_nfrag) {
return 0;
}
if (pos < rx_frag_off[k]) {
return 0;
}
*vp = (unsigned int)*(unsigned char *)
(rx_frag_ptr[k] + (pos - rx_frag_off[k]) * 8);
return 1;
}
/*
* paint_delta(h) -- draw one accepted TILES_DELTA frame. 1 when the list was
* decoded and painted, 0 when the frame was refused (the caller counts that in
* TILES_DROP and VRAM is untouched).
*
* The wire form (frame_parse.h, tiledict.py's delta_frame) is
*
* count u16 | count x position u16 | count x entry u8
*
* with payload_len exactly 2 + 3 * count. A position is the row-major tile
* index ty * TILE_COLS + tx and must be strictly ascending; an entry is a
* dictionary index, one byte.
*
* Two passes, and the first one is the safety property: EVERY position and
* entry is read and bounds-checked before the first blit. A frame with one bad
* entry therefore paints nothing at all, where a decoder that painted as it
* read would leave half a delta on the screen -- and half a delta is worse than
* none, because the next delta is computed against a frame this side never had.
*
* Refused (nothing painted), in the order the checks run:
* - no complete dictionary (DICT_READY 0 or no base), as for TILES_FULL;
* - payload_len < 2: no count field;
* - count 0 or above TILES_SCREEN: an empty list is not sent (the encoder
* returns None) and a screen cannot have more changed tiles than tiles;
* - payload_len != 2 + 3 * count: the list would run past the frame, or the
* frame carries bytes that are not part of the list;
* - a position >= TILES_SCREEN: off the 40x32 grid (a blit aimed there would
* write past the last tile band);
* - a position not greater than the previous one: the list is not canonical,
* and a repeated tile would be painted twice for no reason. This is also
* what lets the paint pass run with one cursor: strictly ascending means
* the nested grid loop visits every named tile exactly once;
* - an entry >= DICT_ENTRIES: one byte cannot express it, so this check is
* dead today, but it is the bound that keeps a future wider encoding from
* reading past the 32 KB dictionary. It is written out rather than left to
* the byte width.
*
* The paint pass walks the 40x32 grid once, comparing the running tile position
* with the next named one -- no division, no search, and the same
* one-PIXBLT-per-tile primitive (and dint/eint pair and canary) as
* paint_tiles(). A tile the list does not name is not touched at all, which is
* the whole point of a delta.
*
* Because the list was validated a moment ago from the same slot, the reads in
* the paint pass cannot fail; their return values are ignored deliberately, and
* the slot cannot be written while this frame is being decoded (rx_switch()
* armed the OTHER slot before the dispatch).
*/
static int paint_delta(h)
fp_header *h;
{
unsigned int count, k, pos, val, prev, lo, hi, sent;
unsigned int tile, tx, ty;
decode_begin();
if (!dict_ready || dict_base == 0) {
return 0;
}
if (h->payload_len < FP_DELTA_HDR) {
return 0;
}
if (!frag_get(h->payload_off, &lo)
|| !frag_get(h->payload_off + 1, &hi)) {
return 0;
}
count = lo | (hi << 8);
if (count == 0 || count > TILES_SCREEN) {
return 0;
}
if (h->payload_len != FP_DELTA_HDR + count * 3) {
return 0;
}
/* Pass 1: the whole list, before anything is painted. */
prev = 0;
for (k = 0; k < count; k++) {
if ((k & 0x3F) == 0) {
decode_sample();
}
if (!frag_get(h->payload_off + FP_DELTA_HDR + k * 2, &lo)
|| !frag_get(h->payload_off + FP_DELTA_HDR + k * 2 + 1,
&hi)) {
return 0;
}
pos = lo | (hi << 8);
if (pos >= TILES_SCREEN) {
return 0;
}
if (k > 0 && pos <= prev) {
return 0;
}
prev = pos;
if (!frag_get(h->payload_off + FP_DELTA_HDR + count * 2 + k,
&val)) {
return 0;
}
if (val >= DICT_ENTRIES) {
return 0;
}
}
/* The same canary rule as paint_tiles(): outside every tile, so an
* unchanged value means nobody painted over this frame's screen. */
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;
}
/* Pass 2: walk the grid, paint the named tiles. `pos` holds the next
* named position; `tile` is the grid position the loop is on. The
* checkpoint here ends the validation stretch (which is O(count)) and
* the per-row samples keep the 16-bit REFCNT counter unambiguous. */
decode_sample();
vid_dint_();
frag_get(h->payload_off + FP_DELTA_HDR, &lo);
frag_get(h->payload_off + FP_DELTA_HDR + 1, &hi);
pos = lo | (hi << 8);
k = 0;
tile = 0;
for (ty = 0; ty < TILE_ROWS; ty++) {
for (tx = 0; tx < TILE_COLS; tx++) {
if (k < count && pos == tile) {
frag_get(h->payload_off + FP_DELTA_HDR
+ count * 2 + k, &val);
tile_blit(dict_base + (val << TILE_SHIFT),
tx, ty);
k = k + 1;
if (k < count) {
frag_get(h->payload_off + FP_DELTA_HDR
+ k * 2, &lo);
frag_get(h->payload_off + FP_DELTA_HDR
+ k * 2 + 1, &hi);
pos = lo | (hi << 8);
}
}
tile = tile + 1;
}
decode_sample();
}
vid_eint_();
decode_end();
return 1;
}
/*
* frag_copy(dst, pos, n) -- copy n frame bytes starting at frame offset `pos`
* into memory at bit address `dst`. 1 on success, 0 when the fragment table
* does not hold the whole range (a truncated frame or a gap).
*
* The frame's bytes are not contiguous in the slot: each datagram's body sits
* 14 bytes into its own 1526-byte buffer, so a dictionary chunk that spans two
* datagrams -- every 1450-byte chunk does, since 16 + 1450 = 1466 -- has to be
* stored piece by piece. paint() walks the same table for the blit, so this is
* one reading of the fragment map, not a second one.
*/
static int frag_copy(dst, pos, n)
unsigned int dst;
unsigned int pos;
unsigned int n;
{
unsigned int k, f, room, i;
k = 0;
while (n > 0) {
while (k < rx_nfrag
&& pos >= rx_frag_off[k] + rx_frag_len[k]) {
k = k + 1;
}
if (k >= rx_nfrag) {
return 0; /* truncated */
}
if (pos < rx_frag_off[k]) {
return 0; /* a gap: never copy across it */
}
f = pos - rx_frag_off[k];
room = rx_frag_len[k] - f;
if (room > n) {
room = n;
}
for (i = 0; i < room; i++) {
*(unsigned char *)(dst + i * 8) =
*(unsigned char *)(rx_frag_ptr[k] + (f + i) * 8);
}
dst = dst + room * 8;
pos = pos + room;
n = n - room;
}
return 1;
}
/* One refused dictionary chunk: count it, ask main() for an immediate status
* report, and leave the accepted prefix alone so the sender's next good chunk
* still lands. The early report is diagnostic: without it a lost chunk makes
* every later chunk fail and no frame is ever accepted, so the 64-frame report
* never fires and the host only sees silence. */
static void dict_refuse()
{
dict_bad = dict_bad + 1;
*(unsigned int *)DICT_BAD = dict_bad;
status_now = 1;
}
/*
* dict_chunk(h) -- assemble one slice of the 32 KB tile dictionary.
*
* The wire form (tiledict.py, restated in frame_parse.h) is the VS header,
* then a u16 little-endian dictionary offset, then up to 1448 dictionary bytes.
* The rules, and what each refuses:
*
* payload_len >= 3 the offset and at least one dictionary byte.
* off == dict_bytes ascending and contiguous. A gap, an overlap, a
* duplicate and a reordered chunk all fail this one
* test. It is also the 128-byte rule Task 2's
* review carried: an offset inside an entry is legal
* only as a continuation of the accepted prefix --
* which is exactly "off equals the prefix end" -- and
* a first chunk that does not begin at entry 0 fails
* it too.
* off + body <= DICT_SIZE past the last entry; the tail chunk that would
* overrun 32,768 is the "partial final entry" case.
* frag_copy() succeeds the bytes are really in the fragment table.
*
* DICT_READY is set only by the chunk that makes dict_bytes exactly DICT_SIZE:
* 256 whole entries. A short upload is therefore never ready, so a tile frame
* that follows it is still refused -- the wire has no end-of-upload marker to
* do that earlier.
*
* After DICT_READY a further chunk is refused rather than restarting the
* upload: plan ruling R1 makes the dictionary fixed for the clip, and a
* re-upload would need a coordinated paint stall the protocol does not have.
*
* The offset is read out of the first fragment, whose body always holds the VS
* header and the first bytes after it; frag_copy() then handles the body
* proper, which may cross into the next datagram.
*/
static void dict_chunk(h)
fp_header *h;
{
unsigned int off, body, p;
if (dict_ready || dict_base == 0 || h->payload_len < 3) {
dict_refuse();
return;
}
if (rx_frag_len[0] < h->payload_off + 2) {
dict_refuse();
return;
}
p = rx_frag_ptr[0] + h->payload_off * 8;
off = (unsigned int)*(unsigned char *)p
| ((unsigned int)*(unsigned char *)(p + 8) << 8);
body = h->payload_len - 2;
if (off != dict_bytes || off + body > DICT_SIZE) {
dict_refuse();
return;
}
if (!frag_copy(dict_base + off * 8, h->payload_off + 2, body)) {
dict_refuse();
return;
}
dict_bytes = off + body;
*(unsigned int *)DICT_BYTES = dict_bytes;
*(unsigned int *)DICT_BAD = dict_bad;
if (dict_bytes == DICT_SIZE) {
dict_ready = 1;
/* Published last, after DICT_BYTES: a reader that sees readiness
* sees the complete record. */
*(unsigned int *)DICT_READY = 1;
}
}
/*
* 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;
}
/* The two slots' bit addresses, published by main() and never changed. */
static unsigned int slot_base[2];
/*
* rx_arm(slot) -- point all 16 descriptors at `slot`, ownership set last.
*
* The chip masks the ring ADDRESS it is given (RING_ADDR_MASK 0xFFFFF8,
* am79c90.h) but uses a descriptor's buffer address as written, in RMD1 bits
* 0-7 (high byte) and RMD0 (low 16). NetBSD's am7990_rint() re-arms a used
* descriptor with "length first, ownership last" and leaves the address alone;
* here the address changes on every frame (the slot alternates), so the whole
* descriptor is rewritten, and RMD1 is written FIRST with OWN CLEAR.
*
* The order is not cosmetic: the chip fetches RMD1 BEFORE the address. In
* am79c90.cpp's receive path it reads `m_rx_md[1] = m_dma_in_cb(ring_address |
* 2)` and only then `m_rx_md[0] = m_dma_in_cb(ring_address | 0)` and
* `m_rx_md[2] = ... | 4`. With OWN set in the same write that carries the new
* high byte, a fetch inside the window pairs the NEW slot's high byte with the
* PREVIOUS slot's RMD0 -- a mixed buffer address. That is harmless only while
* both slots share a 64 KB page; on the unit a 48 KB slot pair can cross one,
* and the chip would then DMA into memory outside the pool.
*
* Publishing the address and length under OWN clear closes it: a fetch inside
* the window reads OWN clear and the packet is a missed packet
* (recv_complete_cb() case -2 latches CSR0 MISS), so it is counted and dropped
* instead of being written somewhere wrong. OWN is then set in the last write.
*
* RMD2 is the buffer size as a negative two's complement count (0x10000 -
* RXBUFLEN), the form am79c90's get_buf_length() decodes and lance_cmd.c arms
* with. RMD3 is the message count the chip fills in; zeroing it first means a
* descriptor completed by anything but the chip reads as a zero-length packet
* rather than as the previous frame's length.
*/
static void rx_arm_one(i, slot)
unsigned int i, slot;
{
unsigned int cb;
cb = (slot_base[slot] >> 3) + i * RXBUFLEN;
/* OWN clear, with the new high byte: a fetch here is a MISS. */
w16(lance_rxring + i * 64 + 16, (cb >> 16) & 0xFF);
w16(lance_rxring + i * 64 + 0, cb & 0xFFFF);
w16(lance_rxring + i * 64 + 32, (0x10000 - RXBUFLEN) & 0xFFFF);
w16(lance_rxring + i * 64 + 48, 0);
/* Ownership last, over a complete descriptor. */
w16(lance_rxring + i * 64 + 16, RMD1_OWN | ((cb >> 16) & 0xFF));
rx_slot_of[i] = slot;
}
static void rx_arm(slot)
unsigned int slot;
{
unsigned int i;
for (i = 0; i < RX_DESC; i++) {
rx_arm_one(i, slot);
}
}
/*
* rx_release() -- give the frame's descriptors back, pointing each at the OTHER
* slot (spec 8.1's overlap: the buffer the chip next writes must not be one the
* consumer is still reading).
*
* This replaces a per-frame "switch slots and re-arm all 16". That flip was
* the burst bug: an all-16 re-arm re-points descriptors the chip had ALREADY
* completed but the consumer had not yet read, so their frames were silently
* lost -- measured on a 23-datagram dictionary upload: the chip delivered all
* 23 (LANCE DMA log, 1.13 ms apart) and only 4 reached these counters. Holding
* and releasing each descriptor individually means nothing the chip delivered
* is ever re-pointed unread, and the ring recycles as fast as the consumer.
*/
static void rx_release()
{
unsigned int k, i;
for (k = 0; k < rx_nhold; k++) {
i = rx_hold[k];
rx_arm_one(i, 1 - rx_slot_of[i]);
}
rx_nhold = 0;
}
/*
* rx_dgram(i) -- consume descriptor i, which the chip has completed.
*
* One Ethernet frame per buffer, so STP and ENP must both be set. A packet
* longer than one buffer would be chained across descriptors, and every
* reference driver (4.4BSD pmax if_le.c, NetBSD am7990.c, Linux 7990.c) drops a
* chained frame rather than reassembling it -- lance_cmd.c cites the same rule
* -- so a chained descriptor contributes nothing here. RMD3 is the delivered
* length INCLUDING the 4-byte FCS the chip appends, so the body is RMD3 minus
* the Ethernet header and the FCS.
*
* The datagram is appended to the fragment table and the frame's own
* payload_len decides when the frame is complete (spec 8.1: payload_len plus
* the bytes actually delivered are authoritative, not the descriptor count).
* RMD3 is per datagram here, because each datagram is its own Ethernet packet.
*/
static void rx_dgram(i)
unsigned int i;
{
unsigned int rmd1, mlen, len, src, plen, total, lim, slot;
unsigned int accepted, refused;
fp_header h;
accepted = 0;
refused = 0;
rmd1 = r16(lance_rxring + i * 64 + 16);
mlen = r16(lance_rxring + i * 64 + 48) & 0x0FFF;
src = slot_base[rx_slot_of[i]] + i * RXBUFLEN * 8 + ETH_HDR_LEN * 8;
len = 0;
if (rmd1 & RMD1_ERR) {
/*
* A datagram the chip flagged -- CRC/FCS, buffer, overflow or
* framing -- delivers NO bytes: am79c90.cpp's
* recv_complete_cb() writes RMD3 as
* (m_rx_md[1] & RMD1_ERR) ? 0 : (result & RMD3_MCNT)
* so the length checks below would drop it silently and a
* body-corrupting packet would never appear in any counter.
*
* The bytes ARE in the buffer (dma_out() ran before the FCS
* was checked), so a datagram of ours is still identifiable by
* its ethertype -- the Ethernet header is written before the
* body, so a body-corrupting error leaves it intact. Count one
* drop for the frame it breaks, abandon that frame and re-arm
* the other slot: with a datagram missing the accumulation can
* never reach the frame's payload_len, and letting later
* datagrams keep appending would mix two frames into one
* picture. Traffic that is not ours is ignored, exactly as at
* a frame start.
*
* If the corruption landed on the ethertype the datagram cannot
* be attributed and is not counted. That is the limit of what
* the chip leaves readable; it is stated in the report rather
* than papered over.
*/
if (*(unsigned char *)(src - ETH_HDR_LEN * 8 + 12 * 8) == 0x88
&& *(unsigned char *)(src - ETH_HDR_LEN * 8 + 13 * 8) == 0xB7) {
*(unsigned int *)RX_FCS_DROP =
*(unsigned int *)RX_FCS_DROP + 1;
rx_acc = 0;
rx_nfrag = 0;
rx_release();
}
return;
}
if ((rmd1 & (RMD1_STP | RMD1_ENP)) == (RMD1_STP | RMD1_ENP)
&& mlen > ETH_HDR_LEN + ETH_FCS
&& mlen - ETH_HDR_LEN - ETH_FCS <= RXBUFLEN - ETH_HDR_LEN) {
len = mlen - ETH_HDR_LEN - ETH_FCS;
}
if (len == 0) {
return; /* runt or chained: nothing appended */
}
if (rx_acc == 0) {
/* A frame starts here. Only a VS frame is ours: the chip
* accepts broadcast and its own address, so other traffic can
* land in this ring, and fp_parse() would count every such
* datagram as a bad frame. Resync on the magic instead. */
if (len < 2
|| *(unsigned char *)src != FP_MAGIC0
|| *(unsigned char *)(src + 8) != FP_MAGIC1) {
return;
}
}
if (rx_nfrag == 0) {
rx_frame_slot = rx_slot_of[i]; /* what RX_SLOT reports */
}
rx_frag_off[rx_nfrag] = rx_acc;
rx_frag_ptr[rx_nfrag] = src;
rx_frag_len[rx_nfrag] = len;
rx_nfrag = rx_nfrag + 1;
rx_acc = rx_acc + len;
if (rx_acc < FP_HEADER_SIZE) {
return;
}
plen = (unsigned int)*(unsigned char *)(rx_frag_ptr[0] + 6 * 8)
| ((unsigned int)*(unsigned char *)(rx_frag_ptr[0] + 7 * 8) << 8);
total = FP_HEADER_SIZE + plen;
if (total > SLOT_BYTES) {
/* The header claims a frame larger than the slot: it can never be
* completed, and no part of it is drawn. Reject it now, then hand
* the OTHER slot to the chip: all 16 descriptors are re-armed,
* including the one this datagram arrived in, so the sender's next
* frame starts on a clean ring. What is left of the rejected frame
* is ignored, exactly as any frame that does not open with the
* magic is. */
*(unsigned int *)RX_BAD = *(unsigned int *)RX_BAD + 1;
rx_acc = 0;
rx_nfrag = 0;
rx_release();
return;
}
if (rx_acc < total) {
return; /* still arriving */
}
/* The frame is complete. Its descriptors stay held -- so the chip
* cannot overwrite the picture while it is parsed and painted -- and are
* released just below, each into the other slot. */
slot = rx_frame_slot;
/*
* Task 4 finding (b): the length handed to fp_parse() is clamped to the
* slot. rx_acc is bounded by RX_DESC * RXBUFLEN and the claim above,
* but fp_parse() walks a caller-supplied length, and a slot-sized bound
* is the contract both of them can be read against.
*/
lim = rx_acc;
if (lim > SLOT_BYTES) {
lim = SLOT_BYTES;
}
if (fp_parse((unsigned char *)rx_frag_ptr[0], lim, &h)) {
if (h.kind == FP_KIND_STRIP) {
*(unsigned int *)RX_LAST_LEN = h.payload_len;
*(unsigned int *)RX_SLOT = slot;
paint(&h);
accepted = 1;
} else if (h.kind == FP_KIND_DICT_CHUNK) {
/* A chunk is not a picture: it is counted by DICT_BYTES
* and DICT_BAD, never by RX_OK. */
dict_chunk(&h);
} else if (h.kind == FP_KIND_TILES_FULL) {
/*
* Task 4: a full frame of indices is painted from the
* dictionary, one PIXBLT per tile. RX_OK and the record
* cells describe only frames that reached the screen, so
* they move after the paint, not before it. A frame the
* dictionary cannot decode (no DICT_READY, short payload)
* is counted in TILES_DROP and touches nothing -- the
* same rule Task 3's pre-dictionary branch had.
*/
if (paint_tiles(&h)) {
*(unsigned int *)RX_LAST_LEN = h.payload_len;
*(unsigned int *)RX_SLOT = slot;
accepted = 1;
} else {
refused = 1;
}
} else if (h.kind == FP_KIND_TILES_DELTA) {
/*
* Task 5: a changed-tile list. paint_delta() validates
* the whole list before it paints, so a refused frame
* touches no pixel; a frame it accepts moves the same
* record cells a TILES_FULL does, and only then.
*/
if (paint_delta(&h)) {
*(unsigned int *)RX_LAST_LEN = h.payload_len;
*(unsigned int *)RX_SLOT = slot;
accepted = 1;
} else {
refused = 1;
}
} else {
/* A well-formed header whose kind nothing here dispatches
* (KIND_STATUS is the terminal's own, travelling the other
* way): received and refused, so it is a bad frame. */
*(unsigned int *)RX_BAD = *(unsigned int *)RX_BAD + 1;
}
} else {
*(unsigned int *)RX_BAD = *(unsigned int *)RX_BAD + 1;
}
rx_acc = 0;
rx_nfrag = 0;
/* Released only now: every byte this frame needed has been read. */
rx_release();
/*
* Published after the release, not inside the dispatch. RX_OK is the
* "frame done" signal a reader polls, and it must not become visible
* while rx_release() is still rewriting descriptors: a sample taken then
* catches a descriptor mid-update (measured: slot B's high byte with
* slot A's low word). A reader that sees RX_OK move now sees a painted
* picture AND a consistent ring.
*/
if (refused) {
tiles_drop = tiles_drop + 1;
*(unsigned int *)TILES_DROP = tiles_drop;
}
if (accepted) {
*(unsigned int *)RX_OK = *(unsigned int *)RX_OK + 1;
}
}
/*
* rx_poll() -- drain every descriptor the chip has completed, in ring order.
*
* Task 4 finding (a): there is no level-triggered "frame ready" cell any more.
* The handshake is the OWN bit, per descriptor: the chip clears it when the
* packet is in the buffer and this side sets it again (in rx_arm) only after
* reading the descriptor's data, so there is no shared flag for two writers to
* race on. `rx_pos` is the only cursor and it advances only after a descriptor
* has been read; the chip advances its own position by one per packet and INIT
* resets both to 0, so consuming in ring order keeps the two in step.
*
* A frame that uses the whole ring without completing is dropped: the ring
* cannot hold it, so it can never complete, and leaving the partial frame in
* place would make every later frame look like its tail.
*/
static void rx_poll()
{
unsigned int i, n;
for (n = 0; n < RX_DESC; n++) {
i = rx_pos;
if (r16(lance_rxring + i * 64 + 16) & RMD1_OWN) {
break; /* the chip has not completed it */
}
rx_pos = (i + 1) & (RX_DESC - 1);
*(unsigned int *)RX_DGRAM = *(unsigned int *)RX_DGRAM + 1;
rx_hold[rx_nhold] = i;
rx_nhold = rx_nhold + 1;
rx_dgram(i);
if (rx_nfrag == RX_DESC && rx_acc != 0) {
*(unsigned int *)RX_RING_DROPS =
*(unsigned int *)RX_RING_DROPS + 1;
rx_acc = 0;
rx_nfrag = 0;
rx_release();
}
}
*(unsigned int *)RX_RING_POS = rx_pos;
*(unsigned int *)RX_RING_ACC = rx_acc;
*(unsigned int *)RX_RING_ARMED = rx_slot_of[rx_pos];
}
/*
* lance_start(ptr) -- take the LANCE over for receive and 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.
*
* Task 7 removed MODE_DRX and gave the receiver a ring, because that is what a
* receive path is: MODE = 0 (no loopback, no transmitter disable, FCS kept and
* appended) and STRT afterwards sets RXON as well as TXON (am79c90.cpp's STRT
* handler: "if (m_mode & MODE_DRX) clear RXON else set RXON"). The descriptors
* are armed for slot A before INIT, so the chip's first packet -- which can
* arrive the instant STRT lands -- finds a buffer.
*/
static int lance_start(ptr)
unsigned int ptr;
{
unsigned int init, ring, buf, rxring, 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 */
rxring = (buf + STATUS_LEN * 8 + 0x3F) & 0xFFFFFFC0; /* after status */
lance_txring = ring;
status_buf = buf;
lance_rxring = rxring;
lance_csr_w(0, CSR0_STOP);
w16(init + 0 * 16, 0); /* MODE: no loopback, FCS on */
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);
cb = rxring >> 3; /* BYTE address of the RX ring */
w16(init + 8 * 16, cb & 0xFFFF);
w16(init + 9 * 16, ((cb >> 16) & 0xFF) | (5 << 13)); /* 32 desc */
cb = ring >> 3; /* BYTE address of the ring */
w16(init + 10 * 16, cb & 0xFFFF);
w16(init + 11 * 16, ((cb >> 16) & 0xFF) | (3 << 13));
/* Every TX 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);
}
/* The RX ring, armed for slot A: this is the state the first packet
* finds, and it is what the MAME ring check asserts. */
rx_nhold = 0;
rx_arm(0);
*(unsigned int *)RX_RING_BASE = rxring;
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 receiver and the transmitter on; no INEA, so no
* interrupt reaches the firmware's network ISR while this payload owns
* the chip. RXON is what makes the descriptors armed above live. */
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 16 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,
* dict_state u8, reserved u8, decode_worst 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.)
*
* dict_state is two bits: 0x01 DICT_READY (the 32 KB dictionary is complete)
* and 0x02 DICT_BAD (at least one chunk was refused since the payload started).
* Both can be set -- a chunk refused after the dictionary was ready leaves the
* dictionary usable -- which is why it is a bit set rather than an enum.
*
* decode_worst is the worst decode time, in REFCNT ticks, since the last
* status frame that was actually sent (decode_begin() above says why ticks and
* not microseconds). It is the instrument for the plan's 12 risk: whether the
* B,XY tile path is fast enough on the unit. A report sent before any frame
* was painted carries 0.
*
* 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. A datagram
* whose FCS the CHIP rejected is not a frame at all: it delivers no bytes
* (RMD3 = 0) and is counted in drops, via RX_FCS_DROP.
*
* drops is three counts of frames that arrived and did not reach the screen:
*
* drops_total CSR0 MISS, counted per REPORT WINDOW in which the latched bit
* was set -- the chip gives no per-packet count, so this is one
* per window at most (a lower bound, see send_status()).
* RX_RING_DROPS frames too large for the 16 descriptors (Task 7).
* RX_FCS_DROP datagrams the chip flagged bad; each abandons its frame.
*
* Neither number is inferred from another.
*
* 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, rdrops, fdrops,
free_kb, dstate;
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 bit it latches. CSR0
* MISS is ONE BIT: am79c90.cpp's recv_complete_cb() only ORs it in
* (case -2), so two missed packets inside one report window look exactly
* like one. drops_total therefore counts report windows in which at
* least one packet was missed -- a lower bound on missed packets, not an
* event count -- and the bit is cleared here after the report is built
* (write-1-to-clear) so the next window starts clean. The status field
* is a "something was missed" indicator, and this comment says so
* instead of claiming a count the hardware cannot give. */
csr0 = lance_csr_r(0);
miss = 0;
if (csr0 & CSR0_MISS) {
drops_total = drops_total + 1;
miss = 1;
}
bad = *(unsigned int *)RX_BAD;
/* The three drop counts described above: the chip's MISS windows, the
* payload's count of frames too large for the ring, and the datagrams
* the chip flagged bad (they carry no bytes, so nothing else sees
* them). */
rdrops = *(unsigned int *)RX_RING_DROPS;
fdrops = *(unsigned int *)RX_FCS_DROP;
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 + rdrops + fdrops) & 0xFF);
wb(off + 7 * 8, ((drops_total + rdrops + fdrops) >> 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);
/* The two Task 6 review fields: dictionary state and the worst decode
* since the last report (frame.py's pack_status() writes the same
* offsets; both bits and the tick unit are in the docstring above). */
dstate = (dict_ready ? 0x01 : 0) | (dict_bad ? 0x02 : 0);
wb(off + 12 * 8, dstate & 0xFF);
wb(off + 13 * 8, 0); /* reserved: keep the u16 aligned */
wb(off + 14 * 8, decode_worst & 0xFF);
wb(off + 15 * 8, (decode_worst >> 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;
}
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 counter from a previous run or a
* slot address that has no allocation behind it. The ring record is
* published by lance_start() once the descriptors are armed. */
*(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_RING_BASE = 0;
*(unsigned int *)RX_RING_POS = 0;
*(unsigned int *)RX_RING_ACC = 0;
*(unsigned int *)RX_RING_ARMED = 0;
*(unsigned int *)RX_RING_DROPS = 0;
*(unsigned int *)RX_FCS_DROP = 0;
*(unsigned int *)RX_DGRAM = 0;
*(unsigned int *)OS_TOUCH = 0;
/* Plan 2's Task 3: the dictionary record starts empty and is published
* as zero before the pool is known, so a reader never sees an earlier
* run's readiness on a pool that has no dictionary behind it. */
*(unsigned int *)DICT_READY = 0;
*(unsigned int *)DICT_BYTES = 0;
*(unsigned int *)DICT_BAD = 0;
*(unsigned int *)TILES_DROP = 0;
dict_ready = 0;
dict_bytes = 0;
dict_bad = 0;
tiles_drop = 0;
status_now = 0;
decode_mark = 0;
decode_ticks = 0;
decode_worst = 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. */
slot_base[0] = ptr;
slot_base[1] = ptr + SLOT_BYTES * 8;
*(unsigned int *)RX_SLOT_A = slot_base[0];
*(unsigned int *)RX_SLOT_B = slot_base[1];
/* Plan 2's Task 3: the dictionary follows the slot pair. It is derived
* from the allocation, never published separately -- POOL_PTR is the
* record, and the pool layout above fixes the offset. build-stream.sh
* asserts at build time that the request can hold it (dict_slack_check
* above); this is the run-time half, for a payload built another way: a
* pool too small for the dictionary leaves dict_base 0, every chunk is
* refused and nothing is written past the allocation. */
if (POOL_BYTES >= DICT_OFF + DICT_SIZE) {
dict_base = ptr + DICT_OFF * 8;
} else {
dict_base = 0;
}
*(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 receive and 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;
rx_pos = 0;
rx_acc = 0;
rx_nfrag = 0;
rx_nhold = 0;
rx_frame_slot = 0;
vid_dint_();
lance_ok = lance_start(ptr);
#if VID_OWN_CPU
vid_dint_(); /* once, for good: nothing may preempt this loop */
#else
vid_eint_();
#endif
*(unsigned int *)ST_BUF = status_buf;
for (;;) {
*(unsigned int *)VID_ALIVE = *(unsigned int *)VID_ALIVE + 1;
rx_poll();
/* 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).
*
* Two out-of-cadence reports exist so the host is never left
* with silence:
* - the first accepted frame, so a host that is sending
* sees that the payload is running before 64 frames have
* arrived (and gets the dictionary state with it);
* - status_now, set by dict_refuse(). A lost dictionary
* chunk makes every later chunk fail, no frame is ever
* accepted, and the 64-frame rule would never fire: the
* host would report "PAYLOAD NOT RUNNING" instead of
* "dictionary incomplete". These are one-shot in the
* sense that status_seen advances; further refusals set
* the flag again, which is wanted while an upload is
* broken.
*/
frames = *(unsigned int *)RX_OK;
if (!lance_ok) {
continue;
}
if (frames - status_seen >= STATUS_EVERY
|| status_now
|| (frames != 0 && status_seen == 0)
|| (*(unsigned int *)RX_EOF != 0 && frames != status_seen)) {
vid_dint_();
sent = send_status(frames);
vid_eint_();
if (sent) {
status_seen = frames;
status_now = 0;
/* "since the last report": reset only after the
* frame really went to the chip. */
decode_worst = 0;
}
}
}
}