SCSI IOX Transaction Log - @ENTER-DIRECTORY,,DISC-SCSI-1,0¶
Full device-side walkthrough of one mount transaction between the SINTRAN ND-100 CPU and the emulated ND-3201 SCSI controller (which wraps an NCR 5386 SCSI protocol chip), reconstructed byte-for-byte from a captured RetroCore trace.
Provenance and scope¶
- Trace (ground truth, device-side only):
/mnt/c/Users/ronny/AppData/Local/trace/file-trace.txt, mount transaction at wall-clock20:33:47(trace lines 157-1907). This file has noOpcodes;CPU lines (verified) - it is device-side only. Every IOX/DMA/interrupt fact below is read directly from those trace bytes and tagged VERIFIED. CPU-side intent is tagged INFERRED and attributed to the C# controller behavior or the reverse-engineering docs. - Controller register + control/status decode (C#):
/mnt/e/Dev/Repos/Ronny/RetroCore/Emulated.HW/ND/CPU/NDBUS/NDBusDiscControllerSCSI.cs(theRegisterenum, thecase Register.RSTAU:status assembler at lines ~874-946, and thecase Register.WCONT:control-word handler at lines ~1064-1176). - NCR 5386 command / interrupt / auxiliary-status decode (C#):
/mnt/e/Dev/Repos/Ronny/RetroCore/Emulated.HW/NCR/SCSI/NCR5386/Enums.cs,NCR5386SCSI.CommandHandling.cs,NCR5386SCSI.StateHandling.cs. - Disk target CDB handling (C#):
/mnt/e/Dev/Repos/Ronny/RetroCore/Emulated.HW/Common/SCSI/SCSIHDD.cs,SCSIHDDMicropolis.cs. - Related repo docs (relative links): nd-scsi-3201.md (register docs), scsi-transfer-status.md (RSTAU gate), SCSI-TRACE-HOWTO.md (what a healthy transaction looks like), SCSI-MOUNT-DEBUG-HANDOFF.md (current bug state, sections 6b/7b), scsi-open-last-block-read.md, scsi-mount-geometry.md.
One-line result of the run (VERIFIED): the mount is not a hang. SINTRAN issues exactly
three SCSI commands - INQUIRY, READ CAPACITY, READ_6 lba=129311 (the LAST block) - the last
one DMAs 1024 bytes cleanly into ND memory, an interrupt is raised and acked, and the console
returns to @ at 20:33:47.178. Block 0 is never read. No WRITE CDB is ever issued. All 24
device interrupts in the transaction are delivered and acknowledged - there is no dropped IRQ.
1. Overview - how the ND-100 drives this SCSI controller over IOX¶
1.1 Address / interrupt basics (VERIFIED from C# constructor)¶
For SCSI Bus 1 (thumbwheel TW2=0) the controller answers at IOX base 144300 (octal), IDENT
code 140440 (octal), logical device 2202, on interrupt level 11 (the disk output
channel). Each reg= name in the trace is an IOX offset added to that base. ND-100 memory and I/O
are word-addressed, 16 bits wide; the DMA Memory Address Register (MAR) below counts words,
not bytes (it advances by 1 per 16-bit word moved).
1.2 Controller register map (VERIFIED from the Register enum)¶
Offsets are octal (as the SINTRAN NPL driver uses them); the absolute IOX is 144300 + offset.
"Wraps NCR" means the controller forwards the access straight to the NCR 5386 chip register.
| Off (oct) | IOX (oct) | Name | R/W | Purpose |
|---|---|---|---|---|
| 00 | 144300 | RLMAR | R | Read DMA Memory Address Register bits 0-15 (LSB word) |
| 01 | 144301 | WLMAR | W | Write MAR bits 0-15 |
| 02 | 144302 | REDAT | R | Read PIO data buffer (non-DMA path) |
| 03 | 144303 | WRDAT | W | Write PIO data buffer (non-DMA path) |
| 04 | 144304 | RSTAU | R | Read controller STATUS word (see 1.4) |
| 05 | 144305 | WCONT | W | Write CONTROL word (see 1.3) |
| 06 | 144306 | RHMAR | R | Read MAR bits 16-23 (MSB) |
| 07 | 144307 | WHMAR | W | Write MAR bits 16-23 |
| 040 | 144340 | RNDAT | R | Wraps NCR: read Data register (1 byte) |
| 041 | 144341 | WNDAT | W | Wraps NCR: write Data register (1 byte) |
| 042 | 144342 | RNCOM | R | Wraps NCR: read Command register |
| 043 | 144343 | WNCOM | W | Wraps NCR: write Command register (executes the command) |
| 044 | 144344 | RNCNT | R | Wraps NCR: read Control register |
| 045 | 144345 | WNCNT | W | Wraps NCR: write Control register |
| 046 | 144346 | RDESI | R | Wraps NCR: read Destination ID |
| 047 | 144347 | WDESI | W | Wraps NCR: write Destination ID |
| 050 | 144350 | RAUXS | R | Wraps NCR: read Auxiliary Status (phase + flags) |
| 051 | 144351 | WAUXS | W | Wraps NCR: write Auxiliary Status |
| 052 | 144352 | ROIDN | R | Wraps NCR: read Own ID |
| 053 | 144353 | WOIDN | W | Wraps NCR: write Own ID |
| 054 | 144354 | RITRG | R | Wraps NCR: read Interrupt register and ack the NCR interrupt |
| 056 | 144356 | RSOUI | R | Wraps NCR: read Source ID |
| 062 | 144362 | RDIST | R | Wraps NCR: read Diagnostic Status |
| 070 | 144370 | RTCM | R | Wraps NCR: read Transfer Counter MSB |
| 071 | 144371 | WTCM | W | Wraps NCR: write Transfer Counter MSB |
| 072 | 144372 | RTC2 | R | Wraps NCR: read Transfer Counter middle byte |
| 073 | 144373 | WTC2 | W | Wraps NCR: write Transfer Counter middle byte |
| 074 | 144374 | RTCL | R | Wraps NCR: read Transfer Counter LSB |
| 075 | 144375 | WTCL | W | Wraps NCR: write Transfer Counter LSB |
(RXWC/RXWC_HI at 010/012 are 3204-only external word-count reads and do not appear in this
3201 trace.)
1.3 Control word (WCONT) bit map (VERIFIED from the WCONT handler)¶
| Bit | Mask | Meaning |
|---|---|---|
| 0 | 0x0001 | Enable Interrupt - controller interrupts level 11 as soon as it is ready (not busy) |
| 2 | 0x0004 | Activate - start the operation whose NCR command was just written |
| 3 | 0x0008 | Test mode (count-address-registers self test) |
| 4 | 0x0010 | Clear Device - resets MAR/buffers, calls ncr5386.DeviceReset(), sets ReadyForTransfer |
| 5 | 0x0020 | ND-100 DMA enable - allow DMA to/from ND memory |
| 6 | 0x0040 | Write ND-100 Memory - DMA direction = NCR -> ND memory (data-in). Clear = ND -> NCR (out) |
| 10 | 0x0400 | Reset SCSI bus |
Control words actually seen in this transaction:
| Value | Decode | Role in the flow |
|---|---|---|
0x0000 |
(all clear) | de-activate + "clear to memory" - the interrupt-handler entry step |
0x0005 |
Enable Int + Activate | fire the NCR command and arm the completion IRQ |
0x0020 |
DMA enable | arm MAR/counter for a command-out DMA (ND -> NCR), not yet active |
0x0025 |
Enable Int + Activate + DMA enable | run the command-out DMA (CDB bytes ND -> NCR) |
0x0060 |
DMA enable + Write ND mem | arm MAR/counter for a data-in DMA (NCR -> ND) |
0x0065 |
Enable Int + Activate + DMA enable + Write ND mem | run the data-in DMA (NCR -> ND memory) |
1.4 Status word (RSTAU) bit map (VERIFIED from the RSTAU assembler)¶
| Bit | Mask | Meaning | Notes |
|---|---|---|---|
| 0 | 0x0001 | Enable Interrupt (echo of control bit 0) | |
| 2 | 0x0004 | Busy / Active | |
| 3 | 0x0008 | Ready for transfer | |
| 4 | 0x0010 | OR of errors (ND-100 bus DMA error) | never set in the emulator |
| 5 | 0x0020 | Reset on SCSI bus | (*) can raise IRQ |
| 6 | 0x0040 | NCR 5386 disabled | |
| 7 | 0x0080 | Single-ended SCSI driver selected | |
| 8 | 0x0100 | Data request from NCR 5386 | |
| 9 | 0x0200 | Interrupt from NCR 5386 | (*) can raise IRQ |
| 10 | 0x0400 | Data acknowledge to NCR 5386 | |
| 11 | 0x0800 | BERROR (ND-100 bus DMA error) | never set in the emulator |
| 12 | 0x1000 | BSY from the SCSI bus | |
| 13 | 0x2000 | REQ from the SCSI bus | |
| 14 | 0x4000 | ACK from the SCSI bus | |
| 15 | 0x8000 | Differential SCSI receivers selected | never set in the emulator |
() These two bits raise a level-11 interrupt to the ND-100 only if control bit 0 (Enable
Interrupt) is set. Status values seen this run: 0x3208 (RFT+IntFromNCR+BSY+REQ), 0x5208
(RFT+IntFromNCR+BSY+ACK), 0x0208 (RFT+IntFromNCR). Bit 4 is never set* - the stale
STATUS 100020 error described in scsi-transfer-status.md does not occur
in this build (VERIFIED: every RSTAU read is 0x0208/0x3208/0x5208).
1.5 How DMA to ND memory works (VERIFIED from WriteNextByteDMA/ReadNextByteDMA/StepGoState)¶
- SINTRAN writes the 24-bit target word address into
WHMAR(bits 16-23) andWLMAR(bits 0-15). Combined they formMAR. - It loads the NCR transfer counter via
WTCM/WTC2/WTCLwith the byte count. - It writes the NCR
WNCOMcommand0x94=TransferInfo | DMA-Mode. - It writes
WCONTwith DMA enable set:0x0025for a command/data-out transfer (WriteNDMemoryclear -> bytes read from ND memory and fed to the NCR), or0x0065for a data-in transfer (WriteNDMemoryset -> NCR bytes written to ND memory). - While
ActiveandDMAEnable,StepGoStatedrains all pending NCR bytes. On a data-in transfer each byte is packed into 16-bit words (WriteNextByteDMA) andMARadvances by 1 word per 2 bytes. The trace prints oneDMA->ND xferline per byte for the data-in direction only; the command/data-out direction (ND -> NCR) is not logged byte-by-byte (INFERRED direction from theWriteNDMemorybit being clear; the resulting CDB is confirmed by the disk'sCDB op=decode).
1.6 How completion is signaled (VERIFIED from Ncr5386_OnInterrupt + StepGoState + RITRG)¶
- When the NCR 5386 finishes a command it calls back and sets
regs.InterruptFromNCR5386 = true(trace:NCR interrupt raised intr=0x01). This sets RSTAU bit 9. - On the next
ClockwhileActive,StepGoStateseesInterruptFromNCR5386, setsactive=false,readyForTransfer=true, and - if control bit 0 (Enable Interrupt) is set - callsSetInterruptBit(true), i.e. raises the level-11 IRQ (trace:completion: active->false rft->true intEnabled=True -> SetInterruptBit). - The ND-100 interrupt handler reads
RSTAUto see why. ARSTAUread deliberately does NOT clear the NCR interrupt (comment in the C#: clearing there drops the completion IRQ and causes a mount timeout loop; see [SCSI-MOUNT-FIX-PLAN.md]-referenced fix #1). The interrupt is instead acknowledged when the handler readsRITRG(RITRG ack: cleared intFromNCR), which clears RSTAU bit 9.
2. The transaction at a glance¶
Console reconstruction (VERIFIED from CONOUT lines):
@ENTER-DIRECTORY,,DISC-SCSI-1,0 typed -> device transaction 20:33:47.119-.178 ->
@ prompt returns at 20:33:47.178. The later stop-system + WAIT with IONI off was the
operator typing @stop-system, not a driver deadlock.
Three SCSI commands, structurally identical handshakes:
| # | NCR select | CDB (op) | data-in | to MAR (word) | result |
|---|---|---|---|---|---|
| 1 | SelectWithATN dev 0 | 0x12 INQUIRY, alloc 8 |
8 bytes | 0x0094B8 |
00 00 05 01 34 00 00 00 |
| 2 | SelectWithATN dev 0 | 0x25 READ CAPACITY |
8 bytes | 0x0094B8 |
00 01 F9 1F 00 00 04 00 |
| 3 | SelectWithATN dev 0 | 0x08 READ_6 lba=129311, 1 blk |
1024 bytes | 0x04C600 |
block 08 00 54 D9 80 ... |
Note the READ CAPACITY op is printed as SC_GET_WINDOW by the disk's CDB decoder - that label is
cosmetic; the code path handles 0x25 as READ CAPACITY (VERIFIED: line 500 command READ
CAPACITY, line 501 READ CAPACITY -> blockSize=1024 lastLBA=129311).
Aggregate counts (VERIFIED by grep over the trace):
| Metric | Count |
|---|---|
IOX writes (IOX W) |
126 |
IOX reads (IOX R) |
90 |
| Distinct IOX accesses total | 216 |
| CDBs issued | 3 (INQUIRY, READ CAPACITY, READ_6) |
NCR commands (WNCOM) |
21 (3x SelectWithATN, 15x TransferInfo, 3x MessageAccepted) |
| NCR interrupts raised | 24 |
Controller completions (SetInterruptBit) |
24 |
| RITRG acks | 24 |
| DMA->ND data-in bytes logged | 1040 (8 + 8 + 1024) |
The 24/24/24 balance is the key interrupt-health fact: every NCR interrupt produced exactly one controller completion and exactly one RITRG ack. Nothing is dropped.
2.1 The canonical per-command handshake (INFERRED CPU intent from the NCR/NPL sequence)¶
Each of the three commands walks the same SCSI initiator sequence. Reading the ordered ledger in section 4, one command expands to:
WDESI=0 ; target id 0
WTCM/WTC2/WTCL ; select timeout into NCR transfer counter (0x0000C8)
WNCOM=0x08 ; SelectWithATN -> NCR raises IRQ (selection won)
WCONT=0x05 ; Enable Int + Activate -> completion IRQ to ND-100
--- interrupt handler (repeats after every step) ---
RSTAU ; why did we interrupt? (bit 9 = NCR int; does NOT clear it)
WCONT=0x00 ; "clear to memory" / de-activate
RAUXS ; SCSI phase (MessageOut / Command / DataIn / Status / MessageIn)
RITRG ; read NCR interrupt cause AND acknowledge it
... dispatch on phase ...
WNDAT=0xC0 ; MessageOut phase: send IDENTIFY byte 0xC0 (single-byte TransferInfo 0x54)
WLMAR/WHMAR/WTCL, WNCOM=0x94, WCONT=0x25 ; Command phase: DMA the 6-byte CDB OUT (ND->NCR)
WLMAR/WHMAR/WTCL, WNCOM=0x94, WCONT=0x65 ; DataIn phase: DMA the reply IN (NCR->ND)
RAUXS=0x1A (Status, TC=0), RNDAT ; Status phase: read 1 status byte (SS_GOOD=0x00)
RAUXS=0x38 (MessageIn), RNDAT ; MessageIn: read COMMAND COMPLETE message (0x00)
WNCOM=0x04 (MessageAccepted) ; ack the message
RITRG=0x04 [Disconnected] ; target drops the bus -> command done
This mirrors the SCINT interrupt handler and SELEC routine in the SINTRAN NPL SCSI driver
(embedded as reference text inside NDBusDiscControllerSCSI.cs): SCINT reads RSTAU, checks
bit 2 (busy) and bit 11 (NCR int), writes 0 to WCONT ("CLEAR TO MEMORY"), reads RAUXS, reads
RITRG, then dispatches; SELEC ends with 5; WCONT ("ENABLE INTERRUPT").
3. The three commands narrated¶
3.1 Command 1 - INQUIRY (trace 160-400)¶
SINTRAN selects target 0 with ATN (WNCOM=0x08), sends the IDENTIFY message byte 0xC0 in the
MessageOut phase, then DMAs a 6-byte INQUIRY CDB out of ND word buffer 0x9480 (WLMAR=0x9480,
WTCL=0x0C, WCONT=0x25). The disk logs CDB op=0x12 (SC_INQUIRY) cdb=00,00,00,08,00
(allocation length 8). The 8-byte reply DMAs in to word buffer 0x94B8 (WCONT=0x65):
observed byte stream 00 00 05 01 34 00 00 00 (VERIFIED from the eight DMA->ND lines). A Status
byte (SS_GOOD = 0x00) and a COMMAND COMPLETE message (0x00) are read one byte at a time via
RNDAT, MessageAccepted is written, and the target disconnects (RITRG=0x04 [Disconnected]).
3.2 Command 2 - READ CAPACITY (trace 401-645)¶
Identical handshake. The CDB DMAs out of 0x9480 again; the disk logs
CDB op=0x25 (SC_GET_WINDOW) but executes READ CAPACITY, replying
READ CAPACITY -> blockSize=1024 lastLBA=129311 capacityBytes=132415488. The 8-byte reply DMAs
in to the same scratch word buffer 0x94B8: observed 00 01 F9 1F 00 00 04 00 (VERIFIED),
which decodes as last LBA = 0x0001F91F = 129311 and block length = 0x00000400 = 1024 bytes.
This 8-byte reply is the only device-supplied geometry input to what happens next.
3.3 Command 3 - READ_6 of the LAST block (trace 646-1907)¶
Same select/IDENTIFY/command handshake. The CDB this time is
CDB op=0x08 (SC_READ_6) cdb=01,F9,1F,01,00, decoded by the disk as
lba=129311 blocks=1 len=1024. The LBA 0x01F91F = 129311 is exactly the lastLBA that READ
CAPACITY just returned - SINTRAN reads the last block of the device, not block 0
(INFERRED: the driver copies the READ-CAPACITY last-LBA straight into the READ_6 CDB; consistent
with scsi-open-last-block-read.md and
scsi-mount-geometry.md, which call this the control-record read (function-42 connect), not a capacity
leak). readBlock lba=129311 returns 08 00 54 D9 80 00 00 00 ... 01 F9 1F ... (the checksummed
area/layout table). This 1024-byte block DMAs in to word address 0x04C600
(WHMAR=0x0004, WLMAR=0xC600, WTC2=0x0004, WTCL=0x0400 = 1024 bytes). MAR advances from
0x04C600 to 0x04C800 (512 words) across the burst (VERIFIED from the 1024 DMA->ND lines).
Status SS_GOOD, COMMAND COMPLETE, MessageAccepted, Disconnect - then one final
WCONT=0x0005 (Enable Interrupt + Activate) at trace line 1907, with no NCR command behind it
and no further activity. The console returns to @.
4. The full chronological IOX ledger¶
Every distinct IOX access, NCR command, phase read, interrupt, ack, CDB and DMA event in the
transaction, in trace order, grouped by command. trc is the line number in the trace file.
Values are as printed by the emulator (hex for register values, NCR command/phase decodes verbatim).
The command-out DMA bursts (6-byte CDB, ND -> NCR) do not appear as
DMA->NDlines because that trace only fires on the NCR -> ND direction; they are represented here by theCDB op=event the disk logs when it receives the CDB. The 8-byte and 1024-byte data-in bursts are collapsed to a single summary row each (individual per-byteDMA->NDlines omitted for readability; byte streams are given in section 3).
4.1 Command 1 - INQUIRY¶
| # | trc | event | detail |
|---|---|---|---|
| 1 | 160 | IOX W | WCONT=0x0000 (clear to memory) |
| 2 | 162 | IOX W | WDESI=0x0000 (target id 0) |
| 3 | 164 | IOX W | WTCM=0x0000 |
| 4 | 166 | IOX W | WTC2=0x0000 |
| 5 | 168 | IOX W | WTCL=0x00C8 (select timeout counter) |
| 6 | 170 | IOX W | WNCOM=0x0008 |
| 7 | 172 | NCR CMD | 0x08 SelectWithATN |
| 8 | 173 | exec | SelectWithATN |
| 9 | 176 | NCR IRQ | intr=0x01 |
| 10 | 177 | IOX W | WCONT=0x0005 (Enable Int + Activate) |
| 11 | 180 | completion | SetInterruptBit |
| 12 | 182 | IOX R | RSTAU=0x3208 (RFT+IntFromNCR+BSY+REQ) |
| 13 | 183 | IOX W | WCONT=0x0000 |
| 14 | 185 | RAUXS | 0x30 Phase 6 MessageOut |
| 15 | 187 | IOX R | RAUXS=0x0030 |
| 16 | 188 | INT reg | 0x01 Function Complete |
| 17 | 191 | RITRG ack | intreg=0x0001 |
| 18 | 192 | IOX R | RITRG=0x0001 |
| 19 | 195 | NCR IRQ | intr=0x01 |
| 20 | 196 | IOX W | WCONT=0x0005 |
| 21 | 199 | completion | SetInterruptBit |
| 22 | 201 | IOX R | RSTAU=0x3208 |
| 23 | 202 | IOX W | WCONT=0x0000 |
| 24 | 204 | RAUXS | 0x30 Phase 6 MessageOut |
| 25 | 206 | IOX R | RAUXS=0x0030 |
| 26 | 207 | INT reg | 0x02 Bus Service |
| 27 | 210 | RITRG ack | intreg=0x0002 |
| 28 | 211 | IOX R | RITRG=0x0002 |
| 29 | 212 | IOX W | WNCOM=0x0054 |
| 30 | 214 | NCR CMD | 0x54 TransferInfo (single-byte) |
| 31 | 215 | exec | TransferInfo |
| 32 | 217 | IOX W | WNDAT=0x00C0 (IDENTIFY byte out) |
| 33 | 221 | NCR IRQ | intr=0x01 |
| 34 | 222 | IOX W | WCONT=0x0005 |
| 35 | 225 | completion | SetInterruptBit |
| 36 | 227 | IOX R | RSTAU=0x3208 |
| 37 | 228 | IOX W | WCONT=0x0000 |
| 38 | 230 | RAUXS | 0x10 Phase 2 Command |
| 39 | 232 | IOX R | RAUXS=0x0010 |
| 40 | 233 | INT reg | 0x02 Bus Service |
| 41 | 236 | RITRG ack | intreg=0x0002 |
| 42 | 237 | IOX R | RITRG=0x0002 |
| 43 | 238 | IOX W | WCONT=0x0020 (DMA enable, arm out) |
| 44 | 240 | IOX W | WHMAR=0x0000 |
| 45 | 241 | IOX W | WLMAR=0x9480 (CDB-out buffer, word) |
| 46 | 242 | IOX W | WTCM=0x0000 |
| 47 | 244 | IOX W | WTC2=0x0000 |
| 48 | 246 | IOX W | WTCL=0x000C (12 = CDB buffer size) |
| 49 | 248 | IOX W | WNCOM=0x0094 |
| 50 | 250 | NCR CMD | 0x94 TransferInfo (DMA mode) |
| 51 | 251 | exec | TransferInfo |
| 52 | 253 | IOX W | WCONT=0x0025 (Activate + DMA out) |
| 53 | 257 | CDB | op=0x12 SC_INQUIRY cdb=00,00,00,08,00 (alloc 8) |
| 54 | 264 | NCR IRQ | intr=0x01 |
| 55 | 265 | completion | SetInterruptBit |
| 56 | 267 | IOX R | RSTAU=0x3208 |
| 57 | 268 | IOX W | WCONT=0x0000 |
| 58 | 270 | RAUXS | 0x08 Phase 1 DataIn |
| 59 | 272 | IOX R | RAUXS=0x0008 |
| 60 | 273 | INT reg | 0x02 Bus Service |
| 61 | 276 | RITRG ack | intreg=0x0002 |
| 62 | 277 | IOX R | RITRG=0x0002 |
| 63 | 278 | IOX W | WCONT=0x0060 (DMA + Write ND mem, arm in) |
| 64 | 280 | IOX W | WHMAR=0x0000 |
| 65 | 281 | IOX W | WLMAR=0x94B8 (reply buffer, word) |
| 66 | 282 | IOX W | WTCM=0x0000 |
| 67 | 284 | IOX W | WTC2=0x0000 |
| 68 | 286 | IOX W | WTCL=0x0008 (8 bytes) |
| 69 | 288 | IOX W | WNCOM=0x0094 |
| 70 | 290 | NCR CMD | 0x94 TransferInfo (DMA mode) |
| 71 | 291 | exec | TransferInfo |
| 72 | 293 | IOX W | WCONT=0x0065 (Activate + DMA + Write ND mem) |
| - | 293-300 | DMA->ND | 8 bytes in @ MAR 0x94B8 -> 00 00 05 01 34 00 00 00 |
| 73 | 306 | NCR IRQ | intr=0x01 |
| 74 | 307 | completion | SetInterruptBit |
| 75 | 309 | IOX R | RSTAU=0x3208 |
| 76 | 310 | IOX W | WCONT=0x0000 |
| 77 | 312 | RAUXS | 0x1A Phase 3 Status (TC=0) |
| 78 | 314 | IOX R | RAUXS=0x001A |
| 79 | 315 | INT reg | 0x02 Bus Service |
| 80 | 318 | RITRG ack | intreg=0x0002 |
| 81 | 319 | IOX R | RITRG=0x0002 |
| 82 | 320 | IOX R | RLMAR=0x94BC (MAR after 8-byte in = 0x94B8+4 words) |
| 83 | 321 | IOX R | RHMAR=0x0000 |
| 84 | 322 | IOX W | WNCOM=0x0054 |
| 85 | 324 | NCR CMD | 0x54 TransferInfo (single-byte) |
| 86 | 325 | exec | TransferInfo |
| 87 | 327 | RAUXS | 0x98 DataRegisterFull + DataIn |
| 88 | 329 | IOX R | RAUXS=0x0098 |
| 89 | 331 | IOX R | RNDAT=0x0000 (status byte = SS_GOOD) |
| 90 | 333 | NCR IRQ | intr=0x01 |
| 91 | 334 | IOX W | WCONT=0x0005 |
| 92 | 337 | completion | SetInterruptBit |
| 93 | 339 | IOX R | RSTAU=0x3208 |
| 94 | 340 | IOX W | WCONT=0x0000 |
| 95 | 342 | RAUXS | 0x38 Phase 7 MessageIn |
| 96 | 344 | IOX R | RAUXS=0x0038 |
| 97 | 345 | INT reg | 0x02 Bus Service |
| 98 | 348 | RITRG ack | intreg=0x0002 |
| 99 | 349 | IOX R | RITRG=0x0002 |
| 100 | 350 | IOX W | WNCOM=0x0054 |
| 101 | 352 | NCR CMD | 0x54 TransferInfo (single-byte) |
| 102 | 353 | exec | TransferInfo |
| 103 | 355 | RAUXS | 0xB8 DataRegisterFull + MessageIn |
| 104 | 357 | IOX R | RAUXS=0x00B8 |
| 105 | 359 | IOX R | RNDAT=0x0000 (COMMAND COMPLETE message) |
| 106 | 361 | NCR IRQ | intr=0x01 |
| 107 | 362 | IOX W | WCONT=0x0005 |
| 108 | 365 | completion | SetInterruptBit |
| 109 | 367 | IOX R | RSTAU=0x5208 (RFT+IntFromNCR+BSY+ACK) |
| 110 | 368 | IOX W | WCONT=0x0000 |
| 111 | 370 | RAUXS | 0x38 Phase 7 MessageIn |
| 112 | 372 | IOX R | RAUXS=0x0038 |
| 113 | 373 | INT reg | 0x01 Function Complete |
| 114 | 376 | RITRG ack | intreg=0x0001 |
| 115 | 377 | IOX R | RITRG=0x0001 |
| 116 | 378 | IOX W | WNCOM=0x0004 |
| 117 | 380 | NCR CMD | 0x04 MessageAccepted |
| 118 | 381 | exec | MessageAccepted |
| 119 | 384 | NCR IRQ | intr=0x01 |
| 120 | 385 | IOX W | WCONT=0x0005 |
| 121 | 388 | completion | SetInterruptBit |
| 122 | 390 | IOX R | RSTAU=0x0208 (RFT+IntFromNCR only - bus dropped) |
| 123 | 391 | IOX W | WCONT=0x0000 |
| 124 | 393 | RAUXS | 0x00 Phase 0 (bus free) |
| 125 | 395 | IOX R | RAUXS=0x0000 |
| 126 | 396 | INT reg | 0x04 Disconnected |
| 127 | 399 | RITRG ack | intreg=0x0004 |
| 128 | 400 | IOX R | RITRG=0x0004 (target disconnected -> INQUIRY done) |
4.2 Command 2 - READ CAPACITY¶
Same 130-step handshake as Command 1 with these differences (full ordered ledger, trace 401-645):
| trc | event | detail |
|---|---|---|
| 401 | IOX W | WCONT=0x0005 (residual arm from CMD1 disconnect) |
| 404-414 | IOX W | WCONT=0x0000, WDESI=0, WTCM=0, WTC2=0, WTCL=0x00C8, WNCOM=0x0008 |
| 416 | NCR CMD | 0x08 SelectWithATN (dev 0) |
| 420-455 | (select) | IRQ, WCONT=0x05, RSTAU=0x3208, RAUXS=0x30 MessageOut, RITRG=0x01 then 0x02 |
| 456-461 | IDENTIFY | WNCOM=0x54, WNDAT=0x00C0 (IDENTIFY byte 0xC0) |
| 465-481 | (command) | IRQ, RSTAU=0x3208, RAUXS=0x10 Command phase, RITRG=0x02 |
| 482-497 | CDB out DMA | WCONT=0x20, WLMAR=0x9480, WTCL=0x000C, WNCOM=0x94, WCONT=0x25 |
| 501 | CDB | op=0x25 SC_GET_WINDOW cdb=00,00,00,00,00 (handled as READ CAPACITY) |
| 504 | READ CAP | blockSize=1024 lastLBA=129311 capacityBytes=132415488 |
| 509-522 | (data-in arm) | IRQ, RSTAU=0x3208, RAUXS=0x08 DataIn, RITRG=0x02 |
| 523-538 | reply DMA | WCONT=0x60, WLMAR=0x94B8, WTCL=0x0008, WNCOM=0x94, WCONT=0x65 |
| 538-.. | DMA->ND | 8 bytes in @ MAR 0x94B8 -> 00 01 F9 1F 00 00 04 00 (lastLBA 129311, blk 1024) |
| 551-566 | (status) | IRQ, RSTAU=0x3208, RAUXS=0x1A Status, RITRG=0x02, RLMAR=0x94BC, RHMAR=0x0000 |
| 567-576 | status byte | WNCOM=0x54, RAUXS=0x98, RNDAT=0x0000 (SS_GOOD) |
| 578-594 | msg in | IRQ, RSTAU=0x3208, RAUXS=0x38 MessageIn, RITRG=0x02 |
| 595-604 | msg byte | WNCOM=0x54, RAUXS=0xB8, RNDAT=0x0000 (COMMAND COMPLETE) |
| 606-622 | complete | IRQ, RSTAU=0x5208 (ACK), RAUXS=0x38, RITRG=0x01 Function Complete |
| 623-626 | accept | WNCOM=0x04 MessageAccepted |
| 629-645 | disconnect | IRQ, RSTAU=0x0208, RAUXS=0x00, RITRG=0x04 Disconnected -> READ CAPACITY done |
(This command contains 42 IOX writes and 30 IOX reads; every one is the same register/value pattern
as Command 1 - written totals per command are 41/30 (CMD1), 42/30 (CMD2), 43/30 (CMD3) - the only
substantive differences being the CDB, the READ CAPACITY reply bytes, and the residual WCONT=0x0005
arm inherited from the prior command's disconnect.)
4.3 Command 3 - READ_6 of block 129311¶
Full ordered ledger, trace 646-1907. Same handshake; the load-bearing differences are the CDB
(READ_6 lba=129311), the 1024-byte data buffer at word 0x04C600, and the dangling final
WCONT=0x0005:
| trc | event | detail |
|---|---|---|
| 646 | IOX W | WCONT=0x0005 (residual arm from CMD2 disconnect) |
| 649-659 | IOX W | WCONT=0x0000, WDESI=0, WTCM/WTC2=0, WTCL=0x00C8, WNCOM=0x0008 |
| 661 | NCR CMD | 0x08 SelectWithATN (dev 0) |
| 665-700 | (select) | IRQ, WCONT=0x05, RSTAU=0x3208, RAUXS=0x30 MessageOut, RITRG=0x01 then 0x02 |
| 701-706 | IDENTIFY | WNCOM=0x54, WNDAT=0x00C0 |
| 710-726 | (command) | IRQ, RSTAU=0x3208, RAUXS=0x10 Command, RITRG=0x02 |
| 727-742 | CDB out DMA | WCONT=0x20, WLMAR=0x9480, WTCL=0x000C, WNCOM=0x94, WCONT=0x25 |
| 745 | CDB | op=0x08 SC_READ_6 cdb=01,F9,1F,01,00 |
| 747 | CDB | op=0x08 SC_READ_6 lba=129311 blocks=1 len=1024 |
| 748,752 | readBlock | lba=129311 -> 08 00 54 D9 80 00 00 00 ... 01 F9 1F ... |
| 757-767 | (data-in arm) | IRQ, RSTAU=0x3208, RAUXS=0x08 DataIn, RITRG=0x02 |
| 768-783 | data DMA | WCONT=0x60, WHMAR=0x0004, WLMAR=0xC600, WTC2=0x0004, WTCL=0x0400 (1024), WNCOM=0x94, WCONT=0x65 |
| 783-1806 | DMA->ND | 1024 bytes in, MAR 0x04C600 -> 0x04C800 (512 words) |
| 1812-1825 | (status) | IRQ, RSTAU=0x3208, RAUXS=0x1A Status, RITRG=0x02, RLMAR=0xC800 RHMAR=0x0004 |
| 1828-1837 | status byte | WNCOM=0x54, RAUXS=0x98, RNDAT=0x0000 (SS_GOOD) |
| 1839-1855 | msg in | IRQ, RSTAU=0x3208, RAUXS=0x38 MessageIn, RITRG=0x02 |
| 1856-1865 | msg byte | WNCOM=0x54, RAUXS=0xB8, RNDAT=0x0000 (COMMAND COMPLETE) |
| 1867-1883 | complete | IRQ, RSTAU=0x5208, RAUXS=0x38, RITRG=0x01 Function Complete |
| 1884-1887 | accept | WNCOM=0x04 MessageAccepted |
| 1890-1906 | disconnect | IRQ, RSTAU=0x0208, RAUXS=0x00, RITRG=0x04 Disconnected -> READ_6 done |
| 1907 | IOX W | WCONT=0x0005 (Enable Int + Activate) - final, no NCR command behind it, no follow-up |
After trace 1907 the next device line is CONOUT '@' at 20:33:47.178. No fourth SelectWithATN,
no block-0 CDB, no WRITE CDB.
5. Interrupt analysis - did any IOX access want an IRQ it never got?¶
This is the point of the exercise: find an IOX write where SINTRAN appears to expect a completion or an IRQ that the emulator never raises, or a register read whose value sends SINTRAN down the wrong branch (e.g. away from queuing the block-0 read).
5.1 Every arm produced its completion (VERIFIED - no dropped IRQ)¶
Cross-checking the ledger for the completion contract in section 1.6:
- 24
WCONTwrites with Activate (bit 2) were issued (0x0005x18,0x0025x3,0x0065x3). - 24
NCR interrupt raisedevents, 24completion ... SetInterruptBit, 24RITRG ack. The three numbers are equal and they interleave 1:1:1 in trace order (section 4). Every armed operation completed and raised a level-11 IRQ, and every IRQ was acknowledged onRITRG. - The Enable-Interrupt bit (control bit 0) was set on every
Activatewrite in the run (0x0005/0x0025/0x0065all have bit 0 set), so every completion was allowed to reach the ND-100. There is noActivate-without-Enable-Interrupt case that would have silently swallowed a completion. RSTAUnever cleared the NCR interrupt (VERIFIED: after eachRSTAU=…208read, the very next relevant access is aWCONT=0x0000thenRAUXSthenRITRG, and only theRITRGline printscleared intFromNCR). The fix documented in the C# (RSTAU must not ack) is holding.
Conclusion (VERIFIED): there is no lost / missing / dropped interrupt in this transaction. The
case 4b "lost completion IRQ -> timeout hang" hypothesis in
SCSI-MOUNT-DEBUG-HANDOFF.md is not supported by this trace - the
device-side interrupt machinery is fully balanced and the command returns to @.
5.2 The one anomalous IOX write - the dangling final arm (VERIFIED event, INFERRED meaning)¶
The single IOX access that "expects something more" is the last one:
trc 1907 : IOX W WCONT=0x0005 [Enable Interrupt][Active] (active=True, intFromNCR=False, rft=False)
This arms the controller (Active + Enable Interrupt) but no NCR command precedes it (the last
WNCOM was MessageAccepted at trc 1884) and nothing follows it - the disk has disconnected,
so DataRequestFromNCR5386 and InterruptFromNCR5386 are both false. In the emulator,
StepGoState will run every Clock (because active=true) but find nothing to do, so no IRQ is
raised and none is expected - this is the SINTRAN SELEC/SCWTI idle re-arm ("check arbitration
queue, enable interrupt, busy-return"), waiting for a reconnect that the mount logic has already
decided not to trigger.
This is the fingerprint of the bug, but it is NOT a device fault. The controller is idling exactly as told. The decision "do not select the target again to read block 0" was already made on the CPU side, before this arm - and this trace has no CPU opcodes, so that branch cannot be pinned from the device side alone (INFERRED, consistent with handoff section 6b/7b H1: a capacity/geometry consistency check aborts the connect before block 0 is queued).
5.3 The register read that actually steers the flow (VERIFIED value, INFERRED consequence)¶
No interrupt is missing, but one data value is load-bearing. The only device-supplied input that could send SINTRAN toward block 0 (or away from it) is the READ CAPACITY reply:
trc 504 : READ CAPACITY -> blockSize=1024 lastLBA=129311 capacityBytes=132415488
DMA->ND @ 0x94B8 : 00 01 F9 1F 00 00 04 00 (lastLBA = 0x0001F91F = 129311, block = 1024)
SINTRAN then uses 129311 verbatim as the READ_6 LBA (trc 745/747), i.e. it reads the last
block as the control-record read (function-42 connect). Per handoff H1, the block-0 PACK-ONE master claims capacity
61036 pages = 122072 blocks, while READ CAPACITY reports physical 129312 blocks
(lastLBA 129311); a consistency field derived from these two is the suspected gate that aborts
before block 0. The only lever on the device side is therefore the content of these 8 bytes -
not any interrupt, DMA, or phase, all of which are behaving. (Handoff also notes reporting
usable 122071 was rejected by the ECAPD check and raw 129311 also fails to mount, so the
correct value is a third thing that must be pinned live on the CPU side, not guessed here.)
5.4 Summary of candidates flagged¶
| Candidate | Verdict |
|---|---|
| An IOX write that armed an IRQ the emulator never raised | None found. 24 arms = 24 completions = 24 acks (VERIFIED). |
| A read that ack'd/cleared a pending IRQ prematurely (RSTAU) | Not happening. RSTAU never clears; only RITRG does (VERIFIED). |
| An IOX access that should have driven a follow-up (block-0) read but did not | trc 1907 WCONT=0x0005 - a dangling idle re-arm with no NCR command and no follow-up select/CDB. Device is idling correctly; the missing block-0 select is a CPU-side decision (INFERRED). |
| A read returning a value that steers SINTRAN down the wrong branch | The READ CAPACITY reply at trc 504 / 0x94B8 (00 01 F9 1F 00 00 04 00) - the last-LBA/blocksize pair SINTRAN keys its geometry check and last-block probe on (VERIFIED bytes; INFERRED consequence). |
6. Diagrams¶
6.1 End-to-end sequence (one command shown in full; x3 for the mount)¶
sequenceDiagram
autonumber
participant CPU as SINTRAN CPU (ND-100, PIL 11)
participant CTL as SCSI Controller (3201)
participant NCR as NCR 5386
participant DSK as Disk target (id 0)
Note over CPU,DSK: Per command: SelectWithATN -> IDENTIFY -> CDB(DMA out) -> data(DMA in) -> status -> msg -> disconnect
CPU->>CTL: IOX W WDESI=0, WTCM/WTC2/WTCL (timeout), WNCOM=0x08
CTL->>NCR: SelectWithATN (dev 0)
NCR->>DSK: arbitrate + select w/ ATN
NCR-->>CTL: interrupt 0x01 (Function Complete)
CPU->>CTL: IOX W WCONT=0x05 (EnInt+Active)
CTL-->>CPU: level-11 IRQ (SetInterruptBit)
CPU->>CTL: IOX R RSTAU=0x3208 (does NOT clear IRQ)
CPU->>CTL: IOX W WCONT=0x00 (clear to memory)
CPU->>CTL: IOX R RAUXS=0x30 (Phase 6 MessageOut)
CPU->>CTL: IOX R RITRG (ACK IRQ, get cause)
CPU->>CTL: IOX W WNCOM=0x54, WNDAT=0xC0 (IDENTIFY out)
CPU->>CTL: IOX W WLMAR=0x9480,WTCL=0x0C,WNCOM=0x94,WCONT=0x25
CTL->>NCR: TransferInfo DMA (CDB out)
Note right of CTL: DMA reads CDB from ND word 0x9480 -> NCR
NCR->>DSK: CDB (INQUIRY / READ CAPACITY / READ_6 lba=129311)
DSK-->>NCR: DATA IN phase
CPU->>CTL: IOX W WLMAR=..,WTCL=size,WNCOM=0x94,WCONT=0x65 (DMA + Write ND mem)
DSK-->>NCR: data bytes
NCR-->>CTL: byte stream
CTL-->>CPU: DMA->ND (8 or 1024 bytes to MAR)
NCR-->>CTL: interrupt (transfer done)
CTL-->>CPU: level-11 IRQ
DSK-->>NCR: STATUS (SS_GOOD) then COMMAND COMPLETE msg
CPU->>CTL: IOX R RNDAT (status byte), RNDAT (message)
CPU->>CTL: IOX W WNCOM=0x04 (MessageAccepted)
NCR-->>CTL: interrupt 0x04 (Disconnected)
CTL-->>CPU: level-11 IRQ ; CPU IOX R RITRG=0x04
Note over CPU,DSK: After READ_6: one WCONT=0x05 idle re-arm, then return to '@' (block 0 never read)
6.2 Control-word / interrupt state flow¶
flowchart TD
A["CPU writes WNCOM (NCR command)"]:::blue --> B["CPU writes WCONT<br/>bit2 Active + bit0 EnInt<br/>(0x05 / 0x25 / 0x65)"]:::teal
B --> C{"DMA enable<br/>(bit5)?"}:::teal
C -- "yes, Write ND mem (0x65)" --> D["StepGoState drains NCR bytes<br/>-> DMA->ND memory at MAR"]:::orange
C -- "yes, out (0x25)" --> E["StepGoState feeds ND bytes -> NCR<br/>(CDB out)"]:::orange
C -- "no (0x05)" --> F["NCR runs single-byte / select op"]:::teal
D --> G["NCR raises interrupt<br/>InterruptFromNCR5386 = true<br/>(RSTAU bit 9)"]:::purple
E --> G
F --> G
G --> H{"StepGoState:<br/>Active AND InterruptFromNCR?"}:::teal
H -- yes --> I["active=false; readyForTransfer=true"]:::green
I --> J{"Enable Interrupt<br/>(bit0) set?"}:::teal
J -- yes --> K["SetInterruptBit(true)<br/>=> level-11 IRQ to ND-100"]:::green
J -- no --> L["no IRQ (completion latched only)"]:::orange
K --> M["CPU: IOX R RSTAU<br/>(reads status, does NOT clear int)"]:::blue
M --> N["CPU: IOX W WCONT=0x00<br/>(clear to memory / de-activate)"]:::blue
N --> O["CPU: IOX R RAUXS<br/>(SCSI phase)"]:::blue
O --> P["CPU: IOX R RITRG<br/>=> clears InterruptFromNCR (ACK)"]:::green
P --> Q{"phase / int cause?"}:::teal
Q -- "0x04 Disconnected" --> R["command done"]:::green
Q -- "Bus Service / Function Complete" --> A
R --> S["READ_6 done: final WCONT=0x05 idle re-arm<br/>(no NCR cmd) -> return to '@'<br/>*** block 0 never queued ***"]:::red
classDef blue fill:#E3F2FD,stroke:#0D47A1,color:#0D47A1;
classDef teal fill:#E0F7FA,stroke:#00838F,color:#00838F;
classDef green fill:#E8F5E9,stroke:#2E7D32,color:#2E7D32;
classDef purple fill:#F3E5F5,stroke:#7B1FA2,color:#7B1FA2;
classDef orange fill:#FFF3E0,stroke:#E65100,color:#E65100;
classDef red fill:#FFEBEE,stroke:#B71C1C,color:#B71C1C;
7. What to look at next (tie-back to the enter-directory bug)¶
The device side of this mount is healthy: selection, IDENTIFY, all three CDBs, both DMA
directions, all 24 interrupts, and all 24 acks behave exactly as the NCR 5386 / 3201 contract
requires, and the command returns cleanly to @. No missing IRQ, no premature ack, no wrong
status bit. So the reason block 0 is never read is not a dropped interrupt or a device-side
handshake gap - it is a CPU-side branch taken after the last-block area table is parsed.
Grounded, honest next steps:
- The only device-side lever is the READ CAPACITY reply at trc 504 / ND word
0x94B8(00 01 F9 1F 00 00 04 00). SINTRAN copieslastLBA=129311straight into the READ_6 CDB and (per handoff H1) feeds the capacity/geometry into a consistency check. If a different capacity or block-length in those 8 bytes makes SINTRAN queue a block-0 select, that is an emulator-testable change - but the handoff already tried usable122071(rejected byECAPD) and raw129311(still no mount), so do not keep guessing capacity numbers; pin the field SINTRAN actually compares. - The unpinned span is CPU-side and this trace cannot see it. Between "area-table DMA
completes" (trc 1806) and "return to
@" (trc 1985) there must be a conditional branch that decides block-0-read vs return. Capture it with an opcode trace or a DATA watchpoint on the device datafield (handoff section 7b) - specifically the field that holds the master-block / first-directory-page LBA that should be0, not129311. - The dangling
WCONT=0x0005at trc 1907 is the observable device-side symptom of that CPU-side abort: the driver re-armed for a reconnect/next-select that its own logic then chose not to start. Confirm on the CPU side that this arm corresponds toSELECreturning with an empty arbitration queue (BUSFL=0) after the consistency check failed.
Every hex/octal value, byte stream, MAR, count, phase, and interrupt in this document was read
directly from /mnt/c/Users/ronny/AppData/Local/trace/file-trace.txt (mount at 20:33:47) and
decoded against the RetroCore C# sources listed in the provenance section. Facts read from those
bytes are VERIFIED; CPU-side intent, which this device-only trace cannot show, is labelled
INFERRED.