From 3fb75079a291521cbc09e45f02dcec6da39b0d64 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Fri, 31 Jan 2020 12:41:13 +0200 Subject: [PATCH] feat(eeprom): Fix eeprom emulation timeout problem, passing the nintendo eeprom tests Former-commit-id: 5eea390de806d03eee8c043203ae4c57d7355caa --- src/core/cartridge/backup/eeprom.rs | 142 ++++++++++++++++------------ src/core/cartridge/mod.rs | 18 ++-- src/core/dma.rs | 10 ++ src/core/sysbus.rs | 2 +- 4 files changed, 102 insertions(+), 70 deletions(-) diff --git a/src/core/cartridge/backup/eeprom.rs b/src/core/cartridge/backup/eeprom.rs index d145b0d..24bfb58 100644 --- a/src/core/cartridge/backup/eeprom.rs +++ b/src/core/cartridge/backup/eeprom.rs @@ -43,7 +43,7 @@ impl Default for SpiState { } #[derive(Serialize, Deserialize, Clone, Debug)] -struct EepromChip +pub struct EepromChip where M: BackupMemoryInterface, { @@ -57,6 +57,10 @@ where tx_buffer: u64, address: usize, + + chip_ready: bool, // used to signal that the eeprom program was finished + // In real hardware, it takes some time for the values to be programmed into the eeprom, + // But we do it right away. } impl EepromChip @@ -75,6 +79,8 @@ where tx_buffer: 0, address: 0, + + chip_ready: false, } } @@ -98,12 +104,12 @@ where self.tx_count = 0; } - fn clock_data_in(&mut self, si: u8) { + fn clock_data_in(&mut self, address: u32, si: u8) { use SpiInstruction::*; use SpiState::*; // Read the si signal into the rx_buffer - trace!("({:?}) RX bit {}", self.state, si); + trace!("({:?}) addr={:#x} RX bit {}", self.state, address, si); self.rx_buffer = (self.rx_buffer << 1) | (if si & 1 != 0 { 1 } else { 0 }); self.rx_count += 1; @@ -124,15 +130,17 @@ where RxAddress(insn) => { if self.rx_count == 6 { self.address = (self.rx_buffer as usize) * 8; - debug!("recvd address = {:#x}", self.address); + debug!( + "{:?} mode , recvd address = {:#x} (rx_buffer={:#x})", + insn, self.address, self.rx_buffer + ); match insn { Read => { - self.reset_rx_buffer(); - self.reset_tx_buffer(); - next_state = Some(StopBit(insn)); + next_state = Some(StopBit(Read)); } Write => { next_state = Some(RxData); + self.chip_ready = false; self.reset_rx_buffer(); } } @@ -146,7 +154,7 @@ where RxData => { if self.rx_count == 64 { let mut data = self.rx_buffer; - debug!("writing {:x} to memory", data); + debug!("writing {:#x} to memory address {:#x}", data, self.address); for i in 0..8 { self.memory .write(self.address + (7 - i), (data & 0xff) as u8); @@ -157,6 +165,7 @@ where } } StopBit(Write) => { + self.chip_ready = true; self.state = RxInstruction; self.reset_rx_buffer(); self.reset_tx_buffer(); @@ -168,7 +177,7 @@ where } } - fn clock_data_out(&mut self) -> u8 { + fn clock_data_out(&mut self, address: u32) -> u8 { use SpiState::*; let mut next_state = None; @@ -183,22 +192,26 @@ where 0 } TxData => { + let result = ((self.tx_buffer >> 63) & 1) as u8; + self.tx_buffer = self.tx_buffer.wrapping_shl(1); self.tx_count += 1; if self.tx_count == 64 { - self.reset_rx_buffer(); self.reset_tx_buffer(); + self.reset_rx_buffer(); next_state = Some(RxInstruction); - 0 + } + result + } + _ => { + if self.chip_ready { + 1 } else { - let result = ((self.tx_buffer >> 63) & 1) as u8; - self.tx_buffer = self.tx_buffer << 1; - result + 0 } } - _ => 0, }; - trace!("({:?}) TX bit {}", self.state, result); + trace!("({:?}) addr={:#x} TX bit {}", self.state, address, result); if let Some(next_state) = next_state { self.state = next_state; } @@ -206,13 +219,19 @@ where result } - fn data_available(&self) -> bool { - if let SpiState::TxData = self.state { - true - } else { - false + pub(in crate) fn is_transmitting(&self) -> bool { + use SpiState::*; + match self.state { + TxData | TxDummy => true, + _ => false, } } + + pub(in crate) fn reset(&mut self) { + self.state = SpiState::RxInstruction; + self.reset_rx_buffer(); + self.reset_tx_buffer(); + } } /// The Eeprom controller is usually mapped to the top 256 bytes of the cartridge memory @@ -222,7 +241,7 @@ pub struct SpiController where M: BackupMemoryInterface, { - chip: RefCell>, + pub(in crate) chip: RefCell>, } impl SpiController @@ -235,53 +254,23 @@ where } } - pub fn write_half(&mut self, value: u16) { - self.chip.borrow_mut().clock_data_in(value as u8); + pub fn write_half(&mut self, address: u32, value: u16) { + self.chip.borrow_mut().clock_data_in(address, value as u8); } - pub fn read_half(&self) -> u16 { + pub fn read_half(&self, address: u32) -> u16 { let mut chip = self.chip.borrow_mut(); - let bit = chip.clock_data_out() as u16; - if chip.data_available() { - bit - } else { - 0 - } - } - - #[cfg(test)] - fn consume_dummy_cycles(&self) { - // ignore the dummy bits - self.read_half(); - self.read_half(); - self.read_half(); - self.read_half(); - } - - #[cfg(test)] - fn rx_data(&self) -> [u8; 8] { - let mut bytes = [0; 8]; - for byte_index in 0..8 { - let mut byte = 0; - for _ in 0..8 { - let bit = self.read_half() as u8; - byte = (byte << 1) | bit; - } - bytes[byte_index] = byte; - } - bytes + chip.clock_data_out(address) as u16 } } #[cfg(test)] mod tests { + use super::super::super::EEPROM_BASE_ADDR; use super::super::BackupMemoryInterface; use super::*; use bit::BitIndex; - use hexdump; - - use std::io::Write; #[derive(Debug)] struct MockMemory { @@ -298,6 +287,29 @@ mod tests { } } + impl SpiController { + fn consume_dummy_cycles(&self) { + // ignore the dummy bits + self.read_half(EEPROM_BASE_ADDR); + self.read_half(EEPROM_BASE_ADDR); + self.read_half(EEPROM_BASE_ADDR); + self.read_half(EEPROM_BASE_ADDR); + } + + fn rx_data(&self) -> [u8; 8] { + let mut bytes = [0; 8]; + for byte_index in 0..8 { + let mut byte = 0u8; + for _ in 0..8 { + let bit = self.read_half(EEPROM_BASE_ADDR) as u8; + byte = (byte.wrapping_shl(1)) | bit; + } + bytes[byte_index] = byte; + } + bytes + } + } + fn make_mock_memory() -> MockMemory { let mut buffer = vec![0; 512]; buffer[16] = 'T' as u8; @@ -369,12 +381,12 @@ mod tests { // 1 bit "0" - stop bit let stream = make_spi_read_request(2); for half in stream.into_iter() { - spi.write_half(half); + spi.write_half(EEPROM_BASE_ADDR, half); } spi.consume_dummy_cycles(); - assert!(spi.chip.borrow().data_available()); + assert!(spi.chip.borrow().is_transmitting()); let data = spi.rx_data(); @@ -398,7 +410,7 @@ mod tests { // First, lets test a read request let stream = make_spi_read_request(2); for half in stream.into_iter() { - spi.write_half(half); + spi.write_half(EEPROM_BASE_ADDR, half); } spi.consume_dummy_cycles(); let data = spi.rx_data(); @@ -420,7 +432,7 @@ mod tests { bytes[4] = expected[4]; let stream = make_spi_write_request(2, bytes); for half in stream.into_iter() { - spi.write_half(half); + spi.write_half(EEPROM_BASE_ADDR, half); } { @@ -434,10 +446,16 @@ mod tests { // Also lets again read the result let stream = make_spi_read_request(2); for half in stream.into_iter() { - spi.write_half(half); + spi.write_half(EEPROM_BASE_ADDR, half); } spi.consume_dummy_cycles(); let data = spi.rx_data(); assert_eq!(expected, &data[0..5]); + { + let chip = spi.chip.borrow(); + assert_eq!(SpiState::RxInstruction, chip.state); + assert_eq!(0, chip.rx_count); + assert_eq!(0, chip.tx_count); + } } } diff --git a/src/core/cartridge/mod.rs b/src/core/cartridge/mod.rs index 157eeac..106603e 100644 --- a/src/core/cartridge/mod.rs +++ b/src/core/cartridge/mod.rs @@ -100,7 +100,7 @@ pub struct Cartridge { pub header: CartridgeHeader, bytes: Box<[u8]>, size: usize, - backup: BackupMedia, + pub(in crate) backup: BackupMedia, } fn load_rom(path: &Path) -> GBAResult> { @@ -199,7 +199,7 @@ fn detect_backup_type(bytes: &[u8]) -> Option { use super::sysbus::consts::*; -const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00; +pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00; impl Bus for Cartridge { fn read_8(&self, addr: Addr) -> u8 { @@ -221,9 +221,11 @@ impl Bus for Cartridge { } fn read_16(&self, addr: u32) -> u16 { - if addr & 0xff000000 == GAMEPAK_WS2_HI && (self.bytes.len() <= 16*1024*1024 || addr >= EEPROM_BASE_ADDR) { + if addr & 0xff000000 == GAMEPAK_WS2_HI + && (self.bytes.len() <= 16 * 1024 * 1024 || addr >= EEPROM_BASE_ADDR) + { if let BackupMedia::Eeprom(spi) = &self.backup { - return spi.read_half(); + return spi.read_half(addr); } } self.default_read_16(addr) @@ -236,14 +238,16 @@ impl Bus for Cartridge { BackupMedia::Sram(memory) => memory.write((addr & 0x7FFF) as usize, value), _ => {} }, - _ => {}, // TODO allow the debugger to write + _ => {} // TODO allow the debugger to write }; } fn write_16(&mut self, addr: u32, value: u16) { - if addr & 0xff000000 == GAMEPAK_WS2_HI && (self.bytes.len() <= 16*1024*1024 || addr >= EEPROM_BASE_ADDR) { + if addr & 0xff000000 == GAMEPAK_WS2_HI + && (self.bytes.len() <= 16 * 1024 * 1024 || addr >= EEPROM_BASE_ADDR) + { if let BackupMedia::Eeprom(spi) = &mut self.backup { - return spi.write_half(value); + return spi.write_half(addr, value); } } self.default_write_16(addr, value); diff --git a/src/core/dma.rs b/src/core/dma.rs index f197d0d..ddda31f 100644 --- a/src/core/dma.rs +++ b/src/core/dma.rs @@ -1,3 +1,4 @@ +use super::cartridge::BackupMedia; use super::iodev::consts::{REG_FIFO_A, REG_FIFO_B}; use super::sysbus::SysBus; use super::{Bus, Interrupt, IrqBitmask}; @@ -129,6 +130,15 @@ impl DmaChannel { } fn xfer(&mut self, sb: &mut SysBus, irqs: &mut IrqBitmask) { + if self.id == 3 { + if let BackupMedia::Eeprom(eeprom) = &mut sb.cartridge.backup { + // this might be a eeprom request, so we need to reset the eeprom state machine if its dirty (due to bad behaving games, or tests roms) + if !eeprom.chip.borrow().is_transmitting() { + eeprom.chip.borrow_mut().reset(); + } + } + } + let word_size = if self.ctrl.is_32bit() { 4 } else { 2 }; let count = match self.internal.count { 0 => match self.id { diff --git a/src/core/sysbus.rs b/src/core/sysbus.rs index 994d0c0..ce91f70 100644 --- a/src/core/sysbus.rs +++ b/src/core/sysbus.rs @@ -115,7 +115,7 @@ pub struct SysBus { bios: BoxedMemory, onboard_work_ram: BoxedMemory, internal_work_ram: BoxedMemory, - cartridge: Cartridge, + pub cartridge: Cartridge, dummy: DummyBus, pub trace_access: bool,