mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-11-01 12:14:56 +02:00
The harness forced the VR150 monitor switch, so the firmware wrote the OS screen at 1024 px / 128-byte rows. VT1200_VRAM_REAL then re-read that framebuffer at the VR319 pitch, which sheared the OS and wrapped the right quarter of every row. Model VR319 (switch 0x60) by default -- the real unit's mode -- so the firmware itself programs 1280x1024 with a 256-byte row pitch (CONVDP=0x14). The renderer's pitch follows VT1200_MONITOR, and the destination span now comes from HEBLNK..HSBLNK instead of x=0, placing the active 1280 px at x=336 in MAME's 1696-wide raster. Verified: clean Terminal Manager at frame 600, legible faithful diagnostics menu and level-1 error screens, tile mode PASS with tile_check 1280/1280 tiles, tablet/mouse pixel-identical. VT1200_MONITOR=VR150 reproduces the old geometry; the regression baseline moves 198 -> 199 (2 PCs) and is recorded in harness/mame/README.md.
1828 lines
72 KiB
C
1828 lines
72 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 */
|
|
|
|
/*
|
|
* 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);
|
|
vid_eint_();
|
|
*(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;
|
|
}
|
|
}
|
|
}
|
|
}
|