forked from arti/vt1200
feat(stream): real rx ring over chained descriptors; unit measurement script
This commit is contained in:
1 parent
7eb7b61234
commit
49a943583c
6 files changed
+1265
-370
No files matched your search
@@ -6,24 +6,32 @@
|
||||
# VT1200_MODE=rx harness/mame/run-vidstream.sh # alloc + the receive path
|
||||
# VT1200_MODE=draw harness/mame/run-vidstream.sh # alloc + rx + the blit
|
||||
# VT1200_MODE=stream harness/mame/run-vidstream.sh # the status back-channel
|
||||
# VT1200_MODE=ring harness/mame/run-vidstream.sh # the RX ring's structure
|
||||
# STREAM_POOL_BYTES=0x7FFF000 harness/mame/run-vidstream.sh # force fallback
|
||||
#
|
||||
# Mode rx injects one valid frame and one corrupted frame with the debugger and
|
||||
# asserts RX_OK == 1 && RX_BAD == 1 (see vidstream.lua and the task-4 report).
|
||||
# Mode rx delivers one valid frame and one corrupted frame through the payload's
|
||||
# own LANCE RX ring and asserts RX_OK == 1 && RX_BAD == 1 (see vidstream.lua and
|
||||
# the task-7 report).
|
||||
#
|
||||
# Mode draw injects one 128-row strip frame (stretch 8 = 1024 screen rows), lets
|
||||
# the payload blit it, and dumps the screen and the delivered frame; this script
|
||||
# then runs harness/mame/vidstream_check.py, which compares every screen row
|
||||
# against its source row. The pixel verdict is the checker's, not the Lua's.
|
||||
# Mode draw delivers one 128-row strip frame (stretch 8 = 1024 screen rows),
|
||||
# lets the payload blit it, and dumps the screen and the delivered frame; this
|
||||
# script then runs harness/mame/vidstream_check.py, which compares every screen
|
||||
# row against its source row. The pixel verdict is the checker's, not the Lua's.
|
||||
#
|
||||
# Mode stream plays the sender: 200 strip frames, then RX_EOF so the terminal
|
||||
# reports the final count. The payload takes the LANCE over for transmit and
|
||||
# builds a KIND_STATUS frame every 64 accepted frames; the Lua reads those
|
||||
# frames back from the payload's own transmit buffer and this script runs
|
||||
# harness/mame/vidstream_status_check.py, which decodes them with the host
|
||||
# sender's wire format. There is no network in this run: the emulated LANCE has
|
||||
# no interface (ruling R11), so the evidence is the buffer's bytes plus the
|
||||
# completed descriptor, and the wire path belongs to Task 7.
|
||||
# reports the final count. The payload takes the LANCE over for receive and
|
||||
# transmit and builds a KIND_STATUS frame every 64 accepted frames; the Lua
|
||||
# reads those frames back from the payload's own transmit buffer and this
|
||||
# script runs harness/mame/vidstream_status_check.py, which decodes them with
|
||||
# the host sender's wire format. There is no network in this run: the emulated
|
||||
# LANCE has no interface (ruling R11), so the evidence is the buffer's bytes
|
||||
# plus the completed descriptor, and the wire path needs the unit.
|
||||
#
|
||||
# Mode ring asserts the transport's structure: 16 descriptors armed with OWN set
|
||||
# for slot A, a completed frame moving them to slot B, and a header claiming
|
||||
# more than SLOT_BYTES rejected without a blit (RX_BAD moves, RX_OK does not,
|
||||
# the screen does not change). The verdict is in the Lua; the evidence is
|
||||
# $LOGDIR/ring-status.txt.
|
||||
#
|
||||
# The run uses VT1200_SKIP_LEVEL1=1: the payload's heap is only initialised in
|
||||
# the OS phase. In the diagnostics menu the free counter (0x1E8BA0) and the
|
||||
@@ -78,6 +86,9 @@ fi
|
||||
if [ "$MODE" = "stream" ]; then
|
||||
rm -f "$LOGDIR/stream-status.bin" "$LOGDIR/stream-status.txt"
|
||||
fi
|
||||
if [ "$MODE" = "ring" ]; then
|
||||
rm -f "$LOGDIR/ring-status.txt"
|
||||
fi
|
||||
# Absolute: the Lua runs with MAME's cwd, so a relative default only resolves
|
||||
# when the caller happens to be at the repo root.
|
||||
VID_PAYLOAD="$ROOT/harness/udpstream/build/vidstream.bin"
|
||||
|
||||
+628
-191
File diff suppressed because it is too large.
Load diff
@@ -106,8 +106,8 @@ def main():
|
||||
# --- a timed-out wait is not a pixel verdict (ruling R15) -------------
|
||||
if status.get("TIMEOUT", 0) != 0:
|
||||
print("FAIL: timed out waiting for the frame (RX_OK=%r, "
|
||||
"RX_READY_A=%r: the payload did not finish with the slot)"
|
||||
% (status.get("RX_OK"), status.get("RX_READY_A")))
|
||||
"RX_DGRAM=%r: the payload did not finish with the frame)"
|
||||
% (status.get("RX_OK"), status.get("RX_DGRAM")))
|
||||
print("vidstream_check: FAIL (timeout)")
|
||||
return 1
|
||||
|
||||
|
||||
@@ -38,6 +38,13 @@
|
||||
*
|
||||
* cell page offset bit address meaning
|
||||
* ------------ ----------- ----------- --------------------------------
|
||||
* RX_RING_BASE +0x080 0x01810080 bit address of the RX ring
|
||||
* RX_RING_POS +0x0A0 0x018100A0 next descriptor to consume (the
|
||||
* chip's ring position)
|
||||
* RX_RING_ACC +0x0C0 0x018100C0 bytes of the frame arriving
|
||||
* RX_RING_ARMED +0x0E0 0x018100E0 slot the ring is armed on (0=A)
|
||||
* RX_RING_DROPS +0x200 0x01810200 frames too big for the ring
|
||||
* RX_DGRAM +0x240 0x01810240 datagrams consumed
|
||||
* POOL_PTR +0x00 0x01810100 bit pointer to the 244 KB pool
|
||||
* POOL_FREEB +0x20 0x01810120 heap free bytes before malloc
|
||||
* POOL_FREEA +0x40 0x01810140 heap free bytes after malloc
|
||||
@@ -54,20 +61,26 @@
|
||||
* is at cell 0x44 -- free between the RX_* counters and the injection cells
|
||||
* below (ruling R12).
|
||||
*
|
||||
* Debugger injection interface (Task 4; the harness plays the receive path):
|
||||
* Debugger injection interface (Task 4; retired by Task 7's ring):
|
||||
*
|
||||
* RX_SLOT_A +0x200 0x01810300 bit address of slot A
|
||||
* RX_SLOT_B +0x220 0x01810320 bit address of slot B
|
||||
* RX_READY_A +0x240 0x01810340 1 = slot A holds RX_LEN_A bytes
|
||||
* RX_LEN_A +0x260 0x01810360 bytes in slot A
|
||||
* RX_READY_B +0x280 0x01810380 1 = slot B holds RX_LEN_B bytes
|
||||
* RX_LEN_B +0x2A0 0x018103A0 bytes in slot B
|
||||
*
|
||||
* The RX_READY_x and RX_LEN_x cells are NOT part of the wire protocol. In the real
|
||||
* transport the LANCE receive interrupt fills a slot and raises that slot's
|
||||
* ready flag; MAME cannot hand this emulated LANCE an Ethernet frame (the
|
||||
* driver attaches no network interface -- ruling R11), so the debugger writes
|
||||
* the slot and raises the flag instead. The payload clears it.
|
||||
* Task 4 received frames through a "slot ready" flag (RX_READY_A/B plus a byte
|
||||
* count) that the debugger raised after poking a frame into a slot. Task 7
|
||||
* removed those cells: the payload now owns a real LANCE RX ring and the
|
||||
* handshake is the descriptor's OWN bit, per descriptor, with the chip clearing
|
||||
* it and the payload setting it again only after reading the buffer. There is
|
||||
* no level-triggered flag for two writers to race on, which was Task 4's
|
||||
* Important finding (a).
|
||||
*
|
||||
* The harness therefore plays the CHIP: it writes a datagram into the buffer of
|
||||
* the descriptor at RX_RING_POS (and the ones after it, for a multi-datagram
|
||||
* frame) and completes it the way am79c90.cpp does -- RMD3 = delivered length
|
||||
* including the FCS, RMD1 = STP | ENP with OWN clear. RX_ARMED says which slot
|
||||
* those descriptors point into. The two slot addresses above remain published
|
||||
* because they are the payload's own record of where its buffers are.
|
||||
* ---------------------------------------------------------------------------
|
||||
*
|
||||
* Task 6 adds one more cell of the same kind and the status record the harness
|
||||
* reads back:
|
||||
@@ -82,12 +95,12 @@
|
||||
* ST_TMLAST +0x480 0x01810480 bit address of the last armed
|
||||
* TX descriptor's TMD1 word
|
||||
*
|
||||
* RX_EOF is harness-only in exactly the same sense as RX_READY_x: it says "the
|
||||
* sender has stopped announcing frames", which on a real wire is silence and
|
||||
* carries no signal. It exists so a finite run can ask for a final status
|
||||
* instead of waiting for the count to reach the next multiple of 64. The
|
||||
* payload reports as soon as it sees it, and keeps reporting for each further
|
||||
* accepted frame; nothing reads it in a continuous stream.
|
||||
* RX_EOF is harness-only in exactly the same sense as the retired injection
|
||||
* flag was: it says "the sender has stopped announcing frames", which on a real
|
||||
* wire is silence and carries no signal. It exists so a finite run can ask for
|
||||
* a final status instead of waiting for the count to reach the next multiple of
|
||||
* 64. The payload reports as soon as it sees it, and keeps reporting for each
|
||||
* further accepted frame; nothing reads it in a continuous stream.
|
||||
* ---------------------------------------------------------------------------
|
||||
*
|
||||
* K&R C only. The 1987 toolchain in build-stream.sh compiles this header, so
|
||||
@@ -130,12 +143,24 @@
|
||||
|
||||
#define OS_TOUCH MARK_CELL(0x44)
|
||||
|
||||
/*
|
||||
* Task 7: the RX ring record. Task 4's injection cells (0x68..0x74) are gone;
|
||||
* these take the free page prefix R12 left for the RX work, at the SAME 4-byte
|
||||
* spacing every other cell uses (n increases by 4 per cell -- 0x10, 0x14, 0x18
|
||||
* and 0x1C are four cells, and a 32-bit store at 0x01810080 does not reach
|
||||
* 0x01810088). Reading "n = 0x10..0x15" as five adjacent cells is the mistake
|
||||
* this comment exists to prevent: those four cells would be ONE BYTE apart and
|
||||
* the 32-bit stores would overlap into zeros.
|
||||
*/
|
||||
#define RX_RING_BASE MARK_CELL(0x10)
|
||||
#define RX_RING_POS MARK_CELL(0x14)
|
||||
#define RX_RING_ACC MARK_CELL(0x18)
|
||||
#define RX_RING_ARMED MARK_CELL(0x1c)
|
||||
#define RX_RING_DROPS MARK_CELL(0x40)
|
||||
#define RX_DGRAM MARK_CELL(0x48)
|
||||
|
||||
#define RX_SLOT_A MARK_CELL(0x60)
|
||||
#define RX_SLOT_B MARK_CELL(0x64)
|
||||
#define RX_READY_A MARK_CELL(0x68)
|
||||
#define RX_LEN_A MARK_CELL(0x6c)
|
||||
#define RX_READY_B MARK_CELL(0x70)
|
||||
#define RX_LEN_B MARK_CELL(0x74)
|
||||
|
||||
/* Task 6: the sender/keyframe control cell and the status record. */
|
||||
#define RX_EOF MARK_CELL(0x78)
|
||||
|
||||
Executable
+158
@@ -0,0 +1,158 @@
|
||||
#!/bin/sh
|
||||
# test-stream.sh -- the unit measurement: drive the stream into the terminal and
|
||||
# report what it received, one CSV line per 30-frame block.
|
||||
#
|
||||
# sudo ./test-stream.sh <iface> <mac> [rows] [stretch]
|
||||
#
|
||||
# sudo ./test-stream.sh enp197s0f3u1 08:00:2b:16:4b:5a 128 8
|
||||
# sudo ./test-stream.sh enp197s0f3u1 ff:ff:ff:ff:ff:ff 128 8
|
||||
#
|
||||
# <iface> is the host NIC the terminal is plugged into (a dedicated port or a
|
||||
# hub -- the terminal answers with broadcast status frames on the same wire),
|
||||
# <mac> the destination address frames are sent to: the terminal's own
|
||||
# 08:00:2b:16:4b:5a, or broadcast, which its LANCE accepts with an empty filter.
|
||||
# [rows] and [stretch] are the strip geometry (default 128 x 8 = the whole
|
||||
# screen, ruling R3); rows*stretch must be 1024 or the encoder refuses.
|
||||
#
|
||||
# WHAT IT RUNS. harness/udpstream/vidsend.py paced at 30 fps for 300 frames,
|
||||
# with the terminal's status frames read back on the same interface. vidsend
|
||||
# prints its own CSV (frame,elapsed_s,fps,bytes) every 30 frames and its status
|
||||
# reports as prose; both are line-flushed, so this script captures them in one
|
||||
# stream and merges them in arrival order into:
|
||||
#
|
||||
# frame,elapsed_s,fps,bytes,frames_rx,delivery_pct,drops,csr0
|
||||
#
|
||||
# frames_rx/drops/csr0 are the LAST status the terminal had sent at that point
|
||||
# (a status only arrives every 64 accepted frames, so the first two blocks
|
||||
# repeat the same report). delivery_pct = 100 * frames_rx / frame, i.e. what
|
||||
# the terminal says it has finished against what the host has sent -- it sits
|
||||
# just under 100 % while a frame is in flight, and a real loss shows as a step.
|
||||
#
|
||||
# Exit status: vidsend.py's (0 only if a status frame arrived and the last one
|
||||
# reports drops == 0 and crc_err == 0), or 2/3 for a missing prerequisite.
|
||||
#
|
||||
# WHAT THIS CANNOT SHOW FROM HERE: OS_TOUCH (spec 8.9's VRAM canary, marker bit
|
||||
# 0x01810220) and the terminal's free heap before the run are on the terminal,
|
||||
# not in the status frame. Read them on the terminal with its own debugger
|
||||
# alongside a run and record them with this CSV; an OS repaint during the run
|
||||
# also shows up here as a fall in delivery_pct.
|
||||
#
|
||||
# The three unit runs (plan Task 7 step 6):
|
||||
#
|
||||
# 1. sudo ./test-stream.sh <iface> 08:00:2b:16:4b:5a 128 8
|
||||
# 2. the same with the blit forced to PIXBLT L,XY instead of B,XY
|
||||
# (harness/udpstream/vram.asm's vram_blit_ vs vram_b.asm's vram_blit_b_:
|
||||
# swap the entry point in vidstream.c's blit_row and rebuild), to measure
|
||||
# what the unit costs per destination word
|
||||
# 3. a 6,300-frame soak (~3.5 min at 30 fps): FRAMES=6300
|
||||
#
|
||||
# These need the physical terminal (ruling R4, R16) and have NOT been run.
|
||||
set -eu
|
||||
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
SENDER="$HERE/vidsend.py"
|
||||
|
||||
IFACE=${1:-}
|
||||
MAC=${2:-}
|
||||
ROWS=${3:-128}
|
||||
STRETCH=${4:-8}
|
||||
FRAMES=${FRAMES:-300}
|
||||
FPS=${FPS:-30}
|
||||
REPORT=${REPORT:-30}
|
||||
|
||||
if [ -z "$IFACE" ] || [ -z "$MAC" ]; then
|
||||
echo "usage: sudo $0 <iface> <mac> [rows] [stretch]" >&2
|
||||
echo " e.g. sudo $0 enp197s0f3u1 08:00:2b:16:4b:5a 128 8" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Fail loudly rather than send nothing and call it a run.
|
||||
if [ ! -f "$SENDER" ]; then
|
||||
echo "test-stream.sh: no sender at $SENDER (this script drives vidsend.py)" >&2
|
||||
exit 3
|
||||
fi
|
||||
if [ ! -d "/sys/class/net/$IFACE" ]; then
|
||||
echo "test-stream.sh: no interface '$IFACE' on this host (have: $(ls /sys/class/net | tr '\n' ' '))" >&2
|
||||
exit 3
|
||||
fi
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "test-stream.sh: raw Ethernet needs CAP_NET_RAW; re-run with sudo" >&2
|
||||
exit 3
|
||||
fi
|
||||
case "$MAC" in
|
||||
*:*:*:*:*:*) ;;
|
||||
*) echo "test-stream.sh: '$MAC' is not a MAC address (aa:bb:cc:dd:ee:ff)" >&2
|
||||
exit 2 ;;
|
||||
esac
|
||||
|
||||
OUT=${OUT:-/tmp/opencode/test-stream.log}
|
||||
mkdir -p "$(dirname "$OUT")"
|
||||
echo "test-stream.sh: $IFACE -> $MAC, $ROWS rows x $STRETCH, $FRAMES frames at $FPS fps"
|
||||
echo "test-stream.sh: capture $OUT"
|
||||
|
||||
set +e
|
||||
python3 "$SENDER" --iface "$IFACE" --dst "$MAC" --rows "$ROWS" \
|
||||
--stretch "$STRETCH" --fps "$FPS" --frames "$FRAMES" \
|
||||
--report-every "$REPORT" > "$OUT" 2>&1
|
||||
STATUS=$?
|
||||
set -e
|
||||
if [ "$STATUS" -ne 0 ] && [ ! -s "$OUT" ]; then
|
||||
echo "test-stream.sh: vidsend.py failed with status $STATUS and wrote nothing" >&2
|
||||
exit "$STATUS"
|
||||
fi
|
||||
|
||||
# Merge the sender's CSV rows with the terminal's status reports, in the order
|
||||
# they arrived. Both are printed line by line with a flush, so the order in
|
||||
# this one file IS the chronological order; no timestamps are needed.
|
||||
set +e
|
||||
python3 - "$OUT" <<'PY'
|
||||
import re, sys
|
||||
|
||||
path = sys.argv[1]
|
||||
row = re.compile(r"^(\d+),([\d.]+),([\d.]+),(\d+)$")
|
||||
status = re.compile(
|
||||
r"^status (\d+): frame_no=(\d+) frames_rx=(\d+) crc_err=(\d+) "
|
||||
r"drops=(\d+) csr0=0x([0-9A-Fa-f]+) heap_free_kb=(\d+)$")
|
||||
|
||||
last = None # the most recent status, as a dict
|
||||
reports = 0
|
||||
print("frame,elapsed_s,fps,bytes,frames_rx,delivery_pct,drops,csr0")
|
||||
bad = 0
|
||||
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
||||
for line in f:
|
||||
line = line.rstrip("\n")
|
||||
m = status.match(line)
|
||||
if m:
|
||||
reports += 1
|
||||
last = dict(frames_rx=int(m.group(3)), crc_err=int(m.group(4)),
|
||||
drops=int(m.group(5)), csr0=int(m.group(6), 16))
|
||||
continue
|
||||
m = row.match(line)
|
||||
if not m:
|
||||
continue
|
||||
frame = int(m.group(1))
|
||||
if last is None:
|
||||
print("%d,%s,%s,%s,,,," % (frame, m.group(2), m.group(3),
|
||||
m.group(4)))
|
||||
continue
|
||||
pct = 100.0 * last["frames_rx"] / frame if frame else 0.0
|
||||
print("%d,%s,%s,%s,%d,%.1f,%d,0x%04X"
|
||||
% (frame, m.group(2), m.group(3), m.group(4),
|
||||
last["frames_rx"], pct, last["drops"], last["csr0"]))
|
||||
if reports == 0:
|
||||
print("test-stream.sh: no status frame arrived, so delivery and drops are "
|
||||
"unknown (the terminal said nothing)", file=sys.stderr)
|
||||
bad = 1
|
||||
sys.exit(bad)
|
||||
PY
|
||||
MERGE=$?
|
||||
set -e
|
||||
|
||||
echo "test-stream.sh: vidsend.py status $STATUS, merge status $MERGE"
|
||||
echo "test-stream.sh: OS_TOUCH and the terminal's heap are NOT in this table:" \
|
||||
"read marker bit 0x01810220 and POOL_FREE* on the terminal and record them" \
|
||||
"with this CSV."
|
||||
if [ "$STATUS" -ne 0 ] || [ "$MERGE" -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
+408
-144
@@ -2,32 +2,50 @@
|
||||
* vidstream.c -- terminal side of the VT1200 video stream.
|
||||
*
|
||||
* Task 3 claimed the working set with ONE malloc at startup and published
|
||||
* where it landed. Task 4 added the receive side: two 24 KB frame slots at the
|
||||
* head of that pool, and a poll loop that hands each completed slot to
|
||||
* fp_parse() (frame_parse.c, shared with the host unit test) and counts what
|
||||
* 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).
|
||||
* 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.
|
||||
*
|
||||
* Pool layout inside the single allocation (POOL_BYTES = 249856 = 244 KB):
|
||||
*
|
||||
* +0x00000 frame slot A 24576 bytes
|
||||
* +0x06000 frame slot B 24576 bytes
|
||||
* +0x00000 frame slot A 24576 bytes (16 x 1526 B RX buffers)
|
||||
* +0x06000 frame slot B 24576 bytes (16 x 1526 B RX buffers)
|
||||
* +0x0C000 framebuffer 163840 bytes (scheme A, Task 5)
|
||||
* +0x34000 dictionary 32768 bytes
|
||||
* +0x3C000 LANCE block 4096 bytes (Task 6: init block, TX ring,
|
||||
* status frame)
|
||||
* +0x3C000 LANCE block 4096 bytes (init block, TX ring, status
|
||||
* frame, RX ring)
|
||||
*
|
||||
* The LANCE block is what was slack. Its ring base is aligned at run time
|
||||
* The LANCE block is what was slack. 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 descriptors.
|
||||
* frame sits right after the 8 TX descriptors, the RX ring after that.
|
||||
*
|
||||
* A slot holds one complete frame -- 16-byte header and payload -- exactly as
|
||||
* it arrives on the wire, so the bytes fp_parse() validates are the bytes the
|
||||
* blit reads. Neither slot is ever split across a ring wrap: the two ring
|
||||
* entries' descriptors are re-armed per slot at frame start.
|
||||
* A slot is 16 packet buffers of RXBUFLEN = 1526 bytes (16 x 1526 = 24416, so
|
||||
* it fits 24576 with 160 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). MAME's own
|
||||
@@ -53,8 +71,10 @@
|
||||
* marker named 0x018100NN -> bit 0x01810000 + NN * 8
|
||||
*
|
||||
* (POOL_PTR = 0x01810100, ...). Task 5's OS_TOUCH is 0x01810220 (ruling R12),
|
||||
* i.e. cell 0x44 -- free between the RX_* counters and the injection cells.
|
||||
* vidstream.lua uses the same rule.
|
||||
* 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.
|
||||
*
|
||||
* K&R C only: no prototypes, no '//', declarations before statements.
|
||||
*/
|
||||
@@ -95,35 +115,30 @@
|
||||
/*
|
||||
* Task 6: the terminal's status frame.
|
||||
*
|
||||
* The payload takes the LANCE over -- STOP, its own init block and TX ring,
|
||||
* INIT, IDON, STRT -- and every 64 accepted frames builds a 60-byte Ethernet
|
||||
* frame in its own buffer and hands it to the chip with a TX descriptor. The
|
||||
* descriptor setup is lance.c's (harness/udpstream/lance.c): the ring words are
|
||||
* TMD0/TMD1/TMD2/TMD3 at byte offsets 0/2/4/6, the buffer address's high byte
|
||||
* lives in TMD1, and the length is (0x10000 - bytes) in TMD2. This is NOT a
|
||||
* second driver: it is the same init/descriptor sequence, with one buffer
|
||||
* instead of a loopback probe's.
|
||||
* 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) and
|
||||
* the 60-byte status frame, all aligned at run time in lance_start(). All of
|
||||
* it is memory this payload owns, which is the point: the firmware's own rings
|
||||
* are in low memory with 32 descriptors (see lance.c's note on the OS init
|
||||
* block), and arming a slot in a ring whose position the chip owns is not
|
||||
* something this payload can do reliably.
|
||||
* 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.
|
||||
*
|
||||
* The receiver is DISABLED (MODE_DRX) on purpose, and that is a gap rather
|
||||
* than a design: the receive path in this task is the harness injection
|
||||
* interface (RX_READY_A/B), the real transport chunks a frame into 15
|
||||
* datagrams, and a ring of one-descriptor-per-datagram that reassembles them
|
||||
* does not exist yet (ruling R11 covers why MAME cannot exercise it either).
|
||||
* Arming an RX ring we have no buffers for would let the chip DMA a datagram
|
||||
* into address 0, so disabling the receiver is the honest state.
|
||||
*
|
||||
* So `drops` in the status body is wired to the chip's own counter -- CSR0's
|
||||
* MISS bit, one per missed-packet event, cleared after each report -- and with
|
||||
* the receiver disabled it can only be 0 here. What it must not be is
|
||||
* invented: the field is the chip's, so an RX ring inherits it unchanged.
|
||||
* Task 7 arms the receiver (MODE = 0, so CSR0's STRT sets RXON) against those
|
||||
* 16 descriptors. `drops` in the status body stays the chip's own counter --
|
||||
* CSR0's MISS bit, one per missed-packet event, cleared after each report --
|
||||
* plus the payload's own count of frames the ring was too small to finish
|
||||
* (RX_RING_DROPS). Both mean "a frame that arrived and did not reach the
|
||||
* screen"; the chip's field is not replaced, and the payload's own count is
|
||||
* 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 */
|
||||
@@ -135,12 +150,27 @@
|
||||
#define CSR0_IDON 0x0100
|
||||
#define CSR0_MISS 0x1000 /* no RX descriptor: a missed-packet event */
|
||||
|
||||
#define MODE_DRX 0x0001 /* disable the receiver (see above) */
|
||||
|
||||
#define TMD1_OWN 0x8000
|
||||
#define TMD1_ENP 0x0100
|
||||
#define TMD1_STP 0x0200
|
||||
|
||||
/*
|
||||
* 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 = 24416 bytes, which fits a 24576-byte slot.
|
||||
*/
|
||||
#define RX_DESC 16
|
||||
#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 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 */
|
||||
@@ -191,12 +221,47 @@ static unsigned int heap_bytes()
|
||||
*/
|
||||
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 events, cumulative */
|
||||
|
||||
/*
|
||||
* Task 7 receive state.
|
||||
*
|
||||
* rx_armed the slot the ring's 16 descriptors point into (0 = A, 1 = B).
|
||||
* Only ever written together with a full re-arm, so a descriptor
|
||||
* never points at a slot the payload is not expecting.
|
||||
* 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.
|
||||
*/
|
||||
static unsigned int rx_armed;
|
||||
static unsigned int rx_pos;
|
||||
static unsigned int rx_acc;
|
||||
static unsigned int rx_nfrag;
|
||||
static unsigned int rx_frag_off[RX_DESC];
|
||||
static unsigned int rx_frag_ptr[RX_DESC];
|
||||
static unsigned int rx_frag_len[RX_DESC];
|
||||
|
||||
/*
|
||||
* blit_row(src, daddr_xy, dydx)
|
||||
*
|
||||
@@ -220,23 +285,29 @@ unsigned int dydx;
|
||||
}
|
||||
|
||||
/*
|
||||
* paint_frame(slot, h)
|
||||
* paint(h) -- draw one accepted strip frame from the fragment table.
|
||||
*
|
||||
* Draw one accepted strip frame: source row r goes down screen rows
|
||||
* r*stretch .. r*stretch+stretch-1. A frame the screen cannot hold is not
|
||||
* drawn: zero rows or zero stretch, more than SCREEN_ROWS screen rows, or a
|
||||
* payload shorter than the rows it claims (fp_parse() has already proved that
|
||||
* payload_len bytes arrived, so that bound is real). A refused frame never
|
||||
* touches VRAM, so a malformed header cannot paint garbage over a good one.
|
||||
* 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_frame(slot, h)
|
||||
unsigned int slot;
|
||||
static void paint(h)
|
||||
fp_header *h;
|
||||
{
|
||||
unsigned int r, rows, stretch, src, sent;
|
||||
unsigned int r, rows, stretch, sent, pos, done, k, f, room, n;
|
||||
|
||||
rows = h->rows;
|
||||
stretch = h->stretch;
|
||||
@@ -249,6 +320,9 @@ fp_header *h;
|
||||
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
|
||||
@@ -261,11 +335,35 @@ fp_header *h;
|
||||
}
|
||||
|
||||
vid_dint_();
|
||||
src = slot + h->payload_off * 8;
|
||||
pos = h->payload_off; /* the frame's first body byte */
|
||||
k = 0;
|
||||
for (r = 0; r < rows; r++) {
|
||||
blit_row(src + r * ROW_PIXELS,
|
||||
(r * stretch) << 16,
|
||||
(stretch << 16) | ROW_PIXELS);
|
||||
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_();
|
||||
}
|
||||
@@ -329,8 +427,202 @@ unsigned int reg;
|
||||
return v;
|
||||
}
|
||||
|
||||
/* The two slots' bit addresses, published by main() and never changed. */
|
||||
static unsigned int slot_base[2];
|
||||
|
||||
/*
|
||||
* lance_start(ptr) -- take the LANCE over for transmit. 1 on success.
|
||||
* 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 RMD0 is
|
||||
* written too -- and before RMD1, because a descriptor with OWN set and a stale
|
||||
* RMD0 would DMA the next packet into the other slot, which is the one being
|
||||
* painted.
|
||||
*
|
||||
* 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(slot)
|
||||
unsigned int slot;
|
||||
{
|
||||
unsigned int i, cb, base;
|
||||
|
||||
base = slot_base[slot];
|
||||
for (i = 0; i < RX_DESC; i++) {
|
||||
cb = (base >> 3) + i * RXBUFLEN;
|
||||
w16(lance_rxring + i * 64 + 0, cb & 0xFFFF);
|
||||
w16(lance_rxring + i * 64 + 48, 0);
|
||||
w16(lance_rxring + i * 64 + 32, (0x10000 - RXBUFLEN) & 0xFFFF);
|
||||
w16(lance_rxring + i * 64 + 16,
|
||||
RMD1_OWN | ((cb >> 16) & 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* rx_switch() -- arm the other slot. Called at the end of a frame, before the
|
||||
* picture is drawn, so the chip can fill N+1 while we paint N and can never
|
||||
* write into the slot being read (spec 8.1's overlap).
|
||||
*/
|
||||
static void rx_switch()
|
||||
{
|
||||
rx_armed = 1 - rx_armed;
|
||||
rx_arm(rx_armed);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
fp_header h;
|
||||
|
||||
rmd1 = r16(lance_rxring + i * 64 + 16);
|
||||
mlen = r16(lance_rxring + i * 64 + 48) & 0x0FFF;
|
||||
src = slot_base[rx_armed] + i * RXBUFLEN * 8 + ETH_HDR_LEN * 8;
|
||||
len = 0;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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_switch();
|
||||
return;
|
||||
}
|
||||
if (rx_acc < total) {
|
||||
return; /* still arriving */
|
||||
}
|
||||
|
||||
/* The frame is complete. Hand the OTHER slot to the chip first, so
|
||||
* nothing it receives from now on lands in the picture below. */
|
||||
slot = rx_armed;
|
||||
rx_switch();
|
||||
|
||||
/*
|
||||
* 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)) {
|
||||
*(unsigned int *)RX_LAST_LEN = h.payload_len;
|
||||
*(unsigned int *)RX_SLOT = slot;
|
||||
paint(&h);
|
||||
/* Published after the paint: a reader that sees RX_OK move
|
||||
* sees a picture that is already on the screen. */
|
||||
*(unsigned int *)RX_OK = *(unsigned int *)RX_OK + 1;
|
||||
} else {
|
||||
*(unsigned int *)RX_BAD = *(unsigned int *)RX_BAD + 1;
|
||||
}
|
||||
rx_acc = 0;
|
||||
rx_nfrag = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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_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_switch();
|
||||
}
|
||||
}
|
||||
*(unsigned int *)RX_RING_POS = rx_pos;
|
||||
*(unsigned int *)RX_RING_ACC = rx_acc;
|
||||
*(unsigned int *)RX_RING_ARMED = rx_armed;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
@@ -344,11 +636,18 @@ unsigned int reg;
|
||||
* 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, cb, i, idon;
|
||||
unsigned int init, ring, buf, rxring, cb, i, idon;
|
||||
|
||||
/*
|
||||
* Alignment is the CHIP's requirement, not a convenience. The Am7990
|
||||
@@ -365,12 +664,14 @@ unsigned int ptr;
|
||||
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, MODE_DRX); /* no loopback, FCS appended */
|
||||
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);
|
||||
@@ -378,13 +679,14 @@ unsigned int ptr;
|
||||
w16(init + 5 * 16, 0);
|
||||
w16(init + 6 * 16, 0);
|
||||
w16(init + 7 * 16, 0);
|
||||
w16(init + 8 * 16, 0); /* RX ring: none (MODE_DRX) */
|
||||
w16(init + 9 * 16, 0);
|
||||
cb = rxring >> 3; /* BYTE address of the RX ring */
|
||||
w16(init + 8 * 16, cb & 0xFFFF);
|
||||
w16(init + 9 * 16, ((cb >> 16) & 0xFF) | (4 << 13)); /* 16 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 descriptor points at the one status buffer; TMD1's OWN stays
|
||||
/* 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;
|
||||
@@ -398,6 +700,12 @@ unsigned int ptr;
|
||||
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_armed = 0;
|
||||
rx_arm(rx_armed);
|
||||
*(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);
|
||||
@@ -415,8 +723,9 @@ unsigned int ptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* STRT turns the transmitter on; no INEA, so no interrupt reaches the
|
||||
* firmware's network ISR while this payload owns the chip. */
|
||||
/* 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++) {
|
||||
}
|
||||
@@ -440,7 +749,10 @@ unsigned int ptr;
|
||||
* 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.
|
||||
* stored -- so this is the only CRC error the terminal can count. drops is
|
||||
* the chip's CSR0 MISS events plus RX_RING_DROPS (frames too large for the 16
|
||||
* descriptors, Task 7); both are frames that arrived and did not reach the
|
||||
* screen, and neither number is inferred from the other.
|
||||
*
|
||||
* 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
|
||||
@@ -451,7 +763,7 @@ unsigned int ptr;
|
||||
static int send_status(frames_rx)
|
||||
unsigned int frames_rx;
|
||||
{
|
||||
unsigned int i, crc, csr0, tmd1, off, hb, miss, bad, free_kb;
|
||||
unsigned int i, crc, csr0, tmd1, off, hb, miss, bad, rdrops, free_kb;
|
||||
|
||||
hb = status_buf;
|
||||
|
||||
@@ -494,6 +806,10 @@ unsigned int frames_rx;
|
||||
}
|
||||
|
||||
bad = *(unsigned int *)RX_BAD;
|
||||
/* Both drop counts are frames that arrived and did not reach the screen:
|
||||
* the chip's missed-packet events (no descriptor free) and the payload's
|
||||
* own count of frames too large for the ring. */
|
||||
rdrops = *(unsigned int *)RX_RING_DROPS;
|
||||
free_kb = heap_bytes() >> 10;
|
||||
off = hb + STAT_BODY * 8;
|
||||
wb(off + 0 * 8, frames_rx & 0xFF);
|
||||
@@ -502,8 +818,8 @@ unsigned int frames_rx;
|
||||
wb(off + 3 * 8, (frames_rx >> 24) & 0xFF);
|
||||
wb(off + 4 * 8, bad & 0xFF);
|
||||
wb(off + 5 * 8, (bad >> 8) & 0xFF);
|
||||
wb(off + 6 * 8, drops_total & 0xFF);
|
||||
wb(off + 7 * 8, (drops_total >> 8) & 0xFF);
|
||||
wb(off + 6 * 8, (drops_total + rdrops) & 0xFF);
|
||||
wb(off + 7 * 8, ((drops_total + rdrops) >> 8) & 0xFF);
|
||||
wb(off + 8 * 8, csr0 & 0xFF);
|
||||
wb(off + 9 * 8, (csr0 >> 8) & 0xFF);
|
||||
wb(off + 10 * 8, free_kb & 0xFF);
|
||||
@@ -554,67 +870,6 @@ unsigned int frames_rx;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle one slot if the receive path has raised its ready flag.
|
||||
*
|
||||
* In the real transport the LANCE receive interrupt fills the slot and sets
|
||||
* the flag; MAME cannot hand this emulated LANCE an Ethernet frame (the
|
||||
* driver attaches no network interface -- ruling R11), so in the harness
|
||||
* vidstream.lua writes the slot and sets the flag instead. The payload does
|
||||
* not care which of the two did it.
|
||||
*
|
||||
* `len` is the number of bytes the transport actually delivered. fp_parse()
|
||||
* rejects a frame whose payload_len wants more than that, which is the rule
|
||||
* that makes payload_len plus the received length authoritative rather than a
|
||||
* descriptor field.
|
||||
*/
|
||||
static void rx_poll(slot)
|
||||
unsigned int slot;
|
||||
{
|
||||
unsigned int readyp, lenp, basep, base, len, ready;
|
||||
fp_header h;
|
||||
int ok;
|
||||
|
||||
if (slot == 0) {
|
||||
readyp = RX_READY_A;
|
||||
lenp = RX_LEN_A;
|
||||
basep = RX_SLOT_A;
|
||||
} else {
|
||||
readyp = RX_READY_B;
|
||||
lenp = RX_LEN_B;
|
||||
basep = RX_SLOT_B;
|
||||
}
|
||||
|
||||
ready = *(unsigned int *)readyp;
|
||||
if (ready == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
base = *(unsigned int *)basep;
|
||||
len = *(unsigned int *)lenp;
|
||||
|
||||
ok = 0;
|
||||
if (base != 0) {
|
||||
ok = fp_parse((unsigned char *)base, len, &h);
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
*(unsigned int *)RX_OK = *(unsigned int *)RX_OK + 1;
|
||||
*(unsigned int *)RX_LAST_LEN = h.payload_len;
|
||||
*(unsigned int *)RX_SLOT = slot;
|
||||
/* Only an accepted frame reaches VRAM: the bytes just validated
|
||||
* are the bytes the blit reads, and a corrupt frame can never
|
||||
* paint over a good one. */
|
||||
paint_frame(base, &h);
|
||||
} else {
|
||||
*(unsigned int *)RX_BAD = *(unsigned int *)RX_BAD + 1;
|
||||
}
|
||||
|
||||
/* Release the slot last: the checks read the counters, and a slot
|
||||
* freed before its counter moved would look like a dropped frame. */
|
||||
*(unsigned int *)readyp = 0;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
unsigned int before, after, ptr, frames, sent;
|
||||
@@ -625,18 +880,21 @@ int main()
|
||||
*(unsigned int *)POOL_FREEA = 0;
|
||||
|
||||
/* Receive record: counters and the two slots are cleared before anything
|
||||
* is published, so a reader never sees a stale ready flag or a slot
|
||||
* address that has no allocation behind it. */
|
||||
* 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_READY_A = 0;
|
||||
*(unsigned int *)RX_LEN_A = 0;
|
||||
*(unsigned int *)RX_READY_B = 0;
|
||||
*(unsigned int *)RX_LEN_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_DGRAM = 0;
|
||||
*(unsigned int *)OS_TOUCH = 0;
|
||||
|
||||
/* Arm the VRAM canary before any frame is accepted, so a repaint
|
||||
@@ -660,14 +918,17 @@ int main()
|
||||
|
||||
/* Slot B follows slot A in the same pool. Both are published before
|
||||
* POOL_PTR so the harness can find them from either one. */
|
||||
*(unsigned int *)RX_SLOT_A = ptr;
|
||||
*(unsigned int *)RX_SLOT_B = ptr + SLOT_BYTES * 8;
|
||||
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];
|
||||
*(unsigned int *)POOL_PTR = ptr; /* publish last: a complete record */
|
||||
|
||||
/* Task 6: the status back-channel. The record starts empty, then the
|
||||
* LANCE is taken over for transmit with interrupts masked -- the OS's
|
||||
* drivers were using this chip a moment ago, and a network ISR running
|
||||
* in the middle of the ring setup would see a half-written descriptor. */
|
||||
* 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;
|
||||
@@ -680,6 +941,10 @@ int main()
|
||||
status_seq = 0;
|
||||
status_seen = 0;
|
||||
drops_total = 0;
|
||||
rx_armed = 0;
|
||||
rx_pos = 0;
|
||||
rx_acc = 0;
|
||||
rx_nfrag = 0;
|
||||
|
||||
vid_dint_();
|
||||
lance_ok = lance_start(ptr);
|
||||
@@ -688,8 +953,7 @@ int main()
|
||||
|
||||
for (;;) {
|
||||
*(unsigned int *)VID_ALIVE = *(unsigned int *)VID_ALIVE + 1;
|
||||
rx_poll(0);
|
||||
rx_poll(1);
|
||||
rx_poll();
|
||||
|
||||
/* Report every STATUS_EVERY accepted frames, and -- once the
|
||||
* harness says the sender has stopped -- for every further
|
||||
|
||||
Reference in new issue
Block a user