networking RE report + LANCE payload variants

harness/mame/NETWORK_REPORT.md: how the VT1200 firmware drives the LANCE,
from ROM disassembly and MAME traces. 50 sections, each claim tied to a bit
address, a trace count or a quoted source line. Covers the driver device
struct (0x23CBF0), init (0x3F9D4BC0), descriptor/DMA encoding, the live
handler (0x3F9D5B40) with per-status-bit dispatch, the RX poll (0x3F9D62B0),
ring arming (0x3F9D36C0), TX completion (0x3F9D8AF0) and the single transmit
path every protocol uses (0x3F6084E0 -> 0x3F9DC890 -> 0x3F9D4BC0), plus the
IP protocol dispatch (0x3F6270F0), ICMP (343-count chain), TCP state tables
and live TIME_WAIT, the ARP responder (0x3F6119A0) and the socket 4-tuple
demux confirmed by a memory dump.

It also records what was withdrawn: five successive explanations for the
real-silicon CSR0=0x1BB / TMD1=0x8338 result, and the descriptor-length
encoding bug found by comparing Linux 7990.c and NetBSD am7990.c (length must
be 0xF000 | -len; MAME masks the difference in get_buf_length).

harness/udpstream: the LANCE loopback/takeover payload and the variants used
to test the init/kick orderings in MAME (bare TDMD, STOP before INIT, double
INIT, long wait with CSR0 sampling, IDON clear, corrected descriptor length).
build-lance.sh takes LANCE_SRC/LANCE_OUT so the variants build from one script.
This commit is contained in:
arti committed 2026-10-30 01:25:21 +02:00
1 parent 41b0979aef
commit 874d1fbbf5
24 files changed
+6365

No files matched your search

File diff suppressed because it is too large. Load diff
+6
View File
@@ -0,0 +1,6 @@
# DOSBox build scratch: extracted 1987 SDK binaries, not project source
work/
# generated listings/maps (the .coff payloads are committed)
build/*.lst
build/*.map
+93
View File
@@ -0,0 +1,93 @@
#!/bin/sh
# Build the raw LANCE receive probe.
#
# harness/udpstream/build.sh
#
# .text at bit 0x018C0000, .bss at 0x01C00000 (both inside the firmware's large
# free block), entry _start, no .cinit (the runtime's autoinit pass does not
# run -- see harness/c-hello/README.md).
set -eu
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
DIR=$(cd "$(dirname "$0")" && pwd)
WORK="$DIR/work"
OUT="$DIR/build"
IMG="$ROOT/tools/tms34010-sdk/1987-05 TMS34010 SDK/1987-11-06 TMS34010 Simulator-C Tools-ASM Tools.img"
DOSBOX_CONF="$ROOT/harness/sdk-scratch/dosbox-bench.conf"
TEXT=018C0000
BSS=01C00000
mkdir -p "$WORK" "$OUT"
if [ ! -x "$WORK/GSPCG.EXE" ]; then
mcopy -s -n -i "$IMG" "::/A2/*" "$WORK/"
mcopy -s -n -i "$IMG" "::/C2/*" "$WORK/"
fi
SRC=${LANCE_SRC:-$DIR/lance.c}
cp "$SRC" "$WORK/LANCE.C"
cp "$DIR/romapi.h" "$WORK/ROMAPI.H"
cp "$DIR/entry.asm" "$WORK/ENTRY.ASM"
{
printf '%s\n' '-a'
printf '%s\n' '-o LANCE.out'
printf '%s\n' '-m LANCE.map'
printf '%s\n' '-e _start'
printf '%s\n' 'LANCE.obj'
printf '%s\n' 'ENTRY.obj'
printf '%s\n' '-l RTS.LIB'
printf '%s\n' 'MEMORY'
printf '%s\n' '{'
printf '%s\n' " prog: origin = ${TEXT}h, length = 20000h"
printf '%s\n' " ram: origin = ${BSS}h, length = 40000h"
printf '%s\n' '}'
printf '%s\n' 'SECTIONS'
printf '%s\n' '{'
printf '%s\n' ' .text: {} > prog'
printf '%s\n' ' .data: {} > prog'
printf '%s\n' ' .cinit: {} > prog'
printf '%s\n' ' .bss: {} > ram'
printf '%s\n' '}'
} > "$WORK/LANCE.CMD"
sed -i 's/$/\r/' "$WORK/LANCE.CMD"
rm -f "$WORK"/LANCE.OBJ "$WORK"/LANCE.OUT "$WORK"/LANCE.LST \
"$WORK"/LANCE.MAP "$WORK"/LANCE.ASM "$WORK"/LANCE.CPP \
"$WORK"/LANCE.IF "$WORK"/ENTRY.OBJ "$WORK"/ENTRY.LST
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
timeout 240 dosbox -conf "$DOSBOX_CONF" -noprimaryconf -nolocalconf \
-c "mount c $WORK" -c "c:" \
-c "gspcpp LANCE" \
-c "gspcc LANCE" \
-c "gspcg LANCE" \
-c "gspa LANCE LANCE.obj LANCE.lst -l" \
-c "gspa ENTRY ENTRY.obj ENTRY.lst -l" \
-c "gsplnk LANCE.cmd" \
-c "exit" -exit > "$WORK/dosbox.log" 2>&1
if [ ! -f "$WORK/LANCE.OUT" ]; then
echo "build.sh: GSPLNK produced no LANCE.OUT" >&2
sed 's/\r$//' "$WORK/LANCE.MAP" 2>/dev/null | tail -20 >&2 || true
for f in LANCE.LST ENTRY.LST; do
sed 's/\r$//' "$WORK/$f" 2>/dev/null | grep -i 'error' | grep -vi 'no errors' >&2 || true
done
exit 1
fi
for f in LANCE.LST ENTRY.LST; do
if sed 's/\r$//' "$WORK/$f" | grep -i 'error' | grep -qvi 'no errors'; then
echo "build.sh: $f diagnostics:" >&2
sed 's/\r$//' "$WORK/$f" | grep -in 'error' | grep -vi 'no errors' >&2
exit 1
fi
done
OUTNAME=${LANCE_OUT:-lance.coff}
cp "$WORK/LANCE.OUT" "$OUT/$OUTNAME"
sed 's/\r$//' "$WORK/LANCE.MAP" > "$OUT/lance.map"
sed 's/\r$//' "$WORK/LANCE.LST" > "$OUT/lance.lst"
sed 's/\r$//' "$WORK/ENTRY.LST" > "$OUT/entry.lst"
echo "build.sh: $OUT/$OUTNAME ($(wc -c < "$OUT/lance.coff") bytes)"
grep -E "ENTRY POINT|^\.(text|cinit|bss)" "$OUT/lance.map" | head -8
+91
View File
@@ -0,0 +1,91 @@
#!/bin/sh
# Build the TCP throughput probe (C + ROM-API thunks).
#
# harness/udpstream/build.sh
#
# .text at bit 0x018C0000, .bss at 0x01C00000 (both inside the firmware's large
# free block), entry _start, no .cinit (the runtime's autoinit pass does not
# run -- see harness/c-hello/README.md).
set -eu
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
DIR=$(cd "$(dirname "$0")" && pwd)
WORK="$DIR/work"
OUT="$DIR/build"
IMG="$ROOT/tools/tms34010-sdk/1987-05 TMS34010 SDK/1987-11-06 TMS34010 Simulator-C Tools-ASM Tools.img"
DOSBOX_CONF="$ROOT/harness/sdk-scratch/dosbox-bench.conf"
TEXT=018C0000
BSS=01C00000
mkdir -p "$WORK" "$OUT"
if [ ! -x "$WORK/GSPCG.EXE" ]; then
mcopy -s -n -i "$IMG" "::/A2/*" "$WORK/"
mcopy -s -n -i "$IMG" "::/C2/*" "$WORK/"
fi
cp "$DIR/tcpstream.c" "$WORK/TCPSTRM.C"
cp "$DIR/romapi.h" "$WORK/ROMAPI.H"
cp "$DIR/entry.asm" "$WORK/ENTRY.ASM"
{
printf '%s\n' '-a'
printf '%s\n' '-o TCPSTRM.out'
printf '%s\n' '-m TCPSTRM.map'
printf '%s\n' '-e _start'
printf '%s\n' 'TCPSTRM.obj'
printf '%s\n' 'ENTRY.obj'
printf '%s\n' '-l RTS.LIB'
printf '%s\n' 'MEMORY'
printf '%s\n' '{'
printf '%s\n' " prog: origin = ${TEXT}h, length = 20000h"
printf '%s\n' " ram: origin = ${BSS}h, length = 40000h"
printf '%s\n' '}'
printf '%s\n' 'SECTIONS'
printf '%s\n' '{'
printf '%s\n' ' .text: {} > prog'
printf '%s\n' ' .data: {} > prog'
printf '%s\n' ' .cinit: {} > prog'
printf '%s\n' ' .bss: {} > ram'
printf '%s\n' '}'
} > "$WORK/TCPSTRM.CMD"
sed -i 's/$/\r/' "$WORK/TCPSTRM.CMD"
rm -f "$WORK"/TCPSTRM.OBJ "$WORK"/TCPSTRM.OUT "$WORK"/TCPSTRM.LST \
"$WORK"/TCPSTRM.MAP "$WORK"/TCPSTRM.ASM "$WORK"/TCPSTRM.CPP \
"$WORK"/TCPSTRM.IF "$WORK"/ENTRY.OBJ "$WORK"/ENTRY.LST
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
timeout 240 dosbox -conf "$DOSBOX_CONF" -noprimaryconf -nolocalconf \
-c "mount c $WORK" -c "c:" \
-c "gspcpp TCPSTRM" \
-c "gspcc TCPSTRM" \
-c "gspcg TCPSTRM" \
-c "gspa TCPSTRM TCPSTRM.obj TCPSTRM.lst -l" \
-c "gspa ENTRY ENTRY.obj ENTRY.lst -l" \
-c "gsplnk TCPSTRM.cmd" \
-c "exit" -exit > "$WORK/dosbox.log" 2>&1
if [ ! -f "$WORK/TCPSTRM.OUT" ]; then
echo "build.sh: GSPLNK produced no TCPSTRM.OUT" >&2
sed 's/\r$//' "$WORK/TCPSTRM.MAP" 2>/dev/null | tail -20 >&2 || true
for f in TCPSTRM.LST ENTRY.LST; do
sed 's/\r$//' "$WORK/$f" 2>/dev/null | grep -i 'error' | grep -vi 'no errors' >&2 || true
done
exit 1
fi
for f in TCPSTRM.LST ENTRY.LST; do
if sed 's/\r$//' "$WORK/$f" | grep -i 'error' | grep -qvi 'no errors'; then
echo "build.sh: $f diagnostics:" >&2
sed 's/\r$//' "$WORK/$f" | grep -in 'error' | grep -vi 'no errors' >&2
exit 1
fi
done
cp "$WORK/TCPSTRM.OUT" "$OUT/tcpstream.coff"
sed 's/\r$//' "$WORK/TCPSTRM.MAP" > "$OUT/tcpstream.map"
sed 's/\r$//' "$WORK/TCPSTRM.LST" > "$OUT/tcpstream.lst"
sed 's/\r$//' "$WORK/ENTRY.LST" > "$OUT/entry.lst"
echo "build.sh: $OUT/tcpstream.coff ($(wc -c < "$OUT/tcpstream.coff") bytes)"
grep -E "ENTRY POINT|^\.(text|cinit|bss)" "$OUT/tcpstream.map" | head -8
+91
View File
@@ -0,0 +1,91 @@
#!/bin/sh
# Build the UDP frame-stream receiver (C + ROM-API thunks).
#
# harness/udpstream/build.sh
#
# .text at bit 0x018C0000, .bss at 0x01C00000 (both inside the firmware's large
# free block), entry _start, no .cinit (the runtime's autoinit pass does not
# run -- see harness/c-hello/README.md).
set -eu
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
DIR=$(cd "$(dirname "$0")" && pwd)
WORK="$DIR/work"
OUT="$DIR/build"
IMG="$ROOT/tools/tms34010-sdk/1987-05 TMS34010 SDK/1987-11-06 TMS34010 Simulator-C Tools-ASM Tools.img"
DOSBOX_CONF="$ROOT/harness/sdk-scratch/dosbox-bench.conf"
TEXT=018C0000
BSS=01C00000
mkdir -p "$WORK" "$OUT"
if [ ! -x "$WORK/GSPCG.EXE" ]; then
mcopy -s -n -i "$IMG" "::/A2/*" "$WORK/"
mcopy -s -n -i "$IMG" "::/C2/*" "$WORK/"
fi
cp "$DIR/udpstream.c" "$WORK/UDPSTRM.C"
cp "$DIR/romapi.h" "$WORK/ROMAPI.H"
cp "$DIR/entry.asm" "$WORK/ENTRY.ASM"
{
printf '%s\n' '-a'
printf '%s\n' '-o UDPSTRM.out'
printf '%s\n' '-m UDPSTRM.map'
printf '%s\n' '-e _start'
printf '%s\n' 'UDPSTRM.obj'
printf '%s\n' 'ENTRY.obj'
printf '%s\n' '-l RTS.LIB'
printf '%s\n' 'MEMORY'
printf '%s\n' '{'
printf '%s\n' " prog: origin = ${TEXT}h, length = 20000h"
printf '%s\n' " ram: origin = ${BSS}h, length = 40000h"
printf '%s\n' '}'
printf '%s\n' 'SECTIONS'
printf '%s\n' '{'
printf '%s\n' ' .text: {} > prog'
printf '%s\n' ' .data: {} > prog'
printf '%s\n' ' .cinit: {} > prog'
printf '%s\n' ' .bss: {} > ram'
printf '%s\n' '}'
} > "$WORK/UDPSTRM.CMD"
sed -i 's/$/\r/' "$WORK/UDPSTRM.CMD"
rm -f "$WORK"/UDPSTRM.OBJ "$WORK"/UDPSTRM.OUT "$WORK"/UDPSTRM.LST \
"$WORK"/UDPSTRM.MAP "$WORK"/UDPSTRM.ASM "$WORK"/UDPSTRM.CPP \
"$WORK"/UDPSTRM.IF "$WORK"/ENTRY.OBJ "$WORK"/ENTRY.LST
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
timeout 240 dosbox -conf "$DOSBOX_CONF" -noprimaryconf -nolocalconf \
-c "mount c $WORK" -c "c:" \
-c "gspcpp UDPSTRM" \
-c "gspcc UDPSTRM" \
-c "gspcg UDPSTRM" \
-c "gspa UDPSTRM UDPSTRM.obj UDPSTRM.lst -l" \
-c "gspa ENTRY ENTRY.obj ENTRY.lst -l" \
-c "gsplnk UDPSTRM.cmd" \
-c "exit" -exit > "$WORK/dosbox.log" 2>&1
if [ ! -f "$WORK/UDPSTRM.OUT" ]; then
echo "build.sh: GSPLNK produced no UDPSTRM.OUT" >&2
sed 's/\r$//' "$WORK/UDPSTRM.MAP" 2>/dev/null | tail -20 >&2 || true
for f in UDPSTRM.LST ENTRY.LST; do
sed 's/\r$//' "$WORK/$f" 2>/dev/null | grep -i 'error' | grep -vi 'no errors' >&2 || true
done
exit 1
fi
for f in UDPSTRM.LST ENTRY.LST; do
if sed 's/\r$//' "$WORK/$f" | grep -i 'error' | grep -qvi 'no errors'; then
echo "build.sh: $f diagnostics:" >&2
sed 's/\r$//' "$WORK/$f" | grep -in 'error' | grep -vi 'no errors' >&2
exit 1
fi
done
cp "$WORK/UDPSTRM.OUT" "$OUT/udpstream.coff"
sed 's/\r$//' "$WORK/UDPSTRM.MAP" > "$OUT/udpstream.map"
sed 's/\r$//' "$WORK/UDPSTRM.LST" > "$OUT/udpstream.lst"
sed 's/\r$//' "$WORK/ENTRY.LST" > "$OUT/entry.lst"
echo "build.sh: $OUT/udpstream.coff ($(wc -c < "$OUT/udpstream.coff") bytes)"
grep -E "ENTRY POINT|^\.(text|cinit|bss)" "$OUT/udpstream.map" | head -8
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+136
View File
@@ -0,0 +1,136 @@
; entry.asm -- C process entry + call-throughs to the firmware socket API.
;
; _start replaces boot.obj (whose exit() spins and whose _c_int00 ends in
; `reti`): set up the C program stack, call main, return with `rets` to the
; create_process trampoline.
;
; The socket wrappers are NOT a bare `calla`/`rets`. The firmware's wrappers
; end in `RETS 2`: they pop the return address *and* two argument words, and
; they restore A14 from a caller-pushed slot. A plain thunk therefore returns
; with SP one word off, and its own `rets` pops the saved-A14 slot (a bit
; pointer into .bss) as the return address -- the CPU then executes data and
; the board lands in its reset/delay path. dwt/tcpip/tftpc.asm avoids this by
; saving SP and A14 in cells around every ROM call; these thunks do the same.
;
; socket = 3F5C9A00h socket(domain, type, protocol) -> fd
; bind = 3F5CAA80h bind(fd, sockaddr*, len) -> 0 / -1
; recvfrom = 3F5D7D50h recvfrom(fd, buf, len, flags, &from, &fromlen) -> n
; close = 3F5E8930h close(fd)
; malloc = 3F701230h malloc(bytes) -> bit pointer
; free = 3F702440h free(bit pointer)
;
; See dwt/notes/TCPIP.md.
.text
.globl _start
.globl _main
_start:
setf 32, 0, 1
movi _cstack, a14
move a14, a13
calla _main
rets
.globl _socket_
_socket_:
move sp, @_sys_sp, 1
move a14, @_sys_a14, 1
calla 3F5C9A00h
move @_sys_sp, sp, 1
move @_sys_a14, a14, 1
rets 2
.globl _bind_
_bind_:
move sp, @_sys_sp, 1
move a14, @_sys_a14, 1
calla 3F5CAA80h
move @_sys_sp, sp, 1
move @_sys_a14, a14, 1
rets 2
.globl _recvfrom_
_recvfrom_:
move sp, @_sys_sp, 1
move a14, @_sys_a14, 1
calla 3F5D7D50h
move @_sys_sp, sp, 1
move @_sys_a14, a14, 1
rets 2
.globl _close_
_close_:
move sp, @_sys_sp, 1
move a14, @_sys_a14, 1
calla 3F5E8930h
move @_sys_sp, sp, 1
move @_sys_a14, a14, 1
rets 2
.globl _malloc_
_malloc_:
move sp, @_sys_sp, 1
move a14, @_sys_a14, 1
calla 3F701230h
move @_sys_sp, sp, 1
move @_sys_a14, a14, 1
rets 2
.globl _delay_
_delay_:
move sp, @_sys_sp, 1
move a14, @_sys_a14, 1
calla 3F833A20h
move @_sys_sp, sp, 1
move @_sys_a14, a14, 1
rets 2
.globl _listen_
_listen_:
move sp, @_sys_sp, 1
move a14, @_sys_a14, 1
calla 3F5CBCD0h
move @_sys_sp, sp, 1
move @_sys_a14, a14, 1
rets 2
.globl _accept_
_accept_:
move sp, @_sys_sp, 1
move a14, @_sys_a14, 1
calla 3F5CCB40h
move @_sys_sp, sp, 1
move @_sys_a14, a14, 1
rets 2
.globl _read_
_read_:
move sp, @_sys_sp, 1
move a14, @_sys_a14, 1
calla 3F5D8E70h
move @_sys_sp, sp, 1
move @_sys_a14, a14, 1
rets 2
.globl _select_
_select_:
move sp, @_sys_sp, 1
move a14, @_sys_a14, 1
calla 3F871CE0h
move @_sys_sp, sp, 1
move @_sys_a14, a14, 1
rets 2
.globl _free_
_free_:
move sp, @_sys_sp, 1
move a14, @_sys_a14, 1
calla 3F702440h
move @_sys_sp, sp, 1
move @_sys_a14, a14, 1
rets 2
.bss _cstack, 32768, 16 ; 32768 bits = 4096 bytes
.bss _sys_sp, 32, 16
.bss _sys_a14, 32, 16
.end
+274
View File
@@ -0,0 +1,274 @@
/*
* lance.c -- LANCE loopback probe using LOW-MEMORY rings.
*
* The OS's own init block (pointer at bit 0x23E7A0 -> byte 0xF80 on a live
* boot) puts its rings at byte 0x0A80 / 0x0E80 and stores only the LENGTH in
* the ring words' bits 13-15 (0xE000 / 0xA000), with no address bits above 16.
* MAME's model packs address bits 16+ into that word; if the real C-LANCE does
* not, a high ring address like 0x380028 is seen as 0x0028. This payload
* therefore places the block, rings and buffers below 64 KB.
*
* Memory used (BYTE addresses, well below 64 KB):
* 0x3000 init block (24 B) 0x3020 RX ring (8 * 8 B) 0x3060 TX ring (8 B)
* 0x3100 TX frame (24 B) 0x3200 RX buffer (2048 B)
*
* Result cells at 0x018C8000 (32-bit): 0x160/0x180 CSR0 samples,
* 0x200 RX RMD1, 0x220 RX MCNT, 0x240 TX TMD1, 0x1A0 payload-found flag.
*
* Panel rows (real unit, 256-byte stride): y 670/700 = CSR0, 730 = TMD1,
* 760 = RMD1, 790 = MCNT; blocks y 850: left = payload returned, right = TX done.
*/
#define RAP 0x05900000
#define RDP 0x05800000
#define MARK 0x018C8000
#define VRAM_BASE 0x04000000
#define MODE_LOOP 0x0004
#define MODE_INTL 0x0040
#define CSR0_STOP 0x0004
#define CSR0_STRT 0x0002
#define CSR0_INIT 0x0001
#define CSR0_TDMD 0x0008
#define CSR0_IDON 0x0100
#define RMD1_OWN 0x8000
#define TMD1_OWN 0x8000
#define TMD1_ENP 0x0100
#define TMD1_STP 0x0200
#define B(byte) ((unsigned int)(byte) * 8) /* bit address of a byte */
#define INITB B(0x3000)
#define RXR B(0x3020)
#define TXR B(0x3060)
#define RXBUF B(0x3200)
#define TXBUF B(0x3180)
#define RING_ENTRIES 8
static void cell(off, v)
unsigned int off, v;
{
*(unsigned int *)(MARK + off) = v;
}
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;
}
static void rect(x, y, w, h, v)
unsigned int x, y, w, h, v;
{
unsigned int row, col;
for (row = 0; row < h; row++) {
for (col = 0; col < w; col = col + 8) {
*(unsigned char *)(VRAM_BASE + (y + row) * 2048 + x + col) = v;
}
}
}
static void bits16(x, y, v)
unsigned int x, y, v;
{
unsigned int b;
for (b = 0; b < 16; b++) {
rect(x + b * 30, y, 24, 24, (v & (1 << b)) ? 0xFF : 0x00);
}
}
static void dot(n)
unsigned int n;
{
rect(60, 650 + n * 44, 24, 24, 0xFF);
}
static void settle()
{
unsigned int i;
for (i = 0; i < 30; i++) {
}
}
static void csr_w(reg, v)
unsigned int reg, v;
{
*(unsigned short *)RAP = reg;
settle();
*(unsigned short *)RDP = v;
settle();
}
static unsigned int csr_r(reg)
unsigned int reg;
{
unsigned int v;
*(unsigned short *)RAP = reg;
settle();
v = *(unsigned short *)RDP;
settle();
return v;
}
int main()
{
unsigned int i, r, idle, ok, rxmd1, txmd1, mcnt;
unsigned int s0[2];
cell(0x000, 1);
dot(0);
csr_w(0, CSR0_STOP);
rect(100, 650, 512, 256, 0x00);
rect(100, 650, 512, 4, 0xFF);
rect(100, 902, 512, 4, 0xFF);
rect(100, 650, 8, 256, 0xFF);
rect(608, 650, 8, 256, 0xFF);
/* init block: mode, PADR, LADRF, RX addr/len, TX addr/len */
w16(INITB + 0 * 16, MODE_LOOP | MODE_INTL);
w16(INITB + 1 * 16, 0x0008);
w16(INITB + 2 * 16, 0x162b);
w16(INITB + 3 * 16, 0x5a4b);
w16(INITB + 4 * 16, 0);
w16(INITB + 5 * 16, 0);
w16(INITB + 6 * 16, 0);
w16(INITB + 7 * 16, 0);
w16(INITB + 8 * 16, RXR >> 3 & 0xFFFF); /* ring addr low 16 */
w16(INITB + 9 * 16, 3 << 13); /* length only: 2^3 = 8 */
w16(INITB + 10 * 16, TXR >> 3 & 0xFFFF);
w16(INITB + 11 * 16, 3 << 13);
cell(0x040, INITB >> 3);
cell(0x060, RXR >> 3);
cell(0x080, TXR >> 3);
/* RX descriptors: all point at the same buffer */
for (i = 0; i < RING_ENTRIES; i++) {
w16(RXR + i * 64 + 0, (RXBUF >> 3) & 0xFFFF);
w16(RXR + i * 64 + 16, RMD1_OWN | (((RXBUF >> 3) >> 16) & 0xFF));
w16(RXR + i * 64 + 32, 0x0A0A); /* 1526 bytes */
w16(RXR + i * 64 + 48, 0);
}
/* one 24-byte frame (MAME's loopback accepts 8..32 bytes) */
wb(TXBUF + 0 * 8, 0x08); wb(TXBUF + 1 * 8, 0x00);
wb(TXBUF + 2 * 8, 0x2b); wb(TXBUF + 3 * 8, 0x16);
wb(TXBUF + 4 * 8, 0x4b); wb(TXBUF + 5 * 8, 0x5a);
wb(TXBUF + 6 * 8, 0x02); wb(TXBUF + 7 * 8, 0x00);
wb(TXBUF + 8 * 8, 0x00); wb(TXBUF + 9 * 8, 0x00);
wb(TXBUF + 10 * 8, 0x00); wb(TXBUF + 11 * 8, 0x01);
wb(TXBUF + 12 * 8, 0x88); wb(TXBUF + 13 * 8, 0xB5);
wb(TXBUF + 14 * 8, 0xDE); wb(TXBUF + 15 * 8, 0xAD);
wb(TXBUF + 16 * 8, 0xBE); wb(TXBUF + 17 * 8, 0xEF);
for (i = 18; i < 24; i++) {
wb(TXBUF + i * 8, i);
}
w16(TXR + 0, (TXBUF >> 3) & 0xFFFF);
w16(TXR + 16, TMD1_OWN | TMD1_STP | TMD1_ENP | (((TXBUF >> 3) >> 16) & 0xFF));
w16(TXR + 32, 0x0FE8); /* 24 bytes */
w16(TXR + 48, 0);
csr_w(1, (INITB >> 3) & 0xFFFF);
csr_w(2, ((INITB >> 3) >> 16) & 0xFF);
csr_w(3, 0);
csr_w(0, CSR0_INIT);
dot(1);
r = 0;
for (idle = 0; idle < 20000; idle++) {
r = csr_r(0);
if (r & CSR0_IDON) {
break;
}
}
cell(0x140, r);
if (!(r & CSR0_IDON)) {
cell(0x000, 0xEE);
return 1;
}
dot(2);
for (idle = 0; idle < 50000; idle++) {
}
csr_w(0, CSR0_STRT);
for (idle = 0; idle < 50000; idle++) {
}
/* own the TX descriptor now, then kick with STRT|TDMD in ONE write:
* CSR0 is a command shadow, a bare TDMD write clears STRT. */
w16(TXR + 16, TMD1_OWN | TMD1_STP | TMD1_ENP | (((TXBUF >> 3) >> 16) & 0xFF));
csr_w(0, CSR0_STRT | CSR0_TDMD);
dot(3);
s0[0] = csr_r(0);
for (idle = 0; idle < 100000; idle++) {
if (!(r16(RXR + 16) & RMD1_OWN)) {
dot(4);
break;
}
if (!(r16(TXR + 16) & TMD1_OWN)) {
dot(4);
}
}
s0[1] = csr_r(0);
rxmd1 = r16(RXR + 16);
mcnt = r16(RXR + 48) & 0x0FFF;
txmd1 = r16(TXR + 16);
ok = 0;
if (*(unsigned char *)(RXBUF + 12 * 8) == 0x88 &&
*(unsigned char *)(RXBUF + 13 * 8) == 0xB5 &&
*(unsigned char *)(RXBUF + 14 * 8) == 0xDE) {
ok = 1;
}
cell(0x160, s0[0]);
cell(0x180, s0[1]);
cell(0x200, rxmd1);
cell(0x220, mcnt);
cell(0x240, txmd1);
cell(0x1A0, ok);
bits16(120, 670, s0[0]);
bits16(120, 700, s0[1]);
bits16(120, 730, txmd1);
bits16(120, 760, rxmd1);
bits16(120, 790, mcnt);
if (ok) {
rect(120, 850, 24, 24, 0xFF);
}
if (!(txmd1 & TMD1_OWN)) {
rect(160, 850, 24, 24, 0xFF);
}
/* best effort: give the chip back to the OS */
{
unsigned int osblk;
osblk = *(unsigned int *)0x23E7A0;
if (osblk != 0) {
csr_w(0, CSR0_STOP);
csr_w(1, osblk & 0xFFFF);
csr_w(2, (osblk >> 16) & 0xFF);
csr_w(3, 0);
csr_w(0, CSR0_INIT);
for (idle = 0; idle < 10000; idle++) {
r = csr_r(0);
if (r & CSR0_IDON) {
break;
}
}
csr_w(0, 0x0042);
}
}
return 0;
}
+274
View File
@@ -0,0 +1,274 @@
/*
* lance.c -- LANCE loopback probe using LOW-MEMORY rings.
*
* The OS's own init block (pointer at bit 0x23E7A0 -> byte 0xF80 on a live
* boot) puts its rings at byte 0x0A80 / 0x0E80 and stores only the LENGTH in
* the ring words' bits 13-15 (0xE000 / 0xA000), with no address bits above 16.
* MAME's model packs address bits 16+ into that word; if the real C-LANCE does
* not, a high ring address like 0x380028 is seen as 0x0028. This payload
* therefore places the block, rings and buffers below 64 KB.
*
* Memory used (BYTE addresses, well below 64 KB):
* 0x3000 init block (24 B) 0x3020 RX ring (8 * 8 B) 0x3060 TX ring (8 B)
* 0x3100 TX frame (24 B) 0x3200 RX buffer (2048 B)
*
* Result cells at 0x018C8000 (32-bit): 0x160/0x180 CSR0 samples,
* 0x200 RX RMD1, 0x220 RX MCNT, 0x240 TX TMD1, 0x1A0 payload-found flag.
*
* Panel rows (real unit, 256-byte stride): y 670/700 = CSR0, 730 = TMD1,
* 760 = RMD1, 790 = MCNT; blocks y 850: left = payload returned, right = TX done.
*/
#define RAP 0x05900000
#define RDP 0x05800000
#define MARK 0x018C8000
#define VRAM_BASE 0x04000000
#define MODE_LOOP 0x0004
#define MODE_INTL 0x0040
#define CSR0_STOP 0x0004
#define CSR0_STRT 0x0002
#define CSR0_INIT 0x0001
#define CSR0_TDMD 0x0008
#define CSR0_IDON 0x0100
#define RMD1_OWN 0x8000
#define TMD1_OWN 0x8000
#define TMD1_ENP 0x0100
#define TMD1_STP 0x0200
#define B(byte) ((unsigned int)(byte) * 8) /* bit address of a byte */
#define INITB B(0x3000)
#define RXR B(0x3020)
#define TXR B(0x3060)
#define RXBUF B(0x3200)
#define TXBUF B(0x3180)
#define RING_ENTRIES 8
static void cell(off, v)
unsigned int off, v;
{
*(unsigned int *)(MARK + off) = v;
}
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;
}
static void rect(x, y, w, h, v)
unsigned int x, y, w, h, v;
{
unsigned int row, col;
for (row = 0; row < h; row++) {
for (col = 0; col < w; col = col + 8) {
*(unsigned char *)(VRAM_BASE + (y + row) * 2048 + x + col) = v;
}
}
}
static void bits16(x, y, v)
unsigned int x, y, v;
{
unsigned int b;
for (b = 0; b < 16; b++) {
rect(x + b * 30, y, 24, 24, (v & (1 << b)) ? 0xFF : 0x00);
}
}
static void dot(n)
unsigned int n;
{
rect(60, 650 + n * 44, 24, 24, 0xFF);
}
static void settle()
{
unsigned int i;
for (i = 0; i < 30; i++) {
}
}
static void csr_w(reg, v)
unsigned int reg, v;
{
*(unsigned short *)RAP = reg;
settle();
*(unsigned short *)RDP = v;
settle();
}
static unsigned int csr_r(reg)
unsigned int reg;
{
unsigned int v;
*(unsigned short *)RAP = reg;
settle();
v = *(unsigned short *)RDP;
settle();
return v;
}
int main()
{
unsigned int i, r, idle, ok, rxmd1, txmd1, mcnt;
unsigned int s0[2];
cell(0x000, 1);
dot(0);
csr_w(0, CSR0_STOP);
rect(100, 650, 512, 256, 0x00);
rect(100, 650, 512, 4, 0xFF);
rect(100, 902, 512, 4, 0xFF);
rect(100, 650, 8, 256, 0xFF);
rect(608, 650, 8, 256, 0xFF);
/* init block: mode, PADR, LADRF, RX addr/len, TX addr/len */
w16(INITB + 0 * 16, MODE_LOOP | MODE_INTL);
w16(INITB + 1 * 16, 0x0008);
w16(INITB + 2 * 16, 0x162b);
w16(INITB + 3 * 16, 0x5a4b);
w16(INITB + 4 * 16, 0);
w16(INITB + 5 * 16, 0);
w16(INITB + 6 * 16, 0);
w16(INITB + 7 * 16, 0);
w16(INITB + 8 * 16, RXR >> 3 & 0xFFFF); /* ring addr low 16 */
w16(INITB + 9 * 16, 3 << 13); /* length only: 2^3 = 8 */
w16(INITB + 10 * 16, TXR >> 3 & 0xFFFF);
w16(INITB + 11 * 16, 3 << 13);
cell(0x040, INITB >> 3);
cell(0x060, RXR >> 3);
cell(0x080, TXR >> 3);
/* RX descriptors: all point at the same buffer */
for (i = 0; i < RING_ENTRIES; i++) {
w16(RXR + i * 64 + 0, (RXBUF >> 3) & 0xFFFF);
w16(RXR + i * 64 + 16, RMD1_OWN | (((RXBUF >> 3) >> 16) & 0xFF));
w16(RXR + i * 64 + 32, 0x0A0A); /* 1526 bytes */
w16(RXR + i * 64 + 48, 0);
}
/* one 24-byte frame (MAME's loopback accepts 8..32 bytes) */
wb(TXBUF + 0 * 8, 0x08); wb(TXBUF + 1 * 8, 0x00);
wb(TXBUF + 2 * 8, 0x2b); wb(TXBUF + 3 * 8, 0x16);
wb(TXBUF + 4 * 8, 0x4b); wb(TXBUF + 5 * 8, 0x5a);
wb(TXBUF + 6 * 8, 0x02); wb(TXBUF + 7 * 8, 0x00);
wb(TXBUF + 8 * 8, 0x00); wb(TXBUF + 9 * 8, 0x00);
wb(TXBUF + 10 * 8, 0x00); wb(TXBUF + 11 * 8, 0x01);
wb(TXBUF + 12 * 8, 0x88); wb(TXBUF + 13 * 8, 0xB5);
wb(TXBUF + 14 * 8, 0xDE); wb(TXBUF + 15 * 8, 0xAD);
wb(TXBUF + 16 * 8, 0xBE); wb(TXBUF + 17 * 8, 0xEF);
for (i = 18; i < 24; i++) {
wb(TXBUF + i * 8, i);
}
w16(TXR + 0, (TXBUF >> 3) & 0xFFFF);
w16(TXR + 16, TMD1_OWN | TMD1_STP | TMD1_ENP | (((TXBUF >> 3) >> 16) & 0xFF));
w16(TXR + 32, 0x0FE8); /* 24 bytes */
w16(TXR + 48, 0);
csr_w(1, (INITB >> 3) & 0xFFFF);
csr_w(2, ((INITB >> 3) >> 16) & 0xFF);
csr_w(3, 0);
csr_w(0, CSR0_INIT);
dot(1);
r = 0;
for (idle = 0; idle < 20000; idle++) {
r = csr_r(0);
if (r & CSR0_IDON) {
break;
}
}
cell(0x140, r);
if (!(r & CSR0_IDON)) {
cell(0x000, 0xEE);
return 1;
}
dot(2);
for (idle = 0; idle < 50000; idle++) {
}
csr_w(0, CSR0_STRT);
for (idle = 0; idle < 50000; idle++) {
}
/* own the TX descriptor now, then kick with STRT|TDMD in ONE write:
* CSR0 is a command shadow, a bare TDMD write clears STRT. */
w16(TXR + 16, TMD1_OWN | TMD1_STP | TMD1_ENP | (((TXBUF >> 3) >> 16) & 0xFF));
csr_w(0, CSR0_TDMD); /* BARE TDMD: no STRT, no INEA */
dot(3);
s0[0] = csr_r(0);
for (idle = 0; idle < 100000; idle++) {
if (!(r16(RXR + 16) & RMD1_OWN)) {
dot(4);
break;
}
if (!(r16(TXR + 16) & TMD1_OWN)) {
dot(4);
}
}
s0[1] = csr_r(0);
rxmd1 = r16(RXR + 16);
mcnt = r16(RXR + 48) & 0x0FFF;
txmd1 = r16(TXR + 16);
ok = 0;
if (*(unsigned char *)(RXBUF + 12 * 8) == 0x88 &&
*(unsigned char *)(RXBUF + 13 * 8) == 0xB5 &&
*(unsigned char *)(RXBUF + 14 * 8) == 0xDE) {
ok = 1;
}
cell(0x160, s0[0]);
cell(0x180, s0[1]);
cell(0x200, rxmd1);
cell(0x220, mcnt);
cell(0x240, txmd1);
cell(0x1A0, ok);
bits16(120, 670, s0[0]);
bits16(120, 700, s0[1]);
bits16(120, 730, txmd1);
bits16(120, 760, rxmd1);
bits16(120, 790, mcnt);
if (ok) {
rect(120, 850, 24, 24, 0xFF);
}
if (!(txmd1 & TMD1_OWN)) {
rect(160, 850, 24, 24, 0xFF);
}
/* best effort: give the chip back to the OS */
{
unsigned int osblk;
osblk = *(unsigned int *)0x23E7A0;
if (osblk != 0) {
csr_w(0, CSR0_STOP);
csr_w(1, osblk & 0xFFFF);
csr_w(2, (osblk >> 16) & 0xFF);
csr_w(3, 0);
csr_w(0, CSR0_INIT);
for (idle = 0; idle < 10000; idle++) {
r = csr_r(0);
if (r & CSR0_IDON) {
break;
}
}
csr_w(0, 0x0042);
}
}
return 0;
}
+274
View File
@@ -0,0 +1,274 @@
/*
* lance.c -- LANCE loopback probe using LOW-MEMORY rings.
*
* The OS's own init block (pointer at bit 0x23E7A0 -> byte 0xF80 on a live
* boot) puts its rings at byte 0x0A80 / 0x0E80 and stores only the LENGTH in
* the ring words' bits 13-15 (0xE000 / 0xA000), with no address bits above 16.
* MAME's model packs address bits 16+ into that word; if the real C-LANCE does
* not, a high ring address like 0x380028 is seen as 0x0028. This payload
* therefore places the block, rings and buffers below 64 KB.
*
* Memory used (BYTE addresses, well below 64 KB):
* 0x3000 init block (24 B) 0x3020 RX ring (8 * 8 B) 0x3060 TX ring (8 B)
* 0x3100 TX frame (24 B) 0x3200 RX buffer (2048 B)
*
* Result cells at 0x018C8000 (32-bit): 0x160/0x180 CSR0 samples,
* 0x200 RX RMD1, 0x220 RX MCNT, 0x240 TX TMD1, 0x1A0 payload-found flag.
*
* Panel rows (real unit, 256-byte stride): y 670/700 = CSR0, 730 = TMD1,
* 760 = RMD1, 790 = MCNT; blocks y 850: left = payload returned, right = TX done.
*/
#define RAP 0x05900000
#define RDP 0x05800000
#define MARK 0x018C8000
#define VRAM_BASE 0x04000000
#define MODE_LOOP 0x0004
#define MODE_INTL 0x0040
#define CSR0_STOP 0x0004
#define CSR0_STRT 0x0002
#define CSR0_INIT 0x0001
#define CSR0_TDMD 0x0008
#define CSR0_IDON 0x0100
#define RMD1_OWN 0x8000
#define TMD1_OWN 0x8000
#define TMD1_ENP 0x0100
#define TMD1_STP 0x0200
#define B(byte) ((unsigned int)(byte) * 8) /* bit address of a byte */
#define INITB B(0x3000)
#define RXR B(0x3020)
#define TXR B(0x3060)
#define RXBUF B(0x3200)
#define TXBUF B(0x3180)
#define RING_ENTRIES 8
static void cell(off, v)
unsigned int off, v;
{
*(unsigned int *)(MARK + off) = v;
}
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;
}
static void rect(x, y, w, h, v)
unsigned int x, y, w, h, v;
{
unsigned int row, col;
for (row = 0; row < h; row++) {
for (col = 0; col < w; col = col + 8) {
*(unsigned char *)(VRAM_BASE + (y + row) * 2048 + x + col) = v;
}
}
}
static void bits16(x, y, v)
unsigned int x, y, v;
{
unsigned int b;
for (b = 0; b < 16; b++) {
rect(x + b * 30, y, 24, 24, (v & (1 << b)) ? 0xFF : 0x00);
}
}
static void dot(n)
unsigned int n;
{
rect(60, 650 + n * 44, 24, 24, 0xFF);
}
static void settle()
{
unsigned int i;
for (i = 0; i < 30; i++) {
}
}
static void csr_w(reg, v)
unsigned int reg, v;
{
*(unsigned short *)RAP = reg;
settle();
*(unsigned short *)RDP = v;
settle();
}
static unsigned int csr_r(reg)
unsigned int reg;
{
unsigned int v;
*(unsigned short *)RAP = reg;
settle();
v = *(unsigned short *)RDP;
settle();
return v;
}
int main()
{
unsigned int i, r, idle, ok, rxmd1, txmd1, mcnt;
unsigned int s0[2];
cell(0x000, 1);
dot(0);
csr_w(0, CSR0_STOP);
rect(100, 650, 512, 256, 0x00);
rect(100, 650, 512, 4, 0xFF);
rect(100, 902, 512, 4, 0xFF);
rect(100, 650, 8, 256, 0xFF);
rect(608, 650, 8, 256, 0xFF);
/* init block: mode, PADR, LADRF, RX addr/len, TX addr/len */
w16(INITB + 0 * 16, MODE_LOOP | MODE_INTL);
w16(INITB + 1 * 16, 0x0008);
w16(INITB + 2 * 16, 0x162b);
w16(INITB + 3 * 16, 0x5a4b);
w16(INITB + 4 * 16, 0);
w16(INITB + 5 * 16, 0);
w16(INITB + 6 * 16, 0);
w16(INITB + 7 * 16, 0);
w16(INITB + 8 * 16, RXR >> 3 & 0xFFFF); /* ring addr low 16 */
w16(INITB + 9 * 16, 3 << 13); /* length only: 2^3 = 8 */
w16(INITB + 10 * 16, TXR >> 3 & 0xFFFF);
w16(INITB + 11 * 16, 3 << 13);
cell(0x040, INITB >> 3);
cell(0x060, RXR >> 3);
cell(0x080, TXR >> 3);
/* RX descriptors: all point at the same buffer */
for (i = 0; i < RING_ENTRIES; i++) {
w16(RXR + i * 64 + 0, (RXBUF >> 3) & 0xFFFF);
w16(RXR + i * 64 + 16, RMD1_OWN | (((RXBUF >> 3) >> 16) & 0xFF));
w16(RXR + i * 64 + 32, 0xFA0A); /* 0xF000 | -1526: top nibble must be ones */
w16(RXR + i * 64 + 48, 0);
}
/* one 24-byte frame (MAME's loopback accepts 8..32 bytes) */
wb(TXBUF + 0 * 8, 0x08); wb(TXBUF + 1 * 8, 0x00);
wb(TXBUF + 2 * 8, 0x2b); wb(TXBUF + 3 * 8, 0x16);
wb(TXBUF + 4 * 8, 0x4b); wb(TXBUF + 5 * 8, 0x5a);
wb(TXBUF + 6 * 8, 0x02); wb(TXBUF + 7 * 8, 0x00);
wb(TXBUF + 8 * 8, 0x00); wb(TXBUF + 9 * 8, 0x00);
wb(TXBUF + 10 * 8, 0x00); wb(TXBUF + 11 * 8, 0x01);
wb(TXBUF + 12 * 8, 0x88); wb(TXBUF + 13 * 8, 0xB5);
wb(TXBUF + 14 * 8, 0xDE); wb(TXBUF + 15 * 8, 0xAD);
wb(TXBUF + 16 * 8, 0xBE); wb(TXBUF + 17 * 8, 0xEF);
for (i = 18; i < 24; i++) {
wb(TXBUF + i * 8, i);
}
w16(TXR + 0, (TXBUF >> 3) & 0xFFFF);
w16(TXR + 16, TMD1_OWN | TMD1_STP | TMD1_ENP | (((TXBUF >> 3) >> 16) & 0xFF));
w16(TXR + 32, 0xFFE8); /* 0xF000 | -24: top nibble must be ones */
w16(TXR + 48, 0);
csr_w(1, (INITB >> 3) & 0xFFFF);
csr_w(2, ((INITB >> 3) >> 16) & 0xFF);
csr_w(3, 0);
csr_w(0, CSR0_INIT);
dot(1);
r = 0;
for (idle = 0; idle < 20000; idle++) {
r = csr_r(0);
if (r & CSR0_IDON) {
break;
}
}
cell(0x140, r);
if (!(r & CSR0_IDON)) {
cell(0x000, 0xEE);
return 1;
}
dot(2);
for (idle = 0; idle < 50000; idle++) {
}
csr_w(0, CSR0_STRT);
for (idle = 0; idle < 50000; idle++) {
}
/* own the TX descriptor now, then kick with STRT|TDMD in ONE write:
* CSR0 is a command shadow, a bare TDMD write clears STRT. */
w16(TXR + 16, TMD1_OWN | TMD1_STP | TMD1_ENP | (((TXBUF >> 3) >> 16) & 0xFF));
csr_w(0, CSR0_STRT | CSR0_TDMD);
dot(3);
s0[0] = csr_r(0);
for (idle = 0; idle < 100000; idle++) {
if (!(r16(RXR + 16) & RMD1_OWN)) {
dot(4);
break;
}
if (!(r16(TXR + 16) & TMD1_OWN)) {
dot(4);
}
}
s0[1] = csr_r(0);
rxmd1 = r16(RXR + 16);
mcnt = r16(RXR + 48) & 0x0FFF;
txmd1 = r16(TXR + 16);
ok = 0;
if (*(unsigned char *)(RXBUF + 12 * 8) == 0x88 &&
*(unsigned char *)(RXBUF + 13 * 8) == 0xB5 &&
*(unsigned char *)(RXBUF + 14 * 8) == 0xDE) {
ok = 1;
}
cell(0x160, s0[0]);
cell(0x180, s0[1]);
cell(0x200, rxmd1);
cell(0x220, mcnt);
cell(0x240, txmd1);
cell(0x1A0, ok);
bits16(120, 670, s0[0]);
bits16(120, 700, s0[1]);
bits16(120, 730, txmd1);
bits16(120, 760, rxmd1);
bits16(120, 790, mcnt);
if (ok) {
rect(120, 850, 24, 24, 0xFF);
}
if (!(txmd1 & TMD1_OWN)) {
rect(160, 850, 24, 24, 0xFF);
}
/* best effort: give the chip back to the OS */
{
unsigned int osblk;
osblk = *(unsigned int *)0x23E7A0;
if (osblk != 0) {
csr_w(0, CSR0_STOP);
csr_w(1, osblk & 0xFFFF);
csr_w(2, (osblk >> 16) & 0xFF);
csr_w(3, 0);
csr_w(0, CSR0_INIT);
for (idle = 0; idle < 10000; idle++) {
r = csr_r(0);
if (r & CSR0_IDON) {
break;
}
}
csr_w(0, 0x0042);
}
}
return 0;
}
+279
View File
@@ -0,0 +1,279 @@
/*
* lance.c -- LANCE loopback probe using LOW-MEMORY rings.
*
* The OS's own init block (pointer at bit 0x23E7A0 -> byte 0xF80 on a live
* boot) puts its rings at byte 0x0A80 / 0x0E80 and stores only the LENGTH in
* the ring words' bits 13-15 (0xE000 / 0xA000), with no address bits above 16.
* MAME's model packs address bits 16+ into that word; if the real C-LANCE does
* not, a high ring address like 0x380028 is seen as 0x0028. This payload
* therefore places the block, rings and buffers below 64 KB.
*
* Memory used (BYTE addresses, well below 64 KB):
* 0x3000 init block (24 B) 0x3020 RX ring (8 * 8 B) 0x3060 TX ring (8 B)
* 0x3100 TX frame (24 B) 0x3200 RX buffer (2048 B)
*
* Result cells at 0x018C8000 (32-bit): 0x160/0x180 CSR0 samples,
* 0x200 RX RMD1, 0x220 RX MCNT, 0x240 TX TMD1, 0x1A0 payload-found flag.
*
* Panel rows (real unit, 256-byte stride): y 670/700 = CSR0, 730 = TMD1,
* 760 = RMD1, 790 = MCNT; blocks y 850: left = payload returned, right = TX done.
*/
#define RAP 0x05900000
#define RDP 0x05800000
#define MARK 0x018C8000
#define VRAM_BASE 0x04000000
#define MODE_LOOP 0x0004
#define MODE_INTL 0x0040
#define CSR0_STOP 0x0004
#define CSR0_STRT 0x0002
#define CSR0_INIT 0x0001
#define CSR0_TDMD 0x0008
#define CSR0_IDON 0x0100
#define RMD1_OWN 0x8000
#define TMD1_OWN 0x8000
#define TMD1_ENP 0x0100
#define TMD1_STP 0x0200
#define B(byte) ((unsigned int)(byte) * 8) /* bit address of a byte */
#define INITB B(0x3000)
#define RXR B(0x3020)
#define TXR B(0x3060)
#define RXBUF B(0x3200)
#define TXBUF B(0x3180)
#define RING_ENTRIES 8
static void cell(off, v)
unsigned int off, v;
{
*(unsigned int *)(MARK + off) = v;
}
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;
}
static void rect(x, y, w, h, v)
unsigned int x, y, w, h, v;
{
unsigned int row, col;
for (row = 0; row < h; row++) {
for (col = 0; col < w; col = col + 8) {
*(unsigned char *)(VRAM_BASE + (y + row) * 2048 + x + col) = v;
}
}
}
static void bits16(x, y, v)
unsigned int x, y, v;
{
unsigned int b;
for (b = 0; b < 16; b++) {
rect(x + b * 30, y, 24, 24, (v & (1 << b)) ? 0xFF : 0x00);
}
}
static void dot(n)
unsigned int n;
{
rect(60, 650 + n * 44, 24, 24, 0xFF);
}
static void settle()
{
unsigned int i;
for (i = 0; i < 30; i++) {
}
}
static void csr_w(reg, v)
unsigned int reg, v;
{
*(unsigned short *)RAP = reg;
settle();
*(unsigned short *)RDP = v;
settle();
}
static unsigned int csr_r(reg)
unsigned int reg;
{
unsigned int v;
*(unsigned short *)RAP = reg;
settle();
v = *(unsigned short *)RDP;
settle();
return v;
}
int main()
{
unsigned int i, r, idle, ok, rxmd1, txmd1, mcnt;
unsigned int s0[2];
cell(0x000, 1);
dot(0);
csr_w(0, CSR0_STOP);
rect(100, 650, 512, 256, 0x00);
rect(100, 650, 512, 4, 0xFF);
rect(100, 902, 512, 4, 0xFF);
rect(100, 650, 8, 256, 0xFF);
rect(608, 650, 8, 256, 0xFF);
/* init block: mode, PADR, LADRF, RX addr/len, TX addr/len */
w16(INITB + 0 * 16, MODE_LOOP | MODE_INTL);
w16(INITB + 1 * 16, 0x0008);
w16(INITB + 2 * 16, 0x162b);
w16(INITB + 3 * 16, 0x5a4b);
w16(INITB + 4 * 16, 0);
w16(INITB + 5 * 16, 0);
w16(INITB + 6 * 16, 0);
w16(INITB + 7 * 16, 0);
w16(INITB + 8 * 16, RXR >> 3 & 0xFFFF); /* ring addr low 16 */
w16(INITB + 9 * 16, 3 << 13); /* length only: 2^3 = 8 */
w16(INITB + 10 * 16, TXR >> 3 & 0xFFFF);
w16(INITB + 11 * 16, 3 << 13);
cell(0x040, INITB >> 3);
cell(0x060, RXR >> 3);
cell(0x080, TXR >> 3);
/* RX descriptors: all point at the same buffer */
for (i = 0; i < RING_ENTRIES; i++) {
w16(RXR + i * 64 + 0, (RXBUF >> 3) & 0xFFFF);
w16(RXR + i * 64 + 16, RMD1_OWN | (((RXBUF >> 3) >> 16) & 0xFF));
w16(RXR + i * 64 + 32, 0x0A0A); /* 1526 bytes */
w16(RXR + i * 64 + 48, 0);
}
/* one 24-byte frame (MAME's loopback accepts 8..32 bytes) */
wb(TXBUF + 0 * 8, 0x08); wb(TXBUF + 1 * 8, 0x00);
wb(TXBUF + 2 * 8, 0x2b); wb(TXBUF + 3 * 8, 0x16);
wb(TXBUF + 4 * 8, 0x4b); wb(TXBUF + 5 * 8, 0x5a);
wb(TXBUF + 6 * 8, 0x02); wb(TXBUF + 7 * 8, 0x00);
wb(TXBUF + 8 * 8, 0x00); wb(TXBUF + 9 * 8, 0x00);
wb(TXBUF + 10 * 8, 0x00); wb(TXBUF + 11 * 8, 0x01);
wb(TXBUF + 12 * 8, 0x88); wb(TXBUF + 13 * 8, 0xB5);
wb(TXBUF + 14 * 8, 0xDE); wb(TXBUF + 15 * 8, 0xAD);
wb(TXBUF + 16 * 8, 0xBE); wb(TXBUF + 17 * 8, 0xEF);
for (i = 18; i < 24; i++) {
wb(TXBUF + i * 8, i);
}
w16(TXR + 0, (TXBUF >> 3) & 0xFFFF);
w16(TXR + 16, TMD1_OWN | TMD1_STP | TMD1_ENP | (((TXBUF >> 3) >> 16) & 0xFF));
w16(TXR + 32, 0x0FE8); /* 24 bytes */
w16(TXR + 48, 0);
csr_w(1, (INITB >> 3) & 0xFFFF);
csr_w(2, ((INITB >> 3) >> 16) & 0xFF);
csr_w(3, 0);
csr_w(0, CSR0_INIT);
dot(1);
r = 0;
for (idle = 0; idle < 20000; idle++) {
r = csr_r(0);
if (r & CSR0_IDON) {
break;
}
}
cell(0x140, r);
if (!(r & CSR0_IDON)) {
cell(0x000, 0xEE);
return 1;
}
/* acknowledge IDON (write 1 to clear it) before starting, as Linux's
* drivers/net/ethernet/amd/7990.c does */
csr_w(0, CSR0_IDON);
for (idle = 0; idle < 2000; idle++) {
}
dot(2);
for (idle = 0; idle < 50000; idle++) {
}
csr_w(0, CSR0_STRT);
for (idle = 0; idle < 50000; idle++) {
}
/* own the TX descriptor now, then kick with STRT|TDMD in ONE write:
* CSR0 is a command shadow, a bare TDMD write clears STRT. */
w16(TXR + 16, TMD1_OWN | TMD1_STP | TMD1_ENP | (((TXBUF >> 3) >> 16) & 0xFF));
csr_w(0, CSR0_STRT | CSR0_TDMD);
dot(3);
s0[0] = csr_r(0);
for (idle = 0; idle < 100000; idle++) {
if (!(r16(RXR + 16) & RMD1_OWN)) {
dot(4);
break;
}
if (!(r16(TXR + 16) & TMD1_OWN)) {
dot(4);
}
}
s0[1] = csr_r(0);
rxmd1 = r16(RXR + 16);
mcnt = r16(RXR + 48) & 0x0FFF;
txmd1 = r16(TXR + 16);
ok = 0;
if (*(unsigned char *)(RXBUF + 12 * 8) == 0x88 &&
*(unsigned char *)(RXBUF + 13 * 8) == 0xB5 &&
*(unsigned char *)(RXBUF + 14 * 8) == 0xDE) {
ok = 1;
}
cell(0x160, s0[0]);
cell(0x180, s0[1]);
cell(0x200, rxmd1);
cell(0x220, mcnt);
cell(0x240, txmd1);
cell(0x1A0, ok);
bits16(120, 670, s0[0]);
bits16(120, 700, s0[1]);
bits16(120, 730, txmd1);
bits16(120, 760, rxmd1);
bits16(120, 790, mcnt);
if (ok) {
rect(120, 850, 24, 24, 0xFF);
}
if (!(txmd1 & TMD1_OWN)) {
rect(160, 850, 24, 24, 0xFF);
}
/* best effort: give the chip back to the OS */
{
unsigned int osblk;
osblk = *(unsigned int *)0x23E7A0;
if (osblk != 0) {
csr_w(0, CSR0_STOP);
csr_w(1, osblk & 0xFFFF);
csr_w(2, (osblk >> 16) & 0xFF);
csr_w(3, 0);
csr_w(0, CSR0_INIT);
for (idle = 0; idle < 10000; idle++) {
r = csr_r(0);
if (r & CSR0_IDON) {
break;
}
}
csr_w(0, 0x0042);
}
}
return 0;
}
+275
View File
@@ -0,0 +1,275 @@
/*
* lance.c -- LANCE loopback probe using LOW-MEMORY rings.
*
* The OS's own init block (pointer at bit 0x23E7A0 -> byte 0xF80 on a live
* boot) puts its rings at byte 0x0A80 / 0x0E80 and stores only the LENGTH in
* the ring words' bits 13-15 (0xE000 / 0xA000), with no address bits above 16.
* MAME's model packs address bits 16+ into that word; if the real C-LANCE does
* not, a high ring address like 0x380028 is seen as 0x0028. This payload
* therefore places the block, rings and buffers below 64 KB.
*
* Memory used (BYTE addresses, well below 64 KB):
* 0x3000 init block (24 B) 0x3020 RX ring (8 * 8 B) 0x3060 TX ring (8 B)
* 0x3100 TX frame (24 B) 0x3200 RX buffer (2048 B)
*
* Result cells at 0x018C8000 (32-bit): 0x160/0x180 CSR0 samples,
* 0x200 RX RMD1, 0x220 RX MCNT, 0x240 TX TMD1, 0x1A0 payload-found flag.
*
* Panel rows (real unit, 256-byte stride): y 670/700 = CSR0, 730 = TMD1,
* 760 = RMD1, 790 = MCNT; blocks y 850: left = payload returned, right = TX done.
*/
#define RAP 0x05900000
#define RDP 0x05800000
#define MARK 0x018C8000
#define VRAM_BASE 0x04000000
#define MODE_LOOP 0x0004
#define MODE_INTL 0x0040
#define CSR0_STOP 0x0004
#define CSR0_STRT 0x0002
#define CSR0_INIT 0x0001
#define CSR0_TDMD 0x0008
#define CSR0_IDON 0x0100
#define RMD1_OWN 0x8000
#define TMD1_OWN 0x8000
#define TMD1_ENP 0x0100
#define TMD1_STP 0x0200
#define B(byte) ((unsigned int)(byte) * 8) /* bit address of a byte */
#define INITB B(0x3000)
#define RXR B(0x3020)
#define TXR B(0x3060)
#define RXBUF B(0x3200)
#define TXBUF B(0x3180)
#define RING_ENTRIES 8
static void cell(off, v)
unsigned int off, v;
{
*(unsigned int *)(MARK + off) = v;
}
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;
}
static void rect(x, y, w, h, v)
unsigned int x, y, w, h, v;
{
unsigned int row, col;
for (row = 0; row < h; row++) {
for (col = 0; col < w; col = col + 8) {
*(unsigned char *)(VRAM_BASE + (y + row) * 2048 + x + col) = v;
}
}
}
static void bits16(x, y, v)
unsigned int x, y, v;
{
unsigned int b;
for (b = 0; b < 16; b++) {
rect(x + b * 30, y, 24, 24, (v & (1 << b)) ? 0xFF : 0x00);
}
}
static void dot(n)
unsigned int n;
{
rect(60, 650 + n * 44, 24, 24, 0xFF);
}
static void settle()
{
unsigned int i;
for (i = 0; i < 30; i++) {
}
}
static void csr_w(reg, v)
unsigned int reg, v;
{
*(unsigned short *)RAP = reg;
settle();
*(unsigned short *)RDP = v;
settle();
}
static unsigned int csr_r(reg)
unsigned int reg;
{
unsigned int v;
*(unsigned short *)RAP = reg;
settle();
v = *(unsigned short *)RDP;
settle();
return v;
}
int main()
{
unsigned int i, r, idle, ok, rxmd1, txmd1, mcnt;
unsigned int s0[2];
cell(0x000, 1);
dot(0);
csr_w(0, CSR0_STOP);
rect(100, 650, 512, 256, 0x00);
rect(100, 650, 512, 4, 0xFF);
rect(100, 902, 512, 4, 0xFF);
rect(100, 650, 8, 256, 0xFF);
rect(608, 650, 8, 256, 0xFF);
/* init block: mode, PADR, LADRF, RX addr/len, TX addr/len */
w16(INITB + 0 * 16, MODE_LOOP | MODE_INTL);
w16(INITB + 1 * 16, 0x0008);
w16(INITB + 2 * 16, 0x162b);
w16(INITB + 3 * 16, 0x5a4b);
w16(INITB + 4 * 16, 0);
w16(INITB + 5 * 16, 0);
w16(INITB + 6 * 16, 0);
w16(INITB + 7 * 16, 0);
w16(INITB + 8 * 16, RXR >> 3 & 0xFFFF); /* ring addr low 16 */
w16(INITB + 9 * 16, 3 << 13); /* length only: 2^3 = 8 */
w16(INITB + 10 * 16, TXR >> 3 & 0xFFFF);
w16(INITB + 11 * 16, 3 << 13);
cell(0x040, INITB >> 3);
cell(0x060, RXR >> 3);
cell(0x080, TXR >> 3);
/* RX descriptors: all point at the same buffer */
for (i = 0; i < RING_ENTRIES; i++) {
w16(RXR + i * 64 + 0, (RXBUF >> 3) & 0xFFFF);
w16(RXR + i * 64 + 16, RMD1_OWN | (((RXBUF >> 3) >> 16) & 0xFF));
w16(RXR + i * 64 + 32, 0x0A0A); /* 1526 bytes */
w16(RXR + i * 64 + 48, 0);
}
/* one 24-byte frame (MAME's loopback accepts 8..32 bytes) */
wb(TXBUF + 0 * 8, 0x08); wb(TXBUF + 1 * 8, 0x00);
wb(TXBUF + 2 * 8, 0x2b); wb(TXBUF + 3 * 8, 0x16);
wb(TXBUF + 4 * 8, 0x4b); wb(TXBUF + 5 * 8, 0x5a);
wb(TXBUF + 6 * 8, 0x02); wb(TXBUF + 7 * 8, 0x00);
wb(TXBUF + 8 * 8, 0x00); wb(TXBUF + 9 * 8, 0x00);
wb(TXBUF + 10 * 8, 0x00); wb(TXBUF + 11 * 8, 0x01);
wb(TXBUF + 12 * 8, 0x88); wb(TXBUF + 13 * 8, 0xB5);
wb(TXBUF + 14 * 8, 0xDE); wb(TXBUF + 15 * 8, 0xAD);
wb(TXBUF + 16 * 8, 0xBE); wb(TXBUF + 17 * 8, 0xEF);
for (i = 18; i < 24; i++) {
wb(TXBUF + i * 8, i);
}
w16(TXR + 0, (TXBUF >> 3) & 0xFFFF);
w16(TXR + 16, TMD1_OWN | TMD1_STP | TMD1_ENP | (((TXBUF >> 3) >> 16) & 0xFF));
w16(TXR + 32, 0x0FE8); /* 24 bytes */
w16(TXR + 48, 0);
csr_w(1, (INITB >> 3) & 0xFFFF);
csr_w(2, ((INITB >> 3) >> 16) & 0xFF);
csr_w(3, 0);
csr_w(0, CSR0_INIT);
csr_w(0, CSR0_INIT); /* second INIT before IDON: does the chip keep INIT set? */
dot(1);
r = 0;
for (idle = 0; idle < 20000; idle++) {
r = csr_r(0);
if (r & CSR0_IDON) {
break;
}
}
cell(0x140, r);
if (!(r & CSR0_IDON)) {
cell(0x000, 0xEE);
return 1;
}
dot(2);
for (idle = 0; idle < 50000; idle++) {
}
csr_w(0, CSR0_STRT);
for (idle = 0; idle < 50000; idle++) {
}
/* own the TX descriptor now, then kick with STRT|TDMD in ONE write:
* CSR0 is a command shadow, a bare TDMD write clears STRT. */
w16(TXR + 16, TMD1_OWN | TMD1_STP | TMD1_ENP | (((TXBUF >> 3) >> 16) & 0xFF));
csr_w(0, CSR0_STRT | CSR0_TDMD);
dot(3);
s0[0] = csr_r(0);
for (idle = 0; idle < 100000; idle++) {
if (!(r16(RXR + 16) & RMD1_OWN)) {
dot(4);
break;
}
if (!(r16(TXR + 16) & TMD1_OWN)) {
dot(4);
}
}
s0[1] = csr_r(0);
rxmd1 = r16(RXR + 16);
mcnt = r16(RXR + 48) & 0x0FFF;
txmd1 = r16(TXR + 16);
ok = 0;
if (*(unsigned char *)(RXBUF + 12 * 8) == 0x88 &&
*(unsigned char *)(RXBUF + 13 * 8) == 0xB5 &&
*(unsigned char *)(RXBUF + 14 * 8) == 0xDE) {
ok = 1;
}
cell(0x160, s0[0]);
cell(0x180, s0[1]);
cell(0x200, rxmd1);
cell(0x220, mcnt);
cell(0x240, txmd1);
cell(0x1A0, ok);
bits16(120, 670, s0[0]);
bits16(120, 700, s0[1]);
bits16(120, 730, txmd1);
bits16(120, 760, rxmd1);
bits16(120, 790, mcnt);
if (ok) {
rect(120, 850, 24, 24, 0xFF);
}
if (!(txmd1 & TMD1_OWN)) {
rect(160, 850, 24, 24, 0xFF);
}
/* best effort: give the chip back to the OS */
{
unsigned int osblk;
osblk = *(unsigned int *)0x23E7A0;
if (osblk != 0) {
csr_w(0, CSR0_STOP);
csr_w(1, osblk & 0xFFFF);
csr_w(2, (osblk >> 16) & 0xFF);
csr_w(3, 0);
csr_w(0, CSR0_INIT);
for (idle = 0; idle < 10000; idle++) {
r = csr_r(0);
if (r & CSR0_IDON) {
break;
}
}
csr_w(0, 0x0042);
}
}
return 0;
}
+288
View File
@@ -0,0 +1,288 @@
/*
* lance.c -- LANCE loopback probe using LOW-MEMORY rings.
*
* The OS's own init block (pointer at bit 0x23E7A0 -> byte 0xF80 on a live
* boot) puts its rings at byte 0x0A80 / 0x0E80 and stores only the LENGTH in
* the ring words' bits 13-15 (0xE000 / 0xA000), with no address bits above 16.
* MAME's model packs address bits 16+ into that word; if the real C-LANCE does
* not, a high ring address like 0x380028 is seen as 0x0028. This payload
* therefore places the block, rings and buffers below 64 KB.
*
* Memory used (BYTE addresses, well below 64 KB):
* 0x3000 init block (24 B) 0x3020 RX ring (8 * 8 B) 0x3060 TX ring (8 B)
* 0x3100 TX frame (24 B) 0x3200 RX buffer (2048 B)
*
* Result cells at 0x018C8000 (32-bit): 0x160/0x180 CSR0 samples,
* 0x200 RX RMD1, 0x220 RX MCNT, 0x240 TX TMD1, 0x1A0 payload-found flag.
*
* Panel rows (real unit, 256-byte stride): y 670/700 = CSR0, 730 = TMD1,
* 760 = RMD1, 790 = MCNT; blocks y 850: left = payload returned, right = TX done.
*/
#define RAP 0x05900000
#define RDP 0x05800000
#define MARK 0x018C8000
#define VRAM_BASE 0x04000000
#define MODE_LOOP 0x0004
#define MODE_INTL 0x0040
#define CSR0_STOP 0x0004
#define CSR0_STRT 0x0002
#define CSR0_INIT 0x0001
#define CSR0_TDMD 0x0008
#define CSR0_IDON 0x0100
#define RMD1_OWN 0x8000
#define TMD1_OWN 0x8000
#define TMD1_ENP 0x0100
#define TMD1_STP 0x0200
#define B(byte) ((unsigned int)(byte) * 8) /* bit address of a byte */
#define INITB B(0x3000)
#define RXR B(0x3020)
#define TXR B(0x3060)
#define RXBUF B(0x3200)
#define TXBUF B(0x3180)
#define RING_ENTRIES 8
static void cell(off, v)
unsigned int off, v;
{
*(unsigned int *)(MARK + off) = v;
}
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;
}
static void rect(x, y, w, h, v)
unsigned int x, y, w, h, v;
{
unsigned int row, col;
for (row = 0; row < h; row++) {
for (col = 0; col < w; col = col + 8) {
*(unsigned char *)(VRAM_BASE + (y + row) * 2048 + x + col) = v;
}
}
}
static void bits16(x, y, v)
unsigned int x, y, v;
{
unsigned int b;
for (b = 0; b < 16; b++) {
rect(x + b * 30, y, 24, 24, (v & (1 << b)) ? 0xFF : 0x00);
}
}
static void dot(n)
unsigned int n;
{
rect(60, 650 + n * 44, 24, 24, 0xFF);
}
static void settle()
{
unsigned int i;
for (i = 0; i < 30; i++) {
}
}
static void csr_w(reg, v)
unsigned int reg, v;
{
*(unsigned short *)RAP = reg;
settle();
*(unsigned short *)RDP = v;
settle();
}
static unsigned int csr_r(reg)
unsigned int reg;
{
unsigned int v;
*(unsigned short *)RAP = reg;
settle();
v = *(unsigned short *)RDP;
settle();
return v;
}
int main()
{
unsigned int i, r, idle, ok, rxmd1, txmd1, mcnt, tdmd_seen, samples;
unsigned int s0[2];
cell(0x000, 1);
dot(0);
csr_w(0, CSR0_STOP);
rect(100, 650, 512, 256, 0x00);
rect(100, 650, 512, 4, 0xFF);
rect(100, 902, 512, 4, 0xFF);
rect(100, 650, 8, 256, 0xFF);
rect(608, 650, 8, 256, 0xFF);
/* init block: mode, PADR, LADRF, RX addr/len, TX addr/len */
w16(INITB + 0 * 16, MODE_LOOP | MODE_INTL);
w16(INITB + 1 * 16, 0x0008);
w16(INITB + 2 * 16, 0x162b);
w16(INITB + 3 * 16, 0x5a4b);
w16(INITB + 4 * 16, 0);
w16(INITB + 5 * 16, 0);
w16(INITB + 6 * 16, 0);
w16(INITB + 7 * 16, 0);
w16(INITB + 8 * 16, RXR >> 3 & 0xFFFF); /* ring addr low 16 */
w16(INITB + 9 * 16, 3 << 13); /* length only: 2^3 = 8 */
w16(INITB + 10 * 16, TXR >> 3 & 0xFFFF);
w16(INITB + 11 * 16, 3 << 13);
cell(0x040, INITB >> 3);
cell(0x060, RXR >> 3);
cell(0x080, TXR >> 3);
/* RX descriptors: all point at the same buffer */
for (i = 0; i < RING_ENTRIES; i++) {
w16(RXR + i * 64 + 0, (RXBUF >> 3) & 0xFFFF);
w16(RXR + i * 64 + 16, RMD1_OWN | (((RXBUF >> 3) >> 16) & 0xFF));
w16(RXR + i * 64 + 32, 0x0A0A); /* 1526 bytes */
w16(RXR + i * 64 + 48, 0);
}
/* one 24-byte frame (MAME's loopback accepts 8..32 bytes) */
wb(TXBUF + 0 * 8, 0x08); wb(TXBUF + 1 * 8, 0x00);
wb(TXBUF + 2 * 8, 0x2b); wb(TXBUF + 3 * 8, 0x16);
wb(TXBUF + 4 * 8, 0x4b); wb(TXBUF + 5 * 8, 0x5a);
wb(TXBUF + 6 * 8, 0x02); wb(TXBUF + 7 * 8, 0x00);
wb(TXBUF + 8 * 8, 0x00); wb(TXBUF + 9 * 8, 0x00);
wb(TXBUF + 10 * 8, 0x00); wb(TXBUF + 11 * 8, 0x01);
wb(TXBUF + 12 * 8, 0x88); wb(TXBUF + 13 * 8, 0xB5);
wb(TXBUF + 14 * 8, 0xDE); wb(TXBUF + 15 * 8, 0xAD);
wb(TXBUF + 16 * 8, 0xBE); wb(TXBUF + 17 * 8, 0xEF);
for (i = 18; i < 24; i++) {
wb(TXBUF + i * 8, i);
}
w16(TXR + 0, (TXBUF >> 3) & 0xFFFF);
w16(TXR + 16, TMD1_OWN | TMD1_STP | TMD1_ENP | (((TXBUF >> 3) >> 16) & 0xFF));
w16(TXR + 32, 0x0FE8); /* 24 bytes */
w16(TXR + 48, 0);
csr_w(1, (INITB >> 3) & 0xFFFF);
csr_w(2, ((INITB >> 3) >> 16) & 0xFF);
csr_w(3, 0);
csr_w(0, CSR0_INIT);
dot(1);
r = 0;
for (idle = 0; idle < 20000; idle++) {
r = csr_r(0);
if (r & CSR0_IDON) {
break;
}
}
cell(0x140, r);
if (!(r & CSR0_IDON)) {
cell(0x000, 0xEE);
return 1;
}
dot(2);
for (idle = 0; idle < 50000; idle++) {
}
csr_w(0, CSR0_STRT);
for (idle = 0; idle < 50000; idle++) {
}
/* own the TX descriptor now, then kick with STRT|TDMD in ONE write:
* CSR0 is a command shadow, a bare TDMD write clears STRT. */
w16(TXR + 16, TMD1_OWN | TMD1_STP | TMD1_ENP | (((TXBUF >> 3) >> 16) & 0xFF));
csr_w(0, CSR0_STRT | CSR0_TDMD);
dot(3);
/* long wait (600k iterations) with sparse CSR0 sampling: 600 polls */
tdmd_seen = 0;
samples = 0;
for (idle = 0; idle < 600000; idle++) {
if ((idle % 1000) == 0) {
r = csr_r(0);
samples = samples + 1;
if (r & CSR0_TDMD) {
tdmd_seen = tdmd_seen + 1;
}
}
}
cell(0x1C0, csr_r(0)); /* CSR0 after the long wait */
cell(0x1E0, tdmd_seen); /* polls that still saw TDMD set (of `samples`) */
s0[0] = csr_r(0);
for (idle = 0; idle < 100000; idle++) {
if (!(r16(RXR + 16) & RMD1_OWN)) {
dot(4);
break;
}
if (!(r16(TXR + 16) & TMD1_OWN)) {
dot(4);
}
}
s0[1] = csr_r(0);
rxmd1 = r16(RXR + 16);
mcnt = r16(RXR + 48) & 0x0FFF;
txmd1 = r16(TXR + 16);
ok = 0;
if (*(unsigned char *)(RXBUF + 12 * 8) == 0x88 &&
*(unsigned char *)(RXBUF + 13 * 8) == 0xB5 &&
*(unsigned char *)(RXBUF + 14 * 8) == 0xDE) {
ok = 1;
}
cell(0x160, s0[0]);
cell(0x180, s0[1]);
cell(0x200, rxmd1);
cell(0x220, mcnt);
cell(0x240, txmd1);
cell(0x1A0, ok);
bits16(120, 670, s0[0]);
bits16(120, 700, s0[1]);
bits16(120, 730, txmd1);
bits16(120, 760, rxmd1);
bits16(120, 790, mcnt);
if (ok) {
rect(120, 850, 24, 24, 0xFF);
}
if (!(txmd1 & TMD1_OWN)) {
rect(160, 850, 24, 24, 0xFF);
}
/* best effort: give the chip back to the OS */
{
unsigned int osblk;
osblk = *(unsigned int *)0x23E7A0;
if (osblk != 0) {
csr_w(0, CSR0_STOP);
csr_w(1, osblk & 0xFFFF);
csr_w(2, (osblk >> 16) & 0xFF);
csr_w(3, 0);
csr_w(0, CSR0_INIT);
for (idle = 0; idle < 10000; idle++) {
r = csr_r(0);
if (r & CSR0_IDON) {
break;
}
}
csr_w(0, 0x0042);
}
}
return 0;
}
+275
View File
@@ -0,0 +1,275 @@
/*
* lance.c -- LANCE loopback probe using LOW-MEMORY rings.
*
* The OS's own init block (pointer at bit 0x23E7A0 -> byte 0xF80 on a live
* boot) puts its rings at byte 0x0A80 / 0x0E80 and stores only the LENGTH in
* the ring words' bits 13-15 (0xE000 / 0xA000), with no address bits above 16.
* MAME's model packs address bits 16+ into that word; if the real C-LANCE does
* not, a high ring address like 0x380028 is seen as 0x0028. This payload
* therefore places the block, rings and buffers below 64 KB.
*
* Memory used (BYTE addresses, well below 64 KB):
* 0x3000 init block (24 B) 0x3020 RX ring (8 * 8 B) 0x3060 TX ring (8 B)
* 0x3100 TX frame (24 B) 0x3200 RX buffer (2048 B)
*
* Result cells at 0x018C8000 (32-bit): 0x160/0x180 CSR0 samples,
* 0x200 RX RMD1, 0x220 RX MCNT, 0x240 TX TMD1, 0x1A0 payload-found flag.
*
* Panel rows (real unit, 256-byte stride): y 670/700 = CSR0, 730 = TMD1,
* 760 = RMD1, 790 = MCNT; blocks y 850: left = payload returned, right = TX done.
*/
#define RAP 0x05900000
#define RDP 0x05800000
#define MARK 0x018C8000
#define VRAM_BASE 0x04000000
#define MODE_LOOP 0x0004
#define MODE_INTL 0x0040
#define CSR0_STOP 0x0004
#define CSR0_STRT 0x0002
#define CSR0_INIT 0x0001
#define CSR0_TDMD 0x0008
#define CSR0_IDON 0x0100
#define RMD1_OWN 0x8000
#define TMD1_OWN 0x8000
#define TMD1_ENP 0x0100
#define TMD1_STP 0x0200
#define B(byte) ((unsigned int)(byte) * 8) /* bit address of a byte */
#define INITB B(0x3000)
#define RXR B(0x3020)
#define TXR B(0x3060)
#define RXBUF B(0x3200)
#define TXBUF B(0x3180)
#define RING_ENTRIES 8
static void cell(off, v)
unsigned int off, v;
{
*(unsigned int *)(MARK + off) = v;
}
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;
}
static void rect(x, y, w, h, v)
unsigned int x, y, w, h, v;
{
unsigned int row, col;
for (row = 0; row < h; row++) {
for (col = 0; col < w; col = col + 8) {
*(unsigned char *)(VRAM_BASE + (y + row) * 2048 + x + col) = v;
}
}
}
static void bits16(x, y, v)
unsigned int x, y, v;
{
unsigned int b;
for (b = 0; b < 16; b++) {
rect(x + b * 30, y, 24, 24, (v & (1 << b)) ? 0xFF : 0x00);
}
}
static void dot(n)
unsigned int n;
{
rect(60, 650 + n * 44, 24, 24, 0xFF);
}
static void settle()
{
unsigned int i;
for (i = 0; i < 30; i++) {
}
}
static void csr_w(reg, v)
unsigned int reg, v;
{
*(unsigned short *)RAP = reg;
settle();
*(unsigned short *)RDP = v;
settle();
}
static unsigned int csr_r(reg)
unsigned int reg;
{
unsigned int v;
*(unsigned short *)RAP = reg;
settle();
v = *(unsigned short *)RDP;
settle();
return v;
}
int main()
{
unsigned int i, r, idle, ok, rxmd1, txmd1, mcnt;
unsigned int s0[2];
cell(0x000, 1);
dot(0);
csr_w(0, CSR0_STOP);
rect(100, 650, 512, 256, 0x00);
rect(100, 650, 512, 4, 0xFF);
rect(100, 902, 512, 4, 0xFF);
rect(100, 650, 8, 256, 0xFF);
rect(608, 650, 8, 256, 0xFF);
/* init block: mode, PADR, LADRF, RX addr/len, TX addr/len */
w16(INITB + 0 * 16, MODE_LOOP | MODE_INTL);
w16(INITB + 1 * 16, 0x0008);
w16(INITB + 2 * 16, 0x162b);
w16(INITB + 3 * 16, 0x5a4b);
w16(INITB + 4 * 16, 0);
w16(INITB + 5 * 16, 0);
w16(INITB + 6 * 16, 0);
w16(INITB + 7 * 16, 0);
w16(INITB + 8 * 16, RXR >> 3 & 0xFFFF); /* ring addr low 16 */
w16(INITB + 9 * 16, 3 << 13); /* length only: 2^3 = 8 */
w16(INITB + 10 * 16, TXR >> 3 & 0xFFFF);
w16(INITB + 11 * 16, 3 << 13);
cell(0x040, INITB >> 3);
cell(0x060, RXR >> 3);
cell(0x080, TXR >> 3);
/* RX descriptors: all point at the same buffer */
for (i = 0; i < RING_ENTRIES; i++) {
w16(RXR + i * 64 + 0, (RXBUF >> 3) & 0xFFFF);
w16(RXR + i * 64 + 16, RMD1_OWN | (((RXBUF >> 3) >> 16) & 0xFF));
w16(RXR + i * 64 + 32, 0x0A0A); /* 1526 bytes */
w16(RXR + i * 64 + 48, 0);
}
/* one 24-byte frame (MAME's loopback accepts 8..32 bytes) */
wb(TXBUF + 0 * 8, 0x08); wb(TXBUF + 1 * 8, 0x00);
wb(TXBUF + 2 * 8, 0x2b); wb(TXBUF + 3 * 8, 0x16);
wb(TXBUF + 4 * 8, 0x4b); wb(TXBUF + 5 * 8, 0x5a);
wb(TXBUF + 6 * 8, 0x02); wb(TXBUF + 7 * 8, 0x00);
wb(TXBUF + 8 * 8, 0x00); wb(TXBUF + 9 * 8, 0x00);
wb(TXBUF + 10 * 8, 0x00); wb(TXBUF + 11 * 8, 0x01);
wb(TXBUF + 12 * 8, 0x88); wb(TXBUF + 13 * 8, 0xB5);
wb(TXBUF + 14 * 8, 0xDE); wb(TXBUF + 15 * 8, 0xAD);
wb(TXBUF + 16 * 8, 0xBE); wb(TXBUF + 17 * 8, 0xEF);
for (i = 18; i < 24; i++) {
wb(TXBUF + i * 8, i);
}
w16(TXR + 0, (TXBUF >> 3) & 0xFFFF);
w16(TXR + 16, TMD1_OWN | TMD1_STP | TMD1_ENP | (((TXBUF >> 3) >> 16) & 0xFF));
w16(TXR + 32, 0x0FE8); /* 24 bytes */
w16(TXR + 48, 0);
csr_w(1, (INITB >> 3) & 0xFFFF);
csr_w(2, ((INITB >> 3) >> 16) & 0xFF);
csr_w(3, 0);
csr_w(0, CSR0_STOP); /* STOP immediately before INIT (datasheet order) */
csr_w(0, CSR0_INIT);
dot(1);
r = 0;
for (idle = 0; idle < 20000; idle++) {
r = csr_r(0);
if (r & CSR0_IDON) {
break;
}
}
cell(0x140, r);
if (!(r & CSR0_IDON)) {
cell(0x000, 0xEE);
return 1;
}
dot(2);
for (idle = 0; idle < 50000; idle++) {
}
csr_w(0, CSR0_STRT);
for (idle = 0; idle < 50000; idle++) {
}
/* own the TX descriptor now, then kick with STRT|TDMD in ONE write:
* CSR0 is a command shadow, a bare TDMD write clears STRT. */
w16(TXR + 16, TMD1_OWN | TMD1_STP | TMD1_ENP | (((TXBUF >> 3) >> 16) & 0xFF));
csr_w(0, CSR0_STRT | CSR0_TDMD);
dot(3);
s0[0] = csr_r(0);
for (idle = 0; idle < 100000; idle++) {
if (!(r16(RXR + 16) & RMD1_OWN)) {
dot(4);
break;
}
if (!(r16(TXR + 16) & TMD1_OWN)) {
dot(4);
}
}
s0[1] = csr_r(0);
rxmd1 = r16(RXR + 16);
mcnt = r16(RXR + 48) & 0x0FFF;
txmd1 = r16(TXR + 16);
ok = 0;
if (*(unsigned char *)(RXBUF + 12 * 8) == 0x88 &&
*(unsigned char *)(RXBUF + 13 * 8) == 0xB5 &&
*(unsigned char *)(RXBUF + 14 * 8) == 0xDE) {
ok = 1;
}
cell(0x160, s0[0]);
cell(0x180, s0[1]);
cell(0x200, rxmd1);
cell(0x220, mcnt);
cell(0x240, txmd1);
cell(0x1A0, ok);
bits16(120, 670, s0[0]);
bits16(120, 700, s0[1]);
bits16(120, 730, txmd1);
bits16(120, 760, rxmd1);
bits16(120, 790, mcnt);
if (ok) {
rect(120, 850, 24, 24, 0xFF);
}
if (!(txmd1 & TMD1_OWN)) {
rect(160, 850, 24, 24, 0xFF);
}
/* best effort: give the chip back to the OS */
{
unsigned int osblk;
osblk = *(unsigned int *)0x23E7A0;
if (osblk != 0) {
csr_w(0, CSR0_STOP);
csr_w(1, osblk & 0xFFFF);
csr_w(2, (osblk >> 16) & 0xFF);
csr_w(3, 0);
csr_w(0, CSR0_INIT);
for (idle = 0; idle < 10000; idle++) {
r = csr_r(0);
if (r & CSR0_IDON) {
break;
}
}
csr_w(0, 0x0042);
}
}
return 0;
}
+93
View File
@@ -0,0 +1,93 @@
/*
* romapi.h -- the VT1200 firmware entry points a C payload may call.
*
* Every address here is a TMS34010 BIT address (= Ghidra byte address x 8).
* The evidence for each is dwt/notes/TCPIP.md (sockets) and dwt/notes/HEAP.md
* (allocator); the ones marked "verified live" have been called from a payload
* on real hardware, not just read out of the ROM.
*
* K&R C only. This compiler rejects ANSI prototypes (`int f(int a)` is a
* syntax error), so the declarations below are old-style: the parameter lists
* live in the comments, and the compiler checks nothing but the name. That is
* the price of using the 1987 toolchain; keep the comments honest.
*
* Calling convention: the C compiler and the firmware agree -- arguments are
* widened to 32 bits and pushed upward on the program stack (A14), first
* argument last, and the result comes back in A8. The thunks in entry.asm
* forward with `calla`, so the caller's already-pushed arguments pass through
* untouched and only A8 has to survive.
*/
#ifndef ROMAPI_H
#define ROMAPI_H
/* ---- ROM entry points -------------------------------------------------- */
#define ROM_SOCKET 0x3F5C9A00 /* Ghidra 07EB9340 verified live */
#define ROM_BIND 0x3F5CAA80 /* Ghidra 07EB9550 BOOTP caller */
#define ROM_LISTEN 0x3F5CBCD0 /* Ghidra 07EB979A inetd caller */
#define ROM_ACCEPT 0x3F5CCB40 /* Ghidra 07EB9968 inetd caller */
#define ROM_SENDTO 0x3F5D3D70 /* Ghidra 07EBA7AE verified live */
#define ROM_RECVFROM 0x3F5D7D50 /* Ghidra 07EBAFAA verified live */
#define ROM_READ 0x3F5D8E70 /* Ghidra 07EBB1CE rdebug / Xlib */
#define ROM_CLOSE 0x3F5E8930 /* Ghidra 07EBD126 verified live */
#define ROM_SETSOCKOPT 0x3F5DD260 /* Ghidra 07EBBA4C BOOTP / Xlib */
#define ROM_DELAY 0x3F833A20 /* Ghidra 07F66744 sleep(ms), telnet caller */
#define ROM_SELECT 0x3F871CE0 /* Ghidra 07F0E39C Xlib caller, candidate */
#define ROM_MALLOC 0x3F701230 /* Ghidra 07EE0246 verified live */
#define ROM_FREE 0x3F702440 /* Ghidra 07EE0488 allocator */
#define ROM_CREATE_PROC 0x3F706A40 /* Ghidra 07EE0D48 GUI work */
/* ---- thunks implemented in entry.asm ----------------------------------- */
/*
* int socket_(domain, type, protocol) -> fd, or -1
* int bind_(fd, sockaddr *, len) -> 0, or -1
* int recvfrom_(fd, buf, len, flags, from *, fromlen *)
* -> bytes, or -1
* int close_(fd) -> 0, or -1
* unsigned int malloc_(bytes) -> BIT pointer, or 0
* int free_(bit pointer) -> 0
*
* `from` is a 16-byte sockaddr the stack fills in; `fromlen` points to an
* int that must be 16 on entry (the call writes back the real length).
*/
int socket_();
int bind_();
int recvfrom_();
int close_();
int listen_();
int accept_();
int read_();
int select_();
int delay_();
unsigned int malloc_();
int free_();
/* ---- socket constants -------------------------------------------------- */
#define AF_INET 2
#define SOCK_DGRAM 2
#define SOCK_STREAM 1
#define IPPROTO_UDP 0
#define SOCKADDR_LEN 16
/*
* struct sockaddr, 16 bytes, NETWORK byte order:
* +0 u16 family (AF_INET = 2 -> bytes 00 02)
* +2 u16 port (high byte first, e.g. 7000 = 0x1B58 -> 1B 58)
* +4 u32 addr (0 = INADDR_ANY)
* +8 u8 zero[8]
*/
/* Fill sa (an unsigned char[16]) with AF_INET, `port`, and INADDR_ANY.
* `port` is in host order so callers can write 7000 rather than 0x1B58. */
static void sa_any(sa, port)
unsigned char *sa;
unsigned int port;
{
sa[0] = 0; sa[1] = AF_INET;
sa[2] = (port >> 8) & 0xFF;
sa[3] = port & 0xFF;
sa[4] = 0; sa[5] = 0; sa[6] = 0; sa[7] = 0;
sa[8] = 0; sa[9] = 0; sa[10] = 0; sa[11] = 0;
sa[12] = 0; sa[13] = 0; sa[14] = 0; sa[15] = 0;
}
#endif /* ROMAPI_H */
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""Host sender for the VT1200 udpstream spike.
Sends full 1280x1024 frames as 8-row datagrams:
+0 u16 start row (LE) +2 u16 row count +4 rows*160 bytes
Usage:
python3 harness/udpstream/send.py --host 100.103.100.103 --fps 5
python3 harness/udpstream/send.py --host 100.103.100.103 --fps 0 --seconds 5
The frame is a black field with a white bar (encoding the frame number), a
moving box, and a border, so a screenshot proves the whole frame arrived and
which frame it was.
"""
from __future__ import annotations
import argparse
import socket
import struct
import sys
import time
W, H = 1280, 1024
ROW_BYTES = W // 8 # 160
ROWS_PER_PKT = 8
PORT = 7000
def frame(n: int) -> bytes:
"""One 1-bpp frame, packed rows of ROW_BYTES, LSB-first."""
rows = bytearray(H * ROW_BYTES)
# border: rows 0,1,H-2,H-1 and columns 0..3 / W-4..W-1 white
for y in (0, 1, H - 2, H - 1):
rows[y * ROW_BYTES:(y + 1) * ROW_BYTES] = b"\xff" * ROW_BYTES
for y in range(H):
r = rows[y * ROW_BYTES:(y + 1) * ROW_BYTES]
r[0] = 0xFF
r[ROW_BYTES - 1] = 0xFF
rows[y * ROW_BYTES:(y + 1) * ROW_BYTES] = r
# moving 128x128 box
x0 = (n * 37) % (W - 128)
y0 = (n * 23) % (H - 128)
for y in range(y0, y0 + 128):
base = y * ROW_BYTES
for xb in range(x0 // 8, (x0 + 128) // 8):
rows[base + xb] = 0xFF
# frame-number bar on row 64: 16 pixels of white per 16-frame step
y = 64
base = y * ROW_BYTES
for i in range(n % 64):
x = 32 + i * 16
rows[base + (x // 8)] |= 0xFF
rows[base + (x // 8) + 1] |= 0xFF
return bytes(rows)
def main(argv: list[str]) -> int:
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--host", default="100.103.100.103")
ap.add_argument("--port", type=int, default=PORT)
ap.add_argument("--fps", type=float, default=5.0,
help="target frames/s; 0 = as fast as possible")
ap.add_argument("--pkt-gap-ms", type=float, default=0.0,
help="sleep between datagrams; gives the cooperative OS a "
"chance to run (a continuous burst starves it)")
ap.add_argument("--seconds", type=float, default=0,
help="stop after N seconds (0 = run until Ctrl-C)")
a = ap.parse_args(argv[1:])
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1 << 20)
dst = (a.host, a.port)
n = sent_bytes = sent_pkts = 0
t0 = last = time.monotonic()
frames_at_last = 0
try:
while True:
f = frame(n)
for r in range(0, H, ROWS_PER_PKT):
payload = f[r * ROW_BYTES:(r + ROWS_PER_PKT) * ROW_BYTES]
pkt = struct.pack("<HH", r, ROWS_PER_PKT) + payload
sock.sendto(pkt, dst)
sent_bytes += len(pkt)
sent_pkts += 1
if a.pkt_gap_ms:
time.sleep(a.pkt_gap_ms / 1000.0)
n += 1
now = time.monotonic()
if now - last >= 1.0:
rate = (n - frames_at_last) / (now - last)
print(f"{n:6d} frames {rate:5.2f} fps "
f"{sent_bytes / (now - t0) / 1e6:5.3f} MB/s "
f"{sent_pkts / (now - t0):6.0f} pkt/s", flush=True)
last = now
frames_at_last = n
if a.seconds and now - t0 >= a.seconds:
break
if a.fps > 0:
sleep = t0 + n / a.fps - time.monotonic()
if sleep > 0:
time.sleep(sleep)
except KeyboardInterrupt:
pass
finally:
dt = time.monotonic() - t0
print(f"sent {n} frames, {sent_bytes} bytes in {dt:.1f}s "
f"({sent_bytes / dt / 1e6:.2f} MB/s, {sent_pkts / dt:.0f} pkt/s)",
flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv))
+121
View File
@@ -0,0 +1,121 @@
/*
* tcpstream.c -- measure the firmware's TCP receive throughput.
*
* The terminal is the server: socket(SOCK_STREAM) -> bind -> listen -> accept,
* then read() into a buffer and copy the bytes into video RAM in order. One
* read() can return a whole socket-buffer's worth of bytes, so this can beat
* the per-datagram recvfrom cost -- by how much is the question.
*
* Host-written cells (before the load):
* 0x018C9000 byte limit (0 = unlimited)
* 0x018C9040 TCP port (0 = 7009)
*
* Markers at 0x018C8000:
* +0x000 stage: 1 entered, 2 socket, 3 bound, 4 listening, 5 accepted,
* 0xEE socket failed, 0xEF bind failed, 0xF0 listen failed,
* 0xF1 accept failed
* +0x020 fd, +0x040 bind ret, +0x060 listen ret, +0x080 accept ret
* +0x0A0 reads, +0x0C0 bytes read, +0x0E0 last read length, +0x100 port
*/
#include "romapi.h"
#define VRAM_BASE 0x04000000
#define VRAM_BYTES 0x40000 /* 256 KiB, the real unit's framebuffer */
#define PORT 7009
#define MARK 0x018C8000
#define LIMIT_CELL 0x018C9000
#define PORT_CELL 0x018C9040
static unsigned char sa[16];
static unsigned char from[16];
static unsigned char buf[1472];
static void cell(off, v)
unsigned int off, v;
{
*(unsigned int *)(MARK + off) = v;
}
static unsigned int getcell(off)
unsigned int off;
{
return *(unsigned int *)(MARK + off);
}
int main()
{
int fd, cfd, n, r;
unsigned int limit, port, off, total, reads, i;
cell(0x000, 1);
limit = *(unsigned int *)LIMIT_CELL;
port = *(unsigned int *)PORT_CELL;
if (port == 0) {
port = PORT;
}
cell(0x100, port);
/* per-process user area is uninitialised (see udpstream.c) */
{
unsigned int proc, user, k;
proc = *(unsigned int *)0x1B3900;
user = *(unsigned int *)(proc + 0x100);
for (k = 0; k < 0x100; k++) {
*(unsigned char *)(user + k * 8) = 0;
}
cell(0x140, user);
}
fd = socket_(AF_INET, SOCK_STREAM, 0);
cell(0x020, fd);
if (fd < 0) { cell(0x000, 0xEE); return 1; }
cell(0x000, 2);
sa_any(sa, port);
r = bind_(fd, sa, SOCKADDR_LEN);
cell(0x040, r);
if (r < 0) { cell(0x000, 0xEF); close_(fd); return 2; }
cell(0x000, 3);
r = listen_(fd, 1);
cell(0x060, r);
if (r < 0) { cell(0x000, 0xF0); close_(fd); return 3; }
cell(0x000, 4);
cfd = accept_(fd, 0, 0);
cell(0x080, cfd);
if (cfd < 0) { cell(0x000, 0xF1); close_(fd); return 4; }
cell(0x000, 5);
cell(0x0A0, 0);
cell(0x0C0, 0);
off = 0;
total = 0;
reads = 0;
for (;;) {
n = read_(cfd, buf, sizeof(buf));
cell(0x0E0, (unsigned int)n);
if (n <= 0) {
close_(cfd);
close_(fd);
return 5;
}
for (i = 0; i < (unsigned int)n; i++) {
*(unsigned char *)(VRAM_BASE + off * 8) = buf[i];
off = off + 1;
if (off >= VRAM_BYTES) {
off = 0;
}
}
total = total + (unsigned int)n;
reads = reads + 1;
cell(0x0A0, reads);
cell(0x0C0, total);
if (limit != 0 && total >= limit) {
close_(cfd);
close_(fd);
return 0;
}
}
}
+161
View File
@@ -0,0 +1,161 @@
/*
* udpstream.c -- spike: receive full frames over UDP and blit them into VRAM.
*
* The terminal is the client; the host sends self-describing datagrams:
*
* +0 u16 start row (little endian)
* +2 u16 row count
* +4 row_count * 160 bytes of 1-bpp pixels (1280 px per row)
*
* Each row lands at bit 0x04000000 + row * 2048 (the real unit's 256-byte row
* pitch). Only the 160 visible bytes of a row are sent; the remaining 96 mask
* bytes are left as they are. 8 rows per datagram = 1284 bytes, under the
* 1500-byte Ethernet MTU.
*
* Markers at 0x018C8000 (32-bit cells):
* +0x000 stage 1 entered main, 2 socket ok, 3 bound, 4 receiving
* 0xEE socket failed, 0xEF bind failed
* +0x020 fd
* +0x040 bind() result
* +0x060 datagrams received
* +0x080 last datagram length
* +0x0A0 rows written
* +0x0C0 bytes blitted
*
* K&R C only; the ROM entry points and the sockaddr layout are in romapi.h.
*/
#include "romapi.h"
#define VRAM_BASE 0x04000000
#define ROW_BITS 2048 /* 256-byte row pitch on the real unit */
#define ROW_BYTES 160 /* 1280 visible pixels per row */
#define MAX_ROWS 8
#define PORT 7000
#define MARK 0x018C8000
#define LIMIT_CELL 0x018C9000 /* host-written datagram limit, 0 = unlimited */
#define PORT_CELL 0x018C9040 /* host-written UDP port, 0 = 7000 */
static unsigned char sa[16];
static unsigned char from[16];
static int fromlen;
static unsigned char buf[4 + MAX_ROWS * ROW_BYTES];
static unsigned int rset[8];
static unsigned int tv[2];
static void cell(off, v)
unsigned int off, v;
{
*(unsigned int *)(MARK + off) = v;
}
static unsigned int getcell(off)
unsigned int off;
{
return *(unsigned int *)(MARK + off);
}
int main()
{
int fd, r, n;
unsigned int limit, count, port;
unsigned int start, rows, row, i, dst, len;
cell(0x000, 1);
limit = *(unsigned int *)LIMIT_CELL;
count = 0;
port = *(unsigned int *)PORT_CELL;
if (port == 0) {
port = PORT;
}
/* The per-process user area (process+0x100 bits) is not initialised by
* create_process: on both the emulator and real hardware it reads as the
* heap fill 0x55. The syscall layer keeps its file table and syscall
* state there, so clear it before the first call. */
{
unsigned int proc, user, k;
proc = *(unsigned int *)0x1B3900;
cell(0x140, proc);
user = *(unsigned int *)(proc + 0x100);
cell(0x160, user);
for (k = 0; k < 0x100; k++) {
*(unsigned char *)(user + k * 8) = 0;
}
}
fd = socket_(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
cell(0x020, fd);
if (fd < 0) {
cell(0x000, 0xEE);
return 1;
}
cell(0x000, 2);
sa_any(sa, port);
cell(0x120, port);
r = bind_(fd, sa, SOCKADDR_LEN);
cell(0x040, r);
if (r < 0) {
cell(0x000, 0xEF);
close_(fd);
return 2;
}
cell(0x000, 3);
/* limit == 0xDEAD: bind and return without reading, so the host can test
* whether traffic to an unread socket is what kills the machine. */
if (limit == 0xDEAD) {
cell(0x000, 0xDD);
return 0;
}
cell(0x060, 0);
cell(0x0A0, 0);
cell(0x0C0, 0);
for (;;) {
fromlen = SOCKADDR_LEN;
n = recvfrom_(fd, buf, sizeof(buf), 0, from, &fromlen);
cell(0x0E0, (unsigned int)n);
if (n <= 0) {
close_(fd);
return 3;
}
if (n < 4) {
close_(fd);
return 4;
}
len = (unsigned int)n;
start = (unsigned int)buf[0] | ((unsigned int)buf[1] << 8);
rows = (unsigned int)buf[2] | ((unsigned int)buf[3] << 8);
if (rows > MAX_ROWS || start + rows > 1024) {
close_(fd);
return 5;
}
if (len < 4 + rows * ROW_BYTES) {
close_(fd);
return 6;
}
for (row = 0; row < rows; row++) {
dst = VRAM_BASE + (start + row) * ROW_BITS;
for (i = 0; i < ROW_BYTES; i++) {
*(unsigned char *)(dst + i * 8) =
buf[4 + row * ROW_BYTES + i];
}
}
cell(0x080, len);
cell(0x060, getcell(0x060) + 1);
cell(0x0A0, getcell(0x0A0) + rows);
cell(0x0C0, getcell(0x0C0) + rows * ROW_BYTES);
cell(0x000, 4);
count = count + 1;
if (limit != 0 && count >= limit) {
cell(0x100, count);
close_(fd);
return 0; /* bounded run: leave the OS to it */
}
}
}