Skip to content

network: multi-record DaynaPort READ(6) matching real SCSI/Link behavior - #399

Open
ingpaschke wants to merge 4 commits into
BlueSCSI:mainfrom
ingpaschke:daynaport-network
Open

network: multi-record DaynaPort READ(6) matching real SCSI/Link behavior#399
ingpaschke wants to merge 4 commits into
BlueSCSI:mainfrom
ingpaschke:daynaport-network

Conversation

@ingpaschke

Copy link
Copy Markdown

Started as an effort to improve DaynaPort SCSILink throughput on an Atari Falcon (stuck ~11 KB/s), which led to disassembling Dayna's original Mac .ENET0 driver (DaynaPORT 7.5.3) to see what the real hardware does. The real SCSI/Link returns a stream of [len|flags|payload|crc] records per READ(6) with a per-record "more pending" flag, and the host chains reads until it clears.

This makes the handler match that protocol. Three standalone commits:

  1. Full inbound ring no longer reads as empty (drops every queued packet — pre-existing bug).
  2. Inbound ring 20 -> 48 slots, scoped to Pico_2_DaynaPORT only (RP2350 has the RAM).
  3. Multi-record READ(6): several records per transaction (16 blind / 4 polled ≈ the real device's ~6 KB buffer), truthful per-record flag, zero-length terminator on capped batches.

Measured with the paired FreeMiNT driver:

  • Atari Falcon (blind): ~11 -> ~90 KB/s
  • Mac SE/30 (System 7.5, DaynaPORT 7.5.3): >100 KB/s even in the worst case: VM on (polled mode) while writing to the same BlueSCSI

Ring change is scoped to the Pico 2 W target; RP2040 and Ultra untouched. The record-stream + terminator match what the Mac driver already expects, so existing setups are unaffected (the terminator also fixes a latent stale-buffer read).

ipaschke added 2 commits July 25, 2026 09:59
scsiNetworkEnqueue advanced writeIndex onto readIndex when the ring
filled, which makes a completely full ring indistinguishable from an
empty one: the DaynaPort READ(6) handler then reports no packets and
every queued packet is dropped at once, showing up as bursts of lost
inbound frames and TCP retransmissions under load.

Compute the next write index first and drop only the incoming packet
when the ring is full.
The 20-slot ring overflows during wifi receive bursts between host
READ(6) polls, dropping packets that then cost TCP retransmissions.
The Pico 2 W target has the RAM for a deeper ring (~1.5 KB/slot), so
set NETWORK_PACKET_QUEUE_SIZE=48 in its target definitions, using the
existing override hook (as Pico_Audio_SPDIF already does with 12).
Other targets keep the 20-slot default.
@jcs

jcs commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

I don't understand why 7c20579 reverted back to one packet per read on "old SCSI Manager, VM present". Surely the Mac Plus with System 6 has the oldest SCSI Manager and can't have virtual memory, yet it was doing multiple packets per read which was the whole point of my work in e7d9629.

@ingpaschke

Copy link
Copy Markdown
Author

A bit outside the scope of this PR, and to be clear the gate isn't mine: 7c20579 came in via #350, which is why there was never a thread for it.

On the substance: CDB[5] is just how the driver tells the device how it intends to clock bytes off the bus. Why it picks one mode over the other is a host-side decision, and I don't think the firmware should be modelling it. Respecting the request is the right call. This new code does multiple packets per read in polled mode anyway (4 records rather than 1, roughly the real SCSI/Link's 6 KB packet memory) so nothing collapses to a single packet either way.

An interesting aside: So your pause timings, which I left untouched, were captured from a real SCSI/Link-3 with a Mac Plus as the host. If the Plus is requesting polled mode, then those numbers characterise polled-mode pacing and we're applying them unchanged to blind hosts, where the requirement is real but the magnitude is unvalidated. I've only tested them on/off: removing them breaks blind-mode on my SE/30 and is worth about 10 KB/s on the Atari Falcon. I never swept the value. Do you know what CDB[5] your Plus actually sends, and did the SCSI/Link pace differently depending on that bit? Could you dump the ROM of the real Dayna SCSI/Link?

I just tested Appletalk performance on my SE/30 with VM on on 7.5.5 and it's totally fine with the new code at least.

@ingpaschke

Copy link
Copy Markdown
Author

I found the ROMs for the Dayna SCSI/Link! Will reverse engineer and that will give me an even better understanding what the real hardware did.

ipaschke and others added 2 commits July 25, 2026 22:01
Derived from Dayna SCSI/Link SL003 device firmware (Z180 ROMs v1.3b2
and v2.0; addresses from v2.0). CDB[5] bit 6 selects the host's
transfer loop (blind/polled); the ROM dispatches on it at 0x0f3b.

- Polled: one record per READ(6), no terminator (ROM 0x0d77).
  DaynaPORT 7.5.3 requests a single record's allocation and wedges
  the bus on more. Honor the CDB[1..2] byte offset; dequeue only
  when the read reached the packet's end or CDB[5] is nonzero
  (0x0ea2-0x0f17); allocation <= 6 is a header-only peek without
  dequeue (0x0ea0).
- Blind: multi-record batch. Cap at 16 records to bound the bus
  hold (device ROM: v1.3 unbounded, v2.0 capped at 200, 0x0d5c).
  Zero terminator record only when a capped batch promised more
  (0x0c1c); none at end of queue (0x0cb0). Never truncate a
  mid-batch record; no batching after a peek, offset, partial, or
  truncated read.
- Both modes: 0x10 'more pending' flag reported truthfully from
  queue occupancy (0x0ca6, 0x0df2); previously never set in polled
  mode. Allocation-length payload cap applies per record, not
  cumulatively (0x0cf2, 0x0e8a). Dequeue a ring slot only after its
  payload left the bus (0x0d4c, 0x0f17), also fixing a concurrent
  Wi-Fi enqueue overwriting a slot mid-transfer.
- 75/300 us gaps unchanged: no timed delays exist in the ROM; the
  measured gaps (e7d9629) are emergent Z180/DMA overhead that
  software-timed blind hosts depend on. The host driver selects blind
  purely on Virtual Memory being off (Gestalt 'vm '), independent of
  machine type, so a Mac Plus (68000, no VM) always drives blind and
  relies on these gaps.

Measured RX: Mac SE/30 (System 7.5) 90 KB/s polled / 100 KB/s
blind; Atari Falcon (FreeMiNT, blind) 100 KB/s. DaynaPORT 7.5.3
sends 0x80 (polled) with VM on, 0xC0 (blind) with VM off.
…[1..2]

Per the SL003 device ROM (v2.0 0x1294, v1.3 0x11d7), WRITE(6)
dispatches on CDB[5] bit 7: clear = one raw packet of cdb[3..4]
bytes, set = the length-prefixed record stream. The previous test
(cdb[5] == 0) parsed any other bit-7-clear value as a stream,
misreading the first 4 payload bytes as a record header. The record
stream format itself (4-byte header, zero-length terminator,
cdb[3..4] ignored) matches the ROM exactly as implemented.

Also reject writes with nonzero CDB[1..2] as the device does (ROM
0x1281).
@ingpaschke

Copy link
Copy Markdown
Author

Updated with new insights from the complete ROM disassembly. The main difference is that polled mode is now strictly 1 record only as per the original ROM. The Mac polled driver sizes each transfer for a single record and stalls hard if you batch more.

The disassembly also pinned down a bug introduced by the mode-gating in 7c20579: in polled mode it forced the loop to exit before computing the header flags, so the "more packets pending" bit (0x10) was always 0. Polled hosts were never told to come back, so they fell to slow timer-polling, that's almost certainly the real cause of the slowdown in #346.

With both fixes together (one record per transfer and a truthful pending flag from actual queue occupancy) the host re-reads immediately and polled mode stays fast: measured 90 KB/s on a Mac SE/30 with VM on, vs. single digits when either piece is wrong.

@appscaptain

appscaptain commented Jul 26, 2026

Copy link
Copy Markdown

Wow, this PR is insane! Have a PB Duo 270c running System 7.1 and connecting to an Appletalk Share (not TCP). I went from around 24 kb/s transfer speeds with the newest BlueSCSI firmware to 80-149 kb/s with the pull request binary (according to Connectix Speed Doubler) 😄 I can basically store many of my apps on my server now and run them directly from there! Wild! This is awesome! 🙏 🏎️ 😎 And this is on the Pico V1 PowerBook BlueSCSI V2. Awesome work @ingpaschke!

@erichelgeson

erichelgeson commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

@ingpaschke This seems good on the surface, though I think we need to test on a 68000 machine like the mac plus. Also I think we need to update the DaynaPORT docs (maybe port them over from the PiSCSI wiki) with findings. Could you also publish the reverse engineering/dissembled ROM's to a repo for reference? It is good we now have tools to quickly RE actual ROM and Drivers vs relying solely on observation - but we need to capture that in the reference docs now as well in case those tools get it wrong.

We also need to understand why @jcs observed multipacket on a mac plus with no VM with the stock driver.

So my two action items are:

@ingpaschke

Copy link
Copy Markdown
Author

@erichelgeson I previously only reverse engineered the relevant SCSI-commands, but why not do the whole ROM:

https://github.com/ingpaschke/daynaport-scsilink/

That also suggests some further improvements around promiscuous mode, performance counters and multicast support that we could tackle later.

I tested this code on 7.01 (but not System 6) on my MacPlus. It works reliably there.

Multipacket transfers are normal on any Mac with VM disabled. The Macintosh driver gates this only on this distinction (Gestalt(' vm '))

@ingpaschke

Copy link
Copy Markdown
Author

I discovered a major deviation from the real device's behavior in my code (blind mode broken on Mac). Not data corruption broken, but only doing one packet per batch broken. I have to dig further into that tomorrow.

Please don't merge for now.

@jcs

jcs commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

I tested on a PowerBook 100 with System 6 and a Pico 1 W and copied an 800K disk image over AppleShare. v2026.03.01 with my original multi-packet changes copied in 14 seconds, and with v2026.04.27 it took 30 seconds.

So 7c20579 seems like it was the wrong fix.

@ingpaschke

ingpaschke commented Jul 28, 2026

Copy link
Copy Markdown
Author

The problem I'm currently investigating is that I unwittingly modified the blind mode to use a transfer size budget that allows the target to put more than one record into a single SCSI transaction because I didn't understand the payload cap fully. The real device doesn't work that way, the payload cap is practically meaningless there (set it to less than 1524 -> packets get truncated, set it to more=it's capped to the record size anyway). Either way the device keeps streaming records; the allocation never bounds the transaction.

The original Macs had to do byte level SCSI-handshaking in software which was slow, so they offered "blind mode" as an alternative: With SCSIReadBlind, you could just read at presumed maximum speed and just had to do the handshaking yourself or hope that it just somehow worked out. More modern Macs and all other hardware that I know of didn't have that problem. SCSI transfers use hardware handshaking and DMA transfers.

So all other initiators should have no problems with blind mode? Yes, but there's a catch: The Mac can do multiple SCSI reads inside a single SCSI Command, so it use one read to just read the record header, then read the payload, hand off the payload to the IP stack and then loop and reuse that single 1524 byte buffer over and over again. That's what the two pauses are for: Read the header -> allow 75us to parse it and set up the next read -> read payload -> wait 300us for the Mac to hand off the Ethernet-packet and set up the next read -> loop...

On most other SCSI-APIs like the Atari's, that's not possible. A SCSIRead is a whole single SCSI Command. I can't peek the header first and I can't reuse the per-record buffer.

Moreover, the SCSI/Link runs a true streaming protocol: The Network device runs concurrently and can fill up the buffer (which seems to be about 14.8 KB / 9 full size Ethernet frames btw.) while SCSI drains it. That allows it to stream an unbounded number of incoming packets to the host (Firmware 2.0 capped that to 200 for some reason). But even if I set up my SCSIRead on the Atari to accept 200 packets and modify BlueSCSI to allow for that, I would then have a ~300 KB pile of Ethernet packets handed to the IP stack in one burst, every ACK delayed by the whole transfer, which wrecks TCP's flow-control timing (and a 300 KB SCSI buffer is not even representable in my driver's 16-bit build).

So blind transfer mode is strictly Mac only. All other initiators have to make do with polled mode. I have to see how fast I can get this on my Falcon. If it's around the 100KB/s I now get with the "wrong" implementation, I'll just settle for that. Otherwise I'd like to propose a third mode that the real hardware didn't have that works along the lines of what I submitted above.

Give me some time to work that out.

The good news is: For Macs, the accurate version should be even faster, since it truly enables multi-record streaming for the first time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants