From 51a79d68daacbfc7cf16c2290adb881b5dde092b Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Tue, 28 Jan 2020 10:25:52 +0200 Subject: [PATCH] feat(eeprom): Start working on Eeprom emulation Former-commit-id: b600daa044e1bdf85fb022017517585a9aca4082 --- src/core/backup/eeprom.rs | 444 ++++++++++++++++++++++++++++++++++++++ src/core/backup/flash.rs | 2 +- src/core/backup/mod.rs | 12 +- src/core/bus.rs | 10 + src/core/cartridge.rs | 38 +++- src/core/sysbus.rs | 40 ++-- 6 files changed, 518 insertions(+), 28 deletions(-) create mode 100644 src/core/backup/eeprom.rs diff --git a/src/core/backup/eeprom.rs b/src/core/backup/eeprom.rs new file mode 100644 index 0000000..bc42f8c --- /dev/null +++ b/src/core/backup/eeprom.rs @@ -0,0 +1,444 @@ +use super::{BackupMemoryInterface}; + +use num::FromPrimitive; +use serde::{Deserialize, Serialize}; + +use std::cell::RefCell; + +#[derive(Debug)] +pub enum EepromSize { + Eeprom512, + Eeprom8k, +} + +impl Into for EepromSize { + fn into(self) -> usize { + match self { + EepromSize::Eeprom512 => 0x0200, + EepromSize::Eeprom8k => 0x2000, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Primitive, PartialEq, Copy, Clone)] +enum SpiInstruction { + Read = 0b011, + Write = 0b010, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)] +enum SpiState { + RxInstruction, + RxAddress(SpiInstruction), + StopBit(SpiInstruction), + TxDummy(SpiInstruction), + TxData(usize), + RxData(usize), +} + +impl Default for SpiState { + fn default() -> SpiState { + SpiState::RxInstruction + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +struct EepromChip +where + M: BackupMemoryInterface, +{ + memory: M, + + state: SpiState, + rx_count: usize, + rx_buffer: u64, + + tx_count: usize, + tx_buffer: u8, + + address: usize, +} + +impl EepromChip +where + M: BackupMemoryInterface, +{ + fn new(memory: M) -> EepromChip { + EepromChip { + memory: memory, + state: SpiState::RxInstruction, + + rx_count: 0, + rx_buffer: 0, + + tx_count: 0, + tx_buffer: 0, + + address: 0, + } + } + + fn rx(&mut self, bit: u8) { + // data is receieved MSB first + let bit = bit & 1; + self.rx_buffer = (self.rx_buffer << 1) | (if bit & 1 != 0 { 1 } else { 0 }); + self.rx_count += 1; + } + + fn tx(&mut self) -> u8 { + // data is transmitted MSB first + let bit = self.tx_buffer >> 7; + self.tx_buffer = self.tx_buffer << 1; + self.tx_count += 1; + + bit + } + + fn reset_rx_buffer(&mut self) { + self.rx_buffer = 0; + self.rx_count = 0; + } + + fn reset_tx_buffer(&mut self) { + self.tx_buffer = 0; + self.tx_count = 0; + } + + fn clock_data_in(&mut self, si: u8) { + use SpiInstruction::*; + use SpiState::*; + + // Read the si signal into the rx_buffer + self.rx(si); + + match self.state { + RxInstruction => { + // If instruction was recvd, proceed to recv the address + if self.rx_count >= 2 { + let insn = SpiInstruction::from_u64(self.rx_buffer).expect(&format!( + "invalid spi command {:#010b}", + self.rx_buffer as u8 + )); + self.state = RxAddress(insn); + self.reset_rx_buffer(); + } + } + RxAddress(insn) => { + if self.rx_count == 6 { + self.address = (self.rx_buffer as usize) * 8; + self.state = match insn { + Read => StopBit(insn), + Write => RxData(0), + }; + self.reset_rx_buffer(); + } + } + StopBit(Read) => { + if si != 0 { + panic!( + "SPI Read - bit 0 was expected for command termination (debug={:?})", + *self + ); + } + self.state = TxDummy(Read); + self.reset_rx_buffer(); + self.reset_tx_buffer(); + } + RxData(rx_bytes) => { + if rx_bytes < 8 { + if self.rx_count % 8 == 0 { + if rx_bytes + 1 == 8 { + self.state = StopBit(Write); + self.reset_rx_buffer(); + } else { + let byte = (self.rx_buffer & 0xff) as u8; + self.memory.write(self.address, byte); + self.reset_rx_buffer(); + self.address += 1; + self.state = RxData(rx_bytes + 1); + } + } + } + } + StopBit(Write) => { + if si != 0 { + panic!( + "SPI Write - bit 0 was expected for command termination (debug={:?})", + *self + ); + } + self.state = RxInstruction; + self.reset_rx_buffer(); + self.reset_tx_buffer(); + } + _ => {} + } + } + + fn clock_data_out(&mut self) -> u8 { + use SpiState::*; + + match self.state { + TxDummy(insn) => { + let bit = self.tx(); + if self.tx_count == 4 { + self.state = TxData(0); + self.reset_tx_buffer(); + } + bit + } + TxData(tx_bytes) => { + if tx_bytes < 8 { + if self.tx_count % 8 == 0 { + let byte = self.memory.read(self.address); + self.tx_buffer = byte; + self.address += 1; + self.state = TxData(tx_bytes + 1); + } + self.tx() + } else { + self.state = RxInstruction; + self.reset_rx_buffer(); + self.reset_tx_buffer(); + 0 + } + } + _ => self.tx(), + } + } + + fn data_available(&self) -> bool { + if let SpiState::TxData(_) = self.state { + true + } else { + false + } + } +} + +/// The Eeprom controller is usually mapped to the top 256 bytes of the cartridge memory +/// Eeprom controller can programmed with DMA accesses in 16bit mode +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct SpiController +where + M: BackupMemoryInterface, +{ + chip: RefCell>, +} + +impl SpiController +where + M: BackupMemoryInterface, +{ + pub fn new(m: M) -> SpiController { + SpiController { + chip: RefCell::new(EepromChip::new(m)), + } + } + + pub fn write_half(&mut self, value: u16) { + self.chip.borrow_mut().clock_data_in(value as u8); + } + + pub fn read_half(&self) -> 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 + } +} + +#[cfg(test)] +mod tests { + use super::super::BackupMemoryInterface; + use super::*; + + use bit::BitIndex; + use hexdump; + + use std::io::Write; + + #[derive(Debug)] + struct MockMemory { + buffer: Vec, + } + + impl BackupMemoryInterface for MockMemory { + fn write(&mut self, offset: usize, value: u8) { + self.buffer[offset] = value; + } + + fn read(&self, offset: usize) -> u8 { + self.buffer[offset] + } + } + + fn make_mock_memory() -> MockMemory { + let mut buffer = vec![0; 512]; + buffer[16] = 'T' as u8; + buffer[17] = 'E' as u8; + buffer[18] = 'S' as u8; + buffer[19] = 'T' as u8; + buffer[20] = '!' as u8; + + MockMemory { buffer } + } + + fn make_spi_read_request(address: usize) -> Vec { + let address = (address & 0x3f) as u16; + let mut bit_stream = vec![0; 2 + 6 + 1]; + + // 2 bits "11" (Read Request) + bit_stream[0] = 1; + bit_stream[1] = 1; + // 6 bits eeprom address + bit_stream[2] = if address.bit(5) { 1 } else { 0 }; + bit_stream[3] = if address.bit(4) { 1 } else { 0 }; + bit_stream[4] = if address.bit(3) { 1 } else { 0 }; + bit_stream[5] = if address.bit(2) { 1 } else { 0 }; + bit_stream[6] = if address.bit(1) { 1 } else { 0 }; + bit_stream[7] = if address.bit(0) { 1 } else { 0 }; + // 1 stop bit + bit_stream[8] = 0; + + bit_stream + } + + fn make_spi_write_request(address: usize, value: [u8; 8]) -> Vec { + let address = (address & 0x3f) as u16; + let mut bit_stream = vec![0; 2 + 6 + 64 + 1]; + + // 2 bits "10" (Write Request) + bit_stream[0] = 1; + bit_stream[1] = 0; + + // 6 bits eeprom address + bit_stream[2] = if address.bit(5) { 1 } else { 0 }; + bit_stream[3] = if address.bit(4) { 1 } else { 0 }; + bit_stream[4] = if address.bit(3) { 1 } else { 0 }; + bit_stream[5] = if address.bit(2) { 1 } else { 0 }; + bit_stream[6] = if address.bit(1) { 1 } else { 0 }; + bit_stream[7] = if address.bit(0) { 1 } else { 0 }; + + // encode the 64bit value + for i in 0..8 { + let mut byte = value[i]; + for j in 0..8 { + let bit = byte >> 7; + byte = byte << 1; + bit_stream[8 + i * 8 + j] = bit as u16; + } + } + + // 1 stop bit + bit_stream[2 + 6 + 64] = 0; + + bit_stream + } + + #[test] + fn test_spi_read() { + let memory = make_mock_memory(); + let mut spi = SpiController::::new(memory); + + // 1 bit "0" - stop bit + let stream = make_spi_read_request(2); + for half in stream.into_iter() { + spi.write_half(half); + } + + spi.consume_dummy_cycles(); + + assert!(spi.chip.borrow().data_available()); + + let data = spi.rx_data(); + + assert_eq!(data[0], 'T' as u8); + assert_eq!(data[1], 'E' as u8); + assert_eq!(data[2], 'S' as u8); + assert_eq!(data[3], 'T' as u8); + assert_eq!(data[4], '!' as u8); + + assert_eq!(spi.chip.borrow().state, SpiState::RxInstruction); + assert_eq!(spi.chip.borrow().rx_count, 0); + } + + #[test] + fn test_spi_read_write() { + let memory = make_mock_memory(); + let mut spi = SpiController::::new(memory); + + let expected = "Work.".as_bytes(); + + // First, lets test a read request + let stream = make_spi_read_request(2); + for half in stream.into_iter() { + spi.write_half(half); + } + spi.consume_dummy_cycles(); + let data = spi.rx_data(); + + assert_eq!("TEST!".as_bytes(), &data[0..5]); + { + let chip = spi.chip.borrow(); + assert_eq!(SpiState::RxInstruction, chip.state); + assert_eq!(0, chip.rx_count); + assert_eq!(0, chip.rx_count); + } + + // Now, modify the eeprom data with a write request + let mut bytes: [u8; 8] = [0; 8]; + bytes[0] = expected[0]; + bytes[1] = expected[1]; + bytes[2] = expected[2]; + bytes[3] = expected[3]; + bytes[4] = expected[4]; + let stream = make_spi_write_request(2, bytes); + for half in stream.into_iter() { + spi.write_half(half); + } + + { + let chip = spi.chip.borrow(); + assert_eq!(expected, &chip.memory.buffer[0x10..0x15]); + assert_eq!(SpiState::RxInstruction, chip.state); + assert_eq!(0, chip.rx_count); + assert_eq!(0, chip.tx_count); + } + + // Also lets again read the result + let stream = make_spi_read_request(2); + for half in stream.into_iter() { + spi.write_half(half); + } + spi.consume_dummy_cycles(); + let data = spi.rx_data(); + assert_eq!(expected, &data[0..5]); + } +} diff --git a/src/core/backup/flash.rs b/src/core/backup/flash.rs index e8b3345..ab179c8 100644 --- a/src/core/backup/flash.rs +++ b/src/core/backup/flash.rs @@ -1,4 +1,4 @@ -use super::BackupMemory; +use super::{BackupMemory, BackupMemoryInterface}; use num::FromPrimitive; use serde::{Deserialize, Serialize}; diff --git a/src/core/backup/mod.rs b/src/core/backup/mod.rs index fc545e1..90ddbf1 100644 --- a/src/core/backup/mod.rs +++ b/src/core/backup/mod.rs @@ -9,6 +9,7 @@ use serde::ser::{Serialize, SerializeStruct, Serializer}; use crate::util::write_bin_file; +pub mod eeprom; pub mod flash; pub const BACKUP_FILE_EXT: &'static str = "sav"; @@ -22,6 +23,11 @@ pub enum BackupType { Flash1M = 4, } +pub trait BackupMemoryInterface: Sized + fmt::Debug { + fn write(&mut self, offset: usize, value: u8); + fn read(&self, offset: usize) -> u8; +} + #[derive(Debug)] pub struct BackupMemory { size: usize, @@ -105,14 +111,16 @@ impl BackupMemory { buffer: buffer, } } +} - pub fn write(&mut self, offset: usize, value: u8) { +impl BackupMemoryInterface for BackupMemory { + fn write(&mut self, offset: usize, value: u8) { self.buffer[offset] = value; self.file.seek(SeekFrom::Start(offset as u64)).unwrap(); self.file.write_all(&[value]).unwrap(); } - pub fn read(&self, offset: usize) -> u8 { + fn read(&self, offset: usize) -> u8 { self.buffer[offset] } } diff --git a/src/core/bus.rs b/src/core/bus.rs index 622b53c..2663541 100644 --- a/src/core/bus.rs +++ b/src/core/bus.rs @@ -6,6 +6,11 @@ pub trait Bus { } fn read_16(&self, addr: Addr) -> u16 { + self.default_read_16(addr) + } + + #[inline(always)] + fn default_read_16(&self, addr: Addr) -> u16 { self.read_8(addr) as u16 | (self.read_8(addr + 1) as u16) << 8 } @@ -17,6 +22,11 @@ pub trait Bus { } fn write_16(&mut self, addr: Addr, value: u16) { + self.default_write_16(addr, value) + } + + #[inline(always)] + fn default_write_16(&mut self, addr: Addr, value: u16) { self.write_8(addr, (value & 0xff) as u8); self.write_8(addr + 1, ((value >> 8) & 0xff) as u8); } diff --git a/src/core/cartridge.rs b/src/core/cartridge.rs index 5f76068..9f6629f 100644 --- a/src/core/cartridge.rs +++ b/src/core/cartridge.rs @@ -11,8 +11,9 @@ use zip::ZipArchive; use super::super::util::read_bin_file; use super::{Addr, Bus, GBAResult}; +use super::backup::eeprom::*; use super::backup::flash::*; -use super::backup::{BackupMemory, BackupType, BACKUP_FILE_EXT}; +use super::backup::{BackupMemory, BackupMemoryInterface, BackupType, BACKUP_FILE_EXT}; /// From GBATEK /// @@ -77,7 +78,7 @@ impl CartridgeHeader { pub enum BackupMedia { Sram(BackupMemory), Flash(Flash), - Eeprom, + Eeprom(SpiController), Undetected, } @@ -87,7 +88,7 @@ impl BackupMedia { match self { Sram(..) => "SRAM", Flash(..) => "FLASH", - Eeprom => "EEPROM", + Eeprom(..) => "EEPROM", Undetected => "Undetected", } } @@ -147,6 +148,8 @@ impl Cartridge { } else { BackupMedia::Undetected }; + + println!("Header: {:?}", header); println!("Backup: {}", backup.type_string()); Cartridge { @@ -169,7 +172,9 @@ fn create_backup(bytes: &[u8], rom_path: &Path) -> BackupMedia { BackupMedia::Flash(Flash::new(backup_path, FlashSize::Flash128k)) } BackupType::Sram => BackupMedia::Sram(BackupMemory::new(0x8000, backup_path)), - BackupType::Eeprom => BackupMedia::Eeprom, + BackupType::Eeprom => { + BackupMedia::Eeprom(SpiController::new(BackupMemory::new(0x200, backup_path))) + } } } else { BackupMedia::Undetected @@ -191,7 +196,9 @@ fn detect_backup_type(bytes: &[u8]) -> Option { return None; } -use super::sysbus::{SRAM_HI, SRAM_LO}; +use super::sysbus::consts::*; + +const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00; impl Bus for Cartridge { fn read_8(&self, addr: Addr) -> u8 { @@ -212,15 +219,32 @@ 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 let BackupMedia::Eeprom(spi) = &self.backup { + return spi.read_half(); + } + } + self.default_read_16(addr) + } + fn write_8(&mut self, addr: u32, value: u8) { - let offset = (addr & 0x01ff_ffff) as usize; match addr & 0xff000000 { SRAM_LO | SRAM_HI => match &mut self.backup { BackupMedia::Flash(flash) => flash.write(addr, value), BackupMedia::Sram(memory) => memory.write((addr & 0x7FFF) as usize, value), _ => {} }, - _ => self.bytes[offset] = value, + _ => {}, // 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 let BackupMedia::Eeprom(spi) = &mut self.backup { + return spi.write_half(value); + } + } + self.default_write_16(addr, value); + } } diff --git a/src/core/sysbus.rs b/src/core/sysbus.rs index 28bb31b..c6ad891 100644 --- a/src/core/sysbus.rs +++ b/src/core/sysbus.rs @@ -8,24 +8,28 @@ use super::gpu::{GpuState, VIDEO_RAM_SIZE}; use super::iodev::IoDevices; use super::{Addr, Bus}; -const WORK_RAM_SIZE: usize = 256 * 1024; -const INTERNAL_RAM_SIZE: usize = 32 * 1024; +pub mod consts { + pub const WORK_RAM_SIZE: usize = 256 * 1024; + pub const INTERNAL_RAM_SIZE: usize = 32 * 1024; -pub const BIOS_ADDR: u32 = 0x0000_0000; -pub const EWRAM_ADDR: u32 = 0x0200_0000; -pub const IWRAM_ADDR: u32 = 0x0300_0000; -pub const IOMEM_ADDR: u32 = 0x0400_0000; -pub const PALRAM_ADDR: u32 = 0x0500_0000; -pub const VRAM_ADDR: u32 = 0x0600_0000; -pub const OAM_ADDR: u32 = 0x0700_0000; -pub const GAMEPAK_WS0_LO: u32 = 0x0800_0000; -pub const GAMEPAK_WS0_HI: u32 = 0x0900_0000; -pub const GAMEPAK_WS1_LO: u32 = 0x0A00_0000; -pub const GAMEPAK_WS1_HI: u32 = 0x0B00_0000; -pub const GAMEPAK_WS2_LO: u32 = 0x0C00_0000; -pub const GAMEPAK_WS2_HI: u32 = 0x0D00_0000; -pub const SRAM_LO: u32 = 0x0E00_0000; -pub const SRAM_HI: u32 = 0x0F00_0000; + pub const BIOS_ADDR: u32 = 0x0000_0000; + pub const EWRAM_ADDR: u32 = 0x0200_0000; + pub const IWRAM_ADDR: u32 = 0x0300_0000; + pub const IOMEM_ADDR: u32 = 0x0400_0000; + pub const PALRAM_ADDR: u32 = 0x0500_0000; + pub const VRAM_ADDR: u32 = 0x0600_0000; + pub const OAM_ADDR: u32 = 0x0700_0000; + pub const GAMEPAK_WS0_LO: u32 = 0x0800_0000; + pub const GAMEPAK_WS0_HI: u32 = 0x0900_0000; + pub const GAMEPAK_WS1_LO: u32 = 0x0A00_0000; + pub const GAMEPAK_WS1_HI: u32 = 0x0B00_0000; + pub const GAMEPAK_WS2_LO: u32 = 0x0C00_0000; + pub const GAMEPAK_WS2_HI: u32 = 0x0D00_0000; + pub const SRAM_LO: u32 = 0x0E00_0000; + pub const SRAM_HI: u32 = 0x0F00_0000; +} + +use consts::*; #[derive(Debug, Copy, Clone)] pub enum MemoryAccessType { @@ -211,7 +215,7 @@ impl SysBus { // "[{}] Possible write to EEPROM", // Colour::Yellow.bold().paint("warn") // ); - (&mut self.dummy, addr) + (&mut self.cartridge, addr) } SRAM_LO | SRAM_HI => (&mut self.cartridge, addr), _ => {