From 250c3b38dafd3eb5b5f343ec05e93ac565ec136b Mon Sep 17 00:00:00 2001 From: Bernie Innocenti Date: Thu, 23 Jul 2026 23:04:34 +0900 Subject: [PATCH 1/2] zorro_device: let bus-master DMA reach motherboard and accelerator RAM The Zorro DMA address decode stopped at chip, slow, and Zorro-board RAM, so a bus master's reads of the motherboard (mb_ram) and CPU-slot (accel_ram) banks returned the 0xFF unmapped sentinel and its writes were dropped. A Zorro III master like the A4091 drives full 32-bit addresses and reaches those banks; with the A4000 profile fitting its stock RAM there, the guest allocates the driver's DMA buffers in motherboard RAM, so the SCSI bus scan read garbage and found no units. Decode both banks in all four dma_read/write byte and word helpers, next to the slow-RAM arm. Co-Authored-By: Claude Opus 4.8 --- src/zorro_device.rs | 78 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/src/zorro_device.rs b/src/zorro_device.rs index 417b5fd..9f61d1e 100644 --- a/src/zorro_device.rs +++ b/src/zorro_device.rs @@ -12,14 +12,17 @@ //! [`DeviceHost`] is the narrow host-services view a device is handed on every //! call. Today it exposes guest memory for DMA; capability hooks (CD audio, //! networking) are added alongside the devices that need them. It also owns the -//! single 24-bit DMA address decode (chip / slow / Zorro-board RAM) that the -//! A2091 and CDTV bus masters previously each open-coded. +//! single DMA address decode (chip / slow / motherboard / accelerator / +//! Zorro-board RAM) that the A2091 and CDTV bus masters previously each +//! open-coded. Zorro II masters only drive the low 24 bits; a Zorro III master +//! like the A4091 reaches the 32-bit motherboard and accelerator banks too. use crate::chipset::paula::CdAudioRing; -use crate::memory::{Memory, SLOW_RAM_BASE}; +use crate::memory::{Memory, ACCEL_RAM_BASE, SLOW_RAM_BASE}; -/// 24-bit DMA bus-master word read: the chip / slow / Zorro-board RAM decode a -/// Zorro DMA controller sees. Returns `None` when the (word-aligned) address is +/// DMA bus-master word read: the chip / slow / motherboard / accelerator / +/// Zorro-board RAM decode a Zorro DMA controller sees. Returns `None` when the +/// (word-aligned) address is /// not backed by RAM, leaving the unmapped sentinel and warn policy to the /// caller. Word reads require both bytes in range, matching the hardware word /// access; the last odd byte of a region therefore does not satisfy a word. @@ -33,6 +36,16 @@ pub(crate) fn dma_read_word(mem: &Memory, addr: u32) -> Option { let o = a - slow; return Some((u16::from(mem.slow_ram[o]) << 8) | u16::from(mem.slow_ram[o + 1])); } + let mb = mem.mb_ram_base() as usize; + if a >= mb && a + 1 < mb + mem.mb_ram.len() { + let o = a - mb; + return Some((u16::from(mem.mb_ram[o]) << 8) | u16::from(mem.mb_ram[o + 1])); + } + let accel = ACCEL_RAM_BASE as usize; + if a >= accel && a + 1 < accel + mem.accel_ram.len() { + let o = a - accel; + return Some((u16::from(mem.accel_ram[o]) << 8) | u16::from(mem.accel_ram[o + 1])); + } if let Some((board, off)) = mem.zorro.region_at(addr, 2) { let ram = mem.zorro.board_ram(board); return Some((u16::from(ram[off]) << 8) | u16::from(ram[off + 1])); @@ -56,6 +69,20 @@ pub(crate) fn dma_write_word(mem: &mut Memory, addr: u32, w: u16) -> bool { mem.slow_ram[o + 1] = w as u8; return true; } + let mb = mem.mb_ram_base() as usize; + if a >= mb && a + 1 < mb + mem.mb_ram.len() { + let o = a - mb; + mem.mb_ram[o] = (w >> 8) as u8; + mem.mb_ram[o + 1] = w as u8; + return true; + } + let accel = ACCEL_RAM_BASE as usize; + if a >= accel && a + 1 < accel + mem.accel_ram.len() { + let o = a - accel; + mem.accel_ram[o] = (w >> 8) as u8; + mem.accel_ram[o + 1] = w as u8; + return true; + } if let Some((board, off)) = mem.zorro.region_at(addr, 2) { let ram = mem.zorro.board_ram_mut(board); ram[off] = (w >> 8) as u8; @@ -77,6 +104,14 @@ pub(crate) fn dma_read_byte(mem: &Memory, addr: u32) -> Option { if a >= slow && a < slow + mem.slow_ram.len() { return Some(mem.slow_ram[a - slow]); } + let mb = mem.mb_ram_base() as usize; + if a >= mb && a < mb + mem.mb_ram.len() { + return Some(mem.mb_ram[a - mb]); + } + let accel = ACCEL_RAM_BASE as usize; + if a >= accel && a < accel + mem.accel_ram.len() { + return Some(mem.accel_ram[a - accel]); + } if let Some((board, off)) = mem.zorro.region_at(addr, 1) { return Some(mem.zorro.board_ram(board)[off]); } @@ -96,6 +131,16 @@ pub(crate) fn dma_write_byte(mem: &mut Memory, addr: u32, b: u8) -> bool { mem.slow_ram[a - slow] = b; return true; } + let mb = mem.mb_ram_base() as usize; + if a >= mb && a < mb + mem.mb_ram.len() { + mem.mb_ram[a - mb] = b; + return true; + } + let accel = ACCEL_RAM_BASE as usize; + if a >= accel && a < accel + mem.accel_ram.len() { + mem.accel_ram[a - accel] = b; + return true; + } if let Some((board, off)) = mem.zorro.region_at(addr, 1) { mem.zorro.board_ram_mut(board)[off] = b; return true; @@ -506,6 +551,29 @@ mod tests { assert_eq!(mem.slow_ram[0x20], 0x12); } + #[test] + fn dma_round_trips_motherboard_and_accelerator_ram() { + // A Zorro III bus master (the A4091) reaches the 32-bit motherboard and + // accelerator fast-RAM banks. The A4000 profile fits its stock RAM + // there, so a driver's DMA buffers land in these banks; a decode that + // stopped at slow RAM read them back as 0xFF and dropped writes. + let mut mem = mem_with(0x100, 0x100); + mem.mb_ram = vec![0u8; 0x100]; + mem.accel_ram = vec![0u8; 0x100]; + + let mb = mem.mb_ram_base() as u32; + assert!(dma_write_word(&mut mem, mb + 0x40, 0xCAFE)); + assert_eq!(dma_read_word(&mem, mb + 0x40), Some(0xCAFE)); + assert!(dma_write_byte(&mut mem, mb + 0x42, 0x5A)); + assert_eq!(dma_read_byte(&mem, mb + 0x42), Some(0x5A)); + + let accel = ACCEL_RAM_BASE as u32; + assert!(dma_write_word(&mut mem, accel + 0x08, 0xF00D)); + assert_eq!(dma_read_word(&mem, accel + 0x08), Some(0xF00D)); + assert!(dma_write_byte(&mut mem, accel + 0x0A, 0xA5)); + assert_eq!(dma_read_byte(&mem, accel + 0x0A), Some(0xA5)); + } + #[test] fn word_access_needs_both_bytes_in_range() { // chip_ram is 0x100 bytes: the last word starts at 0xFE (0xFE,0xFF in From ebf7d6dad122997f6869419c9e3d14e57ca40b21 Mon Sep 17 00:00:00 2001 From: Bernie Innocenti Date: Thu, 23 Jul 2026 23:23:34 +0900 Subject: [PATCH 2/2] zorro_device: bound bank DMA by offset to avoid address overflow The motherboard/accelerator arms tested `a + 1 < base + len`, which wraps when the DMA address is at the top of the space on a 32-bit usize host. Since the `a >= base` guard runs first, bound the access by the offset `a - base` instead, which cannot overflow. Co-Authored-By: Claude Opus 4.8 --- src/zorro_device.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/zorro_device.rs b/src/zorro_device.rs index 9f61d1e..630539f 100644 --- a/src/zorro_device.rs +++ b/src/zorro_device.rs @@ -37,12 +37,12 @@ pub(crate) fn dma_read_word(mem: &Memory, addr: u32) -> Option { return Some((u16::from(mem.slow_ram[o]) << 8) | u16::from(mem.slow_ram[o + 1])); } let mb = mem.mb_ram_base() as usize; - if a >= mb && a + 1 < mb + mem.mb_ram.len() { + if a >= mb && a - mb + 1 < mem.mb_ram.len() { let o = a - mb; return Some((u16::from(mem.mb_ram[o]) << 8) | u16::from(mem.mb_ram[o + 1])); } let accel = ACCEL_RAM_BASE as usize; - if a >= accel && a + 1 < accel + mem.accel_ram.len() { + if a >= accel && a - accel + 1 < mem.accel_ram.len() { let o = a - accel; return Some((u16::from(mem.accel_ram[o]) << 8) | u16::from(mem.accel_ram[o + 1])); } @@ -70,14 +70,14 @@ pub(crate) fn dma_write_word(mem: &mut Memory, addr: u32, w: u16) -> bool { return true; } let mb = mem.mb_ram_base() as usize; - if a >= mb && a + 1 < mb + mem.mb_ram.len() { + if a >= mb && a - mb + 1 < mem.mb_ram.len() { let o = a - mb; mem.mb_ram[o] = (w >> 8) as u8; mem.mb_ram[o + 1] = w as u8; return true; } let accel = ACCEL_RAM_BASE as usize; - if a >= accel && a + 1 < accel + mem.accel_ram.len() { + if a >= accel && a - accel + 1 < mem.accel_ram.len() { let o = a - accel; mem.accel_ram[o] = (w >> 8) as u8; mem.accel_ram[o + 1] = w as u8; @@ -105,11 +105,11 @@ pub(crate) fn dma_read_byte(mem: &Memory, addr: u32) -> Option { return Some(mem.slow_ram[a - slow]); } let mb = mem.mb_ram_base() as usize; - if a >= mb && a < mb + mem.mb_ram.len() { + if a >= mb && a - mb < mem.mb_ram.len() { return Some(mem.mb_ram[a - mb]); } let accel = ACCEL_RAM_BASE as usize; - if a >= accel && a < accel + mem.accel_ram.len() { + if a >= accel && a - accel < mem.accel_ram.len() { return Some(mem.accel_ram[a - accel]); } if let Some((board, off)) = mem.zorro.region_at(addr, 1) { @@ -132,12 +132,12 @@ pub(crate) fn dma_write_byte(mem: &mut Memory, addr: u32, b: u8) -> bool { return true; } let mb = mem.mb_ram_base() as usize; - if a >= mb && a < mb + mem.mb_ram.len() { + if a >= mb && a - mb < mem.mb_ram.len() { mem.mb_ram[a - mb] = b; return true; } let accel = ACCEL_RAM_BASE as usize; - if a >= accel && a < accel + mem.accel_ram.len() { + if a >= accel && a - accel < mem.accel_ram.len() { mem.accel_ram[a - accel] = b; return true; } @@ -598,9 +598,16 @@ mod tests { #[test] fn unmapped_addresses_report_none() { - let mem = mem_with(0x100, 0x100); + let mut mem = mem_with(0x100, 0x100); // Between chip top and slow base: nothing backs it. assert_eq!(dma_read_word(&mem, 0x0040_0000), None); assert_eq!(dma_read_byte(&mem, 0x0040_0000), None); + + // The top of the 32-bit space is above the fitted banks; the offset + // bound must reject it without overflowing `a + 1`. + mem.mb_ram = vec![0u8; 0x100]; + mem.accel_ram = vec![0u8; 0x100]; + assert_eq!(dma_read_word(&mem, 0xFFFF_FFFF), None); + assert_eq!(dma_read_byte(&mem, 0xFFFF_FFFF), None); } }