/* * 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 0x018D0000 /* 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 /* 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; /* ---- 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' ... */ 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); 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 == 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; }