/* * vidstream.c -- terminal side of the VT1200 video stream. * * Task 3 claimed the working set with ONE malloc at startup and published * where it landed. Task 4 added the receive representation: two 24 KB frame * slots at the head of that pool and a poll loop that hands a completed frame * to fp_parse() (frame_parse.c, shared with the host unit test) and counts what * it accepted. Task 5 blits an accepted strip frame to the screen: one * PIXBLT B,XY per source row, repeated down the row `stretch` times by * SPTCH=0 (vram_b.asm). Task 6 adds the back-channel: the LANCE is taken over * for transmit and a KIND_STATUS frame is built and sent every 64 accepted * frames (send_status() below). Task 7 replaces Task 4's debugger-injected * buffer with the payload's own LANCE RX ring: sixteen descriptors whose * buffers tile a slot, armed per slot, consumed in ring order, so the chip * DMAs the wire bytes into the memory fp_parse() and the blit read. * * Pool layout inside the single allocation (POOL_BYTES = 249856 = 244 KB): * * +0x00000 frame slot A 24576 bytes (16 x 1526 B RX buffers) * +0x06000 frame slot B 24576 bytes (16 x 1526 B RX buffers) * +0x0C000 framebuffer 163840 bytes (scheme A, Task 5) * +0x34000 dictionary 32768 bytes * +0x3C000 LANCE block 4096 bytes (init block, TX ring, status * frame, RX ring) * * The LANCE block is what was slack. Its ring bases are aligned at run time * (lance_start() explains why the offset alone is not enough), and the status * frame sits right after the 8 TX descriptors, the RX ring after that. * * A slot is 16 packet buffers of RXBUFLEN = 1526 bytes (16 x 1526 = 24416, so * it fits 24576 with 160 bytes to spare). The chip writes ONE Ethernet frame * per buffer -- 14-byte header, the VS frame's chunk, and the FCS it appends -- * so a VS frame arrives as a series of datagrams in ring order, and the frame's * bytes are NOT contiguous in the slot: they are 14 bytes into each buffer, * 1526 bytes apart. This payload therefore keeps a fragment table (offset, * buffer, length per datagram) and `paint()` reads each source row across the * fragments that hold it; there is no per-frame CPU copy of the picture (the * design's "DMA, no CPU copy" -- spec 8.1). fp_parse() runs over the first * fragment's body, which holds the VS header, with the accumulated length as * its bound. * * Descriptors are re-armed per slot: when a frame completes, all 16 are pointed * at the OTHER slot BEFORE the picture is painted, so the chip can fill N+1 * while we draw N and can never write into the slot being painted. A frame * that needs more than 16 datagrams cannot be received at all: the ring is * exhausted, the partial frame is dropped and counted (RX_RING_DROPS). 128 * rows of 160 bytes is 20,496 bytes = 15 datagrams, so the design's strip frame * fits with one descriptor to spare. * * Screen geometry. VRAM is at bit 0x04000000 and a screen row is 0x800 bits * (256 bytes), the real unit's pitch (harness/c-hello/README.md). MAME's own * renderer decodes the same memory at 128 bytes a row; that difference is a * property of the harness, so the draw test compares VRAM as memory, not the * rendered picture. * * OS repaint canary (spec 8.9). The blit covers screen rows 0..1023 and * columns 0..1279 only. One 16-bit sentinel word sits in the unused right * edge of the last screen row (row 1023, column 1280), outside every pixel the * blit writes. If something else -- the resident OS -- paints over us it also * disturbs that corner; OS_TOUCH counts how often that was seen. This * decoder has no partial update: an accepted frame repaints the whole screen * anyway, so detecting the touch and repainting are the same act here, and * OS_TOUCH is the record. * * Marker layout: see the table in frame_parse.h. Markers are 32-bit cells on * the byte-aligned page at bit 0x01810000, 0x20 bits (4 bytes) apart. The * design plan names them 0x01810020, 0x01810024, ... but the TMS34010 is * bit-addressed, so 0x01810024 is four BITS after 0x01810020 -- inside the * same cell. The plan's numbers are byte offsets into the page; the rule is * * marker named 0x018100NN -> bit 0x01810000 + NN * 8 * * (POOL_PTR = 0x01810100, ...). Task 5's OS_TOUCH is 0x01810220 (ruling R12), * i.e. cell 0x44 -- free between the RX_* counters and Task 4's retired * injection cells. Task 7's ring record takes cells 0x10, 0x14, 0x18, 0x1c, * 0x40 and 0x48 (the free page prefix R12 left for the RX work, at the same * four-byte spacing every other cell uses); vidstream.lua uses the same rule. * * K&R C only: no prototypes, no '//', declarations before statements. */ #include "romapi.h" #include "poolcfg.h" /* POOL_BYTES, written by build-stream.sh */ #include "frmpsr.h" /* frame_parse.h under an 8.3 name; see that file */ #define HEAP_FREE 0x1E8BA0 /* firmware free-byte counter, IN BITS */ #define MARK 0x01A00000 /* .text must end before this bit address */ #define STATIC_BYTES 249856 /* the designed working set, 244 KB */ /* Screen memory and the strip geometry (see the header comment). */ #define VRAM_BASE 0x04000000 /* video RAM, bit address */ #define VRAM_ROW 0x800 /* 2048 bits = 256 bytes a screen row */ #define SCREEN_ROWS 1024 /* rows in the framebuffer */ #define ROW_PIXELS 1280 /* a strip row is 1280 pixels */ #define ROW_BYTES 160 /* 1280 pixels, 1 bpp */ /* * The sentinel word: last screen row, first column past the 1280 the blit * writes. Outside the blitted area by construction, inside mapped VRAM (the * next 256-byte row would be bit 0x06000000, which the driver maps as the NVR, * not as video memory). */ #define SENT_ADDR (VRAM_BASE + (SCREEN_ROWS - 1) * VRAM_ROW + ROW_PIXELS) #define SENT_VALUE 0xA5C3 /* * The two frame slots. SLOT_BYTES is the plan's 24 KB: a 128-row strip frame * (128 x 160 B + 16 B header = 20,496 B = 15 packets) fits, and so does any * smaller strip. Slot addresses are bit addresses, because that is what a * pointer holds in this toolchain (see harness/c-hello/README.md: "pointers * are bit addresses"). */ #define SLOT_BYTES 24576 /* * Task 6: the terminal's status frame. * * The payload takes the LANCE over -- STOP, its own init block, RX and TX * rings, INIT, IDON, STRT -- and every 64 accepted frames builds a 60-byte * Ethernet frame in its own buffer and hands it to the chip with a TX * descriptor. The descriptor setup is lance.c's (harness/udpstream/lance.c): * the ring words are TMD0/TMD1/TMD2/TMD3 at byte offsets 0/2/4/6, the buffer * address's high byte lives in TMD1, and the length is (0x10000 - bytes) in * TMD2. This is NOT a second driver: it is the same init/descriptor sequence, * with one buffer instead of a loopback probe's. * * Where the pieces live: the pool's last 4 KB was slack, so the LANCE block * goes there -- 24 bytes of init block, 64 bytes of TX ring (8 descriptors), the * 60-byte status frame and the RX ring (16 descriptors), all aligned at run * time in lance_start(). All of it is memory this payload owns, which is the * point: the firmware's own rings are in low memory with 32 descriptors (see * lance.c's note on the OS init block), and arming a slot in a ring whose * position the chip owns is not something this payload can do reliably. * * Task 7 arms the receiver (MODE = 0, so CSR0's STRT sets RXON) against those * 16 descriptors. `drops` in the status body is three counts, all meaning "a * frame that arrived and did not reach the screen": the chip's CSR0 MISS bit, * counted once per report window in which it was latched (the bit cannot * distinguish two misses in one window, and send_status() says so), plus the * payload's own count of frames the ring was too small to finish * (RX_RING_DROPS), plus the datagrams the chip flagged bad (RX_FCS_DROP) -- * those deliver no bytes at all, so without the third count a body-corrupting * packet would be invisible. The chip's field is not replaced, and the * payload's own counts are added to it rather than invented. */ #define LANCE_RAP 0x05900000 /* bit address of the register-select port */ #define LANCE_RDP 0x05800000 /* bit address of the data port */ #define CSR0_INIT 0x0001 #define CSR0_STRT 0x0002 #define CSR0_STOP 0x0004 #define CSR0_TDMD 0x0008 #define CSR0_IDON 0x0100 #define CSR0_MISS 0x1000 /* no RX descriptor: a missed-packet event */ #define TMD1_OWN 0x8000 #define TMD1_ENP 0x0100 #define TMD1_STP 0x0200 /* * The RX ring (Task 7). RX_DESC descriptors of 8 bytes each, each pointing at * its own RXBUFLEN-byte buffer inside the armed slot. RXBUFLEN is one full * 1514-byte Ethernet frame plus margin, the figure lance_cmd.c uses on the * unit: a datagram from vidsrc.py is 14 + 1450 = 1464 bytes, the chip appends * the 4-byte FCS, writes it after the frame and reports the total in RMD3. * RX_DESC * RXBUFLEN = 24416 bytes, which fits a 24576-byte slot. */ #define RX_DESC 16 #define RXBUFLEN 1526 #define RMD1_OWN 0x8000 /* the chip owns the descriptor */ #define RMD1_ENP 0x0100 /* end of packet */ #define RMD1_STP 0x0200 /* start of packet */ #define RMD1_BUFF 0x0400 /* buffer (descriptor) error */ #define RMD1_CRC 0x0800 /* CRC/FCS error */ #define RMD1_OFLO 0x1000 /* overflow error */ #define RMD1_FRAM 0x2000 /* framing error */ #define RMD1_ERR 0x4000 /* any of the above: the datagram is bad */ #define ETH_FCS 4 /* the chip stores this after the frame */ #define L_INIT_OFF 0x3C000 /* 12-word init block (24 bytes) */ #define L_RING_OFF 0x3C040 /* 8 TX descriptors, 8 bytes each */ #define L_RING_LEN 8 /* TLEN code 3 = 2^3 descriptors */ #define STATUS_EVERY 64 /* frames per status report */ #define STATUS_LEN 60 /* Ethernet minimum; the chip adds the FCS */ /* Offset of the VS header inside the Ethernet frame, and of the body. */ #define ETH_HDR_LEN 14 #define STAT_BODY (ETH_HDR_LEN + FP_HEADER_SIZE) /* * Static fallback. pool_static is global so the linker map names it and the * MAME check can assert the exact address a failed malloc falls back to. * stack_cushion and the five VRAM words below sit between it and ENTRY.ASM's * _cstack: TMS34010 pushes grow downward, so the 4 KB stack spills into those * 4 KB, never into the pool. */ unsigned int pool_static[STATIC_BYTES / 4]; unsigned int stack_cushion[1024]; /* * VRAM.ASM's externals. vram.asm is linked into every payload in this * directory; the C side owns the values. vram_b.asm (PIXBLT B,XY) shares * blit_src/blit_daddr/blit_dydx and ignores blit_sptch, because its SPTCH is * always 0 -- that is what makes one source row fill `stretch` screen rows. */ unsigned int vram_pattern; unsigned int blit_src, blit_sptch, blit_daddr, blit_dydx; extern void vram_blit_b_(); /* vram_b.asm: PIXBLT B,XY, SPTCH = 0 */ extern void vid_dint_(); /* vram_b.asm: DINT */ extern void vid_eint_(); /* vram_b.asm: EINT */ /* * The firmware's free-space counter is a 32-bit count IN BITS at * 0x1E8BA0 (dwt/notes/HEAP.md). POOL_FREE reports bytes, because the * marker is for humans and the check compares against POOL_BYTES. */ static unsigned int heap_bytes() { return *(unsigned int *)HEAP_FREE / 8; } /* * Task 6 state. The toolchain never runs .cinit, so main() clears these * before they are used (same rule as the markers). */ static unsigned int lance_ok; /* 1 once the LANCE is ours */ static unsigned int lance_txring; /* bit address of the TX ring */ static unsigned int lance_rxring; /* bit address of the RX ring */ static unsigned int status_buf; /* bit address of the status frame */ static unsigned int lance_txslot; /* next TX descriptor to arm */ static unsigned int status_seq; /* reports sent, the status frame_no*/ static unsigned int status_seen; /* frames_rx at the last report */ static unsigned int drops_total; /* CSR0 MISS windows, lower bound on misses */ /* * Task 7 receive state. * * rx_armed the slot the ring's 16 descriptors point into (0 = A, 1 = B). * Only ever written together with a full re-arm, so a descriptor * never points at a slot the payload is not expecting. * rx_pos the next descriptor the consumer expects. The chip fills * descriptors strictly in ring order (am79c90.cpp's * recv_complete_cb advances m_rx_ring_pos by one per packet and * INIT resets it to 0), and this payload consumes them in that * same order, so rx_pos *is* the chip's position -- there is no * second, shadow copy of it that could drift. It is published as * RX_RING_POS so the MAME check can inject at the right descriptor * without guessing. * rx_acc bytes of the current VS frame accumulated so far, i.e. the sum * of the delivered datagram bodies. It doubles as the "still * receiving" flag: 0 means no frame is in progress. * rx_nfrag fragments recorded for the frame being received. * rx_frag_* one entry per datagram: where its body sits in the slot, which * frame byte it starts at, and how many body bytes it delivered. * paint() walks this instead of copying the picture into a * contiguous buffer. * RX_RING_DROPS counts frames abandoned because the ring ran out of descriptors * before the frame completed: a frame larger than 16 datagrams cannot be * received at all, so it is dropped and reported rather than half-painted. * RX_FCS_DROP counts datagrams the chip flagged bad (RMD1_ERR) whose ethertype * says they are ours: they deliver no bytes at all -- the chip writes RMD3 = 0 * for them -- so without this cell a body-corrupting packet would be invisible * to both crc_err and drops. Each one abandons the frame it belongs to. */ static unsigned int rx_armed; static unsigned int rx_pos; static unsigned int rx_acc; static unsigned int rx_nfrag; static unsigned int rx_frag_off[RX_DESC]; static unsigned int rx_frag_ptr[RX_DESC]; static unsigned int rx_frag_len[RX_DESC]; /* * blit_row(src, daddr_xy, dydx) * * One PIXBLT B,XY. The three values are published where vram_b.asm reads * them (see the note there on the calling form; the assembly entry point takes * no arguments). `src` is the packed 1-bpp source row as a bit address, * `daddr_xy` is the screen position as (y << 16) | x, and `dydx` is the * rectangle as (screen rows << 16) | pixels. The primitive always sets * SPTCH = 0, so the "screen rows" dimension reads the same source row that * many times: that is the vertical stretch. */ static void blit_row(src, daddr_xy, dydx) unsigned int src; unsigned int daddr_xy; unsigned int dydx; { blit_src = src; blit_daddr = daddr_xy; blit_dydx = dydx; vram_blit_b_(); } /* * paint(h) -- draw one accepted strip frame from the fragment table. * * Source row r goes down screen rows r*stretch .. r*stretch+stretch-1. The * frame's bytes do not sit contiguously in the slot (each datagram's body is 14 * bytes into its own buffer), so a row is cut into the pieces the fragments * hold it in: the row starts at frame byte payload_off + r*ROW_BYTES, and each * piece is one PIXBLT from the fragment that holds it, with the destination x * advanced by the bytes already drawn. A 1450-byte datagram holds 9 rows and a * fraction, so only about one row in nine is split at all. * * A frame the screen cannot hold is not drawn: zero rows or zero stretch, more * than SCREEN_ROWS screen rows, or a payload shorter than the rows it claims * (fp_parse() and the accumulator have already proved that payload_len body * bytes arrived, so that bound is real). A refused frame never touches VRAM, * so a malformed header cannot paint garbage over a good one. * * The whole burst runs with interrupts masked (spec 8.9) so the resident OS * cannot repaint between two of our source rows and tear the picture. */ static void paint(h) fp_header *h; { unsigned int r, rows, stretch, sent, pos, done, k, f, room, n; rows = h->rows; stretch = h->stretch; if (h->kind != FP_KIND_STRIP || rows == 0 || stretch == 0) { return; } if (rows * stretch > SCREEN_ROWS) { return; } if (rows * ROW_BYTES > h->payload_len) { return; } if (rx_nfrag == 0) { return; } /* The canary word lives outside every pixel this frame writes. If it * is not ours any more, something else painted VRAM: count it and put * it back. There is no partial update here -- this frame repaints the * whole screen anyway -- so OS_TOUCH is the record, not a trigger. */ sent = (unsigned int)*(unsigned short *)SENT_ADDR; if (sent != SENT_VALUE) { *(unsigned int *)OS_TOUCH = *(unsigned int *)OS_TOUCH + 1; *(unsigned short *)SENT_ADDR = SENT_VALUE; } vid_dint_(); pos = h->payload_off; /* the frame's first body byte */ k = 0; for (r = 0; r < rows; r++) { done = 0; while (done < ROW_BYTES) { while (k < rx_nfrag && pos >= rx_frag_off[k] + rx_frag_len[k]) { k = k + 1; } if (k >= rx_nfrag) { vid_eint_(); return; /* truncated: stop drawing */ } if (pos < rx_frag_off[k]) { vid_eint_(); return; /* a gap: never paint across it */ } f = pos - rx_frag_off[k]; room = rx_frag_len[k] - f; n = ROW_BYTES - done; if (room < n) { n = room; } blit_row(rx_frag_ptr[k] + f * 8, ((r * stretch) << 16) | (done * 8), (stretch << 16) | (n * 8)); done = done + n; pos = pos + n; } } vid_eint_(); } /* * The LANCE, transmit only. The sequence is lance.c's, and the comment on * each step names what makes it necessary (harness/mame/README.md rule 4: a * step taken from another driver carries its source). */ static void w16(bit, v) unsigned int bit, v; { *(unsigned short *)bit = v; } static unsigned int r16(bit) unsigned int bit; { return *(unsigned short *)bit; } static void wb(bit, v) unsigned int bit, v; { *(unsigned char *)bit = v; } /* * The chip's register file takes a select write (RAP) then a data access * (RDP), and needs a settle between them: am79c90.cpp's regs_w consumes the * address register on the next access, and lance.c measured that a store * immediately after the select lands in the wrong register. Same 30-pass * settle as lance.c. */ static void lance_settle() { unsigned int i; for (i = 0; i < 30; i++) { } } static void lance_csr_w(reg, v) unsigned int reg, v; { *(unsigned short *)LANCE_RAP = reg; lance_settle(); *(unsigned short *)LANCE_RDP = v; lance_settle(); } static unsigned int lance_csr_r(reg) unsigned int reg; { unsigned int v; *(unsigned short *)LANCE_RAP = reg; lance_settle(); v = *(unsigned short *)LANCE_RDP; lance_settle(); return v; } /* The two slots' bit addresses, published by main() and never changed. */ static unsigned int slot_base[2]; /* * rx_arm(slot) -- point all 16 descriptors at `slot`, ownership set last. * * The chip masks the ring ADDRESS it is given (RING_ADDR_MASK 0xFFFFF8, * am79c90.h) but uses a descriptor's buffer address as written, in RMD1 bits * 0-7 (high byte) and RMD0 (low 16). NetBSD's am7990_rint() re-arms a used * descriptor with "length first, ownership last" and leaves the address alone; * here the address changes on every frame (the slot alternates), so the whole * descriptor is rewritten, and RMD1 is written FIRST with OWN CLEAR. * * The order is not cosmetic: the chip fetches RMD1 BEFORE the address. In * am79c90.cpp's receive path it reads `m_rx_md[1] = m_dma_in_cb(ring_address | * 2)` and only then `m_rx_md[0] = m_dma_in_cb(ring_address | 0)` and * `m_rx_md[2] = ... | 4`. With OWN set in the same write that carries the new * high byte, a fetch inside the window pairs the NEW slot's high byte with the * PREVIOUS slot's RMD0 -- a mixed buffer address. That is harmless only while * both slots share a 64 KB page; on the unit a 48 KB slot pair can cross one, * and the chip would then DMA into memory outside the pool. * * Publishing the address and length under OWN clear closes it: a fetch inside * the window reads OWN clear and the packet is a missed packet * (recv_complete_cb() case -2 latches CSR0 MISS), so it is counted and dropped * instead of being written somewhere wrong. OWN is then set in the last write. * * RMD2 is the buffer size as a negative two's complement count (0x10000 - * RXBUFLEN), the form am79c90's get_buf_length() decodes and lance_cmd.c arms * with. RMD3 is the message count the chip fills in; zeroing it first means a * descriptor completed by anything but the chip reads as a zero-length packet * rather than as the previous frame's length. */ static void rx_arm(slot) unsigned int slot; { unsigned int i, cb, base; base = slot_base[slot]; for (i = 0; i < RX_DESC; i++) { cb = (base >> 3) + i * RXBUFLEN; /* OWN clear, with the new high byte: a fetch here is a MISS. */ w16(lance_rxring + i * 64 + 16, (cb >> 16) & 0xFF); w16(lance_rxring + i * 64 + 0, cb & 0xFFFF); w16(lance_rxring + i * 64 + 32, (0x10000 - RXBUFLEN) & 0xFFFF); w16(lance_rxring + i * 64 + 48, 0); /* Ownership last, over a complete descriptor. */ w16(lance_rxring + i * 64 + 16, RMD1_OWN | ((cb >> 16) & 0xFF)); } } /* * rx_switch() -- arm the other slot. Called at the end of a frame, before the * picture is drawn, so the chip can fill N+1 while we paint N and can never * write into the slot being read (spec 8.1's overlap). */ static void rx_switch() { rx_armed = 1 - rx_armed; rx_arm(rx_armed); } /* * rx_dgram(i) -- consume descriptor i, which the chip has completed. * * One Ethernet frame per buffer, so STP and ENP must both be set. A packet * longer than one buffer would be chained across descriptors, and every * reference driver (4.4BSD pmax if_le.c, NetBSD am7990.c, Linux 7990.c) drops a * chained frame rather than reassembling it -- lance_cmd.c cites the same rule * -- so a chained descriptor contributes nothing here. RMD3 is the delivered * length INCLUDING the 4-byte FCS the chip appends, so the body is RMD3 minus * the Ethernet header and the FCS. * * The datagram is appended to the fragment table and the frame's own * payload_len decides when the frame is complete (spec 8.1: payload_len plus * the bytes actually delivered are authoritative, not the descriptor count). * RMD3 is per datagram here, because each datagram is its own Ethernet packet. */ static void rx_dgram(i) unsigned int i; { unsigned int rmd1, mlen, len, src, plen, total, lim, slot; fp_header h; rmd1 = r16(lance_rxring + i * 64 + 16); mlen = r16(lance_rxring + i * 64 + 48) & 0x0FFF; src = slot_base[rx_armed] + i * RXBUFLEN * 8 + ETH_HDR_LEN * 8; len = 0; if (rmd1 & RMD1_ERR) { /* * A datagram the chip flagged -- CRC/FCS, buffer, overflow or * framing -- delivers NO bytes: am79c90.cpp's * recv_complete_cb() writes RMD3 as * (m_rx_md[1] & RMD1_ERR) ? 0 : (result & RMD3_MCNT) * so the length checks below would drop it silently and a * body-corrupting packet would never appear in any counter. * * The bytes ARE in the buffer (dma_out() ran before the FCS * was checked), so a datagram of ours is still identifiable by * its ethertype -- the Ethernet header is written before the * body, so a body-corrupting error leaves it intact. Count one * drop for the frame it breaks, abandon that frame and re-arm * the other slot: with a datagram missing the accumulation can * never reach the frame's payload_len, and letting later * datagrams keep appending would mix two frames into one * picture. Traffic that is not ours is ignored, exactly as at * a frame start. * * If the corruption landed on the ethertype the datagram cannot * be attributed and is not counted. That is the limit of what * the chip leaves readable; it is stated in the report rather * than papered over. */ if (*(unsigned char *)(src - ETH_HDR_LEN * 8 + 12 * 8) == 0x88 && *(unsigned char *)(src - ETH_HDR_LEN * 8 + 13 * 8) == 0xB7) { *(unsigned int *)RX_FCS_DROP = *(unsigned int *)RX_FCS_DROP + 1; rx_acc = 0; rx_nfrag = 0; rx_switch(); } return; } if ((rmd1 & (RMD1_STP | RMD1_ENP)) == (RMD1_STP | RMD1_ENP) && mlen > ETH_HDR_LEN + ETH_FCS && mlen - ETH_HDR_LEN - ETH_FCS <= RXBUFLEN - ETH_HDR_LEN) { len = mlen - ETH_HDR_LEN - ETH_FCS; } if (len == 0) { return; /* runt or chained: nothing appended */ } if (rx_acc == 0) { /* A frame starts here. Only a VS frame is ours: the chip * accepts broadcast and its own address, so other traffic can * land in this ring, and fp_parse() would count every such * datagram as a bad frame. Resync on the magic instead. */ if (len < 2 || *(unsigned char *)src != FP_MAGIC0 || *(unsigned char *)(src + 8) != FP_MAGIC1) { return; } } rx_frag_off[rx_nfrag] = rx_acc; rx_frag_ptr[rx_nfrag] = src; rx_frag_len[rx_nfrag] = len; rx_nfrag = rx_nfrag + 1; rx_acc = rx_acc + len; if (rx_acc < FP_HEADER_SIZE) { return; } plen = (unsigned int)*(unsigned char *)(rx_frag_ptr[0] + 6 * 8) | ((unsigned int)*(unsigned char *)(rx_frag_ptr[0] + 7 * 8) << 8); total = FP_HEADER_SIZE + plen; if (total > SLOT_BYTES) { /* The header claims a frame larger than the slot: it can never be * completed, and no part of it is drawn. Reject it now, then hand * the OTHER slot to the chip: all 16 descriptors are re-armed, * including the one this datagram arrived in, so the sender's next * frame starts on a clean ring. What is left of the rejected frame * is ignored, exactly as any frame that does not open with the * magic is. */ *(unsigned int *)RX_BAD = *(unsigned int *)RX_BAD + 1; rx_acc = 0; rx_nfrag = 0; rx_switch(); return; } if (rx_acc < total) { return; /* still arriving */ } /* The frame is complete. Hand the OTHER slot to the chip first, so * nothing it receives from now on lands in the picture below. */ slot = rx_armed; rx_switch(); /* * Task 4 finding (b): the length handed to fp_parse() is clamped to the * slot. rx_acc is bounded by RX_DESC * RXBUFLEN and the claim above, * but fp_parse() walks a caller-supplied length, and a slot-sized bound * is the contract both of them can be read against. */ lim = rx_acc; if (lim > SLOT_BYTES) { lim = SLOT_BYTES; } if (fp_parse((unsigned char *)rx_frag_ptr[0], lim, &h)) { *(unsigned int *)RX_LAST_LEN = h.payload_len; *(unsigned int *)RX_SLOT = slot; paint(&h); /* Published after the paint: a reader that sees RX_OK move * sees a picture that is already on the screen. */ *(unsigned int *)RX_OK = *(unsigned int *)RX_OK + 1; } else { *(unsigned int *)RX_BAD = *(unsigned int *)RX_BAD + 1; } rx_acc = 0; rx_nfrag = 0; } /* * rx_poll() -- drain every descriptor the chip has completed, in ring order. * * Task 4 finding (a): there is no level-triggered "frame ready" cell any more. * The handshake is the OWN bit, per descriptor: the chip clears it when the * packet is in the buffer and this side sets it again (in rx_arm) only after * reading the descriptor's data, so there is no shared flag for two writers to * race on. `rx_pos` is the only cursor and it advances only after a descriptor * has been read; the chip advances its own position by one per packet and INIT * resets both to 0, so consuming in ring order keeps the two in step. * * A frame that uses the whole ring without completing is dropped: the ring * cannot hold it, so it can never complete, and leaving the partial frame in * place would make every later frame look like its tail. */ static void rx_poll() { unsigned int i, n; for (n = 0; n < RX_DESC; n++) { i = rx_pos; if (r16(lance_rxring + i * 64 + 16) & RMD1_OWN) { break; /* the chip has not completed it */ } rx_pos = (i + 1) & (RX_DESC - 1); *(unsigned int *)RX_DGRAM = *(unsigned int *)RX_DGRAM + 1; rx_dgram(i); if (rx_nfrag == RX_DESC && rx_acc != 0) { *(unsigned int *)RX_RING_DROPS = *(unsigned int *)RX_RING_DROPS + 1; rx_acc = 0; rx_nfrag = 0; rx_switch(); } } *(unsigned int *)RX_RING_POS = rx_pos; *(unsigned int *)RX_RING_ACC = rx_acc; *(unsigned int *)RX_RING_ARMED = rx_armed; } /* * lance_start(ptr) -- take the LANCE over for receive and transmit. 1 on * success. * * STOP comes first because the init block address (CSR1/CSR2/CSR3) is only * writable while the chip is stopped -- am79c90.cpp regs_w guards each of them * with "if (m_csr[0] & CSR0_STOP)" -- and because STOP resets the chip, which * clears INEA: the network interrupt is off before this payload touches * anything, so the firmware's ISR cannot act on a half-programmed ring. The * caller also masks interrupts for the whole sequence. * * The init block's words are the C-LANCE's own order: MODE, PADR (three * words), LADRF (four), RX ring address/length, TX ring address/length. A * ring word is the address's bits 16-23 in its low byte and the length code * (2^code descriptors) in bits 13-15; the pool sits above 64 KB, so unlike * lance.c's low-memory rings the high byte is not zero and has to be written. * * Task 7 removed MODE_DRX and gave the receiver a ring, because that is what a * receive path is: MODE = 0 (no loopback, no transmitter disable, FCS kept and * appended) and STRT afterwards sets RXON as well as TXON (am79c90.cpp's STRT * handler: "if (m_mode & MODE_DRX) clear RXON else set RXON"). The descriptors * are armed for slot A before INIT, so the chip's first packet -- which can * arrive the instant STRT lands -- finds a buffer. */ static int lance_start(ptr) unsigned int ptr; { unsigned int init, ring, buf, rxring, cb, i, idon; /* * Alignment is the CHIP's requirement, not a convenience. The Am7990 * masks what it is given: INIT_ADDR_MASK 0xFFFFFE for the init block and * RING_ADDR_MASK 0xFFFFF8 for a ring (am79c90.h), because a descriptor * is eight bytes. The pool base is only word-aligned (0x0090C8D0 is * byte 0x12191A), so a fixed offset does NOT give a ring the chip can * find: the first run of this code put the ring at byte 0x15D93A, the * chip masked that to 0x15D938 and read our TMD0 word as TMD1 -- it never * fetched the status buffer at all (proved from VT1200_LANCE_DMA_LOG: * no read of the buffer address, and the chip writing 0x599A into our * TMD0 words). Round up to the alignment the chip will keep. */ init = (ptr + L_INIT_OFF * 8 + 0x0F) & 0xFFFFFFF0; /* 16-bit words */ ring = (ptr + L_RING_OFF * 8 + 0x3F) & 0xFFFFFFC0; /* 8-byte */ buf = ring + L_RING_LEN * 64; /* after the ring */ rxring = (buf + STATUS_LEN * 8 + 0x3F) & 0xFFFFFFC0; /* after status */ lance_txring = ring; status_buf = buf; lance_rxring = rxring; lance_csr_w(0, CSR0_STOP); w16(init + 0 * 16, 0); /* MODE: no loopback, FCS on */ w16(init + 1 * 16, 0x0008); /* PADR 08:00:2b:16:4b:5a */ w16(init + 2 * 16, 0x162b); w16(init + 3 * 16, 0x5a4b); w16(init + 4 * 16, 0); /* LADRF: physical + broadcast */ w16(init + 5 * 16, 0); w16(init + 6 * 16, 0); w16(init + 7 * 16, 0); cb = rxring >> 3; /* BYTE address of the RX ring */ w16(init + 8 * 16, cb & 0xFFFF); w16(init + 9 * 16, ((cb >> 16) & 0xFF) | (4 << 13)); /* 16 desc */ cb = ring >> 3; /* BYTE address of the ring */ w16(init + 10 * 16, cb & 0xFFFF); w16(init + 11 * 16, ((cb >> 16) & 0xFF) | (3 << 13)); /* Every TX descriptor points at the one status buffer; TMD1's OWN stays * clear until a frame is ready, and TMD2 holds the length as a two's * complement (0x10000 - 60), lance.c's descriptor layout. */ cb = buf >> 3; for (i = 0; i < L_RING_LEN; i++) { w16(ring + i * 64 + 0, cb & 0xFFFF); w16(ring + i * 64 + 16, (cb >> 16) & 0xFF); w16(ring + i * 64 + 32, (0x10000 - STATUS_LEN) & 0xFFFF); w16(ring + i * 64 + 48, 0); } for (i = 0; i < STATUS_LEN; i++) { wb(buf + i * 8, 0); } /* The RX ring, armed for slot A: this is the state the first packet * finds, and it is what the MAME ring check asserts. */ rx_armed = 0; rx_arm(rx_armed); *(unsigned int *)RX_RING_BASE = rxring; lance_csr_w(1, (init >> 3) & 0xFFFF); lance_csr_w(2, ((init >> 3) >> 16) & 0xFF); lance_csr_w(3, 0); lance_csr_w(0, CSR0_INIT); idon = 0; for (i = 0; i < 20000; i++) { idon = lance_csr_r(0); if (idon & CSR0_IDON) { break; } } *(unsigned int *)ST_INIT = idon; if (!(idon & CSR0_IDON)) { return 0; } /* STRT turns the receiver and the transmitter on; no INEA, so no * interrupt reaches the firmware's network ISR while this payload owns * the chip. RXON is what makes the descriptors armed above live. */ lance_csr_w(0, CSR0_STRT); for (i = 0; i < 30; i++) { } return 1; } /* * send_status(frames_rx) -- build and transmit one status frame. * * The frame is an ordinary Ethernet frame: broadcast destination (the LANCE * accepts broadcast with an empty LADRF, and the host's capture sees it), the * terminal's own address from its Ethernet ROM, the stream's ethertype 0x88B7 * (frame.py ETHERTYPE), then the same 16-byte VS header every other frame * carries, with rows and stretch zero because a status has no geometry. The * body is 12 bytes, LITTLE-endian as frame.py's pack_status() writes it: * frames_rx u32, crc_err u16, drops u16, csr0 u16, heap_free_kb u16. (The * command protocol in lance_cmd.c is big-endian; this one is not, and getting * it backwards reads 64 frames as 0x40000000 -- which is how the Task 6 * MAME run first found it.) * * crc_err is RX_BAD: the frames fp_parse() rejected (header CRC, magic, * version or length). Since ruling R14 a corrupted *body* cannot appear * here -- the LANCE validated each datagram's FCS before the bytes were * stored -- so this is the only CRC error the terminal can count. A datagram * whose FCS the CHIP rejected is not a frame at all: it delivers no bytes * (RMD3 = 0) and is counted in drops, via RX_FCS_DROP. * * drops is three counts of frames that arrived and did not reach the screen: * * drops_total CSR0 MISS, counted per REPORT WINDOW in which the latched bit * was set -- the chip gives no per-packet count, so this is one * per window at most (a lower bound, see send_status()). * RX_RING_DROPS frames too large for the 16 descriptors (Task 7). * RX_FCS_DROP datagrams the chip flagged bad; each abandons its frame. * * Neither number is inferred from another. * * Returns 1 when the frame was handed to the chip, 0 when the ring slot was * still owned and nothing was armed. Overwriting a descriptor the chip still * owns is what every reference driver refuses to do (Linux stops the queue, * NetBSD prints "missing buffer"), and the caller must not count a report it * never sent: it advances status_seen only on 1. */ static int send_status(frames_rx) unsigned int frames_rx; { unsigned int i, crc, csr0, tmd1, off, hb, miss, bad, rdrops, fdrops, free_kb; hb = status_buf; /* Ethernet header. */ for (i = 0; i < 6; i++) { wb(hb + i * 8, 0xFF); } wb(hb + 6 * 8, 0x08); wb(hb + 7 * 8, 0x00); wb(hb + 8 * 8, 0x2B); wb(hb + 9 * 8, 0x16); wb(hb + 10 * 8, 0x4B); wb(hb + 11 * 8, 0x5A); wb(hb + 12 * 8, 0x88); wb(hb + 13 * 8, 0xB7); /* The VS header, laid out exactly as frame.py's pack_header() does. */ off = hb + ETH_HDR_LEN * 8; wb(off + 0 * 8, FP_MAGIC0); wb(off + 1 * 8, FP_MAGIC1); wb(off + 2 * 8, FP_VERSION); wb(off + 3 * 8, FP_KIND_STATUS); wb(off + 4 * 8, status_seq & 0xFF); wb(off + 5 * 8, (status_seq >> 8) & 0xFF); wb(off + 6 * 8, FP_STATUS_SIZE); wb(off + 7 * 8, 0); wb(off + 8 * 8, 0); /* rows: none */ wb(off + 9 * 8, 0); /* stretch: none */ wb(off + 10 * 8, 0); /* reserved */ wb(off + 11 * 8, 0); crc = fp_crc((unsigned char *)off, FP_PREFIX_SIZE); wb(off + 12 * 8, crc & 0xFF); wb(off + 13 * 8, (crc >> 8) & 0xFF); wb(off + 14 * 8, 0); wb(off + 15 * 8, 0); /* The chip's status word, and the missed-packet bit it latches. CSR0 * MISS is ONE BIT: am79c90.cpp's recv_complete_cb() only ORs it in * (case -2), so two missed packets inside one report window look exactly * like one. drops_total therefore counts report windows in which at * least one packet was missed -- a lower bound on missed packets, not an * event count -- and the bit is cleared here after the report is built * (write-1-to-clear) so the next window starts clean. The status field * is a "something was missed" indicator, and this comment says so * instead of claiming a count the hardware cannot give. */ csr0 = lance_csr_r(0); miss = 0; if (csr0 & CSR0_MISS) { drops_total = drops_total + 1; miss = 1; } bad = *(unsigned int *)RX_BAD; /* The three drop counts described above: the chip's MISS windows, the * payload's count of frames too large for the ring, and the datagrams * the chip flagged bad (they carry no bytes, so nothing else sees * them). */ rdrops = *(unsigned int *)RX_RING_DROPS; fdrops = *(unsigned int *)RX_FCS_DROP; free_kb = heap_bytes() >> 10; off = hb + STAT_BODY * 8; wb(off + 0 * 8, frames_rx & 0xFF); wb(off + 1 * 8, (frames_rx >> 8) & 0xFF); wb(off + 2 * 8, (frames_rx >> 16) & 0xFF); wb(off + 3 * 8, (frames_rx >> 24) & 0xFF); wb(off + 4 * 8, bad & 0xFF); wb(off + 5 * 8, (bad >> 8) & 0xFF); wb(off + 6 * 8, (drops_total + rdrops + fdrops) & 0xFF); wb(off + 7 * 8, ((drops_total + rdrops + fdrops) >> 8) & 0xFF); wb(off + 8 * 8, csr0 & 0xFF); wb(off + 9 * 8, (csr0 >> 8) & 0xFF); wb(off + 10 * 8, free_kb & 0xFF); wb(off + 11 * 8, (free_kb >> 8) & 0xFF); /* Arm the next descriptor: TMD3, then the length, then ownership last, * so the chip cannot take a descriptor whose length is still stale * (lance_cmd.c does the same, and NetBSD's am7990_rint() says why). */ off = lance_txring + lance_txslot * 64; for (i = 0; i < 10000; i++) { if (!(r16(off + 16) & TMD1_OWN)) { break; } } if (r16(off + 16) & TMD1_OWN) { return 0; /* still the chip's: arm nothing */ } w16(off + 48, 0); w16(off + 32, (0x10000 - STATUS_LEN) & 0xFFFF); w16(off + 16, TMD1_OWN | TMD1_STP | TMD1_ENP | ((status_buf >> 3 >> 16) & 0xFF)); /* Where the harness must look to watch the chip finish this frame. */ *(unsigned int *)ST_TMLAST = off + 16; /* One write: a bare TDMD would clear STRT (CSR0 is a command shadow; * lance.c's comment and every reference driver agree). */ lance_csr_w(0, CSR0_STRT | CSR0_TDMD); tmd1 = 0; for (i = 0; i < 20000; i++) { tmd1 = r16(off + 16); if (!(tmd1 & TMD1_OWN)) { break; } } lance_txslot = (lance_txslot + 1) & (L_RING_LEN - 1); status_seq = status_seq + 1; *(unsigned int *)ST_FRAMES = frames_rx; *(unsigned int *)ST_TMD1 = tmd1; /* ST_SENT last: a reader that sees it move reads a frame that was * built, transmitted and recorded, never one in progress. */ *(unsigned int *)ST_SENT = *(unsigned int *)ST_SENT + 1; if (miss) { lance_csr_w(0, CSR0_MISS); } return 1; } int main() { unsigned int before, after, ptr, frames, sent; *(unsigned int *)VID_ALIVE = 0; *(unsigned int *)POOL_PTR = 0; *(unsigned int *)POOL_FREEB = 0; *(unsigned int *)POOL_FREEA = 0; /* Receive record: counters and the two slots are cleared before anything * is published, so a reader never sees a counter from a previous run or a * slot address that has no allocation behind it. The ring record is * published by lance_start() once the descriptors are armed. */ *(unsigned int *)RX_OK = 0; *(unsigned int *)RX_BAD = 0; *(unsigned int *)RX_LAST_LEN = 0; *(unsigned int *)RX_SLOT = 0; *(unsigned int *)RX_SLOT_A = 0; *(unsigned int *)RX_SLOT_B = 0; *(unsigned int *)RX_RING_BASE = 0; *(unsigned int *)RX_RING_POS = 0; *(unsigned int *)RX_RING_ACC = 0; *(unsigned int *)RX_RING_ARMED = 0; *(unsigned int *)RX_RING_DROPS = 0; *(unsigned int *)RX_FCS_DROP = 0; *(unsigned int *)RX_DGRAM = 0; *(unsigned int *)OS_TOUCH = 0; /* Arm the VRAM canary before any frame is accepted, so a repaint * between now and the first blit is counted rather than mistaken for * our own last frame. */ *(unsigned short *)SENT_ADDR = SENT_VALUE; before = heap_bytes(); ptr = malloc_(POOL_BYTES); /* malloc_ takes BYTES, returns BITS */ after = heap_bytes(); if (ptr == 0) { /* The fallback is the 244 KB static pool by definition; a larger * request still reports the failure through the low-bit flag. */ ptr = (unsigned int)pool_static; *(unsigned int *)POOL_FREEB = before | 1; } else { *(unsigned int *)POOL_FREEB = before; } *(unsigned int *)POOL_FREEA = after; /* Slot B follows slot A in the same pool. Both are published before * POOL_PTR so the harness can find them from either one. */ slot_base[0] = ptr; slot_base[1] = ptr + SLOT_BYTES * 8; *(unsigned int *)RX_SLOT_A = slot_base[0]; *(unsigned int *)RX_SLOT_B = slot_base[1]; *(unsigned int *)POOL_PTR = ptr; /* publish last: a complete record */ /* Task 6: the status back-channel. The record starts empty, then the * LANCE is taken over for receive and transmit with interrupts masked -- * the OS's drivers were using this chip a moment ago, and a network ISR * running in the middle of the ring setup would see a half-written * descriptor. */ *(unsigned int *)RX_EOF = 0; *(unsigned int *)ST_SENT = 0; *(unsigned int *)ST_FRAMES = 0; *(unsigned int *)ST_BUF = 0; *(unsigned int *)ST_TMD1 = 0; *(unsigned int *)ST_INIT = 0; *(unsigned int *)ST_TMLAST = 0; lance_txslot = 0; status_seq = 0; status_seen = 0; drops_total = 0; rx_armed = 0; rx_pos = 0; rx_acc = 0; rx_nfrag = 0; vid_dint_(); lance_ok = lance_start(ptr); vid_eint_(); *(unsigned int *)ST_BUF = status_buf; for (;;) { *(unsigned int *)VID_ALIVE = *(unsigned int *)VID_ALIVE + 1; rx_poll(); /* Report every STATUS_EVERY accepted frames, and -- once the * harness says the sender has stopped -- for every further * accepted frame, so a finite run ends with a report whose * frames_rx is the final count rather than the last multiple * of 64 (RX_EOF is harness-only; see frame_parse.h). */ frames = *(unsigned int *)RX_OK; if (!lance_ok) { continue; } if (frames - status_seen >= STATUS_EVERY || (*(unsigned int *)RX_EOF != 0 && frames != status_seen)) { vid_dint_(); sent = send_status(frames); vid_eint_(); if (sent) { status_seen = frames; } } } }