Autoconfig and more details about expansion library

This commit is contained in:
Ilia Sharin 2026-04-30 09:00:24 -04:00
parent b01763982e
commit f19e3f16b5
5 changed files with 1293 additions and 40 deletions

View file

@ -8,6 +8,7 @@
- [address_space.md](address_space.md) — The 24-bit and 32-bit Amiga memory maps
- [memory_types.md](memory_types.md) — Chip RAM, Fast RAM, Slow RAM, and Ranger RAM
- [zorro_bus.md](zorro_bus.md) — Zorro II/III expansion architecture and PCI bridges
- [autoconfig.md](autoconfig.md) — AutoConfig discovery protocol: CFGIN/CFGOUT chain, ExpansionRom, board enumeration
- [cia_chips.md](cia_chips.md) — Complex Interface Adapters (8520): Timers, Parallel, Serial
- [gayle_ide_pcmcia.md](gayle_ide_pcmcia.md) — Gayle system controller: IDE and PCMCIA logic
- [floppy_hardware.md](floppy_hardware.md) — MFM encoding, disk controller, and drive mechanics

View file

@ -0,0 +1,662 @@
[← Home](../../README.md) · [Hardware](../README.md)
# AutoConfig Protocol — Zorro Expansion Discovery & Configuration
## Overview
In 1985, installing an expansion card meant reading a manual, setting DIP switches, and praying you didn't pick an address that collided with something else. The Amiga eliminated all of that with **AutoConfig** — a hardware-level plug-and-play protocol that let the OS discover, size, and map expansion boards without a single jumper. It predated PCI's configuration space by nearly a decade, and it worked with nothing more than a few daisy-chained logic signals and a 256-byte ROM on each card.
AutoConfig runs during early boot, before DOS exists. The Kickstart ROM walks the **CFGIN/CFGOUT** chain, reads each board's identity from a fixed configuration address, assigns it a base address in the Amiga memory map, and tells the board to relocate. Once configured, a board stops responding at the configuration address and appears at its assigned location. The entire process is deterministic, requires no user intervention, and is one of the reasons the Amiga expansion ecosystem remained compatible across three chipset generations.
See [Zorro Bus](zorro_bus.md) for electrical bus specifications and [expansion.library](../../11_libraries/expansion.md) for the software API that queries configured boards at runtime.
---
## Architecture / How It Works
### The CFGIN/CFGOUT Daisy Chain
AutoConfig relies on two active-low signals — **/CFGIN** and **/CFGOUT** — routed sequentially through every Zorro slot. Unlike the data and address buses which run in parallel to all slots, the configuration signals form a strict physical daisy chain. The chain starts at the motherboard and passes through each slot sequentially.
```mermaid
flowchart LR
MB["Motherboard<br/>/CFGIN=GND"] --> S1["Slot 1"]
S1 -- /CFGOUT --> S2["Slot 2"]
S2 -- /CFGOUT --> S3["Slot 3"]
S3 -- /CFGOUT --> END["(Unconnected / Pull-up)"]
style MB fill:#e8f4fd,stroke:#2196f3
style S1 fill:#fff9c4,stroke:#f9a825
style S2 fill:#fff9c4,stroke:#f9a825
style S3 fill:#fff9c4,stroke:#f9a825
```
The protocol depends on a rigid behavioral contract from the expansion boards:
1. **Sleep Mode:** If `/CFGIN` is not asserted, the board is entirely "invisible". It does not decode addresses and completely ignores the configuration space. This prevents bus collisions from multiple boards.
2. **Awake Mode:** When `/CFGIN` is asserted, the board wakes up and responds **only** at the configuration address (`$E80000` or `$FF000000`).
3. **Configured Mode:** Once the OS writes the final nibble of the assigned base address (or writes to `ec_Shutup`), the board stops responding at the configuration address, moves to its new assigned location, and asserts its `/CFGOUT` signal.
4. **Next Board:** The asserted `/CFGOUT` feeds into the `/CFGIN` of the next slot, waking up the next board in the chain.
### Corner Cases: Empty Slots, Risers, and Broken Hardware
**1. The "Empty Slot" Problem:**
In pure theory, an empty slot would break the daisy chain because the signal wouldn't pass from `/CFGIN` to `/CFGOUT`. However, official Commodore motherboards (A2000, A3000, A4000) implement a hardware bypass. They route these signals through specific pins and use pull-ups or pass-through logic (sometimes utilizing the `/SLAVEN` signal or motherboard-level pull-ups). As a result, an empty slot acts transparently, passing the `/CFGIN` signal downstream automatically. You do *not* strictly need to populate slots sequentially on official hardware.
**2. Third-Party Backplanes and Risers:**
Unlike official hardware, some third-party backplanes, busboards (like early tower conversion kits), and Zorro risers did not faithfully recreate this bypass logic. On these systems, an empty slot physically severs the `/CFGIN` to `/CFGOUT` path. In such environments, **cards must be installed sequentially without skipping slots**, starting from the slot nearest the source of the chain, to ensure AutoConfig enumerates all devices.
**3. The "Broken CFGOUT" Hardware Bug:**
A notorious issue in the Amiga hardware ecosystem is expansion cards with faulty PAL/GAL logic or buggy firmware that fail to properly assert `/CFGOUT` after being configured. Even on an official motherboard with perfect slot-bypass logic, a card with a broken `/CFGOUT` will permanently halt the chain, rendering all downstream boards invisible to the OS.
> [!TIP]
> **Workaround:** If you have multiple boards and suspect one is terminating the chain prematurely, **place the suspected broken card in the very last physical slot of the chain**. Because it is the last board, it doesn't matter if it fails to assert `/CFGOUT`, and all properly functioning boards upstream will configure successfully.
### Configuration Address Spaces
Before a board is configured, it responds at a special "configuration" address. After configuration, it relocates to its assigned base address and stops responding at the config address.
| Bus | Config Address | Accessible Region | Notes |
|---|---|---|---|
| **Zorro II** | `$E80000` | `$E80000$E8007F` | 128 bytes; even addresses only |
| **Zorro III** | `$FF000000` | `$FF000000$FF0000FF` | 256 bytes; 32-bit wide |
> [!NOTE]
> Zorro II AutoConfig reads use **only even addresses** (`$E80000`, `$E80002`, ...). The hardware ignores odd-byte access. Zorro III uses full 32-bit access at `$FF000000`.
### The Four-Phase Configuration Sequence
```mermaid
sequenceDiagram
participant ROM as Kickstart ROM
participant SLOT as Active Slot (/CFGOUT held)
participant NEXT as Downstream Slots
ROM->>SLOT: Read config space<br/>($E80000 or $FF000000)
SLOT-->>ROM: er_Type, er_Product, er_Flags
ROM->>SLOT: Read er_Manufacturer, er_SerialNumber
SLOT-->>ROM: 16-bit mfr + 32-bit serial
ROM->>ROM: Compute size from size code<br/>Determine base address
alt Address available
ROM->>SLOT: Write base address via ec_BaseAddress
Note over SLOT: Board latches address,<br/>relocates, asserts /CFGOUT
else No address space left
ROM->>SLOT: Write ec_Shutup
Note over SLOT: Board goes silent,<br/>asserts /CFGOUT
end
SLOT-->>NEXT: /CFGOUT now passes through
ROM->>NEXT: Repeat for next board...
```
**Phase 1 — Discovery:** The OS reads `er_Type`, `er_Product`, and `er_Flags` from the configuration address. This tells it the bus type (Zorro II or III), whether the board is RAM or I/O, and whether a DiagArea ROM exists.
**Phase 2 — Identification:** The OS reads `er_Manufacturer` (16-bit) and `er_SerialNumber` (32-bit) to uniquely identify the board.
**Phase 3 — Sizing & Assignment:** The lower nibble of `er_Type` encodes the board size. The OS computes the required address space and calls `AllocAbs()` or `ConfigBoard()` to reserve a non-overlapping region.
**Phase 4 — Latch & Relocate (or Shut-Up):** If an address is available, the OS writes the assigned base address back into the configuration space. The board latches it, removes itself from the config address, and releases `/CFGOUT` so the next board in the chain can be configured. If no contiguous region of the required size remains, the OS writes to `ec_Shutup` instead — the board goes permanently silent and the chain advances.
---
## Data Structures & Register Tables
### The Nibble-Pair Format & Logical Inversion
The configuration data is logically simple, but its physical presentation on the bus is deeply tied to retro hardware constraints. To keep expansion boards cheap (allowing the use of standard 4-bit PROMs or basic diode/resistor arrays), the 16 bytes of logical configuration data are spread across 64 bytes of physical address space.
- **Nibble Access:** Each logical byte is constructed from two separate read cycles. The hardware only presents data on the upper four data lines (`D7D4`). The software must read the high nibble at a physical `offset`, read the low nibble at `offset + 2`, and merge them:
```c
/* Reading a single logical byte from physical config space */
UBYTE high_nibble = *(volatile UBYTE *)(config_base + offset);
UBYTE low_nibble = *(volatile UBYTE *)(config_base + offset + 2);
UBYTE byte_val = (high_nibble & 0xF0) | ((low_nibble >> 4) & 0x0F);
```
- **The Inversion Sanity Check:** With the exception of the very first byte (`er_Type`), all fields in the configuration ROM are stored **logically inverted** (XOR `$FF`). If the expansion slot is completely empty, the open bus floats high, causing a read to return `$FF`. By inverting this, the OS reads `$00`. Since an inverted `$00` in the size field signifies "no more boards", the OS instantly knows the chain has ended. This clever design prevents bus noise from being interpreted as a ghost board.
### ExpansionRom Logical Layout
The `struct ExpansionRom` represents the unpacked, logical data after Kickstart has read the nibbles and inverted the values.
| Logical Offset | Name | Width | Description |
|---|---|---|---|
| `$00` | `er_Type` | Byte | Board type, size code, flags (Only field NOT inverted in ROM) |
| `$01` | `er_Product` | Byte | Product number (0255) |
| `$02` | `er_Flags` | Byte | Memory/shutup/chain flags |
| `$03` | `er_Reserved03` | Byte | Reserved |
| `$04` | `er_Manufacturer` | Word | Manufacturer ID (16-bit, big-endian) |
| `$06` | `er_SerialNumber` | Long | 32-bit serial number |
| `$0A` | `er_InitDiagVec` | Word | Offset to DiagArea (if valid) |
| `$0C` | `er_Reserved0c` | Long | Reserved |
| `$10` | `er_Reserved10` | Long | Reserved |
*(Note: Physical registers like `ec_BaseAddress` and `ec_Shutup` exist in the `$40$4F` physical offset range, written as nibbles by the OS during configuration, and are not part of the read-only logical structure.)*
### Physical-to-Logical Address Map (Zorro II)
For reference, the full mapping from physical even-byte offsets in the `$E80000` window to logical `ExpansionRom` fields:
| Physical Offset(s) | Nibble | Logical Field |
|---|---|---|
| `$00`, `$02` | High, Low | `er_Type` (NOT inverted) |
| `$04`, `$06` | High, Low | `er_Product` (inverted) |
| `$08`, `$0A` | High, Low | `er_Flags` (inverted) |
| `$0C`, `$0E` | High, Low | `er_Reserved03` (inverted) |
| `$10`, `$12` | High, Low | `er_Manufacturer` high byte (inverted) |
| `$14`, `$16` | High, Low | `er_Manufacturer` low byte (inverted) |
| `$18`, `$1A` | High, Low | `er_SerialNumber` byte 3 (inverted) |
| `$1C`, `$1E` | High, Low | `er_SerialNumber` byte 2 (inverted) |
| `$20`, `$22` | High, Low | `er_SerialNumber` byte 1 (inverted) |
| `$24`, `$26` | High, Low | `er_SerialNumber` byte 0 (inverted) |
| `$28`, `$2A` | High, Low | `er_InitDiagVec` high byte (inverted) |
| `$2C`, `$2E` | High, Low | `er_InitDiagVec` low byte (inverted) |
| `$40``$4E` | — | **Write-only:** `ec_BaseAddress` (written by OS) |
| `$4C` | — | **Write-only:** `ec_Shutup` (silences board) |
```c
/* libraries/configregs.h — NDK39 */
struct ExpansionRom {
UBYTE er_Type; /* board type + size code */
UBYTE er_Product; /* product number (0-255) */
UBYTE er_Flags; /* capability flags */
UBYTE er_Reserved03; /* must be zero */
UWORD er_Manufacturer; /* manufacturer ID (16-bit, big-endian) */
ULONG er_SerialNumber; /* unique serial number */
UWORD er_InitDiagVec; /* offset to DiagArea boot ROM */
APTR er_Reserved0c; /* reserved */
APTR er_Reserved10; /* reserved */
};
```
### er_Type Bit Encoding
| Bits | Field | Values |
|---|---|---|
| `7:6` | Bus type | `11` = Zorro II, `10` = Zorro III, `01` = reserved, `00` = reserved |
| `5` | Memory flag | `1` = board is RAM (add to free mem list if bit set in er_Flags), `0` = I/O board |
| `4` | Chain bit | `1` = another board follows in this slot (chained config), `0` = last board |
| `3:0` | Size code | See size code table below |
### Size Code Table
The lower nibble of `er_Type` encodes the board size. The same code means different things on Zorro II vs Zorro III.
| Code | Zorro II Size | Zorro III Size |
|---|---|---|
| `$0` | 8 MB | 16 MB |
| `$1` | 64 KB | 32 MB |
| `$2` | 128 KB | 64 MB |
| `$3` | 256 KB | 128 MB |
| `$4` | 512 KB | 256 MB |
| `$5` | 1 MB | 512 MB |
| `$6` | 2 MB | 1 GB |
| `$7` | 4 MB | — |
| `$8-$F` | Reserved / extended | Reserved / extended |
### er_Flags Bit Encoding
| Bit | Name | Description |
|---|---|---|
| `7` | `ERFB_SHUTUP` | Board supports "shut up" — can be disabled and mapped out |
| `6` | Reserved | Must be zero |
| `5` | `ERFB_MEMLIST` | If set, board memory is added to the system free memory list |
| `4` | `ERFB_DIAGVALID` | `er_InitDiagVec` points to a valid DiagArea structure |
| `3` | `ERFB_CHAINEDCONFIG` | More boards to configure in this slot (chained) |
| `2:0` | Reserved | Must be zero |
---
## The Software Boot Sequence: expansion.library
While the hardware daisy-chain dictates *how* boards respond, the actual enumeration is driven entirely by software during the earliest stages of the AmigaOS boot process. This is the responsibility of `expansion.library`, which is initialized by `exec.library` as one of the very first resident modules.
### Boot Sequence Position
AutoConfig does not run "whenever" — its position in the Kickstart init sequence is fixed and critical:
```
1. CPU Reset → Kickstart ROM entry point
2. exec.library initializes (memory lists, interrupts, task scheduler)
3. Resident module scan — expansion.library is initialized (priority 110)
4. expansion.library runs ConfigChain() → enumerates all Zorro boards
5. DiagArea boot ROMs execute (SCSI controllers install device handlers here)
6. dos.library initializes, mounts DOS devices
7. Boot from highest-priority bootable device
```
> [!NOTE]
> Because AutoConfig runs *before* DOS, drivers that need to be present at boot time (SCSI, network boot) must use the DiagArea mechanism — they cannot be loaded from disk. See [DiagArea](#diagarea--boot-roms) below.
### Enumeration Loop
When the `expansion` module initializes, it executes a tight loop probing the configuration spaces (`$E80000` for Zorro II, `$FF000000` for Zorro III). Because the system doesn't know how many boards exist, it relies on a purely reactive polling loop:
```c
void ConfigChain(APTR config_base, APTR memory_pool) {
while (TRUE) {
/* Read physical $00 and $02, merge to get er_Type */
UBYTE er_Type = ReadExpansionRom(config_base, 0x00);
/* If bits 7-6 are 00, or the bus floats to $FF, the chain is done */
if ((er_Type & ERT_TYPEMASK) == 0 || er_Type == 0xFF) {
break;
}
/* Read the remaining logical bytes, applying XOR 0xFF inversion */
struct ExpansionRom rom;
rom.er_Type = er_Type;
rom.er_Product = ReadExpansionRom(config_base, 0x04) ^ 0xFF;
rom.er_Flags = ReadExpansionRom(config_base, 0x08) ^ 0xFF;
/* ... read Manufacturer, Serial, etc ... */
/* Allocate Base Address */
ULONG size = ComputeSize(er_Type);
APTR base_addr = AllocateFromPool(memory_pool, size);
if (base_addr) {
/* Latch the base address by writing nibbles to ec_BaseAddress */
WriteExpansionBase(config_base, base_addr);
/* Board moves to base_addr, asserts /CFGOUT */
CreateConfigDevNode(&rom, base_addr, size);
} else {
/* Out of memory! Write to ec_Shutup ($4C) to silence board */
WriteExpansionBase(config_base, E_SHUTUP);
}
}
}
```
The system requires no interrupt or handshake signal from the board. Once the base address is written to the board's latch, the hardware instantly updates its decoders and asserts `/CFGOUT`. The very next iteration of the `while (TRUE)` loop simply reads `$E80000` again, and magically, the next board in the chain is sitting there waiting to be configured.
### DiagArea & Boot ROMs
If `er_Flags` has the `ERFB_DIAGVALID` bit set, the board carries a **DiagArea** — an on-board ROM structure containing executable code that runs during AutoConfig, *before* DOS is available. The `er_InitDiagVec` field gives the byte offset from the board's base address to the `DiagArea` structure:
```c
struct DiagArea {
UBYTE da_Config; /* DAC_WORDWIDE, DAC_BYTEWIDE, DAC_NIBBLEWIDE */
UBYTE da_Flags; /* DAC_CONFIGTIME or DAC_BINDTIME */
UWORD da_Size; /* total size of DiagArea in bytes */
UWORD da_DiagPoint; /* offset to diagnostic routine (optional) */
UWORD da_BootPoint; /* offset to boot code (optional) */
char da_Name[]; /* NUL-terminated handler name (e.g. "scsi.device") */
};
```
`da_Flags` controls *when* the code runs:
- **`DAC_CONFIGTIME`** — The diagnostic/boot code runs immediately during the `ConfigChain()` pass, while AutoConfig is still in progress. Used by SCSI controllers that need to install their `exec.device` handler before DOS mounts volumes.
- **`DAC_BINDTIME`** — The code runs later, after all boards are configured. Used by boards that depend on other hardware being present first.
DiagArea is the mechanism behind bootable SCSI controllers (A2091, GVP Series II), network boot ROMs, and accelerator patches. Without it, there would be no way to boot from expansion hardware since `dos.library` hasn't loaded any filesystem drivers yet.
See [expansion.library — DiagArea](../../11_libraries/expansion.md#diagarea--boot-roms-on-expansion-boards) for the full struct reference and FPGA implementation notes.
---
## API Reference
After configuration, the OS builds a linked list of `ConfigDev` structures. Drivers and applications query this list via `expansion.library`.
### struct ConfigDev
```c
/* libraries/configvars.h — NDK39 */
struct ConfigDev {
struct Node cd_Node;
UBYTE cd_Flags; /* see cd_Flags table below */
UBYTE cd_Pad;
struct ExpansionRom cd_Rom; /* copy of AutoConfig ROM data */
APTR cd_BoardAddr; /* assigned base address */
ULONG cd_BoardSize; /* board size in bytes */
UWORD cd_SlotAddr; /* slot address (config space) */
UWORD cd_SlotSize;
APTR cd_Driver; /* driver bound to this board */
struct ConfigDev *cd_NextCD; /* next ConfigDev in chain */
ULONG cd_Unused[4]; /* reserved */
};
```
### cd_Flags Values
| Flag | Value | Description |
|---|---|---|
| `CDF_SHUTUP` | `$01` | Board has been shut up (disabled via `ec_Shutup`) |
| `CDF_CONFIGME` | `$02` | Board needs a driver — set by the OS during AutoConfig, cleared when a driver claims it via `ConfigBoard()` |
| `CDF_BADMEMORY` | `$04` | Board memory failed diagnostic and should not be added to the free pool |
`CDF_CONFIGME` is particularly important for driver authors: it tells you the board has been discovered and address-mapped by AutoConfig but no driver has claimed it yet. A well-behaved driver checks this flag, performs its initialization, then clears it.
### Key Functions — Runtime API
```c
/* Find a configured board by manufacturer and product */
struct ConfigDev *FindConfigDev(
struct ConfigDev *oldConfigDev, /* NULL to start, previous cd to continue */
LONG manufacturer, /* -1 for wildcard */
LONG product /* -1 for wildcard */
);
/* LVO -72 */
```
```c
/* Bind a driver to a ConfigDev node */
void ConfigBoard(
APTR boardAddr,
struct ConfigDev *configDev
);
/* LVO -48 */
```
### Key Functions — Low-Level Primitives (Kickstart Internal)
These are the actual primitives used by `ConfigChain()` during the boot scan. They are exported by `expansion.library` but are not intended for application use:
```c
/* Read a board's ExpansionRom structure from the config address space.
* Performs the nibble-pair reads and applies XOR $FF inversion.
* Populates the provided ExpansionRom struct. */
BOOL ReadExpansionRom(
APTR board, /* config base address ($E80000 or $FF000000) */
struct ConfigDev *configDev /* output: populated with ROM data */
);
/* LVO -96 */
```
```c
/* Write the assigned base address to the board's ec_BaseAddress register.
* Performs the nibble-pair writes that latch the address and cause the
* board to relocate and assert /CFGOUT. */
void WriteExpansionBase(
APTR board, /* config base address */
ULONG base /* assigned base address */
);
/* LVO -102 */
```
---
## Decision Guides
### RAM Board vs I/O Board Handling
| Aspect | RAM Board | I/O Board |
|---|---|---|
| `er_Type` bit 5 | `1` | `0` |
| `er_Flags` bit 5 | Must be `1` to add to system memory | N/A |
| Z2 address pool | `$200000$9FFFFF` (8 MB expansion RAM region) | `$E90000$EFFFFF` (448 KB I/O region) |
| Z3 address pool | `$10000000+` | `$10000000+` (shared pool) |
| OS action | Calls `AllocAbs()` then adds to MemList | Calls `AllocAbs()` only; driver must claim |
| DiagArea | Optional | Common (SCSI controllers use it for boot ROM) |
> [!NOTE]
> On Zorro II, the RAM and I/O address pools are disjoint. Memory boards are placed in `$200000$9FFFFF`, while I/O boards are placed in `$E90000$EFFFFF`. On Zorro III, both types share a common pool starting at `$10000000`.
### Zorro II vs Zorro III Configuration Differences
| Feature | Zorro II | Zorro III |
|---|---|---|
| Config address | `$E80000` | `$FF000000` |
| Access width | 8-bit even addresses only | 32-bit |
| Max board size | 8 MB | 1 GB |
| Address auto-sizing | No | Yes (board can report actual size < max) |
| Burst mode | No | Yes (must be negotiated) |
| `/CFGIN`/`/CFGOUT` | Same daisy-chain logic | Same daisy-chain logic |
---
## Historical Context & Modern Analogies
### Competitive Landscape (19851994)
| Platform | Expansion Configuration | User Intervention |
|---|---|---|
| **Amiga** | AutoConfig (hardware ROM + daisy chain) | None |
| **Atari ST** | No standard; cards used jumpers or hard-coded addresses | Manual DIP switches |
| **IBM PC/AT** | ISA bus: fixed IRQ/DMA/address jumpers | Manual configuration |
| **Apple Macintosh** | NuBus: software-configurable, but required system tools | Tools-based setup |
| **PCI (introduced 1992)** | Configuration space registers, software enumeration | None |
AutoConfig was genuinely ahead of its time. The NuBus used in the Macintosh was software-configurable but required a separate setup utility. ISA cards were a nightmare of IRQ and address conflicts. AutoConfig solved both problems in hardware with no software utility required.
### Modern Analogies
| AutoConfig Concept | Modern Equivalent | Notes |
|---|---|---|
| CFGIN/CFGOUT chain | PCI bus enumeration order | PCI uses configuration cycles rather than a physical chain |
| `$E80000` config space | PCI Configuration Space (`0xCF8/0xCFC`) | PCI config space is 256 bytes per function |
| `er_Manufacturer` + `er_Product` | PCI Vendor ID + Device ID | Same 16-bit manufacturer / 16-bit device pattern |
| `er_SerialNumber` | PCI Subsystem ID + serial | Less common in PCI; used for unique board tracking |
| DiagArea boot ROM | PCI Option ROM (Expansion ROM) | Both contain x86 or 68k boot code loaded by firmware |
| Shut-up mechanism | PCI BAR disable / bus mastering off | PCI can disable regions via command register |
| `FindConfigDev()` | `pci_find_device()`, `SetupDiEnumDeviceInterfaces()` | Same discovery-by-ID pattern |
The analogy breaks down on two points. First, PCI is software-enumerated via configuration cycles on a shared bus; AutoConfig uses a **physical daisy chain** where electrical signals gate access. Second, PCI supports multifunction devices and bridges natively; AutoConfig handles multi-board slots via the **chain bit** and sequential configuration of the same slot.
---
## Practical Examples
### Walking the ConfigDev Chain
```c
#include <exec/types.h>
#include <exec/libraries.h>
#include <libraries/expansion.h>
#include <libraries/configvars.h>
#include <clib/expansion_protos.h>
#include <stdio.h>
void list_expansion_boards(void)
{
struct Library *ExpansionBase = OpenLibrary("expansion.library", 0);
if (!ExpansionBase) {
printf("Failed to open expansion.library\n");
return;
}
struct ConfigDev *cd = NULL;
while ((cd = FindConfigDev(cd, -1, -1)) != NULL)
{
printf("Board at $%08lx, size %lu bytes\n",
(ULONG)cd->cd_BoardAddr, cd->cd_BoardSize);
printf(" Manufacturer: %u, Product: %u\n",
cd->cd_Rom.er_Manufacturer, cd->cd_Rom.er_Product);
printf(" Serial: %lu\n", cd->cd_Rom.er_SerialNumber);
/* Decode board type */
UBYTE type = cd->cd_Rom.er_Type;
printf(" Bus: %s, ", (type & ERT_TYPEMASK) == ERT_ZORROIII
? "Zorro III" : "Zorro II");
printf("Type: %s\n", (type & ERTF_MEMLIST) ? "RAM" : "I/O");
/* Check for DiagArea */
if (cd->cd_Rom.er_Flags & ERFB_DIAGVALID) {
printf(" Has DiagArea/boot ROM\n");
}
printf("\n");
}
CloseLibrary(ExpansionBase);
}
```
### Finding a Specific Board
```c
#include <libraries/expansion.h>
#define MANUF_INDIVIDUAL_COMPUTERS 2167
#define PROD_BUDDHA 1
struct ConfigDev *find_buddha(void)
{
struct Library *ExpansionBase = OpenLibrary("expansion.library", 0);
if (!ExpansionBase) return NULL;
struct ConfigDev *cd = FindConfigDev(NULL,
MANUF_INDIVIDUAL_COMPUTERS, PROD_BUDDHA);
/* cd is NULL if no Buddha card is installed */
if (cd) {
printf("Buddha IDE controller at $%08lx\n",
(ULONG)cd->cd_BoardAddr);
}
CloseLibrary(ExpansionBase);
return cd;
}
```
---
## Hardware That Does NOT Use AutoConfig
AutoConfig is specifically a **Zorro bus protocol**. Expansion hardware that is not on the Zorro bus uses fixed addressing or its own discovery mechanisms:
| Hardware | Address/Mechanism | Why No AutoConfig |
|---|---|---|
| **A500 Trapdoor 512 KB** | Hardcoded at `$C00000` | Not on Zorro bus — directly wired to Agnus/Gary |
| **A600/A1200 PCMCIA** | CIS (Card Information Structure) | PCMCIA has its own plug-and-play standard |
| **CPU slot accelerators** | Fixed addresses on local bus | CPU slot is a direct processor bus extension, not Zorro |
| **A1200 trapdoor (Blizzard, Apollo)** | Fixed local bus addresses | Direct connection to CPU local bus |
| **A1200 Clock Port** | `$D80001` (fixed register port) | Simple I/O register, no discovery protocol |
| **Chipset peripherals (Paula, CIAs)** | `$BFD000`/`$BFE001`/`$DFF000` | Built into the motherboard at fixed addresses |
| **Bridgeboard (PC side)** | ISA bus with own IRQ/DMA | Amiga side uses AutoConfig; ISA side uses PC conventions |
> [!NOTE]
> Some accelerators straddle both worlds: the CPU and MMU live at fixed addresses on the CPU slot, but the accelerator's **Fast RAM** often appears as an AutoConfig board on the Zorro bus so it integrates cleanly with the system memory pool.
---
## When to Use / When NOT to Use
### When to Use AutoConfig Knowledge
- **Writing a Zorro expansion card driver** — you must understand how the OS discovered and mapped your hardware.
- **Building an FPGA core** — MiSTer and other FPGA implementations must present valid AutoConfig data or the Amiga OS will ignore the emulated hardware.
- **Reverse engineering an accelerator or RTG card** — the `ConfigDev` chain reveals what hardware is present and where it lives.
- **Debugging boot failures** — a misbehaving expansion card can stall the `/CFGIN`/`/CFGOUT` chain and freeze the machine before video appears.
### When NOT to Use (Direct Hardware Access)
- **Application code** — never poke `$E80000` directly. Always use `expansion.library` and `FindConfigDev()` after boot.
- **Driver initialization** — drivers receive their `ConfigDev` via the DiagArea boot vector or are bound by `ConfigBoard()`. Do not re-scan the bus.
- **User tools**`AvailMem()` and `exec.library` already report expansion RAM. Direct AutoConfig access is only for board-specific drivers.
---
## Best Practices & Antipatterns
### Best Practices
1. Always use `FindConfigDev()` rather than hard-coding addresses — boards can appear at different bases depending on slot order.
2. Check `cd_BoardSize` before accessing memory beyond the reported region.
3. Respect the `ERFB_SHUTUP` flag — if a board supports shut-up, your driver should handle disable requests gracefully.
4. Use `er_SerialNumber` for license key binding or unique board tracking.
5. For multi-board drivers, walk the entire chain with `-1` wildcards and filter by manufacturer/product.
### Antipatterns
**The Hardcoded Hunter:** Searching for a board at a fixed address instead of using `FindConfigDev()`.
```c
/* BAD: Assumes board is always at $400000 */
struct MyBoard *board = (struct MyBoard *)0x400000;
/* GOOD: Ask the OS where it put the board */
struct ConfigDev *cd = FindConfigDev(NULL, MANUF_ID, PROD_ID);
struct MyBoard *board = (struct MyBoard *)cd->cd_BoardAddr;
```
**The Size Code Optimist:** Trusting the size code without verifying `cd_BoardSize`.
```c
/* BAD: Assumes 2 MB because the product is known */
#define ASSUMED_SIZE (2 * 1024 * 1024)
/* GOOD: Use the actual configured size */
ULONG actualSize = cd->cd_BoardSize;
```
---
## Pitfalls & Common Mistakes
### 1. Odd-Byte Access on Zorro II Config Space
Zorro II AutoConfig reads must use **even addresses only**. Reading `$E80001` returns undefined data and may confuse some hardware.
```c
/* BAD: Byte access at odd offset */
UBYTE type = *(volatile UBYTE *)0xE80001; /* Undefined! */
/* GOOD: Word access at even offset, mask lower byte */
UWORD type_word = *(volatile UWORD *)0xE80000;
UBYTE type = (UBYTE)(type_word >> 8);
```
### 2. Ignoring the Chain Bit
A slot can contain multiple boards chained together. If you only check one `ConfigDev` per slot, you will miss chained boards.
```c
/* BAD: Only looks at the first ConfigDev in a slot */
struct ConfigDev *cd = FindConfigDev(NULL, manuf, prod);
/* GOOD: Walk the entire chain */
struct ConfigDev *cd = NULL;
while ((cd = FindConfigDev(cd, manuf, prod)) != NULL) {
/* Process each matching board */
}
```
### 3. Accessing Config Address After Boot
Once a board is configured, it no longer responds at `$E80000` or `$FF000000`. Attempting to read the config space after boot will return bus noise or garbage.
---
## Use Cases
- **SCSI controller drivers** — Cards like the A2091, GVP Series II, and Buddha use AutoConfig + DiagArea to install their `exec.device` handlers before DOS boot.
- **RTG graphics cards** — Picasso II/IV, CyberVision, and Retina all present AutoConfig data so `graphics.library` or Picasso96 can locate their registers and VRAM.
- **Accelerator boards** — Blizzard and CyberStorm cards use AutoConfig to map their Fast RAM into the system memory list and their control registers into I/O space.
- **RAM expansion** — Simple RAM boards set `er_Flags` bit 5 so the OS automatically adds their memory to the free pool with no driver required.
- **FPGA cores** — MiSTer cores emulating Zorro hardware must implement the full CFGIN/CFGOUT chain, present valid ExpansionRom data, and latch the assigned base address.
---
## FAQ
**Q: Can AutoConfig handle hot-plug?**
A: No. AutoConfig runs once during early boot. Zorro has no hot-plug support — adding or removing a card requires a power cycle.
**Q: What happens if two boards have the same manufacturer/product ID?**
A: The OS distinguishes them by `er_SerialNumber` and by their position in the `ConfigDev` chain. Drivers should walk the chain rather than assuming uniqueness.
**Q: Why does my Zorro III card not configure on an A2000?**
A: The A2000 only has Zorro II slots. A Zorro III card will not assert CFGOUT or respond to `$E80000` access unless it has a Zorro II fallback mode.
**Q: Can a board refuse an assigned address?**
A: No. The OS writes the address and the board must accept it. However, a board can signal that it prefers a specific alignment or address range via the size code and type bits.
**Q: What is the difference between `ERFB_MEMLIST` and the RAM bit in `er_Type`?**
A: `er_Type` bit 5 says "this is a memory board." `er_Flags` bit 5 (`ERFB_MEMLIST`) says "add this memory to the system free list." A diagnostic RAM board might set the RAM bit but clear `ERFB_MEMLIST` so the OS does not use it until a diagnostic passes.
**Q: What happens during a warm reboot (Ctrl-Amiga-Amiga)?**
A: The `/RESET` signal fires, which causes all expansion boards to de-latch their assigned addresses and return to the configuration window. AutoConfig runs again from scratch — the entire `ConfigChain()` loop executes as if the machine had been power-cycled. Board ordering is preserved since it's determined by physical slot position.
**Q: Can I run AutoConfig manually after boot?**
A: Not meaningfully. `ConfigChain()` runs once during Kickstart init. After boot, the configuration address space is inert — configured boards have already moved to their assigned addresses and no longer respond at `$E80000`. The only post-boot interface is `FindConfigDev()` to query the already-built `ConfigDev` list.
---
## References
- NDK39: `libraries/configregs.h`, `libraries/configvars.h`, `libraries/expansion.h`
- ADCD 2.1 Autodocs: `expansion` — http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node025B.html
- *Amiga Hardware Reference Manual* 3rd ed. — AutoConfig and Zorro expansion chapters
- Dave Haynie: *"The Amiga Zorro III Bus Specification"* — definitive reference for Z3 extensions
- See also: [Zorro Bus](zorro_bus.md) — electrical bus architecture, bandwidth, PCI bridges
- See also: [expansion.library](../../11_libraries/expansion.md) — software API, manufacturer ID table, DiagArea details
- See also: [device_driver_basics.md](../../16_driver_development/device_driver_basics.md) — driver binding to ConfigDev
- See also: [address_space.md](address_space.md) — Amiga memory map and expansion regions

View file

@ -8,6 +8,8 @@
Understanding AutoConfig is essential for FPGA core development — MiSTer cores that emulate expansion hardware (RAM boards, accelerators, RTG cards) must present valid AutoConfig data to the boot ROM.
See [AutoConfig Protocol](../01_hardware/common/autoconfig.md) for the full hardware-level specification including the `/CFGIN`/`/CFGOUT` daisy-chain mechanics, nibble-pair ROM format, and corner cases with empty slots and third-party risers.
---
## Zorro Bus Architecture
@ -44,38 +46,61 @@ flowchart LR
## AutoConfig Sequence
The AutoConfig mechanism runs during early boot, before DOS is loaded:
The AutoConfig mechanism runs during early boot, before DOS is loaded. `expansion.library` is initialized by `exec.library` as a resident module at **priority 110**, placing it very early in the Kickstart init sequence:
```
1. CPU Reset → Kickstart ROM entry point
2. exec.library initializes (memory, interrupts, scheduler)
3. Resident module scan → expansion.library initialized (priority 110)
4. expansion.library runs ConfigChain() → enumerates all Zorro boards
5. DiagArea boot ROMs execute (SCSI, network boot handlers)
6. dos.library initializes, mounts filesystems
7. Boot from highest-priority bootable device
```
The enumeration loop reads the configuration window (`$E80000` for Zorro II, `$FF000000` for Zorro III) in a tight polling loop. Each iteration discovers one board:
```mermaid
sequenceDiagram
participant ROM as Kickstart ROM
participant BUS as Zorro Bus
participant BOARD as Expansion Board
participant SLOT as Active Slot (/CFGOUT held)
participant NEXT as Downstream Slots
ROM->>BUS: Assert CFGOUT chain
BOARD->>BUS: Assert CFGIN (ready to configure)
ROM->>BOARD: Read config area at $E80000
Note over ROM: Parse manufacturer, product, size, type
ROM->>BOARD: Write assigned base address
Note over BOARD: Board relocates to assigned address
ROM->>BUS: Pass CFGIN to next board
Note over ROM: Repeat until no more boards respond
ROM->>SLOT: Read config space ($E80000)
SLOT-->>ROM: er_Type, er_Product, er_Flags
ROM->>SLOT: Read er_Manufacturer, er_SerialNumber
SLOT-->>ROM: 16-bit mfr + 32-bit serial
ROM->>ROM: Compute size, allocate base address
alt Address available
ROM->>SLOT: Write base address via ec_BaseAddress
Note over SLOT: Board latches address,<br/>relocates, asserts /CFGOUT
else No address space left
ROM->>SLOT: Write ec_Shutup
Note over SLOT: Board goes silent,<br/>asserts /CFGOUT
end
SLOT-->>NEXT: /CFGOUT passes through
ROM->>NEXT: Repeat for next board...
```
The system requires no handshake signal — once the base address is written, the board instantly relocates and the next iteration finds the next board in the window. See [AutoConfig Protocol — The Four-Phase Configuration Sequence](../01_hardware/common/autoconfig.md#the-four-phase-configuration-sequence) for full details.
### AutoConfig ROM Layout ($E80000)
The board presents its identity at the configuration address. Each byte is read from **even** addresses only ($E80000, $E80002, $E80004...):
The board presents its identity at the configuration address. Each logical byte is read via **two even-address accesses** (nibble-pair format): the high nibble from bits `D7D4` at `offset`, the low nibble from bits `D7D4` at `offset + 2`. This allows boards to use cheap 4-bit PROMs. All fields except `er_Type` are stored **inverted** in the ROM (XOR `$FF`) — an empty bus reads `$FF`, which inverts to `$00`, cleanly signaling "no more boards."
See [AutoConfig Protocol — Nibble-Pair Format](../01_hardware/common/autoconfig.md#the-nibble-pair-format--logical-inversion) for the full physical-to-logical address map.
| Offset | Register | Bits | Description |
|---|---|---|---|
| $00 | `er_Type` | 7:6 | Board type: 11=Zorro III, 10=Zorro II |
| $00 | `er_Type` | 7:6 | Board type: `11`=Zorro II, `10`=Zorro III |
| $00 | `er_Type` | 5 | Memory board (1) or I/O board (0) |
| $00 | `er_Type` | 4 | Chain bit — more boards follow |
| $00 | `er_Type` | 3:0 | Size code (see table below) |
| $02 | `er_Product` | 7:0 | Product number (0255) |
| $04 | `er_Flags` | 7 | Can be shut up (mapped out) |
| $04 | `er_Flags` | 5 | Board's memory is free (add to system pool) |
| $08/$0A | `er_Manufacturer` | 15:0 | Manufacturer ID (IANA-like registry) |
| $04 | `er_Flags` | 4 | `er_InitDiagVec` is valid (`ERFB_DIAGVALID`) |
| $08/$0A | `er_Manufacturer` | 15:0 | Manufacturer ID (assigned by Commodore) |
| $0C$12 | `er_SerialNumber` | 31:0 | Serial number (unique per board) |
| $20/$22 | `er_InitDiagVec` | 15:0 | Offset to optional boot ROM (DiagArea) |
@ -100,7 +125,7 @@ The board presents its identity at the configuration address. Each byte is read
/* libraries/configvars.h — NDK39 */
struct ConfigDev {
struct Node cd_Node;
UBYTE cd_Flags; /* CDF_CONFIGME, CDF_BADMEMORY, etc. */
UBYTE cd_Flags; /* see cd_Flags table below */
UBYTE cd_Pad;
struct ExpansionRom cd_Rom; /* AutoConfig ROM data (copied) */
APTR cd_BoardAddr; /* assigned base address */
@ -109,6 +134,7 @@ struct ConfigDev {
UWORD cd_SlotSize;
APTR cd_Driver; /* driver bound to this board */
struct ConfigDev *cd_NextCD; /* next in chain */
ULONG cd_Unused[4]; /* reserved */
};
struct ExpansionRom {
@ -124,9 +150,69 @@ struct ExpansionRom {
};
```
### cd_Flags Values
| Flag | Value | Description |
|---|---|---|
| `CDF_SHUTUP` | `$01` | Board has been shut up (disabled via `ec_Shutup`) |
| `CDF_CONFIGME` | `$02` | Board needs a driver — set by the OS during AutoConfig, cleared when a driver claims it via `ConfigBoard()` |
| `CDF_BADMEMORY` | `$04` | Board memory failed diagnostic and should not be added to the free pool |
`CDF_CONFIGME` is the key flag for driver authors: it indicates the board has been discovered and address-mapped but no driver has claimed it yet. A well-behaved driver checks this flag, performs initialization, then clears it.
---
## Finding Expansion Boards
## API Reference
### Runtime API — Finding Expansion Boards
```c
/* Find a configured board by manufacturer and product.
* Pass NULL to start, previous cd to continue iterating.
* Use -1 for manufacturer or product as wildcard. */
struct ConfigDev *FindConfigDev(
struct ConfigDev *oldConfigDev,
LONG manufacturer,
LONG product
);
/* LVO -72 */
```
```c
/* Bind a driver to a ConfigDev node (clears CDF_CONFIGME) */
void ConfigBoard(
APTR boardAddr,
struct ConfigDev *configDev
);
/* LVO -48 */
```
### Low-Level Primitives (Kickstart Internal)
These are the actual primitives used by `ConfigChain()` during the boot scan. They are exported by `expansion.library` but are not intended for application use:
```c
/* Read a board's ExpansionRom structure from the config address space.
* Performs the nibble-pair reads and applies XOR $FF inversion. */
BOOL ReadExpansionRom(
APTR board, /* config base ($E80000 or $FF000000) */
struct ConfigDev *configDev /* output: populated with ROM data */
);
/* LVO -96 */
```
```c
/* Write the assigned base address to the board's ec_BaseAddress register.
* Performs nibble-pair writes that latch the address and cause the
* board to relocate and assert /CFGOUT. */
void WriteExpansionBase(
APTR board, /* config base address */
ULONG base /* assigned base address */
);
/* LVO -102 */
```
### Usage Example
```c
struct Library *ExpansionBase = OpenLibrary("expansion.library", 0);
@ -174,44 +260,547 @@ while ((cd = FindConfigDev(cd, -1, -1)))
## DiagArea — Boot ROMs on Expansion Boards
If `er_InitDiagVec` is non-zero, the board has a boot ROM that runs during expansion configuration:
If `er_Flags` has the `ERFB_DIAGVALID` bit set, the board carries a **DiagArea** — an on-board ROM structure containing executable code that runs during AutoConfig, *before* DOS is available. The `er_InitDiagVec` field gives the byte offset from the board's configured base address to the `DiagArea` structure.
```c
struct DiagArea {
UBYTE da_Config; /* DAC_WORDWIDE, DAC_BYTEWIDE, etc. */
UBYTE da_Config; /* DAC_WORDWIDE, DAC_BYTEWIDE, DAC_NIBBLEWIDE */
UBYTE da_Flags; /* DAC_CONFIGTIME or DAC_BINDTIME */
UWORD da_Size; /* total size of DiagArea */
UWORD da_DiagPoint; /* offset to diagnostic routine */
UWORD da_BootPoint; /* offset to boot code */
char da_Name[]; /* optional handler name */
UWORD da_Size; /* total size of DiagArea in bytes */
UWORD da_DiagPoint; /* offset to diagnostic routine (optional) */
UWORD da_BootPoint; /* offset to boot code (optional) */
char da_Name[]; /* NUL-terminated handler name (e.g. "scsi.device") */
};
```
Boot ROMs are used by:
- SCSI controllers (to boot from hard disk)
- Network cards (to boot via TFTP)
- Accelerator boards (to patch CPU-specific features)
### da_Flags — Execution Timing
| Flag | Description |
|---|---|
| `DAC_CONFIGTIME` | Code runs immediately during the `ConfigChain()` pass, while AutoConfig is still in progress. Used by SCSI controllers that must install their `exec.device` handler before DOS mounts volumes. |
| `DAC_BINDTIME` | Code runs later, after all boards have been configured. Used by boards that depend on other expansion hardware being present first. |
### da_Config — Data Width
| Flag | Description |
|---|---|
| `DAC_BYTEWIDE` | DiagArea ROM uses byte-wide access |
| `DAC_WORDWIDE` | DiagArea ROM uses word-wide (16-bit) access |
| `DAC_NIBBLEWIDE` | DiagArea ROM uses nibble-wide (4-bit) access — same format as the AutoConfig ROM itself |
### Common Uses
- **SCSI controllers** (A2091, GVP Series II) — install `scsi.device` handler at `DAC_CONFIGTIME` so the system can boot from hard disk
- **Network cards** — install network device handler for TFTP/BOOTP boot
- **Accelerator boards** — patch CPU-specific features, install MMU tables
- **RTG graphics cards** — some cards use DiagArea to install early display initialization code
> [!NOTE]
> DiagArea execution happens at step 5 in the [boot sequence](../01_hardware/common/autoconfig.md#boot-sequence-position), after all boards have been assigned addresses but before `dos.library` loads. Without DiagArea, there is no mechanism to boot from expansion hardware.
---
## FPGA Implementation Notes
## Use Case: Booting from an External SCSI Controller
For MiSTer or other FPGA cores emulating Zorro expansion:
This walkthrough traces the complete lifecycle of a Zorro II SCSI controller (using the A2091 as a concrete example) from power-on through disk boot. It shows every address, signal, and function call involved.
### Phase 1 — Power-On and AutoConfig Discovery
```
Power on → CPU executes Kickstart ROM at $FC0000
→ exec.library initializes
→ expansion.library init (priority 110) calls ConfigChain($E80000)
```
The motherboard asserts `/CFGIN` on Slot 1. The A2091 wakes up and responds at `$E80000`:
```
ConfigChain reads $E80000 (physical):
$E80000/$E80002 → er_Type high/low nibble → $C1 (Zorro II, I/O board, size=64KB)
$E80004/$E80006 → er_Product (inverted) → $03 (product 3)
$E80008/$E8000A → er_Flags (inverted) → ERFB_DIAGVALID set
$E80010$E80016 → er_Manufacturer → $0202 (514 = Commodore)
$E80018$E80026 → er_SerialNumber → $00000000
$E80028$E8002E → er_InitDiagVec → $0040 (DiagArea at base + $40)
```
### Phase 2 — Address Assignment
The OS computes: product 3, size code `$1` = 64 KB. It finds a free region in the Zorro II I/O pool (`$E90000$EFFFFF`) and assigns base address `$E90000`:
```
ConfigChain writes base address as nibbles:
Write $E9 high nibble → physical offset $44 (ec_BaseAddress)
Write $00 high nibble → physical offset $46
Write $00 high nibble → physical offset $48
```
The A2091 latches `$E90000`, stops responding at `$E80000`, starts responding at `$E90000`, and asserts `/CFGOUT`. The OS creates a `ConfigDev` node:
```c
cd->cd_BoardAddr = 0x00E90000; /* assigned base */
cd->cd_BoardSize = 0x00010000; /* 64 KB */
cd->cd_Rom.er_Manufacturer = 514; /* Commodore */
cd->cd_Rom.er_Product = 3; /* A2091 */
cd->cd_Flags = CDF_CONFIGME; /* needs a driver */
```
`ConfigChain` continues — reads `$E80000` again, finds the next board (or chain termination).
### Phase 3 — DiagArea Execution
After *all* boards are configured, Kickstart walks the `ConfigDev` list and processes boards with `ERFB_DIAGVALID`. For the A2091:
```
Board base = $E90000
er_InitDiagVec = $0040
→ DiagArea struct located at $E90040
```
Kickstart reads the `DiagArea` header:
```
$E90040: da_Config = DAC_WORDWIDE (word-wide ROM access)
$E90041: da_Flags = DAC_CONFIGTIME (execute NOW, during boot)
$E90042: da_Size = $2000 (8 KB DiagArea ROM)
$E90044: da_DiagPoint = $0100 (diagnostic at base + $40 + $100)
$E90046: da_BootPoint = $0200 (boot code at base + $40 + $200)
$E90048: da_Name = "scsi.device\0"
```
Because `da_Flags = DAC_CONFIGTIME`, Kickstart executes the code immediately:
**Step 3a — Diagnostic routine** (`da_DiagPoint`):
```
JSR to $E90140 (base + $40 + $100)
→ Board self-test: verify WD33C93 SCSI chip responds
→ Initialize DMA controller
→ Return D0=1 (success) or D0=0 (failure, board disabled)
```
**Step 3b — Boot code** (`da_BootPoint`):
This is the critical step. The boot code must perform four tasks, in order, using only `exec.library` and `expansion.library` calls (no DOS is available yet):
**Task 1 — Create and install the Exec device:**
The boot code allocates memory for the device handler structure and its code, then registers it with Exec:
```c
/* Simplified pseudocode — actual implementation is 68K assembly */
/* The device struct lives in memory allocated from the DiagArea code */
struct Device *scsiDev = AllocMem(sizeof(struct MyDeviceBase), MEMF_PUBLIC|MEMF_CLEAR);
/* Fill in the standard Exec Library/Device fields */
scsiDev->dd_Library.lib_Node.ln_Name = "scsi.device";
scsiDev->dd_Library.lib_Node.ln_Type = NT_DEVICE;
scsiDev->dd_Library.lib_Version = 40;
scsiDev->dd_Library.lib_IdString = "a2091 scsi.device 40.10";
/* Install the function vectors (the "jump table") */
/* These are negative offsets from the device base pointer */
SetFunction(scsiDev, DEV_OPEN, myOpenFunc); /* -6 */
SetFunction(scsiDev, DEV_CLOSE, myCloseFunc); /* -12 */
SetFunction(scsiDev, DEV_EXPUNGE, myExpungeFunc); /* -18 */
SetFunction(scsiDev, DEV_BEGINIO, myBeginIOFunc); /* -30 */
SetFunction(scsiDev, DEV_ABORTIO, myAbortIOFunc); /* -36 */
/* Register the device with Exec's global device list */
AddDevice(scsiDev);
/* Now any code can call OpenDevice("scsi.device", unit, ...) */
```
After `AddDevice()`, the device name `"scsi.device"` is visible to the entire system. Any code — including `dos.library` later — can call `OpenDevice("scsi.device", unitNumber, ioRequest, 0)` and the Exec dispatch will route it to your `BeginIO` handler.
**Task 2 — Build a DOS device node:**
The boot code must describe the disk partition so `dos.library` knows how to mount it. This uses `expansion.library`'s `MakeDosNode()`:
```c
/* Build the parameter packet for MakeDosNode() */
/* This is a LONG array describing the DOS device: */
ULONG params[] = {
(ULONG)"DH0:", /* dosName — what users see in the CLI */
(ULONG)"scsi.device",/* execName — the Exec device to open */
0, /* unit number (SCSI ID 0) */
0, /* flags */
16, /* number of surfaces (heads) */
1, /* sectors per block */
32, /* blocks per track */
0, /* reserved blocks at start */
0, /* reserved blocks at end */
0, /* interleave */
0, 0, /* lowCyl, highCyl — filled from RDB later */
5, /* numBuffers */
MEMF_PUBLIC, /* bufMemType */
0x7FFFFFFF, /* maxTransfer */
0xFFFFFFFE, /* mask (all but bit 0) */
-1, /* boot priority (-1 = don't auto-boot) */
(ULONG)"FFS\0", /* DOS type (0x444F5303 for FFS) */
};
struct DeviceNode *dn = MakeDosNode(params);
```
**Task 3 — Register as bootable:**
```c
/* AddBootNode registers the device as bootable.
* The priority determines boot order — higher boots first.
* dn is the DeviceNode from MakeDosNode().
* cd is the ConfigDev pointer passed in registers by Kickstart. */
AddBootNode(0, ADNF_STARTPROC, dn, cd);
/* ^ ^ ^ ^
* | | | +-- ConfigDev (links device to board)
* | | +------ DeviceNode to mount
* | +---------------------- Start handler process at boot
* +------------------------- Boot priority (0 = normal)
*/
```
> [!NOTE]
> `AddBootNode()` does NOT mount the filesystem. It adds an entry to an internal **boot node list** that `dos.library` will walk later. The `ADNF_STARTPROC` flag tells DOS to actually start a handler process for this device — without it, the node exists but remains dormant.
**Task 4 — Return to Kickstart:**
The boot code returns with `D0 = 1` (success). Kickstart continues processing any remaining DiagArea boards.
At this point, `scsi.device` is live in Exec's device list and "DH0:" is queued in the boot node list — but no disk I/O has occurred yet. The SCSI bus hasn't even been scanned. That happens next.
### Phase 4 — DOS Boot and Device Discovery
When `dos.library` initializes (Kickstart boot step 6), it performs the actual disk discovery:
**Step 4a — Walk the boot node list:**
```
dos.library sorts boot nodes by priority (highest first)
For each boot node:
→ Calls OpenDevice() with the execName and unit from the DeviceNode
→ This triggers scsi.device's Open handler (your myOpenFunc above)
```
**Step 4b — Device Open handler scans the bus:**
When `scsi.device` receives its first `OpenDevice()` call, it typically performs a deferred bus scan:
```
scsi.device Open(unit=0):
→ If first open: enumerate SCSI bus
→ Send INQUIRY to SCSI IDs 06
→ Record which IDs have devices
→ Send READ CAPACITY to each device (get size)
→ Set up per-unit state for the requested unit
→ Return success (or error if unit not found)
```
This deferred approach is important — scanning the SCSI bus during DiagArea (step 3b) would slow boot for every board, even if the system won't boot from that controller.
**Step 4c — RigidDiskBlock parsing:**
```
dos.library reads block 0 from the opened device
→ Looks for RDB signature ("RDSK" / $5244534B) in blocks 015
→ If found: parse partition table entries
→ For each partition: create/update DeviceNode with actual cylinder ranges
→ Mount the filesystem (spawn a handler process)
→ If no RDB: try to mount using the parameters from MakeDosNode()
```
**Step 4d — Filesystem boot:**
```
dos.library picks the highest-priority mounted device
→ Opens the root directory
→ Looks for L:FastFileSystem (if needed) or uses ROM filesystem
→ Reads S:Startup-Sequence
→ Begins executing commands → normal Workbench boot
```
### How Does the OS "Know" a New Controller Exists?
The answer is surprisingly simple — it's a **push model**, not a pull model:
1. **The board firmware tells the OS.** The DiagArea boot code explicitly calls `AddDevice()` and `AddBootNode()`. The OS doesn't scan for controllers — the controller announces itself.
2. **The device name is the contract.** After `AddDevice("scsi.device")`, any code can call `OpenDevice("scsi.device", unit, ...)`. There is no central "disk controller registry" — Exec's device list *is* the registry.
3. **Multiple controllers coexist by name.** If two SCSI controllers are installed, they typically use different device names (`"scsi.device"` and `"gvpscsi.device"`). Each registers its own boot nodes with different priorities. The user controls which boots first via HDToolBox (boot priority field in the RDB).
4. **Post-boot discovery works the same way.** If you add an IDE controller that doesn't use DiagArea (driver loaded from disk), the startup-sequence or user runs a mount command that calls `OpenDevice("ide.device", unit, ...)`. The OS doesn't care *when* the device was registered — only that it's in Exec's device list when needed.
### Complete Timeline
```mermaid
sequenceDiagram
participant CPU as CPU / Kickstart
participant EXP as expansion.library
participant CARD as A2091 Card
participant SCSI as SCSI Drive
participant DOS as dos.library
CPU->>EXP: Init expansion.library (priority 110)
EXP->>CARD: Read $E80000 (config space)
CARD-->>EXP: er_Type, er_Product, er_Manufacturer
EXP->>CARD: Write base address $E90000
Note over CARD: Relocates to $E90000<br/>Asserts /CFGOUT
EXP->>EXP: Create ConfigDev node
Note over CPU: All boards configured
CPU->>CARD: Execute DiagArea at $E90040
CARD->>CARD: Self-test WD33C93
CARD->>CPU: AddDevice("scsi.device")
CARD->>CPU: AddBootNode("DH0:", pri=0)
CPU->>DOS: Init dos.library
DOS->>CARD: OpenDevice("scsi.device", 0)
CARD->>SCSI: SCSI INQUIRY
SCSI-->>CARD: Device info
CARD->>SCSI: READ block 0 (RDB)
SCSI-->>CARD: Partition table
DOS->>DOS: Mount DH0:, load filesystem
DOS->>DOS: Execute S:Startup-Sequence
```
---
## Developing Compliant Expansion Board Firmware
How complex is it to develop firmware for an AutoConfig-compliant expansion board? The answer depends entirely on the device class.
### Difficulty Tiers
#### Tier 1 — Trivial: RAM Board (No Firmware Required)
A RAM expansion board needs **zero firmware**. The AutoConfig ROM is just a small PROM (or diode array) with 16 bytes of static identity data. There is no executable code, no DiagArea, no driver. The hardware requirements are:
- A 4-bit PROM or register array wired to the upper data bus (`D7D4`)
- Address decode logic for the `$E80000` config window
- A latch to capture the assigned base address
- `/CFGIN`/`/CFGOUT` gating logic
This can be implemented with a handful of 74-series TTL chips or a single small CPLD. Many hobbyist RAM boards use exactly this approach.
#### Tier 2 — Moderate: I/O Board Without Boot (Network, Audio, Sampler)
These boards need AutoConfig ROM + a device-specific register interface. The firmware consists of:
- **AutoConfig PROM:** Same 16 bytes as a RAM board
- **Register decode logic:** Map the device chip's registers into the assigned address space
- **Interrupt routing:** Wire the device's IRQ to the Zorro INT2 or INT6 line
The *Amiga-side driver* (SANA-II `.device` for network, AHI `.driver` for audio) is loaded from disk after boot — you don't need DiagArea. This means the board firmware itself is still purely hardware logic, no executable 68K code.
**Estimated complexity:** A CPLD or small FPGA plus the device controller chip. Firmware is gate-level logic, not software.
#### Tier 3 — Complex: Bootable Storage Controller (SCSI, IDE)
This is where real firmware development begins. In addition to everything in Tier 2, you need:
- **DiagArea ROM:** Executable 68000 code stored in an EPROM on the board. This code must:
- Self-test the controller hardware
- Create and install an `exec.device` handler (`scsi.device` or similar)
- Build DOS device nodes and register them as bootable via `AddBootNode()`
- **Full Exec device handler:** Your `scsi.device` must implement the standard trackdisk-compatible command set:
```c
/* Minimum command set for a bootable device */
CMD_READ /* Read sectors */
CMD_WRITE /* Write sectors */
CMD_UPDATE /* Flush write cache */
CMD_CLEAR /* Invalidate read cache */
TD_GETGEOMETRY /* Report disk geometry */
TD_MOTOR /* Motor control (can be no-op for SCSI) */
TD_CHANGENUM /* Media change detection */
HD_SCSICMD /* Direct SCSI passthrough (optional but expected) */
```
- **RDB parsing awareness:** While `dos.library` handles RigidDiskBlock parsing, your device must support the block sizes and addressing the RDB specifies.
- **Interrupt-driven I/O:** DMA completion must trigger INT2/INT6 so the CPU doesn't have to poll.
**Estimated complexity:** 28 KB of 68000 assembly for the DiagArea boot ROM + device handler. The A2091 ROM is approximately 8 KB. Writing and debugging this code requires:
- Knowledge of Exec device handler conventions (`BeginIO`, `AbortIO`, `Open`, `Close`)
- SCSI/ATA protocol knowledge
- Ability to test without DOS (your code runs before the filesystem exists)
> [!WARNING]
> DiagArea code runs in **Supervisor mode** with **no DOS**, **no stdio**, and **no disk access**. You cannot use `printf()`, `Open()`, or any DOS function. Only `exec.library` calls are available. Debugging typically requires a serial port or LED register on the board itself.
#### Tier 4 — Expert: RTG Graphics Card
The most demanding firmware category. Beyond Tier 3 complexity:
- **No DiagArea device handler** — RTG cards don't install an `exec.device`. Instead, they rely on a Picasso96 or CyberGraphX `.card` driver loaded from disk.
- **But** the register interface is vastly more complex — you must emulate or expose a full VGA/SVGA register set (hundreds of registers for mode setting, palette, blitter, cursor, etc.)
- **Memory-mapped framebuffer** — must support multiple pixel formats (CLUT8, RGB15, RGB16, RGB24, ARGB32) and mode switching
- **Hardware blitter** — acceleration of `BltBitMap`, `RectFill`, line drawing (optional but expected for performance)
The firmware itself (CPLD/FPGA gate logic) is substantial, but the *Amiga-side driver* is where most of the development effort lies — it must implement the entire P96 or CGX card driver API.
### Compliance Checklist
| Requirement | RAM | I/O (No Boot) | Bootable Storage | RTG |
|---|---|---|---|---|
| AutoConfig PROM (16 bytes) | ✅ | ✅ | ✅ | ✅ |
| `/CFGIN`/`/CFGOUT` logic | ✅ | ✅ | ✅ | ✅ |
| Base address latch | ✅ | ✅ | ✅ | ✅ |
| Shut-up support | ✅ | ✅ | ✅ | ✅ |
| Register decode | — | ✅ | ✅ | ✅ |
| Interrupt routing | — | Usually | ✅ | ✅ |
| DiagArea ROM | — | — | ✅ | Optional |
| Exec device handler (68K code) | — | — | ✅ | — |
| `AddBootNode()` | — | — | ✅ | — |
| Amiga-side disk driver | — | From disk | In ROM | From disk |
| Hardware complexity | Low | Medium | High | Very High |
| 68K firmware code | 0 bytes | 0 bytes | 28 KB | 02 KB |
---
## Emulator & FPGA Implementation Guide
This section is for anyone building emulated Zorro hardware — whether in an FPGA (MiSTer Minimig, Vampire), a software emulator (UAE), or a modern reimplementation. The goal is for AmigaOS to discover and use your emulated device exactly as it would a real expansion card.
### The Universal AutoConfig Contract
Every emulated Zorro board, regardless of device type, must implement these five behaviors:
**1. Present Valid AutoConfig ROM Data:**
When the board's `/CFGIN` is asserted, it must respond at the configuration address (`$E80000` for Zorro II, `$FF000000` for Zorro III) with valid `ExpansionRom` data in nibble-pair format. At minimum you need:
- `er_Type` — correct bus type bits (`11` = Z2, `10` = Z3), memory/I/O flag, and accurate size code
- `er_Product` — your product number (0255)
- `er_Manufacturer` — a valid 16-bit manufacturer ID (use an existing registered ID or a well-known test ID like `$6502`)
- `er_Flags` — set `ERFB_MEMLIST` if the board's memory should be added to the system pool; set `ERFB_DIAGVALID` if you provide a DiagArea boot ROM
**2. Accept Base Address Write:**
When the OS writes nibbles to `ec_BaseAddress` (physical offsets `$44``$4E`), the board must latch the assigned address, immediately stop responding at the config window, and begin responding at the new base address.
**3. Implement `/CFGIN`/`/CFGOUT`:**
The board must only respond in the config window when `/CFGIN` is asserted. After configuration, it must assert `/CFGOUT` to allow the next board in the chain to be discovered. In software emulators this is typically handled by iterating a virtual slot array; in FPGA you must implement the actual signal chain.
**4. Support Shut-Up:**
If the OS writes to `ec_Shutup` (physical offset `$4C`), the board must go permanently silent — stop responding at both the config address and any other address. This happens when the OS has no address space left for the board.
**5. Respect Bus Width:**
Zorro II boards must respond to 16-bit access only. Zorro III boards must handle full 32-bit access. Getting this wrong causes bus errors or data corruption.
### Per-Device-Type Requirements
While AutoConfig discovery is identical for all boards, the **post-configuration behavior** varies dramatically by device class. The table below summarizes what each type needs beyond the universal contract:
#### RAM Expansion (Simplest Case)
The easiest device to emulate. No driver, no DiagArea, no interrupts.
| Aspect | Requirement |
|---|---|
| AutoConfig ROM | Must present valid er_Type, er_Product, er_Manufacturer at $E80000 |
| Address assignment | Must accept base address write and relocate accordingly |
| CFGIN/CFGOUT chain | Must implement daisy-chain protocol for multi-board configs |
| Memory type flag | Set `er_Flags` bit 5 if board memory should be added to system pool |
| Shut-up | Board must go silent after configuration (stop responding at $E80000) |
| `er_Type` bit 5 | Set to `1` (memory board) |
| `er_Flags` bit 5 | Set `ERFB_MEMLIST` so OS adds memory to free pool automatically |
| DiagArea | Not needed |
| Post-config behavior | Just be readable/writable RAM at the assigned address |
| Driver | None — the OS handles everything |
> [!TIP]
> RAM boards are the ideal first test when bringing up AutoConfig on a new platform. If `AvailMem(MEMF_FAST)` shows your memory after boot, your AutoConfig implementation is correct.
#### SCSI / IDE Storage Controllers
Storage controllers are the most complex case because they must be functional *before* DOS loads.
| Aspect | Requirement |
|---|---|
| `er_Type` bit 5 | `0` (I/O board) |
| DiagArea | **Required** — must provide a `DAC_CONFIGTIME` boot ROM that installs an `exec.device` handler (e.g. `scsi.device`) |
| Interrupts | Must generate Zorro INT2 or INT6 for DMA completion and command status |
| Register space | Must emulate the controller's register set (e.g. WD33C93 for A2091, NCR 53C710 for Warp Engine) at offsets from the assigned base address |
| DMA | Must implement DMA transfers between board memory and Amiga address space |
| Boot priority | The DiagArea `da_BootPoint` code must set up a bootable device node so `dos.library` can mount the filesystem |
#### RTG Graphics Cards (Picasso, CyberVision, etc.)
RTG cards need a large framebuffer region plus a register interface. The Amiga-side software stack (Picasso96, CyberGraphX) talks to the registers and maps the framebuffer.
| Aspect | Requirement |
|---|---|
| `er_Type` bit 5 | `0` (I/O board) — even though the framebuffer is memory, it's not system RAM |
| Size code | Must request enough space for both registers and VRAM (typically 216 MB) |
| DiagArea | Optional but common — some cards use it for early display init |
| Register emulation | Must emulate the specific graphics chip register set (e.g. Cirrus GD5434 for Picasso IV, S3 Trio64 for CyberVision) |
| Framebuffer | Contiguous read/write memory region within the board's address space; the RTG driver stack writes pixels here directly |
| Interrupts | Required for vertical blank sync and, on some cards, blitter completion |
| Driver | Requires a Picasso96 or CyberGraphX `.card` driver on the Amiga side that knows your register layout |
> [!NOTE]
> The RTG driver stack does not use AutoConfig to *identify* the graphics chip — it uses `FindConfigDev()` to locate the board by manufacturer/product ID, then talks directly to the chip registers. Your emulated hardware must match the register layout that the corresponding `.card` driver expects.
#### Network Cards (A2065, X-Surf, Ariadne, etc.)
Network cards are I/O boards that typically use a SANA-II driver on the Amiga side.
| Aspect | Requirement |
|---|---|
| `er_Type` bit 5 | `0` (I/O board) |
| DiagArea | Optional — only needed if you want network boot (TFTP/BOOTP) |
| Register emulation | Must emulate the NIC chip register set (e.g. AMD LANCE Am7990 for A2065, RTL8019AS for X-Surf) |
| Interrupts | **Critical** — network cards are interrupt-driven; packet receive must trigger INT2/INT6 |
| DMA / buffer | Most NICs use shared memory buffers for ring descriptors and packet data; these must be accessible in the board's address space |
| Driver | SANA-II `.device` driver on the Amiga side; loaded from disk (not via DiagArea, unless doing network boot) |
**Key difference from storage:** Network cards typically don't need DiagArea because the driver loads from disk after DOS is up. The exception is network boot, where DiagArea installs a minimal handler at `DAC_CONFIGTIME`.
#### Audio Cards (Prelude, Delfina, Toccata, etc.)
Audio boards are I/O boards with sample playback/recording capability.
| Aspect | Requirement |
|---|---|
| `er_Type` bit 5 | `0` (I/O board) |
| DiagArea | Not needed |
| Register emulation | Emulate the specific audio codec/DSP registers (e.g. AD1848 for Toccata, CS4231 for Prelude, DSP56001 for Delfina) |
| Interrupts | Required for buffer-empty/buffer-full events during playback and recording |
| DMA / buffer | Audio data is typically written to an on-board FIFO or sample buffer by the driver |
| Driver | AHI `.driver` on the Amiga side; loaded from disk |
**Unique challenge:** Audio requires strict timing. The emulated hardware must generate interrupts at the correct sample rate interval (e.g. every 256 samples at 44.1 kHz), or playback will stutter, skip, or run at the wrong speed. In FPGA this maps to a hardware timer; in software emulators it requires careful cycle-accurate interrupt scheduling.
#### Samplers & Video Capture (GVP IV24, Vlab Motion, etc.)
These are specialized I/O boards that digitize external analog signals.
| Aspect | Requirement |
|---|---|
| `er_Type` bit 5 | `0` (I/O board) |
| Size code | Often large — video capture boards may need 28 MB for frame buffers |
| DiagArea | Not needed |
| Register emulation | Must emulate the capture chip registers (e.g. SAA7146 for Vlab Motion, Bt848 for some clone designs) |
| Interrupts | Required — frame-complete and field-complete interrupts drive the capture pipeline |
| DMA | Video capture boards typically DMA directly into Amiga memory or into an on-board frame buffer that the driver reads |
| Driver | Custom `.device` or library; loaded from disk |
**Unique challenge:** Video capture involves continuous high-bandwidth data flow. The emulated board must sustain the data rate (PAL: ~10 MB/s for uncompressed YUV) without overrunning buffers or starving the CPU. Some boards (Vlab Motion) include on-board JPEG compression hardware that must also be emulated.
### Summary: What Makes Each Device Type Different
| Device Class | AutoConfig Type | DiagArea Needed? | Interrupts? | Key Post-Config Requirement |
|---|---|---|---|---|
| **RAM** | Memory | No | No | Just be RAM |
| **SCSI/IDE** | I/O | **Yes** (`CONFIGTIME`) | Yes | Boot ROM + device handler |
| **RTG Graphics** | I/O | Optional | Yes (vblank) | Register emulation + framebuffer |
| **Network** | I/O | Only for net boot | Yes (packet RX) | NIC register emulation + shared buffers |
| **Audio** | I/O | No | Yes (sample IRQ) | Codec registers + timing-accurate IRQs |
| **Sampler/Capture** | I/O | No | Yes (frame IRQ) | High-bandwidth DMA + capture registers |
> [!IMPORTANT]
> The AutoConfig part is **identical** for all device types. The `/CFGIN`/`/CFGOUT` daisy chain, nibble-pair ROM format, base address latching, and shut-up behavior are the same whether you're emulating a $20 RAM board or a $2000 video capture card. What differs is everything that happens *after* the board is configured — register layout, interrupt behavior, DMA mechanics, and which Amiga-side driver stack talks to your hardware.
---
## References
- NDK39: `libraries/configvars.h`, `libraries/expansion.h`
- ADCD 2.1: expansion.library autodocs
- NDK39: `libraries/configregs.h`, `libraries/configvars.h`, `libraries/expansion.h`
- ADCD 2.1: expansion.library autodocs — http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_3._guide/node025B.html
- Dave Haynie: *"The Amiga Zorro III Bus Specification"* — definitive bus reference
- See also: [AutoConfig Protocol](../01_hardware/common/autoconfig.md) — hardware-level specification: daisy-chain, nibble format, corner cases, boot sequence
- See also: [Zorro Bus](../01_hardware/common/zorro_bus.md) — electrical bus architecture, bandwidth, PCI bridges
- See also: [address_space.md](../01_hardware/common/address_space.md) — Amiga memory map and expansion regions
- See also: [rtg_driver.md](../16_driver_development/rtg_driver.md) — RTG cards use Zorro AutoConfig
- See also: [device_driver_basics.md](../16_driver_development/device_driver_basics.md) — driver binding to ConfigDev

View file

@ -74,7 +74,7 @@ The Amiga's documentation was scattered across out-of-print manuals, Usenet post
### 01 — Hardware (by chipset generation)
| Folder | Coverage |
|---|---|
| [common/](01_hardware/common/) | M68k CPU, address space, **memory types (Chip/Fast/Slow RAM)**, CIA chips, Zorro bus, **Gayle IDE/PCMCIA** |
| [common/](01_hardware/common/) | M68k CPU, address space, **memory types (Chip/Fast/Slow RAM)**, CIA chips, Zorro bus, **AutoConfig protocol**, **Gayle IDE/PCMCIA** |
| [ocs_a500/](01_hardware/ocs_a500/) | OCS chipset: custom registers, copper, blitter, sprites, Paula, **A1000 WCS**, **A2000 Zorro II**, **CDTV hardware**, **Gary CSG 5719 system controller (bus arbitration, ROM overlay, AutoConfig, Slow RAM)** |
| [ecs_a600_a3000/](01_hardware/ecs_a600_a3000/) | ECS chipset: Super Agnus, productivity modes, **Gary & Fat Gary system controller (bus arbitration, AutoConfig, SCSI/FPU glue, antipatterns)** |
| [aga_a1200_a4000/](01_hardware/aga_a1200_a4000/) | AGA chipset: Alice, Lisa, copper, blitter (64-bit), palette, **CD32 Akiko**, **A4000T SCSI** |

View file

@ -1,7 +1,7 @@
# Amiga Knowledge Base — Gap Analysis & Improvement Proposal
> **Date:** 2026-04-25
> **Updated:** 2026-04-27 (per-article line-count tracking)
> **Updated:** 2026-04-30 (expansion.library & autoconfig.md hardening)
> **Scope:** All 17 sections, ~90 articles, `/doc/amiga/`
---
@ -113,6 +113,7 @@ Articles were scored against [AGENTS.md](../amiga/AGENTS.md) "Deep" criteria:
| `address_space.md` | common | 175 | ✅ Adequate | 24-bit memory map: Chip, Fast, ROM, I/O, Zorro II |
| `m68k_cpu.md` | common | 141 | ✅ Adequate | 68000 architecture baseline: registers, exceptions |
| `zorro_bus.md` | common | 139 | ✅ Adequate | Zorro II/III expansion bus: AutoConfig, bus arbitration |
| `autoconfig.md` | common | 662 | ✅ Deep | AutoConfig protocol: CFGIN/CFGOUT chain, ExpansionRom encoding, nibble-pair map, size codes, shut-up mechanism, Z2/Z3 differences, antipatterns, FPGA notes, boot sequence positioning |
| `cdtv_hardware.md` | ocs | 283 | ✅ Adequate | CDTV-specific: CD-ROM controller, front panel, boot ROM |
| `custom_registers.md` | ocs | 183 | ✅ Adequate | OCS custom chip register map |
| `blitter.md` | ocs | 154 | ✅ Adequate | Blitter engine basics: channels, minterms, line draw |
@ -314,7 +315,7 @@ Articles were scored against [AGENTS.md](../amiga/AGENTS.md) "Deep" criteria:
| `mathffp.md` | 468 | ✅ Adequate | Motorola FFP and IEEE 754 floating point formats |
| `locale.md` | 265 | ✅ Adequate | .cd/.ct catalog system, locale-aware formatting |
| `layers.md` | 224 | ❌ Pending | Tier 3 #23: needs Mermaid layer stacking diagram, damage region pitfalls |
| `expansion.md` | 217 | ✅ Adequate | Zorro II/III, AutoConfig ROM layout, FPGA notes |
| `expansion.md` | 806 | ✅ Deep | Zorro II/III, AutoConfig ROM layout, FPGA/Emulator implementation guide, firmware complexity tiers, SCSI boot use-case walkthrough (Phase 1-4) |
| `utility.md` | 203 | ✅ Adequate | TagItem lists, callback hooks, date/time utilities |
| `workbench.md` | 194 | ✅ Adequate | WBStartup, AppWindow, AppIcon, AppMenuItem |
| `icon.md` | 188 | ✅ Adequate | .info format, DiskObject, ToolTypes, true-color icons |
@ -379,8 +380,8 @@ Articles were scored against [AGENTS.md](../amiga/AGENTS.md) "Deep" criteria:
| Status | Count |
|---|---|
| ✅ Deep | 56 |
| ✅ Adequate | 113 |
| ✅ Deep | 57 |
| ✅ Adequate | 112 |
| ⚠️ Thin | 0 |
| ❌ Pending (Tier 3) | 10 |
| **Total** | **179** |