mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-10-31 12:05:05 +02:00
cmd 8 alternates 0xFFFF/0x0000 per frame so the rate is visible: 300 frames in 9.775 s on the unit, 32.6 ms each, 30.69 fps sustained. cmd 9 is the same byte-store loop aimed at a DRAM buffer, to measure the CPU's store rate without the VRAM arbiter and size a frame-expansion stage. vtcmd.py --dram selects it. Report 74.3 records the independent Ghidra pass over the ROM: the firmware sets CONVSP/CONVDP=0x14 (pitch 0x800 bits = 256 B), PSIZE=1, PMASK=0 and OFFSET=0x04000000 for XY addressing -- the same values vram.asm uses -- and fills with FILL_L chunked at 0x7FC0 pixels because DYDX is 15 bits per axis. There is no VBLT and no wait-state/arbitration register in the ROM, so the 12 us/byte CPU store cost is inherent to this board, not a setting.
681 lines
27 KiB
C
681 lines
27 KiB
C
/*
|
|
* 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 0x01A00000 /* after .text (which now reaches 0x018CACD0) */
|
|
#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 RMD1_ENP 0x0100
|
|
#define RMD1_STP 0x0200
|
|
#define TMD1_OWN 0x8000
|
|
#define TMD1_ENP 0x0100
|
|
#define TMD1_STP 0x0200
|
|
|
|
/* Memory layout (BYTE addresses; the rings are 8-byte descriptors):
|
|
*
|
|
* 0x3000 init block (12 words = 24 B)
|
|
* 0x3020 RX ring, 16 entries -> 0x3020..0x309F
|
|
* 0x30C0 TX ring, 8 entries -> 0x30C0..0x30FF
|
|
* 0x3180 TX buffer
|
|
* 0x4000 RX buffers, RXBUFLEN apart
|
|
*
|
|
* The TX ring used to sit at 0x3060, which was fine while the RX ring had 8
|
|
* entries (0x3020..0x305F) but silently overlapped once it grew to 16. The
|
|
* chip then set OWN in what we believed were our TX descriptors, so every
|
|
* reply was discarded as "owned but not start-of-packet" and never reached
|
|
* the wire. Keep the two rings apart: NRX*8 must not reach TXR.
|
|
*/
|
|
#define B(byte) ((unsigned int)(byte) * 8) /* bit address of a byte */
|
|
#define INITB B(0x3000)
|
|
#define RXR B(0x3020)
|
|
#define TXR B(0x30C0)
|
|
#define RXBUF B(0x3200)
|
|
#define TXBUF B(0x3180)
|
|
#define NRX 16
|
|
#define RXBUFLEN 1526 /* one full 1514-byte MTU frame + margin */
|
|
#define ETHCMD 0x88B6
|
|
|
|
/* read by vram.asm (symbol _vram_pattern) */
|
|
unsigned int vram_pattern;
|
|
|
|
extern void vram_fill_();
|
|
|
|
/* Compile-time guard: the RX ring must end before the TX ring starts.
|
|
* (Bit addresses: NRX descriptors of 64 bits each.) Violating this is what
|
|
* broke the first version of this payload, and it fails silently at runtime. */
|
|
typedef char rx_tx_rings_do_not_overlap[(RXR + NRX * 64 <= TXR) ? 1 : -1];
|
|
|
|
/* Re-arm one RX descriptor the way NetBSD's am7990_rint() does: message count
|
|
* and length first, ownership last, so the chip cannot take the descriptor
|
|
* while its length field is stale. */
|
|
#define REARM_RX(i, buf) do { \
|
|
w16(RXR + (i) * 64 + 48, 0); \
|
|
w16(RXR + (i) * 64 + 32, (0x10000 - RXBUFLEN) & 0xFFFF); \
|
|
w16(RXR + (i) * 64 + 16, RMD1_OWN | (((buf) >> 16) & 0xFF)); \
|
|
} while (0)
|
|
|
|
#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 unsigned int rb(bit)
|
|
unsigned int bit;
|
|
{
|
|
return *(unsigned char *)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;
|
|
}
|
|
|
|
/* ---- 5x7 text -------------------------------------------------------
|
|
* Glyphs are built at runtime: this toolchain never runs .cinit, so a
|
|
* statically initialised table would arrive as zeros (same reason
|
|
* harness/c-hello builds its font in code).
|
|
*/
|
|
static unsigned char glyph[36][7];
|
|
static unsigned char lab_csr0[5], lab_csr1[5], lab_tmd1[5], lab_rmd1[5];
|
|
static unsigned char lab_mcnt[5], lab_rest[5], lab_ok[3], lab_yes[4], lab_no[3];
|
|
static unsigned char lab_mac1[5], lab_mac2[5];
|
|
|
|
static unsigned int gi(ch)
|
|
unsigned int ch;
|
|
{
|
|
if (ch >= '0' && ch <= '9') {
|
|
return ch - '0';
|
|
}
|
|
return 10 + (ch - 'A');
|
|
}
|
|
|
|
static void fset(i, r0, r1, r2, r3, r4, r5, r6)
|
|
unsigned int i, r0, r1, r2, r3, r4, r5, r6;
|
|
{
|
|
glyph[i][0] = r0; glyph[i][1] = r1; glyph[i][2] = r2; glyph[i][3] = r3;
|
|
glyph[i][4] = r4; glyph[i][5] = r5; glyph[i][6] = r6;
|
|
}
|
|
|
|
static void build_font()
|
|
{
|
|
fset(0, 0x0E,0x11,0x13,0x15,0x19,0x11,0x0E); /* 0 */
|
|
fset(1, 0x04,0x0C,0x04,0x04,0x04,0x04,0x0E); /* 1 */
|
|
fset(2, 0x0E,0x11,0x01,0x02,0x04,0x08,0x1F); /* 2 */
|
|
fset(3, 0x1F,0x02,0x04,0x02,0x01,0x11,0x0E); /* 3 */
|
|
fset(4, 0x02,0x06,0x0A,0x12,0x1F,0x02,0x02); /* 4 */
|
|
fset(5, 0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E); /* 5 */
|
|
fset(6, 0x06,0x08,0x10,0x1E,0x11,0x11,0x0E); /* 6 */
|
|
fset(7, 0x1F,0x01,0x02,0x04,0x08,0x08,0x08); /* 7 */
|
|
fset(8, 0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E); /* 8 */
|
|
fset(9, 0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C); /* 9 */
|
|
fset(gi('A'), 0x0E,0x11,0x11,0x1F,0x11,0x11,0x11);
|
|
fset(gi('B'), 0x1E,0x11,0x11,0x1E,0x11,0x11,0x1E);
|
|
fset(gi('C'), 0x0E,0x11,0x10,0x10,0x10,0x11,0x0E);
|
|
fset(gi('D'), 0x1E,0x11,0x11,0x11,0x11,0x11,0x1E);
|
|
fset(gi('E'), 0x1F,0x10,0x10,0x1E,0x10,0x10,0x1F);
|
|
fset(gi('F'), 0x1F,0x10,0x10,0x1E,0x10,0x10,0x10);
|
|
fset(gi('K'), 0x11,0x12,0x14,0x18,0x14,0x12,0x11);
|
|
fset(gi('M'), 0x11,0x1B,0x15,0x15,0x11,0x11,0x11);
|
|
fset(gi('N'), 0x11,0x19,0x19,0x15,0x13,0x13,0x11);
|
|
fset(gi('O'), 0x0E,0x11,0x11,0x11,0x11,0x11,0x0E);
|
|
fset(gi('R'), 0x1E,0x11,0x11,0x1E,0x14,0x12,0x11);
|
|
fset(gi('S'), 0x0F,0x10,0x10,0x0E,0x01,0x01,0x1E);
|
|
fset(gi('T'), 0x1F,0x04,0x04,0x04,0x04,0x04,0x04);
|
|
fset(gi('X'), 0x11,0x11,0x0A,0x04,0x0A,0x11,0x11);
|
|
fset(gi('Y'), 0x11,0x11,0x0A,0x04,0x04,0x04,0x04);
|
|
}
|
|
|
|
static void put4(a, c0, c1, c2, c3)
|
|
unsigned char *a;
|
|
unsigned int c0, c1, c2, c3;
|
|
{
|
|
a[0] = c0; a[1] = c1; a[2] = c2; a[3] = c3; a[4] = 0;
|
|
}
|
|
|
|
static void draw_glyph(x, y, ch)
|
|
unsigned int x, y, ch;
|
|
{
|
|
unsigned int r, c, row, idx;
|
|
|
|
idx = gi(ch);
|
|
for (r = 0; r < 7; r++) {
|
|
row = glyph[idx][r];
|
|
for (c = 0; c < 5; c++) {
|
|
if (row & (1 << (4 - c))) {
|
|
rect(x + c * 8, y + r * 3, 8, 3, 0xFF);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void draw_text(x, y, s)
|
|
unsigned int x, y;
|
|
unsigned char *s;
|
|
{
|
|
while (*s) {
|
|
if (*s != ' ') {
|
|
draw_glyph(x, y, *s);
|
|
}
|
|
x = x + 48;
|
|
s = s + 1;
|
|
}
|
|
}
|
|
|
|
static void hex4(x, y, v)
|
|
unsigned int x, y, v;
|
|
{
|
|
unsigned int d, nib;
|
|
|
|
/* Wipe the field first. Only the glyph pixels were being written before,
|
|
* so 0005 drawn over 1234 left the tails of the old digits on screen. */
|
|
rect(x, y, 200, 24, 0x00);
|
|
|
|
for (d = 0; d < 4; d++) {
|
|
nib = (v >> ((3 - d) * 4)) & 0xF;
|
|
if (nib < 10) {
|
|
draw_glyph(x + d * 48, y, '0' + nib);
|
|
} else {
|
|
draw_glyph(x + d * 48, y, 'A' + nib - 10);
|
|
}
|
|
}
|
|
}
|
|
|
|
int main()
|
|
{
|
|
unsigned int i, n, idle, rxf, bytes, cmds, laststat, txslot, ncmd;
|
|
unsigned int buf, len, et, j, rmd1, chaindrop, txfull, errbits;
|
|
unsigned int jaddr, dojump;
|
|
|
|
cell(0x000, 1);
|
|
build_font();
|
|
put4(lab_csr0, 'R', 'X', 'F', ' ');
|
|
put4(lab_csr1, 'K', 'B', ' ', ' ');
|
|
put4(lab_tmd1, 'C', 'M', 'D', ' ');
|
|
put4(lab_rmd1, 'S', 'T', 'A', 'T');
|
|
put4(lab_mcnt, 'C', 'S', 'R', '0');
|
|
put4(lab_rest, 'R', 'E', 'S', 'T');
|
|
dot(0);
|
|
|
|
/* ---- chip: normal mode, our MAC, broadcast accepted ---- */
|
|
csr_w(0, CSR0_STOP);
|
|
w16(INITB + 0 * 16, 0x0000);
|
|
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);
|
|
w16(INITB + 9 * 16, 4 << 13); /* 16 RX descriptors */
|
|
w16(INITB + 10 * 16, (TXR >> 3) & 0xFFFF);
|
|
w16(INITB + 11 * 16, 3 << 13); /* 8 TX descriptors */
|
|
|
|
for (i = 0; i < NRX; i++) {
|
|
buf = 0x4000 + i * RXBUFLEN;
|
|
w16(RXR + i * 64 + 0, buf & 0xFFFF);
|
|
w16(RXR + i * 64 + 16, RMD1_OWN | ((buf >> 16) & 0xFF));
|
|
w16(RXR + i * 64 + 32, (0x10000 - RXBUFLEN) & 0xFFFF);
|
|
w16(RXR + i * 64 + 48, 0);
|
|
}
|
|
for (i = 0; i < 8; i++) {
|
|
w16(TXR + i * 64 + 0, (TXBUF >> 3) & 0xFFFF);
|
|
w16(TXR + i * 64 + 16, ((TXBUF >> 3) >> 16) & 0xFF);
|
|
w16(TXR + i * 64 + 32, 0xF000);
|
|
w16(TXR + i * 64 + 48, 0);
|
|
}
|
|
|
|
csr_w(1, (INITB >> 3) & 0xFFFF);
|
|
csr_w(2, ((INITB >> 3) >> 16) & 0xFF);
|
|
csr_w(3, 0);
|
|
csr_w(0, 0x7F41);
|
|
for (idle = 0; idle < 20000; idle++) {
|
|
if (csr_r(0) & CSR0_IDON) {
|
|
break;
|
|
}
|
|
}
|
|
csr_w(0, 0x0042);
|
|
dot(1);
|
|
|
|
/* the ring is live from here: draw the panel only now, so frames
|
|
* that arrive during the (slow) VRAM fill are not missed */
|
|
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);
|
|
|
|
|
|
|
|
rxf = 0; bytes = 0; cmds = 0; laststat = 0xEEEE; txslot = 0; ncmd = 0;
|
|
chaindrop = 0; txfull = 0; errbits = 0; jaddr = 0; dojump = 0;
|
|
|
|
/* ---- announce: the ring is armed, so say hello on the wire ----------
|
|
* Three broadcast frames, ethertype 0x88B6, cmd 0xFF, magic "LANC".
|
|
* A host waiting for this knows the payload is live and does not have to
|
|
* guess with a sleep. The frame is sent three times because this is a
|
|
* half-duplex segment and one lost broadcast should not cost a cycle.
|
|
* No clock is involved: back-to-back is enough at 10 Mbit. */
|
|
{
|
|
unsigned int a;
|
|
for (a = 0; a < 6; a++) {
|
|
wb(TXBUF + a * 8, 0xFF); /* broadcast dst */
|
|
}
|
|
wb(TXBUF + 6 * 8, 0x08); wb(TXBUF + 7 * 8, 0x00);
|
|
wb(TXBUF + 8 * 8, 0x2B); wb(TXBUF + 9 * 8, 0x16);
|
|
wb(TXBUF + 10 * 8, 0x4B); wb(TXBUF + 11 * 8, 0x5A);
|
|
wb(TXBUF + 12 * 8, 0x88); wb(TXBUF + 13 * 8, 0xB6);
|
|
wb(TXBUF + 14 * 8, 'K'); wb(TXBUF + 15 * 8, 'S');
|
|
wb(TXBUF + 16 * 8, 0xFF); /* announce opcode */
|
|
wb(TXBUF + 17 * 8, 0x4C); wb(TXBUF + 18 * 8, 0x41);
|
|
wb(TXBUF + 19 * 8, 0x4E); wb(TXBUF + 20 * 8, 0x43); /* "LANC" */
|
|
for (a = 21; a < 60; a++) {
|
|
wb(TXBUF + a * 8, 0);
|
|
}
|
|
for (a = 0; a < 3; a++) {
|
|
/* Slot 0 is where the chip's ring position starts, and it only
|
|
* transmits from that position -- arming any other slot does
|
|
* nothing until the ring gets there. Announce skips are not
|
|
* counted in txfull, which is the reply path's diagnostic. */
|
|
for (j = 0; j < 8000; j++) {
|
|
if (!(r16(TXR + 16) & TMD1_OWN)) {
|
|
break;
|
|
}
|
|
}
|
|
if (r16(TXR + 16) & TMD1_OWN) {
|
|
break; /* still busy: stop trying */
|
|
}
|
|
w16(TXR + 48, 0);
|
|
w16(TXR + 32, (0x10000 - 60) & 0xFFFF);
|
|
w16(TXR + 16, TMD1_OWN | TMD1_STP | TMD1_ENP);
|
|
csr_w(0, 0x0040 | CSR0_TDMD);
|
|
for (j = 0; j < 60000; j++) {
|
|
if (!(r16(TXR + 16) & TMD1_OWN)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* the chip's TX ring position is now 1, so replies must start there */
|
|
txslot = 1;
|
|
}
|
|
|
|
/* ---- main loop: receive, count, and serve commands ---- */
|
|
for (idle = 0; ; idle++) {
|
|
for (i = 0; i < NRX; i++) {
|
|
rmd1 = r16(RXR + i * 64 + 16);
|
|
if (rmd1 & RMD1_OWN) {
|
|
continue;
|
|
}
|
|
buf = 0x4000 + i * RXBUFLEN;
|
|
len = r16(RXR + i * 64 + 48) & 0x0FFF;
|
|
/* Every reference driver (4.4BSD pmax if_le.c, NetBSD am7990.c,
|
|
* Linux 7990.c) requires STP *and* ENP on the same descriptor and
|
|
* drops a frame that spans descriptors instead of reassembling it.
|
|
* Testing ENP alone would count the tail of a jumbo frame as a
|
|
* frame and parse it from the middle of the buffer. */
|
|
if ((rmd1 & (RMD1_STP | RMD1_ENP)) != (RMD1_STP | RMD1_ENP)) {
|
|
chaindrop = chaindrop + 1;
|
|
REARM_RX(i, buf);
|
|
continue;
|
|
}
|
|
if (len < 60) {
|
|
len = 60;
|
|
}
|
|
rxf = rxf + 1;
|
|
/* the chip writes the 4-byte FCS into the buffer and includes it
|
|
* in RMD3; all three drivers subtract it (`mblength - 4`) */
|
|
if (len > 4) {
|
|
bytes = bytes + len - 4;
|
|
}
|
|
et = (rb((buf + 12) * 8) << 8) | rb((buf + 13) * 8);
|
|
ncmd = ncmd + 1;
|
|
|
|
/* command frame? ethertype 0x88B6, then 'K' 'S' <cmd> ... */
|
|
if (et == ETHCMD && rb((buf + 14) * 8) == 'K' && rb((buf + 15) * 8) == 'S') {
|
|
unsigned int cmd, addr, rlen, k, v;
|
|
cmd = rb((buf + 16) * 8);
|
|
addr = ((rb((buf + 17) * 8) << 24) | (rb((buf + 18) * 8) << 16) |
|
|
(rb((buf + 19) * 8) << 8) | rb((buf + 20) * 8));
|
|
rlen = (rb((buf + 21) * 8) << 8) | rb((buf + 22) * 8);
|
|
cmds = cmds + 1;
|
|
laststat = 0;
|
|
|
|
/* reply layout: [16]=cmd, [17..18]=status, [19..]=data */
|
|
for (j = 0; j < 6; j++) {
|
|
wb(TXBUF + j * 8, rb((buf + 6 + j) * 8)); /* dst = requester */
|
|
}
|
|
/* src = our own MAC. This used to copy the request's
|
|
* destination (a broadcast address) and a frame with a
|
|
* multicast source is dropped by the tap, so replies never
|
|
* arrived even though the command was handled. */
|
|
wb(TXBUF + 6 * 8, 0x08); wb(TXBUF + 7 * 8, 0x00);
|
|
wb(TXBUF + 8 * 8, 0x2B); wb(TXBUF + 9 * 8, 0x16);
|
|
wb(TXBUF + 10 * 8, 0x4B); wb(TXBUF + 11 * 8, 0x5A);
|
|
wb(TXBUF + 12 * 8, 0x88); wb(TXBUF + 13 * 8, 0xB6);
|
|
wb(TXBUF + 14 * 8, 'K'); wb(TXBUF + 15 * 8, 'S');
|
|
wb(TXBUF + 16 * 8, cmd);
|
|
/* Zero the data area before filling it. Without this a reply
|
|
* that writes fewer bytes than the previous one leaves stale
|
|
* data behind, and the host cannot tell it from a real field. */
|
|
for (k = 0; k < 24; k++) {
|
|
wb(TXBUF + (19 + k) * 8, 0);
|
|
}
|
|
|
|
len = 60;
|
|
if (cmd == 1) {
|
|
/* read memory: addr is a bit address, rlen bytes */
|
|
if (rlen > 64) {
|
|
rlen = 64;
|
|
laststat = 0xE001;
|
|
}
|
|
for (k = 0; k < 19 + rlen; k++) {
|
|
wb(TXBUF + (19 + k) * 8, 0);
|
|
}
|
|
for (k = 0; k < rlen; k++) {
|
|
v = rb(addr + k * 8);
|
|
wb(TXBUF + (19 + k) * 8, v);
|
|
}
|
|
if (19 + rlen > 60) {
|
|
len = 19 + rlen;
|
|
}
|
|
} else if (cmd == 2) {
|
|
/* statistics: rxf(4) bytes(4) cmds(2) chaindrop(2) txfull(2) */
|
|
wb(TXBUF + 19 * 8, (rxf >> 24) & 0xFF);
|
|
wb(TXBUF + 20 * 8, (rxf >> 16) & 0xFF);
|
|
wb(TXBUF + 21 * 8, (rxf >> 8) & 0xFF);
|
|
wb(TXBUF + 22 * 8, rxf & 0xFF);
|
|
wb(TXBUF + 23 * 8, (bytes >> 24) & 0xFF);
|
|
wb(TXBUF + 24 * 8, (bytes >> 16) & 0xFF);
|
|
wb(TXBUF + 25 * 8, (bytes >> 8) & 0xFF);
|
|
wb(TXBUF + 26 * 8, bytes & 0xFF);
|
|
wb(TXBUF + 27 * 8, (cmds >> 8) & 0xFF);
|
|
wb(TXBUF + 28 * 8, cmds & 0xFF);
|
|
wb(TXBUF + 29 * 8, (chaindrop >> 8) & 0xFF);
|
|
wb(TXBUF + 30 * 8, chaindrop & 0xFF);
|
|
wb(TXBUF + 31 * 8, (txfull >> 8) & 0xFF);
|
|
wb(TXBUF + 32 * 8, txfull & 0xFF);
|
|
wb(TXBUF + 33 * 8, (errbits >> 8) & 0xFF);
|
|
wb(TXBUF + 34 * 8, errbits & 0xFF);
|
|
} else if (cmd == 3) {
|
|
/* poke: the request carries the bytes from [23] on.
|
|
* `addr` is a BIT address, like every other address in
|
|
* this payload, so a byte address must be sent as
|
|
* byte_addr * 8. */
|
|
unsigned int wlen;
|
|
wlen = rlen;
|
|
if (wlen > 1024) { /* one frame holds this */
|
|
wlen = 1024;
|
|
laststat = 0xE003;
|
|
}
|
|
for (k = 0; k < wlen; k++) {
|
|
wb(addr + k * 8, rb((buf + 23 + k) * 8));
|
|
}
|
|
wb(TXBUF + 19 * 8, (wlen >> 8) & 0xFF);
|
|
wb(TXBUF + 20 * 8, wlen & 0xFF);
|
|
} else if (cmd == 9) {
|
|
/* The same byte-store loop as cmd 7 but into DRAM, so the
|
|
* CPU's store rate is measured without the VRAM arbiter.
|
|
* This is the number that sizes a frame-expansion stage. */
|
|
unsigned int nd, fd, yd, xd, od, rd, st;
|
|
nd = rlen;
|
|
if (nd > 100) {
|
|
nd = 100;
|
|
}
|
|
rd = addr;
|
|
if (rd == 0 || rd > 1024) {
|
|
rd = 1024;
|
|
}
|
|
st = 0;
|
|
for (fd = 0; fd < nd; fd++) {
|
|
for (yd = 0; yd < rd; yd++) {
|
|
od = 0x01A10000 + yd * 2048;
|
|
for (xd = 0; xd < 160; xd++) {
|
|
*(unsigned char *)(od + xd * 8) =
|
|
(unsigned char)(yd + xd + fd);
|
|
st = st + 1;
|
|
}
|
|
}
|
|
}
|
|
wb(TXBUF + 19 * 8, (nd >> 8) & 0xFF);
|
|
wb(TXBUF + 20 * 8, nd & 0xFF);
|
|
wb(TXBUF + 21 * 8, (rd >> 8) & 0xFF);
|
|
wb(TXBUF + 22 * 8, rd & 0xFF);
|
|
wb(TXBUF + 23 * 8, (st >> 24) & 0xFF);
|
|
wb(TXBUF + 24 * 8, (st >> 16) & 0xFF);
|
|
wb(TXBUF + 25 * 8, (st >> 8) & 0xFF);
|
|
wb(TXBUF + 26 * 8, st & 0xFF);
|
|
} else if (cmd == 8) {
|
|
/* FILL L benchmark: the same full frame, done by the GSP's
|
|
* array engine instead of byte stores. */
|
|
unsigned int nf2, f2;
|
|
nf2 = rlen;
|
|
if (nf2 > 400) { /* 400 x 32.7 ms = 13 s max */
|
|
nf2 = 400;
|
|
}
|
|
for (f2 = 0; f2 < nf2; f2++) {
|
|
/* alternate black/white so the rate is visible */
|
|
vram_pattern = (f2 & 1) ? 0x0000 : 0xFFFF;
|
|
vram_fill_();
|
|
}
|
|
wb(TXBUF + 19 * 8, (nf2 >> 8) & 0xFF);
|
|
wb(TXBUF + 20 * 8, nf2 & 0xFF);
|
|
wb(TXBUF + 21 * 8, (nf2 * 80) >> 8 & 0xFF);
|
|
wb(TXBUF + 22 * 8, (nf2 * 80) & 0xFF);
|
|
} else if (cmd == 7) {
|
|
/* VRAM fill benchmark: write `rlen` full frames of
|
|
* 1024 rows x 160 bytes (1280x1024, 256-byte stride) and
|
|
* reply when done, so the host can time it. This is the
|
|
* same store pattern the renderer uses, i.e. the number is
|
|
* the achievable frame rate, not a synthetic one. */
|
|
unsigned int nf, f, y, x, o, rows, stores;
|
|
nf = rlen;
|
|
if (nf > 100) {
|
|
nf = 100;
|
|
}
|
|
/* `addr` selects how many of the 1024 rows to write, so the
|
|
* per-store cost can be separated from any fixed overhead */
|
|
rows = addr;
|
|
if (rows == 0 || rows > 1024) {
|
|
rows = 1024;
|
|
}
|
|
stores = 0;
|
|
for (f = 0; f < nf; f++) {
|
|
for (y = 0; y < rows; y++) {
|
|
o = VRAM_BASE + y * 2048;
|
|
for (x = 0; x < 160; x++) {
|
|
*(unsigned char *)(o + x * 8) =
|
|
(unsigned char)(y + x + f);
|
|
stores = stores + 1;
|
|
}
|
|
}
|
|
}
|
|
/* reply: frames(2) rows(2) stores(4) so the host can
|
|
* compute the rate from what the payload counted, not
|
|
* from what it was asked to do */
|
|
wb(TXBUF + 19 * 8, (nf >> 8) & 0xFF);
|
|
wb(TXBUF + 20 * 8, nf & 0xFF);
|
|
wb(TXBUF + 21 * 8, (rows >> 8) & 0xFF);
|
|
wb(TXBUF + 22 * 8, rows & 0xFF);
|
|
wb(TXBUF + 23 * 8, (stores >> 24) & 0xFF);
|
|
wb(TXBUF + 24 * 8, (stores >> 16) & 0xFF);
|
|
wb(TXBUF + 25 * 8, (stores >> 8) & 0xFF);
|
|
wb(TXBUF + 26 * 8, stores & 0xFF);
|
|
} else if (cmd == 6) {
|
|
/* OS cold start: the entry POST branches to at 0x3F2042D0
|
|
* when it skips the level-1 batch. Unlike the reset
|
|
* vector this path first STOPs the LANCE and re-programs
|
|
* the board window, which is exactly the state a payload
|
|
* leaves behind -- see NETWORK_REPORT 72.5. */
|
|
jaddr = 0x3F5788C0;
|
|
dojump = 1;
|
|
wb(TXBUF + 19 * 8, (jaddr >> 24) & 0xFF);
|
|
wb(TXBUF + 20 * 8, (jaddr >> 16) & 0xFF);
|
|
wb(TXBUF + 21 * 8, (jaddr >> 8) & 0xFF);
|
|
wb(TXBUF + 22 * 8, jaddr & 0xFF);
|
|
} else if (cmd == 4 || cmd == 5) {
|
|
/* jump (4) to an address, or reboot (5): jump to the ROM
|
|
* reset vector, which is the same entry the CPU takes at
|
|
* power-on. Its first instruction is DINT and it then
|
|
* initialises both DUARTs, so it does not depend on state
|
|
* this payload leaves behind. The reply is transmitted
|
|
* below, before control is transferred. */
|
|
if (cmd == 5) {
|
|
jaddr = *(unsigned int *)0xFFFFFFE0;
|
|
} else {
|
|
jaddr = addr;
|
|
}
|
|
dojump = 1;
|
|
wb(TXBUF + 19 * 8, (jaddr >> 24) & 0xFF);
|
|
wb(TXBUF + 20 * 8, (jaddr >> 16) & 0xFF);
|
|
wb(TXBUF + 21 * 8, (jaddr >> 8) & 0xFF);
|
|
wb(TXBUF + 22 * 8, jaddr & 0xFF);
|
|
} else {
|
|
laststat = 0xE002;
|
|
}
|
|
wb(TXBUF + 17 * 8, (laststat >> 8) & 0xFF);
|
|
wb(TXBUF + 18 * 8, laststat & 0xFF);
|
|
|
|
/* All the reference drivers check that the ring slot is free
|
|
* before reusing it (Linux stops the queue, NetBSD prints
|
|
* "missing buffer", 4.4BSD panics) -- we just skip. */
|
|
if (r16(TXR + txslot * 64 + 16) & TMD1_OWN) {
|
|
txfull = txfull + 1;
|
|
} else {
|
|
w16(TXR + txslot * 64 + 48, 0); /* TMD3 */
|
|
w16(TXR + txslot * 64 + 32, (0x10000 - len) & 0xFFFF);
|
|
w16(TXR + txslot * 64 + 16,
|
|
TMD1_OWN | TMD1_STP | TMD1_ENP | (((TXBUF >> 3) >> 16) & 0xFF));
|
|
/* 4.4BSD/NetBSD/Linux all kick with TDMD|INEA only; a
|
|
* repeated start (STRT) is not part of any of them. */
|
|
csr_w(0, 0x0040 | CSR0_TDMD);
|
|
for (j = 0; j < 20000; j++) {
|
|
if (!(r16(TXR + txslot * 64 + 16) & TMD1_OWN)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
txslot = (txslot + 1) & 7;
|
|
|
|
if (dojump) {
|
|
/* the reply has been transmitted (or waited on) above;
|
|
* from here this payload does not return */
|
|
void (*fp)();
|
|
fp = (void (*)()) jaddr;
|
|
(*fp)();
|
|
}
|
|
}
|
|
/* re-arm immediately (length and count first, ownership last, as
|
|
* NetBSD does: rmd1_bits = OWN; rmd2 = -LEBLEN|ONES; rmd3 = 0) */
|
|
REARM_RX(i, buf);
|
|
}
|
|
/* sticky chip error bits: ERR|BABL|CERR|MISS|MERR. MISS set means
|
|
* the chip had no descriptor to receive into (ring too small / loop
|
|
* too slow); clear means the frames never reached the chip. */
|
|
errbits = errbits | (csr_r(0) & 0xF800);
|
|
|
|
/* publish counters as we go */
|
|
cell(0x160, rxf); cell(0x180, bytes); cell(0x1A0, cmds); cell(0x200, laststat);
|
|
if ((idle & 0xFFF) == 0) {
|
|
draw_text(104, 654, lab_csr0); hex4(312, 654, rxf);
|
|
draw_text(104, 686, lab_csr1); hex4(312, 686, bytes >> 10);
|
|
draw_text(104, 718, lab_tmd1); hex4(312, 718, cmds);
|
|
draw_text(104, 750, lab_rmd1); hex4(312, 750, laststat);
|
|
draw_text(104, 782, lab_mcnt); hex4(312, 782, csr_r(0));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|