Skip to content

Case Study: A4091 / 53C710 SCSI driver

This is the deep end of the drivers pillar: writing a brand-new SCSI host adapter driver for Amix, for a board the OS was never meant to support, and getting Amix to boot with root, swap and filesystem entirely on it. It pulls together the device-driver model, Zorro AUTOCONFIG, the kernel build and the boot process โ€” all the other driver pages applied at once to a real board.

The work is first-party and reproduced locally in Amiberry on the real Amix 2.1c distribution. Claims tagged โœ… below were reproduced on the running system; ๐ŸŸก claims are emulation-only and need real-hardware confirmation; ๐Ÿ”ด marks a belief we held and disproved. The source is the A4091-on-Amix project โ€” src/a4091-wr.c, the src/kernel-patches/, and the lab notebook NOTES.md.

What the A4091 is

The A4091 is Commodore's Zorro III SCSI-2 host adapter, built on the NCR/Symbios 53C710 SCSI I/O processor โœ…. It is the high-end successor to the Zorro II A2091 (WD33C93), intended for the A3000T/A4000T class.

Its AUTOCONFIG identity and memory window โœ… (src/a4091-wr.c, docs/a4091-53c710-reference.md ยง0):

Property Value Source
AutoConfig manufacturer 0x0202 (Commodore) a4091-wr.c A4091_PROD
AutoConfig product 0x54 a4091-wr.c A4091_PROD
Combined product id 0x02020054 a4091-wr.c #define A4091_PROD 0x02020054
er_Type 0x90 (Zorro III) reference ยง0
Physical board base 0x40000000 a4091-wr.c a4091map() fallback
Board window 0x01000000 (16 MB), sparse reference ยง1
53C710 register block board + 0x00800000 โ†’ 0x40800000 a4091-wr.c SIOP_OFF

Amix (Commodore's SVR4.0 port to 68030 Amigas, the "2.1c" distribution โœ…) shipped with no 53C710 driver and is widely described as Zorro II only โœ… โ€” see Zorro AUTOCONFIG for drivers and Supported hardware. The stock kernel only knows the A2090/A2091 (WD33C93) and the A3000 on-board SCSI. This project wrote a native 53C710 block driver and made the A4091 a first-class, auto-detected SCSI controller alongside them.

The Zorro III obstacle and the sptalloc() solution

The board is in Zorro III space, and that is exactly where the stock drivers' trick breaks down.

๐Ÿ”ด "Zorro III is unaddressable" โ€” was thought absolute, is actually a TT-register gap with a work-around. Amix's 68030 maps physical address space with two Transparent Translation registers (amiga/ml/ttrap.s) โœ…:

tt0 = 0x003F0143   maps phys 0x00000000 - 0x3FFFFFFF
tt1 = 0x807F0143   maps phys 0x80000000 - 0xFFFFFFFF

That leaves 0x40000000 - 0x7FFFFFFF an unmapped gap โ€” and the A4091 sits in it โœ…. Zorro II boards are โ‰ค 24-bit (e.g. the A3000 SCSI at 0xDD0000), so they fall inside TT0; that is why the stock WD33C93 drivers can simply dereference the address autocon() returns. Widening TT0 to cover the gap is unsafe โ€” amiga/ml/syms.s places the page-mapped u-area at 0x40000000 โœ….

The fix is the kernel primitive sptalloc(npages, prot, pfn, flag), which page-table-maps physical pages into kernel virtual address space โœ…. The driver maps the board's AutoConfig page and the 53C710 register block (src/a4091-wr.c, a4091map()):

#define SIOP_OFF    0x00800000  /* 53C710 register block within the board window */
...
acfg = (volatile uchar *)sptalloc( 1, PG_V, phystopfn( (paddr_t)base), 0);          /* board base    */
siop = (volatile uchar *)sptalloc( 1, PG_V, phystopfn( (paddr_t)base + SIOP_OFF), 0); /* 53C710 regs */

Post-boot these return working kernel VAs (observed siop = 0x401CA000) and the 53C710 is fully addressable โœ…. DMA is unaffected: the 53C710 bus-masters to Amix RAM at phys 0x07800000+, which is inside TT0, so DMA targets are reachable no matter where the board's registers land in kernel VA โœ…. (See Zorro AUTOCONFIG for autocon() and the Zorro III detection that complements this mapping.)

The 53C710 driver model โ€” how it plugs into the Amix SCSI stack

โœ… The Amix SCSI stack (amiga/alien/) layers like this:

block disk driver dd.c / gsioctl passthrough  ->  sd.c selector  ->  per-card queue function  ->  chip

The command unit handed down the stack is struct sdcom (sd.h): cdb[12], the data addr/nbyte, unit/card, the reading flag, status/okay, and an intr() completion callback โœ…. See the device-driver model for the general switch-table mechanism this sits on top of.

The card registry in sd.c

sd.c carries a registry table scsicard[] mapping AutoConfig product ids to queue functions. The patch adds one row for the A4091 (src/kernel-patches/sd.c) โœ…:

static struct {
    uint    pn;             /* product number */
    bool    (*f)( );            /* queuing function */
    char    *hardwarename;
} scsicard[] = {
    0x0202F003, &a3091queue, "A3000 Internal SCSI",
    0x02020001, &a2090queue, "A2090 SCSI",
    0x02020003, &a2091queue, "A2091 SCSI",
    0x02020054, &a4091queue, "A4091 SCSI",   /* <- our addition */
};

At the first root open, sd.c init() calls autocon(productid, โ€ฆ) for each row and insert()s the cards it finds, sorted ascending by board base address โœ…:

static void
init( )
{
    if (not queue->f)
        for (i=0; i<nel( scsicard); ++i) {
            int c = 0;
            while (autocon( scsicard[i].pn, c, &a, &o))
                insert( scsicard[i].f, c++, a, scsicard[i].hardwarename);
        }
}

So on a machine with both controllers, the A3000 SCSI (0xDD0000) becomes queue[0] (card 0) and the A4091 (0x40000000) becomes queue[1] (card 1) โ€” the lower base sorts first โœ…. This ordering is the entire story behind booting root from the A4091 โ€” see the boot process: root on an A4091 and the phantom-A3000 fix in Zorro AUTOCONFIG.

The matching build patch is src/kernel-patches/alien-Makefile, which adds a4091.o to the amiga/alien OBJ list.

The driver itself: a4091-wr.c

The production driver is src/a4091-wr.c (the READ+WRITE driver) โœ…. Its key pieces:

  • a4091map() โ€” sptalloc-maps the board base and the register block (above), and caches the mapping so it only happens once (if (siop) return 0). Falls back to the known base 0x40000000 / size 0x01000000 if autocon() does not find the board โœ….
  • a4091init() โ€” a 53C710 soft reset followed by minimal polled-mode register init. Reset is via ISTAT bit 6 (not DCNTL โ€” that is the older 53C700) and is not self-clearing, so it is set then explicitly cleared. The init writes SCNTL0=0xCC, SCNTL1=0x20, DCNTL=0x00, DMODE=0xE0 (burst-8 | FC2, the A4091 value), the one-hot host id SCID = 1 << 7 = 0x80, and the A4091 bus-arbitration errata bit CTEST8 |= 0x01 (SM). No SCSI/DMA interrupts are enabled โœ…. All register writes go to reg + 0x40 (the A4091 write-shadow WRSHADOW), reads at reg + 0; accesses are 8- or 32-bit only, never 16-bit โœ….
  • a4091queue(c, cp) โ€” drives any CDB from the caller's sdcom through a table-indirect 53C710 SCRIPTS program (inq_script[], a static ulong[] that lives in kernel .data, so its bus address equals &inq_script[0] under the TT0 identity mapping). CDB length is derived from the SCSI group code (top 3 bits of the opcode โ†’ 6 / 10 / 12 bytes). The DSA (struct siop_ds) binds IDENTIFY / CDB / status / message / data buffers; the DATA phase dispatches on the live bus phase, so one SCRIPTS program performs both READ(10) and WRITE(10). Data DMAs straight into cp->addr โ€” the caller's buffer, already a physical address via dd.c's vtop() โœ….

The SCRIPTS program's data-phase dispatch (from inq_script[] in src/a4091-wr.c) is what makes one program serve reads and writes:

0x808b0000, 0x00000018,     /* JUMP dataout, WHEN DATA_OUT  (write)         */
0x818b0000, 0x00000020,     /* JUMP datain,  WHEN DATA_IN   (read)          */
0x838b0000, 0x00000020,     /* JUMP status,  WHEN STATUS    (no data)       */

The DSA table binds the caller's buffer for the data move โœ…:

ds.data1len = cp->nbyte;  ds.data1buf = (uchar *)cp->addr;  /* DMA -> caller buf */

Completion: the one honest ๐ŸŸก in the driver

After launching SCRIPTS (a write to R_DSP), the driver reads completion with a single ISTAT read:

WR32( R_DSP, (ulong)inq_script);
/* nopoll completion -- single ISTAT read (emulated target instant) */
istat = RD8(R_ISTAT);
...
else if ((dstat & DSTAT_SIR) && dsps == 0xff00) rc = 0;

The emulated target answers synchronously, so that one read already sees DIP set and dsps = 0xff00 (the SCRIPTS "done" INT vector) โœ…. A physical disk is not instant โ€” real hardware needs a bounded poll loop or interrupt-driven completion ๐ŸŸก. The hooks for that already exist: an empty a4091intr() and the int2_tbl level-2 ISR slot (the A2090/A2091 use the same table โ€” see device list). Keep this ๐ŸŸก until it is run on metal.

For the full 53C710 register map, SCRIPTS encoding, one-hot SELECT-ID encoding, the soft-reset sequence, the write-shadow at reg+0x40, and the emulator's full-window SIGSEGV hazard, see the project's docs/a4091-53c710-reference.md (the verified chip reference behind this driver).

The A4091 disk's /dev numbering

โœ… With both an A3000 and an A4091 present, the A4091 (card 1) target 0 slice 0 is reached at:

/dev/dsk/c8d0s0    block, major 18, minor 8
/dev/rdsk/c8d0s0   char,  major 40, minor 8

The cN in the name is computed, not stored โ€” see the SCSI minor-number scheme on the device list. From amiga/alien/sd.h:

sdunit(dev) = (dev>>0) & 7      /* SCSI target id 0-7        */
sdcard(dev) = (dev>>3) & 1      /* card index (queue[] slot) -- only 0 or 1; SDCARDS = 2 */
sdpart(dev) = (dev>>4) & 7      /* slice/partition           */

So cN = sdcard*8 + sdunit: c8d0s0 = card 1, target 0, slice 0 = makedevice(18, 8); the stock A3000-root device c6d0s1 = card 0, target 6, slice 1 = makedevice(18, 22) โœ…. A dev_t is (major << 18) | minor โœ….

The READ/WRITE path was validated end to end โœ…:

  • Raw SCSI passthrough via the gsioctl userspace path (src/gsio.c, /dev/scsi major 11) โ€” INQUIRY, READ(10), WRITE(10) all returning correct data.
  • mkfs ufs + mount on the A4091 disk, with files written and surviving a reboot (genuine persistence to the disk behind the A4091, not a RAM artefact).

Driver variants โ€” the evolution

Only a4091-wr.c is the production driver; the earlier variants are kept under src/ to show the incremental bring-up path โœ…:

File Stage
a4091.c detection-only (proves sptalloc + register read)
a4091-nopoll.c INQUIRY only
a4091-blk.c READ-only block device
a4091-wr.c READ + WRITE โ€” production
a4091-wr-dbg.c same as production + A4: printf diagnostics (used to trace the early-boot path)

Build & install

The driver is a standard kernel-relink job (kernel build & install), with the A4091-specific steps:

  1. Drop the driver src/a4091-wr.c into the kernel tree as /usr/sys/amiga/alien/a4091.c.
  2. Register it in the Makefile โ€” add a4091.o to the amiga/alien OBJ list (src/kernel-patches/alien-Makefile).
  3. Apply the SCSI-stack patch โ€” the scsicard[] row in sd.c (src/kernel-patches/sd.c).
  4. Apply the auto-detect patch โ€” the chipset-gated autocon() in support.c (src/kernel-patches/support.c); this is what lets one kernel boot both an A3000 and an A4000+A4091. The why is on Zorro AUTOCONFIG and the boot process.
  5. Build with the clean-gate. The Amix ld intermittently corrupts the linked kernel (the "D245 boot-breaker"); relink until the checksum recurs rather than trusting a single make. Use tools/build-clean-kernel.sh and verify with tools/relsim.py / checkunix.c โ€” full detail on kernel build & install. Never make install an unverified kernel onto your working disk โ€” it rewrites the boot partition and bricks it if the kernel is corrupt.
  6. Install carefully onto a throwaway / clone disk โ€” see adding drivers to a custom boot disk and the "boot root on the A4091" install recipe on the boot process.

The SCRIPTS source (reference/scripts/inq-wr.ss) is assembled with the NetBSD ncr53cxxx assembler from the open-source a4091.device project, which also provides the A4091 autoboot ROM used by the emulator (Amix on Amiberry).

Status

Claim Status
A4091/53C710 driver detects, reads and writes; root + swap + filesystem on the A4091; multi-user, networking โœ… emulation-proven (Amiberry, real Amix 2.1c)
One universal kernel boots both A3000(ECS) and A4000(AGA) + A4091 โœ… emulation-proven, one binary
Bounded-poll / interrupt-driven completion (real disks are not instant) ๐ŸŸก pending โ€” single-ISTAT completion is an emulation shortcut
Real-hardware validation on a physical A3000 and A4000 + A4091 ๐ŸŸก pending

See also

  • Zorro AUTOCONFIG for drivers โ€” autocon(), the Zorro III / sptalloc question, and the chipset-gated auto-detection that registers the A3000 SCSI only when present.
  • Building & installing a kernel โ€” the make โ†’ relocunix โ†’ make bootpart flow and the D245 clean-gate that this driver's build depends on.
  • The boot process โ€” booting Amix with root on the A4091, and the rootdev โ†” card-index device numbering.
  • Device list โ€” the SCSI block/char major numbers and the card/target/slice minor scheme.
  • Supported hardware & requirements โ€” the Zorro II / Zorro III rules the A4091 work pushes against.
  • Amix on Amiberry โ€” the A4091-in-Amiberry config and the A3000(ECS) vs A4000(AGA) Kickstart finding.
  • The Amix device-driver model โ€” the switch-table mechanism underneath the SCSI stack.

Sources

  • The A4091-on-Amix project โ€” NOTES.md ยง1โ€“ยง20 (lab notebook, reproduced locally โœ…) and src//tools/.
  • src/a4091-wr.c โ€” the 53C710 READ+WRITE driver (A4091_PROD 0x02020054, SIOP_OFF 0x00800000, sptalloc() mapping, inq_script[], a4091queue()); SCRIPTS from the a4091-software repo's ncr53cxxx source.
  • src/kernel-patches/{sd.c,support.c,alien-Makefile} โ€” the kernel diffs (the scsicard[] row, the chipset-gated autocon(), the a4091.o OBJ entry).
  • docs/a4091-53c710-reference.md in the project repo โ€” the verified 53C710 register/SCRIPTS detail behind this driver.
  • a4091.device open-source project: https://github.com/A4091/a4091-software (A4091 ROM + ncr53cxxx SCRIPTS assembler).