Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,17 @@ profile in a config file.
rom = "KICK13.ROM" # Kickstart image, exactly 512 KiB
extended_rom = "cd32ext.rom" # optional: CDTV (256K at $F00000) or
# CD32 (512K at $E00000) extended ROM
# identify = false # drop the Copperline identification board
# from the Zorro chain (default: present)
```

`identify` controls a small, inert Zorro autoconfig board Copperline puts on
the expansion chain (manufacturer 5192 / product 2) so guest software such
as [identify.library](https://github.com/shred/identify) can detect that it
is running under the emulator. It is on by default and does not change the
machine's usable memory; set `identify = false` for a chain with no
emulator-identifying board. See [](../zorro) for details.

The ROM path can be overridden by a positional CLI argument. Omit `rom`
entirely (and pass no ROM argument) to boot the bundled AROS open-source
Kickstart replacement, which ships with Copperline as the default boot ROM;
Expand Down
39 changes: 36 additions & 3 deletions docs/zorro.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ Field notes:
link the board's space into the Exec free-memory list. Leave it `true`
for RAM boards; a future I/O-style board would set it `false`.
- `manufacturer`/`product`/`serial` are what the guest OS sees in the
expansion database. `0x07DB` is the conventional "hacker" ID used by the
built-in boards.
expansion database. `0x07DB` is the conventional "hacker"/"prototype" ID
for homemade boards. Copperline's own built-in boards instead use its
registered manufacturer ID (5192 / `0x1448`, dec0de Consulting); see
[The Copperline manufacturer ID](#the-copperline-manufacturer-id) below.

The spec is validated on load (`BoardSpec::validate`,
`src/zorro.rs`): bad sizes, unknown `zorro` versions, and unknown backing
Expand Down Expand Up @@ -85,6 +87,7 @@ Successful configuration is logged:

```
zorro II board "fast RAM" autoconfigured at 0x00200000
zorro II board "Copperline" autoconfigured at 0x00E90000
```

Once configured, accesses inside a board's window are routed by
Expand Down Expand Up @@ -112,6 +115,35 @@ ways:
On CDTV machines the DMAC occupies the config window first; the Zorro chain
follows once it is configured, matching real-machine autoconfig order.

## The Copperline manufacturer ID

Copperline's built-in virtual boards autoconfig under manufacturer ID
**5192** (`0x1448`) -- the registered ID of dec0de Consulting, which also
makes the real ROMulus flash-ROM board. The product numbers under it are:

| Product | Board |
| ------- | ----- |
| 1 | ROMulus (physical hardware; not emulated) |
| 2 | Copperline identification board |
| 3 | Built-in fast RAM (`[memory] fast`) |
| 4 | Built-in Zorro III RAM (`[memory] z3`) |

The **identification board** (`BoardSpec::copperline_id`) is always added to
the chain (unless disabled, below) so guest software can detect that it is
running under Copperline rather than on real hardware or another emulator --
for example [identify.library](https://github.com/shred/identify) calling
`FindConfigDev(5192, 2)`. It is the smallest legal Zorro II board (64K), is
kept out of the Exec free-memory list, and never autoboots, so it sits
inertly on the chain without changing the machine's usable memory map. Its
autoconfig serial number carries the running Copperline version packed as
`major << 16 | minor << 8 | patch`, so a tool can report the exact version
and not just the emulator name.

The board is added last, after the RAM and `[[zorro]]` boards, so those keep
the base addresses they would get without it. Set `identify = false` in the
configuration to drop it entirely (for a chain with no emulator-identifying
board); see the `identify` option in [](guide/configuration).

## Adding a board in Rust

For board types that need code (a new `BoardBacking` beyond RAM), the flow
Expand All @@ -128,12 +160,13 @@ example of a device-backed board:
Self {
name: "fast RAM".into(),
version: ZorroVersion::II,
manufacturer: HACKER_MANUFACTURER_ID,
manufacturer: COPPERLINE_MANUFACTURER_ID,
product: PRODUCT_FAST_RAM,
serial: 0,
size_bytes,
backing: BoardBacking::Ram,
memlist: true,
diag_vec: None,
}
}
```
Expand Down
48 changes: 46 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ pub struct Config {
/// Extra Zorro boards loaded from `[[zorro]]` metadata files, in
/// autoconfig chain order after the built-in RAM boards.
pub zorro_boards: Vec<BoardSpec>,
/// Advertise the Copperline identification board on the Zorro autoconfig
/// chain (manufacturer 5192 / product 2) so guest software such as
/// identify.library can detect the emulator. Defaults to true; set
/// `identify = false` for a chain with no emulator-identifying board.
pub identify_board: bool,
pub chipset: Chipset,
/// Concrete chip revisions derived from the `[chipset] revision` preset,
/// installed chip RAM, and the optional `agnus`/`denise` overrides.
Expand Down Expand Up @@ -520,6 +525,7 @@ impl Default for Config {
slow_ram_bytes: A500_TRAPDOOR_RAM_BYTES,
z3_ram_bytes: 0,
zorro_boards: Vec::new(),
identify_board: true,
chipset: Chipset::Ocs,
agnus_revision: AgnusRevision::Ocs,
denise_revision: DeniseRevision::Ocs,
Expand Down Expand Up @@ -589,8 +595,11 @@ impl Config {
}

/// Build the Zorro autoconfig chain this config asks for: the built-in
/// Zorro II fast RAM board, the built-in Zorro III RAM board, then any
/// `[[zorro]]` metadata boards in file order.
/// Zorro II fast RAM board, the built-in Zorro III RAM board, any
/// `[[zorro]]` metadata boards in file order, and finally (unless
/// `identify = false`) the Copperline identification board. The ID board
/// comes last so the configured RAM boards keep the autoconfig base
/// addresses they would get without it.
pub fn build_zorro_chain(&self) -> Result<ZorroChain> {
let mut chain = ZorroChain::default();
if self.fast_ram_bytes > 0 {
Expand All @@ -602,6 +611,9 @@ impl Config {
for board in &self.zorro_boards {
chain.add_board(board.clone())?;
}
if self.identify_board {
chain.add_board(BoardSpec::copperline_id())?;
}
Ok(chain)
}
}
Expand Down Expand Up @@ -722,6 +734,9 @@ struct RawConfig {
/// `[[zorro]]` board entries, configured in file order.
#[serde(default)]
zorro: Vec<RawZorroBoard>,
/// `identify = false` drops the Copperline identification board from the
/// autoconfig chain (default: present).
identify: Option<bool>,
}

#[derive(Debug, Default, Deserialize)]
Expand Down Expand Up @@ -1067,6 +1082,7 @@ impl TryFrom<RawConfig> for Config {
slow_ram_bytes,
z3_ram_bytes,
zorro_boards,
identify_board: raw.identify.unwrap_or(defaults.identify_board),
chipset,
agnus_revision,
denise_revision,
Expand Down Expand Up @@ -2116,6 +2132,34 @@ mod tests {
Ok(())
}

#[test]
fn identify_board_present_by_default() -> Result<()> {
// A bare config (no fast/Z3/metadata boards) still puts the
// Copperline identification board on the chain.
let cfg = parse_config("")?;
assert!(cfg.identify_board);
let chain = cfg.build_zorro_chain()?;
let base = crate::zorro::AUTOCONFIG_BASE;
// er_Type: Zorro II, no MEMLIST, 64K (size code 1) = 0xC1, exposed
// high nibble then low nibble (er_Type is not inverted).
assert_eq!(chain.config_read(base, 1), 0xC0);
assert_eq!(chain.config_read(base + 2, 1), 0x10);
// er_Product = 2, inverted to 0xFD on the physical nibbles.
assert_eq!(chain.config_read(base + 4, 1), 0xF0);
assert_eq!(chain.config_read(base + 6, 1), 0xD0);
Ok(())
}

#[test]
fn identify_false_drops_the_board() -> Result<()> {
let cfg = parse_config("identify = false")?;
assert!(!cfg.identify_board);
// No boards configured at all: the autoconfig window floats.
let chain = cfg.build_zorro_chain()?;
assert_eq!(chain.config_read(crate::zorro::AUTOCONFIG_BASE, 1), 0xFF);
Ok(())
}

#[test]
fn slow_ram_parses_for_a500_trapdoor_memory() -> Result<()> {
let cfg = parse_config(
Expand Down
95 changes: 86 additions & 9 deletions src/zorro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,19 @@ const EC_BASEADDRESS_PHYS: u64 = 0x48;
const EC_BASEADDRESS_LO_PHYS: u64 = 0x4A;
const EC_SHUTUP_PHYS: u64 = 0x4C;

/// "Hacker" manufacturer ID, reserved for uses like this emulator's
/// built-in boards.
pub const HACKER_MANUFACTURER_ID: u16 = 0x07DB;
const PRODUCT_FAST_RAM: u8 = 0x01;
const PRODUCT_Z3_RAM: u8 = 0x02;
/// Copperline's registered Amiga expansion manufacturer ID (dec0de
/// Consulting, 5192 / 0x1448). The same ID labels the real ROMulus
/// flash-ROM board (product 1); the emulator's virtual boards take
/// distinct product numbers under it so tools like identify.library can
/// tell them apart.
pub const COPPERLINE_MANUFACTURER_ID: u16 = 0x1448;
/// The always-present identification board guest software finds (via
/// expansion.library FindConfigDev) to detect that it is running under
/// Copperline. Product 1 is the physical ROMulus board, so the emulator's
/// product numbering starts at 2.
const PRODUCT_COPPERLINE_ID: u8 = 0x02;
const PRODUCT_FAST_RAM: u8 = 0x03;
const PRODUCT_Z3_RAM: u8 = 0x04;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
// "II"/"III" are the bus generations' proper names (roman numerals), not
Expand Down Expand Up @@ -93,7 +101,7 @@ impl BoardSpec {
Self {
name: "fast RAM".into(),
version: ZorroVersion::II,
manufacturer: HACKER_MANUFACTURER_ID,
manufacturer: COPPERLINE_MANUFACTURER_ID,
product: PRODUCT_FAST_RAM,
serial: 0,
size_bytes,
Expand All @@ -108,7 +116,7 @@ impl BoardSpec {
Self {
name: "Zorro III RAM".into(),
version: ZorroVersion::III,
manufacturer: HACKER_MANUFACTURER_ID,
manufacturer: COPPERLINE_MANUFACTURER_ID,
product: PRODUCT_Z3_RAM,
serial: 0,
size_bytes,
Expand All @@ -118,6 +126,29 @@ impl BoardSpec {
}
}

/// The Copperline identification board: a tiny, inert Zorro II board
/// kept on the autoconfig chain so guest software can detect the
/// emulator by finding manufacturer [`COPPERLINE_MANUFACTURER_ID`] /
/// product [`PRODUCT_COPPERLINE_ID`] (e.g. identify.library calling
/// FindConfigDev). It is the smallest legal Zorro II size, is left out
/// of the free memory list, and never autoboots, so it does not perturb
/// the machine's memory map. Its serial number carries the running
/// Copperline version (see [`copperline_ident_serial`]) so a tool can
/// report the exact version, not just the emulator name.
pub fn copperline_id() -> Self {
Self {
name: "Copperline".into(),
version: ZorroVersion::II,
manufacturer: COPPERLINE_MANUFACTURER_ID,
product: PRODUCT_COPPERLINE_ID,
serial: copperline_ident_serial(),
size_bytes: 0x1_0000,
backing: BoardBacking::Ram,
memlist: false,
diag_vec: None,
}
}

/// The A2091 SCSI controller board: the DMAC supplies the autoconfig
/// identity (Commodore West Chester, product 3) with a valid DiagArea
/// vector pointing at the boot ROM, which appears at $2000 in the
Expand Down Expand Up @@ -550,6 +581,23 @@ fn parse_board_size(s: &str) -> Result<usize> {
Ok(bytes as usize)
}

/// Pack a `major.minor.patch` version string into a 32-bit autoconfig
/// serial number: byte 2 holds major, byte 1 minor, byte 0 patch. Missing
/// or non-numeric components (e.g. a `-dev` pre-release suffix) count as 0.
fn pack_version_serial(version: &str) -> u32 {
let mut parts = version.split('.');
let major: u32 = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
let minor: u32 = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
let patch: u32 = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
(major << 16) | (minor << 8) | patch
}

/// The running Copperline version packed into a board serial number, so the
/// identification board ([`BoardSpec::copperline_id`]) can advertise it.
fn copperline_ident_serial() -> u32 {
pack_version_serial(env!("CARGO_PKG_VERSION"))
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -588,9 +636,38 @@ mod tests {
assert_eq!(chain.config_read(AUTOCONFIG_BASE + 2, 1), 0x40);

// er_Product is inverted before being exposed on the physical
// nibbles: product 0x01 appears as 0xFE.
// nibbles: product 0x03 appears as 0xFC.
assert_eq!(chain.config_read(AUTOCONFIG_BASE + 4, 1), 0xF0);
assert_eq!(chain.config_read(AUTOCONFIG_BASE + 6, 1), 0xE0);
assert_eq!(chain.config_read(AUTOCONFIG_BASE + 6, 1), 0xC0);
}

#[test]
fn copperline_id_board_advertises_registered_manufacturer() {
let spec = BoardSpec::copperline_id();
// dec0de Consulting's registered manufacturer ID, with a product
// number distinct from the physical ROMulus board (product 1).
assert_eq!(spec.manufacturer, COPPERLINE_MANUFACTURER_ID);
assert_eq!(spec.manufacturer, 5192);
assert_eq!(spec.product, PRODUCT_COPPERLINE_ID);
// Inert: smallest Zorro II size, out of the free memory list, and
// no autoboot, so it does not perturb the machine's memory map.
assert_eq!(spec.version, ZorroVersion::II);
assert_eq!(spec.size_bytes, 64 * 1024);
assert!(!spec.memlist);
assert!(spec.diag_vec.is_none());
// Serial advertises the running Copperline version.
assert_eq!(spec.serial, copperline_ident_serial());
spec.validate().unwrap();
}

#[test]
fn version_serial_packs_major_minor_patch() {
assert_eq!(pack_version_serial("0.6.0"), 0x0000_0600);
assert_eq!(pack_version_serial("1.2.3"), 0x0001_0203);
assert_eq!(pack_version_serial("0.5"), 0x0000_0500);
assert_eq!(pack_version_serial("12.34.56"), (12 << 16) | (34 << 8) | 56);
// A pre-release suffix on the patch component counts as 0.
assert_eq!(pack_version_serial("0.6.0-dev"), 0x0000_0600);
}

#[test]
Expand Down
Loading