From BlueSCSI Ultra to DaynaPORT: Connecting Legacy SCSI Devices to Linux and Windows
The project did not start with networking. It started with a much more practical problem: I wanted to use my old SCSI devices again.
I had devices such as a Sony SMO-F551 magneto-optical drive and Iomega Jaz 1 GB / 2 GB drives. The drives were still useful to me, but connecting them to a modern computer was the difficult part. At the time, I could not find a suitable old SCSI controller.
Instead, I used a BlueSCSI Ultra and began exploring how far its bridge capabilities could be taken. My original goal was straightforward: give a modern Linux or Windows host access to physical legacy SCSI peripherals, without first having to source a period-correct SCSI host adapter.
That distinction matters. BlueSCSI is widely known for emulating SCSI peripherals, such as hard disks backed by SD-card images. My problem was the other way around: I already had real SCSI devices and wanted to communicate with them.
How networking entered the picture
The original goal: access real legacy SCSI hardware without a vintage host adapter.
Working on the SCSI bridge exposed another possibility. BlueSCSI also has DaynaPORT-related functionality, based on the DaynaPORT SCSI/Link network adapter protocol from the 1990s. It was worth exploring whether the bridge and a host-side driver could expose that interface to Linux and Windows as an ordinary network adapter.
That led to the work described here: BlueSCSI bridge changes, a Linux DaynaPORT driver, and a Windows DaynaPORT driver family. The network interface was an extension of the original SCSI-access project, not the reason I bought or started using BlueSCSI Ultra.
The work now spans three repositories:
BlueSCSI-v2, with my experimentalavkghost/birdge-with-daynaportbranchdaynaport-scsilink-linux-driverdaynaport-scsilink-windows-driver
The interesting part is not only that the driver works like a network card. It is that the same basic DaynaPORT protocol has to survive several very different environments:
- native SCSI,
- BlueSCSI's USB bridge,
- Linux kernels from 2.0 to 7.x,
- Windows 9x,
- Windows NT 4.0,
- Windows 2000/XP,
- and Windows 10/11.
The result is a useful example of what happens when a packet protocol from the early 1990s meets several decades of operating-system driver architecture.
DaynaPORT is not a disk
A DaynaPORT SCSI/Link appears on the SCSI bus as a Processor device (type 0x03), not as a Direct Access device.
The host discovers it with a normal SCSI INQUIRY, looking for something equivalent to:
Vendor: Dayna
Product: SCSI/Link
Type: Processor (0x03)
From there, Ethernet frames are moved with ordinary-looking six-byte SCSI commands that have Dayna-specific semantics.
The core operations are surprisingly small:
INQUIRY 0x12 identify the device
READ(6) 0x08 receive Ethernet frames
WRITE(6) 0x0A transmit Ethernet frames
RETRIEVE STATS 0x09 retrieve MAC address and counters
ENABLE/DISABLE 0x0E bring the interface up or down
A Linux or Windows driver therefore sits between two completely different subsystems:
BlueSCSI itself does not run DHCP for the host and does not own the host's IP configuration. In this mode it behaves much more like an Ethernet bridge: the operating system still owns the network stack.
The first important limitation: there is no RX interrupt
The operating system handles networking; the DaynaPORT driver converts Ethernet traffic to SCSI commands.
A normal Ethernet controller raises an interrupt when a packet arrives.
DaynaPORT does not.
The only way to discover incoming traffic is to issue READ(6) repeatedly.
That creates one of the central design problems in the entire driver.
Poll too slowly and latency becomes terrible or the device's RX queue fills.
Poll too aggressively and the driver wastes CPU time and occupies the SCSI bus.
There is another constraint: effectively there is one command path. RX polling and TX must be arbitrated. If receive traffic monopolizes the SCSI transport, transmit suffers. If transmit is allowed to dominate, the receive queue may overflow.
This becomes much more important once BlueSCSI is placed behind another bridge.
Native SCSI was the easy case
With a real SCSI HBA, one Ethernet frame can be transferred in one SCSI operation.
A typical receive record looks like this:
BlueSCSI can also return several records in one READ(6) when the host uses the multi-packet form.
The driver therefore cannot assume that one SCSI read equals one Ethernet frame. It has to walk the returned buffer record by record.
For transmit, the simplest path is even cleaner: one raw Ethernet frame becomes one WRITE(6).
This is close to how the original hardware was intended to be used.
Then came the USB bridge.
The 512-byte problem
My BlueSCSI bridge work introduced a very different transport constraint.
The early USB Mass Storage bridge path moved SCSI data through a 512-byte buffer.
That is perfectly natural for block storage.
It is not natural for Ethernet.
A normal Ethernet frame can be roughly 1.5 KB, so a packet that was previously transferred in one SCSI operation suddenly no longer fits.
The transport now looks more like this:
This is where the driver stopped being only a DaynaPORT driver and started becoming a DaynaPORT-over-bridge driver.
Adding chunking without breaking DaynaPORT
Bridge-specific fragmentation preserves the Ethernet frame despite a smaller transport limit.
The bridge protocol uses a continuation flag (0x20) so that one Ethernet frame can be spread across multiple SCSI transactions.
On receive, the driver collects chunks until it sees the final record and then reconstructs the Ethernet frame.
On transmit, a large frame is split into several WRITE(6) commands and reassembled on the BlueSCSI side.
For the USB bridge path, TX chunks are limited to 506 bytes because some bytes are consumed by protocol framing.
Conceptually:
A zero-length write can be used to abort an incomplete transmit chain.
The important architectural decision was to keep this extension outside the normal DaynaPORT semantics as much as possible. Native SCSI should continue to behave like native SCSI. Chunking is transport-specific.
That distinction appears throughout the Linux and Windows implementations.
The Linux driver became three drivers
The original Linux driver work comes from Jeff Flitton. My fork adds the bridge/chunking support, diagnostics, shared protocol work, and subsequent improvements.
One source file cannot realistically cover the Linux kernel API from 2.0 through 7.x, so the repository now contains three separate driver implementations:
linux-2.0/
linux-2.4/
linux-7.0/
What can be shared lives in:
lib/daynaport.h
That file deliberately contains protocol logic rather than Linux kernel API calls.
It is C89-clean so that the same protocol core can be used by very old compilers and modern toolchains.
This turned out to be one of the most valuable design choices in the project, because the same core could later be reused by the Windows ports.
Discovery instead of hard-coded SCSI IDs
Another small but important rule is that the driver must not assume a fixed SCSI target.
BlueSCSI lets the user choose the SCSI ID of the emulated DaynaPORT.
The driver therefore follows normal SCSI discovery and binds to a device matching the Dayna identity:
Peripheral type: 0x03
Vendor: Dayna
Product: SCSI/Link
This sounds obvious, but it keeps the driver compatible with:
- real DaynaPORT hardware,
- BlueSCSI,
- ZuluSCSI,
- PiSCSI,
- and potentially more than one adapter on a bus.
Another historical detail: wait after ENABLE
One of the less obvious pieces of the protocol is the delay after enabling the interface.
After:
0E 00 00 00 00 80
the host should wait about 500 ms before issuing further commands.
That behavior is visible in other DaynaPORT driver implementations as well.
It is exactly the sort of detail that disappears if you only implement the obvious packet path and do not compare multiple implementations and the original command documentation.
The protocol-reference work in the Linux repository became almost as important as the driver itself.
Polling was the real performance problem
At first it was tempting to assume that the 512-byte bridge buffer would be the main bottleneck.
The benchmarks showed something more interesting.
The Linux test notes record roughly:
During active receive traffic, the driver reached roughly 70–85 SCSI reads per second.
That was close to the device round-trip limit in the tested configuration.
The polling logic was changed so that after a productive read the driver could continue polling aggressively for a short fast window rather than immediately sleeping for the normal poll interval.
When the link becomes idle, it falls back to the slower polling cadence.
This is important because a fixed polling interval creates a nasty feedback loop:
With back-to-back packets, every artificial sleep directly reduces throughput.
The optimized path instead behaves approximately like:
packet arrives
|
productive READ
|
immediate READ
|
productive READ
|
immediate READ
|
...
|
empty READs
|
return to idle cadence
The cost is temporarily higher CPU usage, so the driver exposes parameters such as poll_ms, poll0_ms, fast_hold, and tx_burst.
Measuring the bridge separately changed the diagnosis
The most useful benchmark was not an Ethernet benchmark.
It was block I/O through the same bridge.
Using the raw bridge with a SCSI Jaz drive produced approximately:
block read: ~2.7 Mbit/s
block write: ~2.6 Mbit/s
That was several times faster than the DaynaPORT network download result.
This immediately showed that the USB bridge itself had significant headroom.
The network RX path was being limited somewhere else.
That is exactly why end-to-end benchmarking alone can be misleading. If I had only measured iperf3, it would have been easy to blame USB chunking for everything.
Increasing the USB buffer to 4096 bytes
A later experiment increased the TinyUSB MSC buffer from 512 bytes to 4096 bytes.
That changed block-read performance significantly.
The benchmark notes show approximately:
512-byte era:
block read ~2.7 Mbit/s
4096-byte buffer:
block read ~5.35 Mbit/s
A full Ethernet frame could now fit into one bridge read instead of being split across up to three 512-byte transactions.
But something else was revealing:
The Jaz drive itself was now the write-side limit.
And the expected DaynaPORT network improvement was small, because the Wi-Fi inbound path was already the tighter bottleneck.
That is the kind of result I like in low-level projects: changing a parameter makes one subsystem dramatically faster while proving that another subsystem was never limited by that parameter in the first place.
From Linux to Windows
Once the DaynaPORT protocol logic had been separated from the Linux kernel glue, porting the concept to Windows became much more realistic.
But "Windows driver" does not mean one architecture.
The repository currently targets four eras.
Windows 10 / 11
Modern Windows uses a two-driver split:
daynaportfn.sys
KMDF companion driver
owns the SCSI-side communication
daynaportnd.sys
NDIS 6 miniport
exposes the network adapter
This keeps SCSI access and NDIS networking separated.
Windows 2000 / XP
The 2000/XP version uses a single NDIS 5.1 miniport.
It talks to the SCSI device through:
Windows NT 4.0
NT 4 follows the same broad idea but targets NDIS 4.0 and the NT-era driver environment.
Windows 95 / 98 / ME
Windows 9x is different again.
The design uses:
The amusing part is that the network protocol itself did not fundamentally change.
The operating-system plumbing changed completely.
One protocol core, decades of driver APIs
Platform-specific driver plumbing stays separate from the protocol core.
The Windows repository vendors the shared DaynaPORT protocol header derived from the Linux project.
That means the project can be viewed as two layers:
+-------------------------------------------+
| OS-specific driver |
| |
| Linux 2.0 / 2.4 / 7.x |
| Win9x / NT4 / 2K-XP / Win10-11 |
+--------------------+----------------------+
|
v
+-------------------------------------------+
| shared DaynaPORT protocol core |
| framing, opcodes, RX parsing, chunking |
+--------------------+----------------------+
|
v
+-------------------------------------------+
| SCSI transport / BlueSCSI |
+-------------------------------------------+
This is probably the most reusable part of the whole exercise.
The protocol is old.
The driver APIs are not.
Keeping those two facts separate makes the code much easier to reason about.
Bridge options on Windows
The Windows side follows the same split between normal DaynaPORT traffic and bridge-specific transport.
The legacy configuration includes tunables for:
UsbBridge selects the USB MSC chunked path.
RawBridge selects the BlueSCSI raw DaynaPORT bridge path, where both directions must respect the bridge transfer limit.
The important point is that the network stack should not care.
It still sends and receives Ethernet frames.
Only the SCSI transport layer knows whether a frame was moved in one command or reconstructed from several.
What the repository history shows
Looking across the repositories, the project did not evolve as one giant "write a driver" commit.
It evolved in layers:
1. Understand the DaynaPORT command set
2. Build a Linux driver around normal SCSI
3. Separate reusable protocol logic
4. Add multi-kernel Linux implementations
5. Add USB-bridge chunking
6. Add diagnostics and benchmark tooling
7. Tune the polling loop
8. Benchmark storage and network paths independently
9. Increase the MSC buffer and measure again
10. Reuse the protocol core for Windows
11. Split Windows support by driver era
This progression matters because several design decisions only became obvious after measurement.
For example, the 512-byte bridge limitation looked like the obvious performance problem.
It was a real limitation — but not the dominant limitation for Wi-Fi receive.
Likewise, making the poll loop faster improved behavior, but only until the device round-trip became the next ceiling.
Current status
The Linux repository is the most mature part of the project.
It contains:
- Linux 2.0.x support
- Linux 2.4.x support
- Linux 7.x support
- shared DaynaPORT protocol code
- USB bridge chunking
- diagnostics
- a benchmark runbook
- protocol documentation
The Windows repository currently contains implementations for:
- Windows 10/11
- Windows 2000/XP
- Windows NT 4.0
- Windows 9x
The Windows 10/11 and 2000/XP code paths are structurally complete in the repository, while the NT4 and 9x trees are currently best treated as completed scaffolding.
Most importantly, the Windows repository still needs real validation with the corresponding Microsoft toolchains and hardware/emulator combinations.
I consider that distinction important: source code that looks complete is not the same thing as a validated driver.
What I learned
A few lessons from this project are useful beyond BlueSCSI.
Storage bridges and packet protocols have different assumptions
A 512-byte transfer buffer is perfectly reasonable for a storage bridge.
It becomes a protocol-design problem when the payload is a 1500-byte Ethernet frame.
Polling behavior can dominate throughput
When there is no receive interrupt, a few milliseconds of unnecessary sleep can matter more than the raw bus bandwidth.
Measure layers independently
iperf3 told me the network was slow.
Block I/O through the same bridge showed that USB itself was much faster.
That changed where I looked for the bottleneck.
Keep protocol code separate from OS APIs
The shared C89 DaynaPORT core made it possible to support Linux kernels separated by decades and then reuse the same logic in Windows drivers with completely different architectures.
Old protocols are often better documented in code than in manuals
The useful details came from several places:
- the original Dayna command reference,
- BlueSCSI/ZuluSCSI firmware,
- PiSCSI,
- existing BSD driver behavior,
- and real benchmark results.
No single source tells the whole story.
Final result
My original motivation was to connect physical SCSI hardware — including the Sony SMO-F551 and Iomega Jaz 1 GB / 2 GB drives — when a suitable vintage SCSI controller was not available. BlueSCSI Ultra provided the starting point for that experiment.
The original DaynaPORT was a SCSI-to-Ethernet adapter designed for computers of the early 1990s.
BlueSCSI already proved that this protocol can be emulated.
The next step was to turn that emulation around and make Linux and Windows machines treat it as a network interface — including when the SCSI traffic itself has to cross another 512-byte USB bridge.
That required more than wrapping READ(6) and WRITE(6) in a network driver.
It required:
- protocol reconstruction,
- SCSI discovery,
- polling and arbitration,
- packet chunking and reassembly,
- cross-kernel Linux work,
- several generations of Windows driver architecture,
- and a lot of benchmarking to determine which bottleneck was actually real.
That is what made the project interesting.
The network protocol is more than thirty years old.
The engineering problems are not.
Repositories
- BlueSCSI v2 experimental branch: https://github.com/avkghost/BlueSCSI-v2/tree/avkghost/birdge-with-daynaport
- Linux DaynaPORT driver: https://github.com/avkghost/daynaport-scsilink-linux-driver
- Linux bridge branch: https://github.com/avkghost/daynaport-scsilink-linux-driver/tree/avkghost/birdge-with-daynaport
- Windows DaynaPORT drivers: https://github.com/avkghost/daynaport-scsilink-windows-driver
- Linux DaynaPORT protocol notes: https://github.com/avkghost/daynaport-scsilink-linux-driver/blob/main/reference/daynaport.md
- Linux bridge benchmark notes: https://github.com/avkghost/daynaport-scsilink-linux-driver/blob/main/reference/benchmark.md