diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 09a63f46..4ea5754e 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -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; diff --git a/docs/zorro.md b/docs/zorro.md index 466e9dda..05f78a72 100644 --- a/docs/zorro.md +++ b/docs/zorro.md @@ -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 @@ -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 @@ -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 @@ -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, } } ``` diff --git a/src/config.rs b/src/config.rs index 55d2cb32..7f8249f5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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, + /// 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. @@ -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, @@ -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 { let mut chain = ZorroChain::default(); if self.fast_ram_bytes > 0 { @@ -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) } } @@ -722,6 +734,9 @@ struct RawConfig { /// `[[zorro]]` board entries, configured in file order. #[serde(default)] zorro: Vec, + /// `identify = false` drops the Copperline identification board from the + /// autoconfig chain (default: present). + identify: Option, } #[derive(Debug, Default, Deserialize)] @@ -1067,6 +1082,7 @@ impl TryFrom for Config { slow_ram_bytes, z3_ram_bytes, zorro_boards, + identify_board: raw.identify.unwrap_or(defaults.identify_board), chipset, agnus_revision, denise_revision, @@ -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( diff --git a/src/zorro.rs b/src/zorro.rs index 578a3e0d..18d1b378 100644 --- a/src/zorro.rs +++ b/src/zorro.rs @@ -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 @@ -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, @@ -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, @@ -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 @@ -550,6 +581,23 @@ fn parse_board_size(s: &str) -> Result { 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::*; @@ -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]