feat(eeprom): Start working on Eeprom emulation

Former-commit-id: b600daa044e1bdf85fb022017517585a9aca4082
This commit is contained in:
Michel Heily 2020-01-28 10:25:52 +02:00
parent eda3d3e3e7
commit 51a79d68da
6 changed files with 518 additions and 28 deletions

444
src/core/backup/eeprom.rs Normal file
View file

@ -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<usize> 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<M>
where
M: BackupMemoryInterface,
{
memory: M,
state: SpiState,
rx_count: usize,
rx_buffer: u64,
tx_count: usize,
tx_buffer: u8,
address: usize,
}
impl<M> EepromChip<M>
where
M: BackupMemoryInterface,
{
fn new(memory: M) -> EepromChip<M> {
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<M>
where
M: BackupMemoryInterface,
{
chip: RefCell<EepromChip<M>>,
}
impl<M> SpiController<M>
where
M: BackupMemoryInterface,
{
pub fn new(m: M) -> SpiController<M> {
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<u8>,
}
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<u16> {
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<u16> {
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::<MockMemory>::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::<MockMemory>::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]);
}
}

View file

@ -1,4 +1,4 @@
use super::BackupMemory; use super::{BackupMemory, BackupMemoryInterface};
use num::FromPrimitive; use num::FromPrimitive;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View file

@ -9,6 +9,7 @@ use serde::ser::{Serialize, SerializeStruct, Serializer};
use crate::util::write_bin_file; use crate::util::write_bin_file;
pub mod eeprom;
pub mod flash; pub mod flash;
pub const BACKUP_FILE_EXT: &'static str = "sav"; pub const BACKUP_FILE_EXT: &'static str = "sav";
@ -22,6 +23,11 @@ pub enum BackupType {
Flash1M = 4, Flash1M = 4,
} }
pub trait BackupMemoryInterface: Sized + fmt::Debug {
fn write(&mut self, offset: usize, value: u8);
fn read(&self, offset: usize) -> u8;
}
#[derive(Debug)] #[derive(Debug)]
pub struct BackupMemory { pub struct BackupMemory {
size: usize, size: usize,
@ -105,14 +111,16 @@ impl BackupMemory {
buffer: buffer, 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.buffer[offset] = value;
self.file.seek(SeekFrom::Start(offset as u64)).unwrap(); self.file.seek(SeekFrom::Start(offset as u64)).unwrap();
self.file.write_all(&[value]).unwrap(); self.file.write_all(&[value]).unwrap();
} }
pub fn read(&self, offset: usize) -> u8 { fn read(&self, offset: usize) -> u8 {
self.buffer[offset] self.buffer[offset]
} }
} }

View file

@ -6,6 +6,11 @@ pub trait Bus {
} }
fn read_16(&self, addr: Addr) -> u16 { 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 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) { 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, (value & 0xff) as u8);
self.write_8(addr + 1, ((value >> 8) & 0xff) as u8); self.write_8(addr + 1, ((value >> 8) & 0xff) as u8);
} }

View file

@ -11,8 +11,9 @@ use zip::ZipArchive;
use super::super::util::read_bin_file; use super::super::util::read_bin_file;
use super::{Addr, Bus, GBAResult}; use super::{Addr, Bus, GBAResult};
use super::backup::eeprom::*;
use super::backup::flash::*; use super::backup::flash::*;
use super::backup::{BackupMemory, BackupType, BACKUP_FILE_EXT}; use super::backup::{BackupMemory, BackupMemoryInterface, BackupType, BACKUP_FILE_EXT};
/// From GBATEK /// From GBATEK
/// ///
@ -77,7 +78,7 @@ impl CartridgeHeader {
pub enum BackupMedia { pub enum BackupMedia {
Sram(BackupMemory), Sram(BackupMemory),
Flash(Flash), Flash(Flash),
Eeprom, Eeprom(SpiController<BackupMemory>),
Undetected, Undetected,
} }
@ -87,7 +88,7 @@ impl BackupMedia {
match self { match self {
Sram(..) => "SRAM", Sram(..) => "SRAM",
Flash(..) => "FLASH", Flash(..) => "FLASH",
Eeprom => "EEPROM", Eeprom(..) => "EEPROM",
Undetected => "Undetected", Undetected => "Undetected",
} }
} }
@ -147,6 +148,8 @@ impl Cartridge {
} else { } else {
BackupMedia::Undetected BackupMedia::Undetected
}; };
println!("Header: {:?}", header);
println!("Backup: {}", backup.type_string()); println!("Backup: {}", backup.type_string());
Cartridge { Cartridge {
@ -169,7 +172,9 @@ fn create_backup(bytes: &[u8], rom_path: &Path) -> BackupMedia {
BackupMedia::Flash(Flash::new(backup_path, FlashSize::Flash128k)) BackupMedia::Flash(Flash::new(backup_path, FlashSize::Flash128k))
} }
BackupType::Sram => BackupMedia::Sram(BackupMemory::new(0x8000, backup_path)), 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 { } else {
BackupMedia::Undetected BackupMedia::Undetected
@ -191,7 +196,9 @@ fn detect_backup_type(bytes: &[u8]) -> Option<BackupType> {
return None; return None;
} }
use super::sysbus::{SRAM_HI, SRAM_LO}; use super::sysbus::consts::*;
const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00;
impl Bus for Cartridge { impl Bus for Cartridge {
fn read_8(&self, addr: Addr) -> u8 { 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) { fn write_8(&mut self, addr: u32, value: u8) {
let offset = (addr & 0x01ff_ffff) as usize;
match addr & 0xff000000 { match addr & 0xff000000 {
SRAM_LO | SRAM_HI => match &mut self.backup { SRAM_LO | SRAM_HI => match &mut self.backup {
BackupMedia::Flash(flash) => flash.write(addr, value), BackupMedia::Flash(flash) => flash.write(addr, value),
BackupMedia::Sram(memory) => memory.write((addr & 0x7FFF) as usize, 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);
}
} }

View file

@ -8,24 +8,28 @@ use super::gpu::{GpuState, VIDEO_RAM_SIZE};
use super::iodev::IoDevices; use super::iodev::IoDevices;
use super::{Addr, Bus}; use super::{Addr, Bus};
const WORK_RAM_SIZE: usize = 256 * 1024; pub mod consts {
const INTERNAL_RAM_SIZE: usize = 32 * 1024; 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 BIOS_ADDR: u32 = 0x0000_0000;
pub const EWRAM_ADDR: u32 = 0x0200_0000; pub const EWRAM_ADDR: u32 = 0x0200_0000;
pub const IWRAM_ADDR: u32 = 0x0300_0000; pub const IWRAM_ADDR: u32 = 0x0300_0000;
pub const IOMEM_ADDR: u32 = 0x0400_0000; pub const IOMEM_ADDR: u32 = 0x0400_0000;
pub const PALRAM_ADDR: u32 = 0x0500_0000; pub const PALRAM_ADDR: u32 = 0x0500_0000;
pub const VRAM_ADDR: u32 = 0x0600_0000; pub const VRAM_ADDR: u32 = 0x0600_0000;
pub const OAM_ADDR: u32 = 0x0700_0000; pub const OAM_ADDR: u32 = 0x0700_0000;
pub const GAMEPAK_WS0_LO: u32 = 0x0800_0000; pub const GAMEPAK_WS0_LO: u32 = 0x0800_0000;
pub const GAMEPAK_WS0_HI: u32 = 0x0900_0000; pub const GAMEPAK_WS0_HI: u32 = 0x0900_0000;
pub const GAMEPAK_WS1_LO: u32 = 0x0A00_0000; pub const GAMEPAK_WS1_LO: u32 = 0x0A00_0000;
pub const GAMEPAK_WS1_HI: u32 = 0x0B00_0000; pub const GAMEPAK_WS1_HI: u32 = 0x0B00_0000;
pub const GAMEPAK_WS2_LO: u32 = 0x0C00_0000; pub const GAMEPAK_WS2_LO: u32 = 0x0C00_0000;
pub const GAMEPAK_WS2_HI: u32 = 0x0D00_0000; pub const GAMEPAK_WS2_HI: u32 = 0x0D00_0000;
pub const SRAM_LO: u32 = 0x0E00_0000; pub const SRAM_LO: u32 = 0x0E00_0000;
pub const SRAM_HI: u32 = 0x0F00_0000; pub const SRAM_HI: u32 = 0x0F00_0000;
}
use consts::*;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum MemoryAccessType { pub enum MemoryAccessType {
@ -211,7 +215,7 @@ impl SysBus {
// "[{}] Possible write to EEPROM", // "[{}] Possible write to EEPROM",
// Colour::Yellow.bold().paint("warn") // Colour::Yellow.bold().paint("warn")
// ); // );
(&mut self.dummy, addr) (&mut self.cartridge, addr)
} }
SRAM_LO | SRAM_HI => (&mut self.cartridge, addr), SRAM_LO | SRAM_HI => (&mut self.cartridge, addr),
_ => { _ => {