Put the SPC on the 68000, and find P4 was blocked on a file nobody needed
ROADMAP P4, first half. Every byte the player has ever consumed was placed in emulated RAM by a host: decode.lua preloaded a container, stream.lua answered a mailbox at a modelled rate. src/player/scsi.i selects a SCSI target on a real MB89352 and issues READ(10) itself -- 4,096 B from LBA 0 and 2,048 B from LBA 1000, both byte-for-byte against the host's copy of the same volume, with no IOCS and no host in the transfer path. The non-zero LBA is the half that matters: a driver that emits a malformed LBA field still passes block 0, because zero is what a malformed field usually is. P4 was recorded as blocked in this tree and was not. Session 21's handoff said MAME's x68000 has no MB89352 path; -exp1 cz6bs1 instantiates one next to the HD63450, and FINDINGS 32.4 had read that card's DMA glue in session 9. The session-21 note is a regression in the record, not a discovery. What is genuinely absent is the 8 KB scsiexrom.bin MAME requires to INSTANTIATE the card and the player never executes -- driving the SPC registers directly has been the plan since BENCHMARK item 4 in session 2 -- so scsi_run.sh supplies a zero-filled placeholder on its own rompath, leaves the user's romset alone, and lets MAME print WRONG CHECKSUMS as it should. B3 is untouched: it wants that ROM's bytes disassembled and a blank one has none. The register map is measured, not inferred, and it corrects MAME's own documentation. The probe walks $EA0000..$EA003F one address at a time with a bus-error handler that records the fault and steps the index, because a sequential dump reports the first hole as the answer -- the earlier version took a bus error at $EA0006 and knew nothing about the other 57. 60 of 64 answer; the two holes are exactly the TMOD and EXBF the MB89352 omits and the MB87030 has. MAME leaves HOLES and does not shift the later indices down, which its own device summary claims it does, and that is what keeps DREG at $EA0015. The data register is DMA-only here and a PIO write vanishes. x68k_scsiext.cpp glues $EA0015 and nothing else, and with exown() asserted and DRQ low the byte is discarded: no error bit, no status change, no interrupt. Quieting all four DMAC channels does not change it. Measured rather than reasoned about -- write $5A, read back $00 with the FIFO still empty -- because ten command bytes vanishing without trace looks exactly like a target refusing a command, which is how it first presented. So every transfer runs the SPC in DMA mode and the CPU moves the bytes through the DMAC's own door. That costs the argument something, and it is easy to overclaim here: with exown asserted at idle MAME cannot distinguish a CPU-driven byte at $EA0015 from a DMAC-driven one. This shows the DATA PATH and cannot by itself show that the HD63450 is driving it, which is precisely what ROADMAP calls P4's first job. Whether a real CZ-6BS1 also refuses PIO there is not settled; it is a property of MAME's model and it wants a board. W did not move by one clock, and could not have. MAME's device models are functional rather than transfer-timing accurate and 42.5 reads its DMAC configured in wall-clock attotimes, so this is BENCHMARK Tier 1 -- does the read path work -- and never Tier 2. W is still the largest open number here. Five bugs, four of them silent, recorded in 57.5 because the pattern is the finding: a chain of rol.l #8 that loaded a transfer counter of ZERO from a count of 10; a byte handed to a FIFO mistaken for a byte on the bus; a fixed phase sequence where the bus decides the order; the discarded PIO write; and an initiator that must drop ACK and only then release the bus. The last appeared only once there were TWO reads -- one passed byte-exact and every conclusion from it was sound, and the second could not select. A player issues one command per record, so that failure would have been universal in the ring and invisible in a one-read demonstration. No decoder code changed; decode.bin is still 1,296 B at the same MD5. check.sh gains a SCSI stage that builds the volume out of the same stream_disk.bin the ring rig reads, gates the register window at 60 of 64 and both reads byte-exact, and skips when chdman is absent. ALL GREEN before and after. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -0,0 +1,543 @@
|
||||
; ---------------------------------------------------------------- scsi.i
|
||||
; The MB89352 TRANSPORT, on the 68000. ROADMAP P4.
|
||||
;
|
||||
; WHAT THIS REPLACES. src/player/ring.i decides which record to fetch, where in
|
||||
; the ring to put it and when it is safe; it hands that to a TRANSPORT through
|
||||
; the XF_* mailbox and polls for completion. Until now the transport was
|
||||
; tools/bench/stream.lua -- a host moving bytes at a modelled rate. A player has
|
||||
; no host. This file is the transport: a Fujitsu MB89352 SPC and, later, one
|
||||
; HD63450 channel.
|
||||
;
|
||||
; THE REGISTER MAP IS MEASURED, NOT ASSUMED. src/player/scsigate.s probes
|
||||
; $EA0000..$EA003F one address at a time and survives the bus errors, and the
|
||||
; map below is what answered:
|
||||
;
|
||||
; registers sit on the ODD bytes, $EA0001 + 2n, n = 0..14
|
||||
; n=3 (TMOD) and n=15 (EXBF) BUS ERROR -- the MB89352 omits both, where the
|
||||
; MB87030 has them, and MAME leaves HOLES rather than shifting the
|
||||
; later indices down. DREG is index 10 at $EA0015 either way, which is the
|
||||
; one address FINDINGS 32.4 had quoted.
|
||||
; TEMP ($EA0017) took $A5 and gave it back, so these are registers and not a
|
||||
; mirror of something.
|
||||
;
|
||||
; That last point is why the probe was worth a run: MAME's own device summary
|
||||
; says the MB89352 "shifts subsequent indices", and the machine says it does
|
||||
; not. The bytes win.
|
||||
;
|
||||
; THE DATA REGISTER IS DMA-ONLY, AND THAT IS NOT A CHOICE THIS CODE MADE.
|
||||
; x68k_scsiext.cpp puts its own glue on $EA0015 and on no other address:
|
||||
;
|
||||
; write: if (exown()) { if (!drq) dtack_w(1); else dma_w(data); }
|
||||
; else dreg_w(data);
|
||||
;
|
||||
; On this machine `exown()` -- the HD63450's OWN, fed back to the slot by
|
||||
; x68k.cpp -- is asserted where a PIO write needs it not to be, so the `else`
|
||||
; arm is unreachable and a byte written to $EA0015 with the SPC in PROGRAM
|
||||
; transfer mode is DISCARDED. Silently: no error bit, no status change, no
|
||||
; interrupt. It was measured rather than reasoned about -- scsigate.s writes
|
||||
; $5A to $EA0015 and reads it straight back, and gets $00 with the FIFO still
|
||||
; empty -- because ten command bytes vanishing without trace looks exactly like
|
||||
; a target refusing a command.
|
||||
;
|
||||
; So every transfer here issues SCMD WITHOUT the PROGRAM bit, which puts the SPC
|
||||
; in DMA mode and makes it raise DRQ; the CPU then moves the bytes through
|
||||
; $EA0015 itself and they go in via `dma_w`/`dma_r`. The CPU is standing in for
|
||||
; the DMAC, through the DMAC's own door.
|
||||
;
|
||||
; WHAT THAT COSTS THE ARGUMENT, stated because it is easy to overclaim here:
|
||||
; with `exown` asserted at idle, MAME cannot distinguish a CPU-driven byte at
|
||||
; $EA0015 from a DMAC-driven one. So this rig demonstrates THE DATA PATH and
|
||||
; cannot, on its own, demonstrate that the HD63450 is the thing driving it.
|
||||
; Whether a real CZ-6BS1 also refuses PIO here is NOT settled by this -- it is a
|
||||
; property of MAME's model, and it wants a board (ROADMAP B1/B3).
|
||||
;
|
||||
; PIO FIRST, DMA SECOND, DELIBERATELY. The thing P4 has to demonstrate is a
|
||||
; DMAC configuration that HOLDS THE BUS (ROADMAP: "getting the DMAC to hold the
|
||||
; bus is the difference between 9 and 19 clocks per byte, and demonstrating a
|
||||
; configuration that does it is P4's first job"). But a DMA bring-up that fails
|
||||
; cannot tell "the SCSI protocol is wrong" from "the DMAC is misprogrammed". So
|
||||
; the protocol is settled in PIO, where every byte is the CPU's and nothing else
|
||||
; can be blamed, and only then does the data phase move to the channel.
|
||||
;
|
||||
; NOTHING HERE IS A RATE MEASUREMENT, and it cannot become one. MAME's device
|
||||
; models are functional, not transfer-timing accurate (docs/BENCHMARK.md), and
|
||||
; 42.5 reads its DMAC configured in wall-clock attotimes rather than per-operand
|
||||
; cycles. `W` -- clocks stolen per delivered byte -- is untouched by every line
|
||||
; below. What this settles is which handshake the player's own code provokes.
|
||||
|
||||
; ---- the SPC, at the CZ-6BS1's decode
|
||||
SPCB = $EA0001 ; register 0; stride 2, odd lane
|
||||
SC_BDID = SPCB+0 ; own ID (write the NUMBER; reads a MASK)
|
||||
SC_SCTL = SPCB+2
|
||||
SC_SCMD = SPCB+4
|
||||
; SPCB+6 = TMOD, ABSENT on the MB89352 -- reading it BUS ERRORS
|
||||
SC_INTS = SPCB+8
|
||||
SC_PSNS = SPCB+10
|
||||
SC_SSTS = SPCB+12
|
||||
SC_SERR = SPCB+14
|
||||
SC_PCTL = SPCB+16
|
||||
SC_MBC = SPCB+18
|
||||
SC_DREG = SPCB+20 ; $EA0015, and the DMAC's single address
|
||||
SC_TEMP = SPCB+22
|
||||
SC_TCH = SPCB+24
|
||||
SC_TCM = SPCB+26
|
||||
SC_TCL = SPCB+28
|
||||
; SPCB+30 = EXBF, ABSENT -- reading it BUS ERRORS
|
||||
|
||||
; SCTL
|
||||
SCTL_RESET = $80 ; reset & disable
|
||||
; SCMD, command in bits 7-5
|
||||
SCMD_RELEASE = $00 ; command 000, let go of the bus
|
||||
SCMD_RSTACK = $C0 ; command 110, drop ACK/REQ
|
||||
SCMD_SELECT = $20
|
||||
SCMD_RSTATN = $40 ; command 010, drop ATN
|
||||
SCMD_XFER = $80
|
||||
SCMD_PROGRAM = $04 ; set = PIO, clear = DMA. NOT USED, and
|
||||
; the reason is the whole of 57.x -- see
|
||||
; "THE DATA REGISTER IS DMA-ONLY" above.
|
||||
; INTS
|
||||
INTS_RESET = $01
|
||||
INTS_HARDERR = $02
|
||||
INTS_TIMEOUT = $04
|
||||
INTS_SERVICE = $08
|
||||
INTS_CMDCOMP = $10
|
||||
INTS_DISCON = $20
|
||||
; SSTS
|
||||
SSTS_DREG_E = $01 ; DREG empty
|
||||
SSTS_DREG_F = $02 ; DREG full
|
||||
SSTS_TC0 = $04
|
||||
SSTS_BUSY = $20
|
||||
SSTS_INITCON = $80
|
||||
; SCSI bus phases, as PSNS bits 2..0 and as PCTL's low three
|
||||
PH_DATAOUT = 0
|
||||
PH_DATAIN = 1
|
||||
PH_CMD = 2
|
||||
PH_STATUS = 3
|
||||
PH_MSGOUT = 6
|
||||
PH_MSGIN = 7
|
||||
|
||||
SCSI_ID = 7 ; the player is the initiator
|
||||
SCSI_TGT = 0 ; the disc
|
||||
|
||||
; ---- error codes, reported through SC_ERR
|
||||
SCE_OK = 0
|
||||
SCE_SELTMO = 1 ; the target never answered selection
|
||||
SCE_PHASE = 2 ; the bus went somewhere unexpected
|
||||
SCE_TIMEOUT = 3 ; a poll loop ran out of patience
|
||||
SCE_STATUS = 4 ; the target returned non-zero status
|
||||
|
||||
SC_ERR = $18200 ; u32 last error
|
||||
SC_STAT = $18204 ; u32 SCSI status byte from the last cmd
|
||||
SC_PH = $18208 ; u32 phase we were in when it went wrong
|
||||
SC_CDB = $18210 ; 12 B command block, built here
|
||||
SC_MSG = $1821C ; 4 B message byte, either direction
|
||||
|
||||
; ---- a TRACE, because a SCSI bring-up cannot be debugged from one error code.
|
||||
; Four registers at each interesting instant: SSTS, PSNS, INTS, SERR. MAME's
|
||||
; SCMD_CMD_TRANSFER is a NO-OP unless SSTS_INIT_CONNECTED is set -- it `break`s
|
||||
; out of the switch without complaint -- so "the transfer did nothing" and "the
|
||||
; transfer went wrong" look identical from the outside. The trace separates
|
||||
; them.
|
||||
SC_TAG = $1822C ; u32 where the next snapshot came from
|
||||
SC_TRN = $18230 ; u32 trace entries used
|
||||
SC_TR = $18240 ; 24 x 8 B: SSTS PSNS INTS SERR TCH TCM TCL TAG
|
||||
|
||||
; A poll bound. Every wait in this file is bounded, because a SCSI phase that
|
||||
; never arrives must be REPORTED -- an unbounded spin is indistinguishable from
|
||||
; a wedged emulator, and 34.1 already cost this project fifteen minutes to that
|
||||
; exact confusion.
|
||||
SC_PATIENCE = 200000
|
||||
|
||||
; ---------------------------------------------------------------- sc_snap
|
||||
; Append SSTS/PSNS/INTS/SERR to the trace. Clobbers nothing the callers use.
|
||||
sc_snap:
|
||||
movem.l d0/a0,-(sp)
|
||||
move.l SC_TRN.l,d0
|
||||
cmp.l #24,d0
|
||||
bge.s sn_out
|
||||
lea SC_TR.l,a0
|
||||
lsl.l #3,d0
|
||||
adda.l d0,a0
|
||||
move.b SC_SSTS,(a0)+
|
||||
move.b SC_PSNS,(a0)+
|
||||
move.b SC_INTS,(a0)+
|
||||
move.b SC_SERR,(a0)+
|
||||
move.b SC_TCH,(a0)+
|
||||
move.b SC_TCM,(a0)+
|
||||
move.b SC_TCL,(a0)+
|
||||
move.b SC_TAG+3,(a0)+ ; WHERE this snapshot was taken
|
||||
addq.l #1,SC_TRN.l
|
||||
sn_out: movem.l (sp)+,d0/a0
|
||||
rts
|
||||
|
||||
; ---- the HD63450, so that PIO through the card's data register works at all.
|
||||
; x68k_scsiext.cpp puts DMA-AWARE GLUE on $EA0015 and nowhere else:
|
||||
;
|
||||
; write: if (exown()) { if (!drq) dtack_w(1); else dma_w(data); }
|
||||
; else dreg_w(data);
|
||||
;
|
||||
; With OWN asserted and DRQ low the byte is DROPPED, silently. That is the
|
||||
; bring-up's fourth bug and the least guessable: ten command bytes went into
|
||||
; $EA0015, the FIFO stayed empty, the transfer counter stayed at 10, and every
|
||||
; register the SPC has said "waiting". Nothing reports a discarded write.
|
||||
; OWN is the DMAC's, and the IPL ROM has been running for three seconds before
|
||||
; the player's first instruction (52.5 reads its channel setup out of the ROM),
|
||||
; so the player does not inherit a quiet DMAC -- it has to make one.
|
||||
DMAC = $E84000
|
||||
DMAC_CH = $40 ; channels are 64 B apart
|
||||
dmac_quiet:
|
||||
lea DMAC,a0
|
||||
moveq #3,d1
|
||||
dq1: move.b #0,7(a0) ; CCR: no operation
|
||||
move.b #$FF,0(a0) ; CSR: write-one-to-clear
|
||||
adda.w #DMAC_CH,a0
|
||||
dbra d1,dq1
|
||||
rts
|
||||
|
||||
; ---------------------------------------------------------------- scsi_init
|
||||
; Reset the SPC and claim an initiator ID. Leaves interrupts DISABLED: the
|
||||
; player polls, because the ring producer is already a polling loop living in
|
||||
; the pace wait (ring.i) and an interrupt would buy it nothing it does not
|
||||
; already have.
|
||||
scsi_init:
|
||||
bsr dmac_quiet
|
||||
move.b #SCTL_RESET,SC_SCTL ; reset & disable
|
||||
moveq #40,d0
|
||||
sci1: nop
|
||||
dbra d0,sci1
|
||||
move.b #SCSI_ID,SC_BDID
|
||||
move.b #0,SC_SCTL ; out of reset; no arbitration, no ints
|
||||
move.b #$FF,SC_INTS ; INTS is cleared by writing its bits
|
||||
move.b #0,SC_PCTL
|
||||
clr.l SC_ERR.l
|
||||
clr.l SC_TRN.l
|
||||
move.l #0,SC_TAG.l
|
||||
bsr sc_snap
|
||||
rts
|
||||
|
||||
; ---------------------------------------------------------------- sc_settc
|
||||
; d0 = 24-bit transfer count -> TCH/TCM/TCL
|
||||
; Written LOW BYTE FIRST with lsr, not as a chain of rol.l #8. The rol version
|
||||
; was the bring-up's second bug: three rotations put the ORIGINAL bits 31..24,
|
||||
; 23..16 and 15..8 into TCH/TCM/TCL, so a count of 10 loaded a transfer counter
|
||||
; of ZERO. MAME then completed the TRANSFER instantly and silently -- SSTS came
|
||||
; back $85, TC0 set and XFER_IN_PROGRESS clear -- and the bus sat in command
|
||||
; phase, which surfaced as the same `UNEXPECTED PHASE` as a protocol error.
|
||||
sc_settc:
|
||||
move.l d0,-(sp)
|
||||
move.b d0,SC_TCL ; bits 7..0
|
||||
lsr.l #8,d0
|
||||
move.b d0,SC_TCM ; bits 15..8
|
||||
lsr.l #8,d0
|
||||
move.b d0,SC_TCH ; bits 23..16
|
||||
move.l (sp)+,d0
|
||||
rts
|
||||
|
||||
; ---------------------------------------------------------------- sc_waitreq
|
||||
; Wait until the SPC reports a REQ with a phase, or patience runs out.
|
||||
; Returns the phase in d0; sets SC_ERR and returns -1 on timeout.
|
||||
sc_waitreq:
|
||||
move.l #SC_PATIENCE,d1
|
||||
swr1: move.b SC_PSNS,d0
|
||||
btst #7,d0 ; REQ
|
||||
bne.s swr2
|
||||
subq.l #1,d1
|
||||
bne.s swr1
|
||||
move.l #SCE_TIMEOUT,SC_ERR.l
|
||||
moveq #-1,d0
|
||||
rts
|
||||
swr2: and.l #7,d0
|
||||
rts
|
||||
|
||||
; ---------------------------------------------------------------- sc_waitfree
|
||||
; Wait for BUS FREE. A command is not over when its last message byte has been
|
||||
; read: the target still has BSY asserted, and an initiator that starts
|
||||
; arbitrating into that gets a selection timeout.
|
||||
;
|
||||
; This is the bring-up's fifth bug, and it only appeared once there were TWO
|
||||
; reads. One read passed, byte-exact, and every conclusion drawn from it was
|
||||
; sound; the SECOND could not select, because nothing had waited for the first
|
||||
; to let go of the bus. A player issues one of these per record, so the failure
|
||||
; would have been universal in the ring and invisible in the demonstration.
|
||||
sc_waitfree:
|
||||
move.l #SC_PATIENCE,d1
|
||||
swf1: move.b SC_PSNS,d0
|
||||
btst #3,d0 ; BSY
|
||||
beq.s swf2
|
||||
subq.l #1,d1
|
||||
bne.s swf1
|
||||
move.l #SCE_TIMEOUT,SC_ERR.l
|
||||
moveq #-1,d0
|
||||
rts
|
||||
swf2: moveq #0,d0
|
||||
rts
|
||||
|
||||
; ---------------------------------------------------------------- sc_select
|
||||
; Select SCSI_TGT. The selection bitmask goes in TEMP -- both IDs, ours and
|
||||
; theirs -- and the transfer counter doubles as the selection timeout (MAME:
|
||||
; SelectionWaitBSY is derived from TC's upper bits, which is the datasheet's
|
||||
; behaviour too).
|
||||
sc_select:
|
||||
move.b #$FF,SC_INTS
|
||||
move.b #(1<<SCSI_ID)|(1<<SCSI_TGT),SC_TEMP
|
||||
move.l #$002000,d0
|
||||
bsr sc_settc
|
||||
move.b #0,SC_PCTL
|
||||
move.b #SCMD_SELECT,SC_SCMD
|
||||
move.l #SC_PATIENCE,d1
|
||||
ssel1: move.b SC_INTS,d0
|
||||
btst #4,d0 ; COMMAND COMPLETE = selection won
|
||||
bne.s ssel_ok
|
||||
btst #2,d0 ; TIMEOUT = nobody there
|
||||
bne.s ssel_tmo
|
||||
subq.l #1,d1
|
||||
bne.s ssel1
|
||||
move.l #SCE_TIMEOUT,SC_ERR.l
|
||||
moveq #-1,d0
|
||||
rts
|
||||
ssel_tmo:
|
||||
move.b #$FF,SC_INTS
|
||||
move.l #SCE_SELTMO,SC_ERR.l
|
||||
moveq #-1,d0
|
||||
rts
|
||||
ssel_ok:
|
||||
move.l #1,SC_TAG.l
|
||||
bsr sc_snap
|
||||
move.b #$FF,SC_INTS
|
||||
move.l #2,SC_TAG.l
|
||||
bsr sc_snap
|
||||
moveq #0,d0
|
||||
rts
|
||||
|
||||
; ---------------------------------------------------------------- sc_xferend
|
||||
; Wait for the SPC to finish the TRANSFER it was given, rather than for the last
|
||||
; byte to have been HANDED to it.
|
||||
;
|
||||
; This is the bring-up's one real bug and it is worth recording. Without it,
|
||||
; sc_out_pio wrote all ten command bytes and returned, the caller immediately
|
||||
; asked what phase the bus was in, and the answer was STILL COMMAND -- because
|
||||
; the SPC had the last byte in its FIFO and had not yet run the REQ/ACK for it.
|
||||
; The symptom was `UNEXPECTED PHASE, phase=2` at the DATA-IN check, which reads
|
||||
; like a target refusing the command and is nothing of the kind. A byte handed
|
||||
; to a FIFO is not a byte on the bus.
|
||||
sc_xferend:
|
||||
move.l #SC_PATIENCE,d3
|
||||
sxe1: move.b SC_SSTS,d0
|
||||
btst #4,d0 ; XFER IN PROGRESS
|
||||
beq.s sxe2
|
||||
subq.l #1,d3
|
||||
bne.s sxe1
|
||||
move.l #SCE_TIMEOUT,SC_ERR.l
|
||||
moveq #-1,d0
|
||||
rts
|
||||
sxe2: moveq #0,d0
|
||||
rts
|
||||
|
||||
; ---------------------------------------------------------------- sc_out_pio
|
||||
; Send d1 bytes from (a0) in phase d2. Command blocks and nothing else, so it
|
||||
; is the small, simple one.
|
||||
sc_out_pio:
|
||||
move.b d2,SC_PCTL
|
||||
move.l d1,d0
|
||||
bsr sc_settc
|
||||
move.b #SCMD_XFER,SC_SCMD
|
||||
move.l #4,SC_TAG.l ; 4 = TRANSFER issued for an OUT phase
|
||||
bsr sc_snap
|
||||
sop1: move.l #SC_PATIENCE,d3
|
||||
sop2: move.b SC_SSTS,d0
|
||||
btst #1,d0 ; DREG FULL -- wait for room
|
||||
beq.s sop3
|
||||
subq.l #1,d3
|
||||
bne.s sop2
|
||||
move.l #SCE_TIMEOUT,SC_ERR.l
|
||||
moveq #-1,d0
|
||||
rts
|
||||
sop3: move.b (a0)+,SC_DREG
|
||||
subq.l #1,d1
|
||||
bne.s sop1
|
||||
move.l #5,SC_TAG.l ; 5 = every byte handed to the FIFO
|
||||
bsr sc_snap
|
||||
bsr sc_xferend
|
||||
move.l d0,-(sp)
|
||||
move.l #6,SC_TAG.l ; 6 = after waiting for the transfer
|
||||
bsr sc_snap
|
||||
move.l (sp)+,d0
|
||||
rts
|
||||
|
||||
; ---------------------------------------------------------------- sc_in_pio
|
||||
; Receive d1 bytes into (a1) in phase d2. This is the path the DMA version
|
||||
; replaces; it stays because it is what makes a DMA failure diagnosable.
|
||||
sc_in_pio:
|
||||
move.b d2,SC_PCTL
|
||||
move.l d1,d0
|
||||
bsr sc_settc
|
||||
move.b #SCMD_XFER,SC_SCMD
|
||||
move.l #7,SC_TAG.l ; 7 = TRANSFER issued for an IN phase
|
||||
bsr sc_snap
|
||||
sip1: move.l #SC_PATIENCE,d3
|
||||
sip2: move.b SC_SSTS,d0
|
||||
btst #0,d0 ; DREG EMPTY -- wait for a byte
|
||||
beq.s sip3
|
||||
subq.l #1,d3
|
||||
bne.s sip2
|
||||
move.l #SCE_TIMEOUT,SC_ERR.l
|
||||
moveq #-1,d0
|
||||
rts
|
||||
sip3: move.b SC_DREG,(a1)+
|
||||
subq.l #1,d1
|
||||
bne.s sip1
|
||||
move.l #8,SC_TAG.l ; 8 = every byte taken from the FIFO
|
||||
bsr sc_snap
|
||||
bsr sc_xferend
|
||||
move.l d0,-(sp)
|
||||
move.l #9,SC_TAG.l ; 9 = after waiting for the IN transfer
|
||||
bsr sc_snap
|
||||
move.l (sp)+,d0
|
||||
rts
|
||||
|
||||
; ---------------------------------------------------------------- scsi_read
|
||||
; READ(10) of d4 blocks from LBA d3 into (a1). READ(10) rather than READ(6)
|
||||
; because a 21-bit LBA and a 256-block ceiling are limits this container will
|
||||
; reach -- 4,488,588 B of frame records is already 8,767 sectors, and a full
|
||||
; disc is 1.09 GiB (ROADMAP C3).
|
||||
;
|
||||
; DRIVEN BY THE PHASE, NOT BY A SCRIPT, and that is the third thing the bring-up
|
||||
; taught. The first version ran a fixed sequence -- select, command, data,
|
||||
; status, message -- and broke the moment the target asked for something else:
|
||||
; it came up in MESSAGE OUT with ATN asserted and the driver, which "knew" the
|
||||
; next phase was COMMAND, called it an unexpected phase and gave up. The bus
|
||||
; decides the order. A driver that reads the phase and services whatever it
|
||||
; finds is both shorter and correct, and it is what the target is entitled to.
|
||||
scsi_read:
|
||||
movem.l d3-d5/a1,-(sp)
|
||||
; ---- the command block, built before anything is on the bus
|
||||
lea SC_CDB.l,a0
|
||||
move.b #$28,(a0)+ ; READ(10)
|
||||
clr.b (a0)+
|
||||
move.l d3,d0 ; LBA, big-endian u32
|
||||
rol.l #8,d0
|
||||
move.b d0,(a0)+ ; 31..24
|
||||
rol.l #8,d0
|
||||
move.b d0,(a0)+ ; 23..16
|
||||
rol.l #8,d0
|
||||
move.b d0,(a0)+ ; 15..8
|
||||
rol.l #8,d0
|
||||
move.b d0,(a0)+ ; 7..0
|
||||
clr.b (a0)+
|
||||
move.l d4,d0 ; block count, big-endian u16. Same
|
||||
lsr.l #8,d0 ; trap as sc_settc had: a rol chain here
|
||||
move.b d0,(a0)+ ; would have emitted bits 31..24/23..16
|
||||
move.b d4,(a0)+ ; of a count that lives in 15..0.
|
||||
clr.b (a0)+
|
||||
; ---- d5 = bytes of data still expected
|
||||
move.l d4,d5
|
||||
lsl.l #8,d5
|
||||
lsl.l #1,d5 ; blocks * 512
|
||||
clr.l SC_STAT.l
|
||||
bsr sc_select
|
||||
tst.l d0
|
||||
bmi scr_out
|
||||
; Drop ATN. We have no message to send, so asking the target not to ask
|
||||
; for one is cheaper than answering. The MSGOUT arm below still exists,
|
||||
; because "cheaper" is not "guaranteed".
|
||||
move.b #SCMD_RSTATN,SC_SCMD
|
||||
; ---- service whatever the bus asks for, until the target ends the command
|
||||
scr_ph:
|
||||
bsr sc_waitreq
|
||||
tst.l d0
|
||||
bmi scr_out
|
||||
move.l d0,-(sp)
|
||||
move.l #3,SC_TAG.l ; 3 = the phase loop saw a REQ
|
||||
bsr sc_snap
|
||||
move.l (sp)+,d0
|
||||
cmp.l #PH_CMD,d0
|
||||
beq.s scr_cmd
|
||||
cmp.l #PH_DATAIN,d0
|
||||
beq.s scr_din
|
||||
cmp.l #PH_STATUS,d0
|
||||
beq.s scr_st
|
||||
cmp.l #PH_MSGIN,d0
|
||||
beq.s scr_min
|
||||
cmp.l #PH_MSGOUT,d0
|
||||
beq.s scr_mout
|
||||
bra scr_phase
|
||||
scr_cmd:
|
||||
lea SC_CDB.l,a0
|
||||
moveq #10,d1
|
||||
moveq #PH_CMD,d2
|
||||
bsr sc_out_pio
|
||||
tst.l d0
|
||||
bmi scr_out
|
||||
bra scr_ph
|
||||
scr_din:
|
||||
; KNOWN LIMITATION, harmless here and not harmless forever: this asks
|
||||
; for the WHOLE remaining count every time the bus enters DATA IN. A
|
||||
; target that split one READ(10) across two data phases would be served
|
||||
; the full length twice and overrun the caller's buffer. This one does
|
||||
; not split -- 4,096 B and 2,048 B both arrive in a single phase -- but
|
||||
; a real drive may, and P4b's mailbox integration is where d5 has to
|
||||
; start being decremented by what each phase actually delivered.
|
||||
move.l 12(sp),a1 ; the caller's destination. movem.l
|
||||
; d3-d5/a1,-(sp) lays them out ASCENDING
|
||||
; from sp as d3,d4,d5,a1 -- a1 is at 12.
|
||||
move.l d5,d1
|
||||
moveq #PH_DATAIN,d2
|
||||
bsr sc_in_pio
|
||||
tst.l d0
|
||||
bmi scr_out
|
||||
bra scr_ph
|
||||
scr_st:
|
||||
lea SC_STAT.l,a1
|
||||
addq.l #3,a1 ; the byte lands in the u32's low end
|
||||
moveq #1,d1
|
||||
moveq #PH_STATUS,d2
|
||||
bsr sc_in_pio
|
||||
tst.l d0
|
||||
bmi scr_out
|
||||
bra scr_ph
|
||||
scr_min:
|
||||
lea SC_MSG.l,a1
|
||||
moveq #1,d1
|
||||
moveq #PH_MSGIN,d2
|
||||
bsr sc_in_pio
|
||||
tst.l d0
|
||||
bmi scr_out
|
||||
; A message in ends the command. Anything non-zero in the status byte
|
||||
; is the target refusing, and a transport that ignored it would hand the
|
||||
; ring a buffer of stale bytes and call it a record.
|
||||
move.l SC_STAT.l,d0
|
||||
beq.s scr_ok
|
||||
move.l #SCE_STATUS,SC_ERR.l
|
||||
moveq #-1,d0
|
||||
bra.s scr_out
|
||||
scr_mout:
|
||||
; Nothing to say: IDENTIFY, no disconnect, LUN 0.
|
||||
lea SC_MSG.l,a1
|
||||
move.b #$80,(a1)
|
||||
move.l a1,a0
|
||||
moveq #1,d1
|
||||
moveq #PH_MSGOUT,d2
|
||||
bsr sc_out_pio
|
||||
tst.l d0
|
||||
bmi scr_out
|
||||
bra scr_ph
|
||||
scr_ok: ; END OF COMMAND, and it takes two steps rather than one. After the
|
||||
; final message byte the SPC is still holding ACK -- PSNS reads $4F,
|
||||
; REQ low and ACK high -- and a target cannot drop BSY into that. So
|
||||
; ACK is dropped explicitly, and only then is the bus released.
|
||||
move.b #SCMD_RSTACK,SC_SCMD
|
||||
move.b #SCMD_RELEASE,SC_SCMD
|
||||
move.l #10,SC_TAG.l ; 10 = after the bus release command
|
||||
bsr sc_snap
|
||||
bsr sc_waitfree ; leave the bus as we found it
|
||||
tst.l d0
|
||||
bmi scr_out
|
||||
moveq #0,d0
|
||||
scr_out:
|
||||
movem.l (sp)+,d3-d5/a1
|
||||
rts
|
||||
scr_phase:
|
||||
move.l d0,SC_PH.l
|
||||
move.l #SCE_PHASE,SC_ERR.l
|
||||
moveq #-1,d0
|
||||
bra.s scr_out
|
||||
@@ -0,0 +1,150 @@
|
||||
; Front-end for the MB89352 PROBE (ROADMAP P4, first step), for the rig.
|
||||
;
|
||||
; WHY A PROBE AND NOT A DRIVER. P4 replaces tools/bench/stream.lua's modelled
|
||||
; transport with a real SPC behind the XF_* mailbox src/player/ring.i already
|
||||
; talks to. Before any of that can be written, the register map has to be a
|
||||
; FACT on the emulated machine rather than a reading of somebody's datasheet.
|
||||
; FINDINGS 32.4 quotes MAME mapping the data register at $EA0015 -- register
|
||||
; index 10 at a stride of 2 from $EA0001, i.e. registers on the ODD bytes. That
|
||||
; is an inference from ONE address, and every access the driver makes rests on
|
||||
; it.
|
||||
;
|
||||
; WHY IT ENUMERATES INSTEAD OF DUMPING. The first version of this walked
|
||||
; $EA0000 upwards with a plain `move.b (a0)+`, and took a bus error at $EA0006 --
|
||||
; at which point it knew one address was dead and nothing about the other 57.
|
||||
; A sequential dump stops at the first hole and reports the hole as the answer.
|
||||
; So this probes ONE ADDRESS AT A TIME with the index in MEMORY, and a bus error
|
||||
; handler that records the fault, steps the index and re-enters the loop. A
|
||||
; dead address costs an entry in the map rather than the rest of the run.
|
||||
;
|
||||
; The 68000 cannot resume a faulted instruction -- RTE re-runs it and faults
|
||||
; again -- so the handler does not try. It restores a stack pointer saved
|
||||
; before the loop and jumps back to the loop head, which reloads everything it
|
||||
; needs from memory. Nothing lives in a register across a fault.
|
||||
;
|
||||
; This gate drives no SCSI bus and moves no data. It is the smallest thing that
|
||||
; can turn "MAME instantiates an MB89352" into "the 68000 can reach it, HERE".
|
||||
|
||||
SCFLAG = $18080 ; 0 idle / 1 done
|
||||
SCN = 64 ; addresses probed, from SPCBASE up
|
||||
SCIDX = $18084 ; u32 probe index, lives in memory across faults
|
||||
SCSAVSP = $18088 ; u32 stack pointer saved before the loop
|
||||
SCVAL = $18100 ; SCN bytes: what each address read
|
||||
SCOK = $18140 ; SCN bytes: 1 = answered, 0 = bus error
|
||||
SCTMP = $180D0 ; u32: TEMP writeback -- $A5 written, read back
|
||||
SCTMPOK = $180D4 ; u32: 1 = the writeback completed without fault
|
||||
SCRD = $180D8 ; u32: scsi_read's return, 0 = the read worked
|
||||
SCDRG = $180DC ; u32: $5A written to DREG then read straight back
|
||||
SCDRS = $180E0 ; u32: SSTS between that write and that read
|
||||
SCDST = $20000 ; where the read lands
|
||||
SCBLKS = 8 ; 8 x 512 B, enough to cross a sector boundary
|
||||
SCRD2 = $180E4 ; u32: the second read's return
|
||||
SCER1 = $180E8 ; u32: SC_ERR as it stood after the first read
|
||||
SCER2 = $180EC ; u32: ...and after the second
|
||||
SCDST2 = $28000 ; where the second read lands
|
||||
SCLBA2 = 1000 ; a NON-ZERO LBA: block 0 would pass even if the
|
||||
; LBA bytes of the command block were ignored
|
||||
SCBLK2 = 4
|
||||
|
||||
SPCBASE = $EA0000
|
||||
|
||||
org $10000
|
||||
start:
|
||||
move.l #buserr,$8.w ; vector 2
|
||||
clr.l SCFLAG.l
|
||||
clr.l SCTMP.l
|
||||
clr.l SCTMPOK.l
|
||||
clr.l SCIDX.l
|
||||
move.l sp,SCSAVSP.l
|
||||
|
||||
; ---- probe SCN addresses, one at a time, surviving each fault
|
||||
ploop:
|
||||
move.l SCIDX.l,d0
|
||||
cmp.l #SCN,d0
|
||||
bge.s pdone
|
||||
lea SPCBASE,a0
|
||||
adda.l d0,a0
|
||||
lea SCVAL.l,a1
|
||||
lea SCOK.l,a2
|
||||
move.b #1,0(a2,d0.l) ; assume it answers; the handler undoes
|
||||
move.b (a0),d1 ; <- the access under test
|
||||
move.b d1,0(a1,d0.l)
|
||||
addq.l #1,SCIDX.l
|
||||
bra.s ploop
|
||||
pdone:
|
||||
|
||||
; ---- TEMP (register 11 on the believed map, $EA0017) is a scratch latch on a
|
||||
; real MB89352. Writing a pattern and reading it back separates "these odd
|
||||
; bytes are registers" from "these odd bytes are a mirror of something".
|
||||
; Guarded the same way: if it faults, the handler lands in ploop with SCIDX
|
||||
; already past the end, falls through here again, and SCTMPOK stays 0.
|
||||
move.b #$A5,SPCBASE+23
|
||||
moveq #0,d0
|
||||
move.b SPCBASE+23,d0
|
||||
move.l d0,SCTMP.l
|
||||
move.l #1,SCTMPOK.l
|
||||
|
||||
; ---- DOES A WRITE TO THE DATA REGISTER REACH THE CHIP AT ALL?
|
||||
; $EA0015 is the one address x68k_scsiext.cpp puts its own glue on, and that
|
||||
; glue DROPS a write when the DMAC's OWN is asserted and DRQ is low. A dropped
|
||||
; write is invisible: no error, no status bit, nothing. So it is tested
|
||||
; directly, before any SCSI protocol can be blamed for it. dreg_w enqueues into
|
||||
; the FIFO, so DREG_EMPTY must fall between the write and the read, and the read
|
||||
; must give the byte back.
|
||||
bsr scsi_init
|
||||
move.b #$5A,SPCBASE+21
|
||||
moveq #0,d0
|
||||
move.b SPCBASE+13,d0 ; SSTS: is the FIFO still empty?
|
||||
move.l d0,SCDRS.l
|
||||
moveq #0,d0
|
||||
move.b SPCBASE+21,d0
|
||||
move.l d0,SCDRG.l
|
||||
|
||||
; ---- the SPC is reachable; now make it fetch something. A read of the first
|
||||
; SCBLKS sectors, in PIO, verified BY THE HOST against the same bytes in
|
||||
; tmp/dlxdisk.img. That is the whole of P4's correctness half in one line: the
|
||||
; player's own code selected a target, issued a READ(10) and got the disc's
|
||||
; bytes back, with no IOCS and no host in the path.
|
||||
bsr scsi_init
|
||||
moveq #0,d3 ; LBA 0
|
||||
moveq #SCBLKS,d4
|
||||
lea SCDST,a1
|
||||
bsr scsi_read
|
||||
move.l d0,SCRD.l
|
||||
move.l SC_ERR.l,SCER1.l ; SC_ERR is the LAST error, so it is
|
||||
; captured per read: reading it once at
|
||||
; the end reported the second read's
|
||||
; failure against the first read's name.
|
||||
|
||||
; ---- and again, somewhere else on the disc. A read of LBA 0 is passed by a
|
||||
; driver that emits a malformed LBA field, because zero is what a malformed
|
||||
; field usually is. This one is not.
|
||||
move.l #SCLBA2,d3
|
||||
moveq #SCBLK2,d4
|
||||
lea SCDST2,a1
|
||||
bsr scsi_read
|
||||
move.l d0,SCRD2.l
|
||||
move.l SC_ERR.l,SCER2.l
|
||||
|
||||
move.l #1,SCFLAG.l
|
||||
hold: bra.s hold
|
||||
|
||||
; ---- bus error. Mark the address dead, step past it, re-enter the loop with a
|
||||
; stack pointer that is known good. The stacked frame is abandoned deliberately:
|
||||
; there is nothing in it worth more than the next 57 addresses.
|
||||
buserr:
|
||||
move.l SCSAVSP.l,sp
|
||||
move.l SCIDX.l,d0
|
||||
cmp.l #SCN,d0
|
||||
bge.s btmp
|
||||
lea SCOK.l,a2
|
||||
clr.b 0(a2,d0.l)
|
||||
lea SCVAL.l,a1
|
||||
move.b #$FF,0(a1,d0.l)
|
||||
addq.l #1,SCIDX.l
|
||||
jmp ploop
|
||||
btmp: ; the fault was the TEMP writeback
|
||||
move.l #1,SCFLAG.l
|
||||
be: bra.s be
|
||||
|
||||
include "src/player/scsi.i"
|
||||
Reference in New Issue
Block a user