; ---------------------------------------------------------------- xfer.i ; THE TRANSPORT BEHIND THE MAILBOX. ROADMAP P4b. ; ; src/player/ring.i has always ended at a seam: it decides which record to ; fetch, where in the ring to put it and when that is safe, writes those four ; words into XF_SLOT and bumps XF_GO, and then polls XF_ACK. On the other side ; of that seam, until now, was tools/bench/stream.lua -- a host moving bytes at ; a MODELLED rate, with XF_ACK synthesised out of emulated time. A player has ; no host. This file is the other side: XF_GO is answered by src/player/scsi.i ; issuing a real READ(10) to a real MB89352, and XF_ACK is a word the 68000 ; bumps when the bytes have landed. ; ; NOTHING ABOVE THE SEAM CHANGED, and that is deliberate for the same reason it ; was in sessions 22 and 23: ring.i cannot tell which transport answered it, so ; a green run here is a test of THIS file rather than of a new producer. The ; two hooks in ring.i (one in ring_poll, one in ring_seek's quiet-wait) are the ; whole of the change on that side, and with XF_SCSI = 0 they are a tst and a ; branch. ; ; IT IS SYNCHRONOUS, AND THAT IS NOT A SHORTCUT -- IT IS THE FINDING. The ; modelled transport overlapped: a request issued at time t landed at t + len/ ; rate while the 68000 got on with decoding, which is what a DMAC channel does. ; Here the CPU moves every byte itself through $EA0015 (57.3: a PIO write to ; that address is discarded, so even "PIO" runs the SPC in DMA mode with the CPU ; standing in for the channel), so `bsr xf_service` does not start a transfer, ; it PERFORMS one. A two-deep request queue therefore buys nothing at all: the ; second slot is filled and drained by the same instruction stream that would ; have been decoding. FINDINGS 55.3's whole result -- that a one-deep queue ; gives away 6.8% of the pipe -- is about a transport that runs in parallel with ; the CPU, and this one does not. ; ; So what this file is FOR is not to be the shipping transport. It is to make ; the shipping transport's cost measurable: the same 120 pixel-exact frames, ; delivered by the machine off a real volume, with the CPU cost of doing it ; charged where a rate model cannot hide it. P4a -- the HD63450 holding the bus ; -- is what makes the transfer overlap again, and until it exists this is the ; honest floor. ; ---- state. Above src/player/ring.i's instruments (last: SK_WAIT at $1837C) ; and below the disc-offset table at $19400. XF_SCSI = $18380 ; 1 = the 68000 is the transport (input) XS_LBA0 = $18384 ; LBA of byte 0 of the scene's frame stream XS_NXFER = $18388 ; transfers completed XS_NBYTE = $1838C ; record bytes delivered into the ring XS_NWIRE = $18390 ; bytes actually read off the disc, sectors and ; all -- the two differ and 58.3 is why XS_ERR = $18394 ; SC_ERR of the FIRST failure, 0 = none XS_ERRAT = $18398 ; ...and the request index it failed on ; ---------------------------------------------------------------- xf_init ; Clears the instruments and brings the SPC up, if this run has one. Called ; before ring_init, because ring_init ends in a ring_seek and a seek waits on ; the transport. xf_init: clr.l XS_NXFER.l clr.l XS_NBYTE.l clr.l XS_NWIRE.l clr.l XS_ERR.l move.l #-1,XS_ERRAT.l tst.l XF_SCSI.l beq.s .out bsr scsi_init .out: rts ; ---------------------------------------------------------------- xf_service ; Answer at most ONE outstanding request, then return. Preserves every ; register: it is called from inside ring_poll, which is itself called from ; inside the decoder's wait loops and must be invisible to them. ; ; ONE PER CALL, not "drain the queue". ring_poll retires exactly one completed ; request per call as well, and a transport that answered both queued requests ; in one visit would hand the retire loop two acks it can only take one poll at ; a time -- which is legal, but it also means the decoder's wait loop would ; disappear for two record times instead of one. One per call keeps the two ; sides stepping at the same rate. ; ; A RECORD IS NOT A SECTOR, and this is where that is dealt with. ring.i asks ; for a byte offset and a length; the target answers in 512 B blocks. So the ; command covers the sectors the record lies in, and SC_WSKIP/SC_WKEEP tell ; src/player/scsi.i's DATA IN loop which of those bytes to store. The ones ; outside the window are pulled from the FIFO and dropped -- they are NOT ; written past the ends of the destination, because the bytes on either side of ; a record in the stream belong to records the decoder may still be reading and ; the block loop has no bounds check (49.2). ; ; XS_NWIRE counts what the disc actually moved and XS_NBYTE what the ring got. ; They are not the same number and the gap is a delivery cost, not an accounting ; detail: it is bytes on the wire that no frame contains. xf_service: tst.l XF_SCSI.l beq.s .idle move.l XF_ACK.l,d0 cmp.l XF_GO.l,d0 bcs.s .work ; XF_ACK < XF_GO: something outstanding .idle: rts .work: movem.l d0-d7/a0-a2,-(sp) move.l XF_ACK.l,d0 move.l d0,d1 and.l #XF_SLM,d1 lsl.l #4,d1 ; * XF_SLSZ lea XF_SLOT.l,a0 adda.l d1,a0 move.l (a0),d1 ; disc byte offset within the stream movea.l 4(a0),a1 ; destination in the ring move.l 8(a0),d2 ; length ; ---- sector arithmetic move.l d1,d3 and.l #511,d3 ; bytes of the first sector to drop move.l d1,d4 lsr.l #8,d4 lsr.l #1,d4 ; sector index within the stream add.l XS_LBA0.l,d4 ; ...and where the stream begins move.l d3,d5 add.l d2,d5 addi.l #511,d5 lsr.l #8,d5 lsr.l #1,d5 ; sectors the record lies in move.l d3,SC_WSKIP.l move.l d2,SC_WKEEP.l add.l d2,XS_NBYTE.l move.l d5,d0 lsl.l #8,d0 lsl.l #1,d0 add.l d0,XS_NWIRE.l move.l d4,d3 ; d3 = LBA move.l d5,d4 ; d4 = blocks bsr scsi_read_win tst.l d0 bmi.s .err addq.l #1,XS_NXFER.l addq.l #1,XF_ACK.l ; LAST: the bytes are all in the ring ; before the request is called done movem.l (sp)+,d0-d7/a0-a2 rts ; ---- a failed read is NOT acked. The record never becomes resident, the ; decoder spins out in waitrec and reports a stalled producer, and XS_ERR says ; which request failed and why. Acking a failed transfer would publish a ; descriptor for a buffer full of whatever was there before -- and the decoder ; would find a plausible-looking length word in it and desync somewhere else ; entirely. .err: tst.l XS_ERR.l bne.s .err2 move.l SC_ERR.l,XS_ERR.l move.l XF_ACK.l,XS_ERRAT.l .err2: movem.l (sp)+,d0-d7/a0-a2 rts