/* * frame_parse.h -- the wire format and the receive-side frame check. * * This is the one place the wire format is written down in C; the host sender * writes it down in harness/udpstream/frame.py. The two must agree byte for * byte, and harness/udpstream/tests/test_frame_parse.c pins the agreement with * a frame generated by frame.py (a golden vector, not a value this code * produced itself). * * offset size field * ------ ---- ------------------------------------------------------ * 0 2 magic, ASCII 'V','S' * 2 1 version, 0x01 * 3 1 kind (KIND_STRIP = 1, KIND_STATUS = 5) * 4 2 frame_no, u16 little-endian * 6 2 payload_len, u16 little-endian * 8 1 rows * 9 1 stretch * 10 2 reserved, zero * 12 2 crc16, u16 little-endian * 14 2 padding, zero * * The CRC is CCITT-FALSE (polynomial 0x1021, initial value 0xFFFF, no input or * output reflection, no final xor) over header bytes 0..11 ONLY (ruling R14); * the payload is never part of it. A corrupted payload byte is therefore * accepted -- the Ethernet FCS, checked by the LANCE per datagram, is the * check for those bytes -- while a corrupted header byte is rejected. What * catches a truncated frame is the `len >= FP_HEADER_SIZE + payload_len` bound * in fp_parse()'s contract at the end of this file. * * --------------------------------------------------------------------------- * Marker page: bit 0x01810000, byte 0x00302000. A cell below is one 32-bit * word. Cells are 0x20 BITS apart (4 bytes), which is the rule Task 3 * established: "marker named 0x018100NN" means bit 0x01810000 + NN*8, because * the TMS34010 is bit-addressed and four bits after a word would still be * inside the same word. The plan numbers the page from 0x01810100, so both * offsets are given. * * cell page offset bit address meaning * ------------ ----------- ----------- -------------------------------- * RX_RING_BASE +0x080 0x01810080 bit address of the RX ring * RX_RING_POS +0x0A0 0x018100A0 next descriptor to consume (the * chip's ring position) * RX_RING_ACC +0x0C0 0x018100C0 bytes of the frame arriving * RX_RING_ARMED +0x0E0 0x018100E0 slot the ring is armed on (0=A) * RX_RING_DROPS +0x200 0x01810200 frames too big for the ring * RX_DGRAM +0x240 0x01810240 datagrams consumed * RX_FCS_DROP +0x260 0x01810260 datagrams the chip flagged bad * (no bytes delivered; each * abandons its frame) * POOL_PTR +0x00 0x01810100 bit pointer to the 244 KB pool * POOL_FREEB +0x20 0x01810120 heap free bytes before malloc * POOL_FREEA +0x40 0x01810140 heap free bytes after malloc * VID_ALIVE +0x60 0x01810160 heartbeat while the loop runs * RX_OK +0x80 0x01810180 frames accepted by fp_parse * RX_BAD +0xA0 0x018101A0 frames rejected by fp_parse * RX_LAST_LEN +0xC0 0x018101C0 payload_len of the last accepted * RX_SLOT +0xE0 0x018101E0 slot of the last accepted (0=A) * OS_TOUCH +0x220 0x01810220 times the VRAM canary was not ours * * NOTE ON OWNERSHIP OF THE PAGE: nothing allocates this page. The firmware's * large free block starts at byte 0x0012191A and grows upward, the page sits at * byte 0x00302000 inside it and .text starts at byte 0x00318000 just above, so * an allocator asked for enough memory -- or a long soak of small allocations -- * can hand these cells to someone else and the markers will be overwritten * under the payload. Read them WHILE the payload runs (a live debugger peek, * or a dump taken during the run); a marker page read after a soak is a claim * about whatever now owns the memory, not about the payload. * * OS_TOUCH is Task 5's OS-repaint canary (design spec 8.9): the blit leaves one * 16-bit sentinel word in the unused right edge of the last screen row, and * OS_TOUCH counts how often that word was not what the payload left there. It * is at cell 0x44 -- free between the RX_* counters and the injection cells * below (ruling R12). * * Debugger injection interface (Task 4; retired by Task 7's ring): * * RX_SLOT_A +0x200 0x01810300 bit address of slot A * RX_SLOT_B +0x220 0x01810320 bit address of slot B * * Task 4 received frames through a "slot ready" flag (RX_READY_A/B plus a byte * count) that the debugger raised after poking a frame into a slot. Task 7 * removed those cells: the payload now owns a real LANCE RX ring and the * handshake is the descriptor's OWN bit, per descriptor, with the chip clearing * it and the payload setting it again only after reading the buffer. There is * no level-triggered flag for two writers to race on, which was Task 4's * Important finding (a). * * The harness therefore plays the CHIP: it writes a datagram into the buffer of * the descriptor at RX_RING_POS (and the ones after it, for a multi-datagram * frame) and completes it the way am79c90.cpp does -- RMD3 = delivered length * including the FCS, RMD1 = STP | ENP with OWN clear. RX_ARMED says which slot * those descriptors point into. The two slot addresses above remain published * because they are the payload's own record of where its buffers are. * --------------------------------------------------------------------------- * * Task 6 adds one more cell of the same kind and the status record the harness * reads back: * * RX_EOF +0x3C0 0x018103C0 1 = the sender stopped * ST_SENT +0x3E0 0x018103E0 status frames transmitted * ST_FRAMES +0x400 0x01810400 frames_rx in the last status * ST_BUF +0x420 0x01810420 bit address of the 60-byte frame * ST_TMD1 +0x440 0x01810440 TMD1 as the payload read it at * the end of its own wait * ST_INIT +0x460 0x01810460 CSR0 after the LANCE INIT wait * ST_TMLAST +0x480 0x01810480 bit address of the last armed * TX descriptor's TMD1 word * * RX_EOF is harness-only in exactly the same sense as the retired injection * flag was: it says "the sender has stopped announcing frames", which on a real * wire is silence and carries no signal. It exists so a finite run can ask for * a final status instead of waiting for the count to reach the next multiple of * 64. The payload reports as soon as it sees it, and keeps reporting for each * further accepted frame; nothing reads it in a continuous stream. * --------------------------------------------------------------------------- * * K&R C only. The 1987 toolchain in build-stream.sh compiles this header, so * there are no ANSI prototypes, no '//' comments and no 'const' (romapi.h is * written the same way): the parameter lists live in the comments and in the * definitions in frame_parse.c. * * 8.3 names. The 1987 preprocessor cannot open an 11-character base name * (Task 3 hit this with VIDSTREAM.C), so this file is also reachable as * frmpsr.h -- a symlink to it -- and build-stream.sh stages it as FRMPSR.H. * Host compiles may use either name. */ #ifndef FRAME_PARSE_H #define FRAME_PARSE_H #define FP_HEADER_SIZE 16 /* bytes of fixed header before the payload */ #define FP_PREFIX_SIZE 12 /* header bytes the CRC covers, before payload */ #define FP_CRC_OFF 12 /* offset of the stored crc16, low byte first */ #define FP_MAGIC0 0x56 /* 'V' */ #define FP_MAGIC1 0x53 /* 'S' */ #define FP_VERSION 1 #define FP_KIND_STRIP 1 #define FP_KIND_STATUS 5 /* Marker page cells; see the table above. */ #define MARK_PAGE 0x01810000 #define MARK_CELL(n) (MARK_PAGE + (n) * 8) #define POOL_PTR MARK_CELL(0x20) #define POOL_FREEB MARK_CELL(0x24) #define POOL_FREEA MARK_CELL(0x28) #define VID_ALIVE MARK_CELL(0x2c) #define RX_OK MARK_CELL(0x30) #define RX_BAD MARK_CELL(0x34) #define RX_LAST_LEN MARK_CELL(0x38) #define RX_SLOT MARK_CELL(0x3c) #define OS_TOUCH MARK_CELL(0x44) /* * Task 7: the RX ring record. Task 4's injection cells (0x68..0x74) are gone; * these take the free page prefix R12 left for the RX work, at the SAME 4-byte * spacing every other cell uses (n increases by 4 per cell -- 0x10, 0x14, 0x18 * and 0x1C are four cells, and a 32-bit store at 0x01810080 does not reach * 0x01810088). Reading "n = 0x10..0x15" as five adjacent cells is the mistake * this comment exists to prevent: those four cells would be ONE BYTE apart and * the 32-bit stores would overlap into zeros. */ #define RX_RING_BASE MARK_CELL(0x10) #define RX_RING_POS MARK_CELL(0x14) #define RX_RING_ACC MARK_CELL(0x18) #define RX_RING_ARMED MARK_CELL(0x1c) #define RX_RING_DROPS MARK_CELL(0x40) #define RX_DGRAM MARK_CELL(0x48) #define RX_FCS_DROP MARK_CELL(0x4c) #define RX_SLOT_A MARK_CELL(0x60) #define RX_SLOT_B MARK_CELL(0x64) /* Task 6: the sender/keyframe control cell and the status record. */ #define RX_EOF MARK_CELL(0x78) #define ST_SENT MARK_CELL(0x7c) #define ST_FRAMES MARK_CELL(0x80) #define ST_BUF MARK_CELL(0x84) #define ST_TMD1 MARK_CELL(0x88) #define ST_INIT MARK_CELL(0x8c) #define ST_TMLAST MARK_CELL(0x90) /* The status body (Task 6): frames_rx u32, then four u16, little-endian. */ #define FP_STATUS_SIZE 12 /* What a valid frame told us. payload_off is FP_HEADER_SIZE on success. */ typedef struct { unsigned int kind; unsigned int frame_no; unsigned int rows; unsigned int stretch; unsigned int payload_len; unsigned int payload_off; } fp_header; /* * int fp_parse(unsigned char *buf, unsigned int len, fp_header *out) * * 1 and *out filled when buf[0..len) is a well-formed frame, 0 otherwise. * A frame is well formed when len is at least FP_HEADER_SIZE, the magic and * version match, len is at least FP_HEADER_SIZE + payload_len, and the stored * crc16 equals the CCITT-FALSE CRC over buf[0..11]. Bytes beyond the frame * are ignored: payload_len is authoritative (the transport hands us exactly * what arrived, and a tail is not part of the frame). */ int fp_parse(); /* * unsigned int fp_crc(unsigned char *buf, unsigned int len) * * The CCITT-FALSE CRC of len bytes (poly 0x1021, init 0xFFFF, not reflected, * no final xor) -- the same routine fp_parse() runs over the 12 header bytes, * exported so the terminal can seal the status header it builds (Task 6). * One implementation, so the receive and transmit checks cannot drift apart. * Returns the 16-bit CRC in the low half of an unsigned int. */ unsigned int fp_crc(); #endif /* FRAME_PARSE_H */