feat(eeprom): Implement eeprom size runtime autodetection
The Minish cap works :) Former-commit-id: 4c04d7907bd44edaac47a9110c98fb0869d01ace
This commit is contained in:
parent
bb111f0d0b
commit
13037f334c
|
@ -102,6 +102,21 @@ impl BackupFile {
|
|||
buffer: buffer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes(&self) -> &[u8] {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
pub fn bytes_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.buffer
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) {
|
||||
if let Some(file) = &mut self.file {
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
file.write_all(&self.buffer).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BackupMemoryInterface for BackupFile {
|
||||
|
@ -116,4 +131,10 @@ impl BackupMemoryInterface for BackupFile {
|
|||
fn read(&self, offset: usize) -> u8 {
|
||||
self.buffer[offset]
|
||||
}
|
||||
|
||||
fn resize(&mut self, new_size: usize) {
|
||||
self.size = new_size;
|
||||
self.buffer.resize(new_size, 0xff);
|
||||
self.flush();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,44 @@
|
|||
use super::BackupMemoryInterface;
|
||||
use super::{BackupFile, BackupMemoryInterface};
|
||||
|
||||
use num::FromPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EepromSize {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum EepromType {
|
||||
Eeprom512,
|
||||
Eeprom8k,
|
||||
}
|
||||
|
||||
impl Into<usize> for EepromSize {
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
enum EepromAddressBits {
|
||||
Eeprom6bit,
|
||||
Eeprom14bit,
|
||||
}
|
||||
|
||||
impl EepromType {
|
||||
fn size(&self) -> usize {
|
||||
match self {
|
||||
EepromType::Eeprom512 => 0x0200,
|
||||
EepromType::Eeprom8k => 0x2000,
|
||||
}
|
||||
}
|
||||
fn bits(&self) -> EepromAddressBits {
|
||||
match self {
|
||||
EepromType::Eeprom512 => EepromAddressBits::Eeprom6bit,
|
||||
EepromType::Eeprom8k => EepromAddressBits::Eeprom14bit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<usize> for EepromAddressBits {
|
||||
fn into(self) -> usize {
|
||||
match self {
|
||||
EepromSize::Eeprom512 => 0x0200,
|
||||
EepromSize::Eeprom8k => 0x2000,
|
||||
EepromAddressBits::Eeprom6bit => 6,
|
||||
EepromAddressBits::Eeprom14bit => 14,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +49,12 @@ enum SpiInstruction {
|
|||
Write = 0b010,
|
||||
}
|
||||
|
||||
impl Default for SpiInstruction {
|
||||
fn default() -> SpiInstruction {
|
||||
SpiInstruction::Read /* TODO this is an arbitrary choice */
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
|
||||
enum SpiState {
|
||||
RxInstruction,
|
||||
|
@ -43,11 +72,9 @@ impl Default for SpiState {
|
|||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct EepromChip<M>
|
||||
where
|
||||
M: BackupMemoryInterface,
|
||||
{
|
||||
memory: M,
|
||||
pub struct EepromChip {
|
||||
memory: BackupFile,
|
||||
addr_bits: EepromAddressBits,
|
||||
|
||||
state: SpiState,
|
||||
rx_count: usize,
|
||||
|
@ -63,13 +90,13 @@ where
|
|||
// But we do it right away.
|
||||
}
|
||||
|
||||
impl<M> EepromChip<M>
|
||||
where
|
||||
M: BackupMemoryInterface,
|
||||
{
|
||||
fn new(memory: M) -> EepromChip<M> {
|
||||
impl EepromChip {
|
||||
fn new(eeprom_type: EepromType, mut memory: BackupFile) -> EepromChip {
|
||||
memory.resize(eeprom_type.size());
|
||||
EepromChip {
|
||||
memory: memory,
|
||||
addr_bits: eeprom_type.bits(),
|
||||
|
||||
state: SpiState::RxInstruction,
|
||||
|
||||
rx_count: 0,
|
||||
|
@ -84,6 +111,11 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn set_type(&mut self, eeprom_type: EepromType) {
|
||||
self.addr_bits = eeprom_type.bits();
|
||||
self.memory.resize(eeprom_type.size());
|
||||
}
|
||||
|
||||
fn reset_rx_buffer(&mut self) {
|
||||
self.rx_buffer = 0;
|
||||
self.rx_count = 0;
|
||||
|
@ -128,11 +160,13 @@ where
|
|||
}
|
||||
}
|
||||
RxAddress(insn) => {
|
||||
if self.rx_count == 6 {
|
||||
if self.rx_count == self.addr_bits.into() {
|
||||
self.address = (self.rx_buffer as usize) * 8;
|
||||
debug!(
|
||||
trace!(
|
||||
"{:?} mode , recvd address = {:#x} (rx_buffer={:#x})",
|
||||
insn, self.address, self.rx_buffer
|
||||
insn,
|
||||
self.address,
|
||||
self.rx_buffer
|
||||
);
|
||||
match insn {
|
||||
Read => {
|
||||
|
@ -187,7 +221,7 @@ where
|
|||
if self.tx_count == 4 {
|
||||
next_state = Some(TxData);
|
||||
self.fill_tx_buffer();
|
||||
debug!("transmitting data bits, tx_buffer = {:#x}", self.tx_buffer);
|
||||
trace!("transmitting data bits, tx_buffer = {:#x}", self.tx_buffer);
|
||||
}
|
||||
0
|
||||
}
|
||||
|
@ -234,60 +268,110 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
// #[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Default)]
|
||||
// struct ChipSizeDetectionState {
|
||||
// insn: SpiInstruction,
|
||||
// rx_buffer: u64,
|
||||
// rx_count: usize,
|
||||
// }
|
||||
|
||||
/// 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,
|
||||
{
|
||||
pub(in crate) chip: RefCell<EepromChip<M>>,
|
||||
pub struct EepromController {
|
||||
pub(in crate) chip: RefCell<EepromChip>,
|
||||
detect: bool,
|
||||
}
|
||||
|
||||
impl<M> SpiController<M>
|
||||
where
|
||||
M: BackupMemoryInterface,
|
||||
{
|
||||
pub fn new(m: M) -> SpiController<M> {
|
||||
SpiController {
|
||||
chip: RefCell::new(EepromChip::new(m)),
|
||||
impl EepromController {
|
||||
pub fn new(path: Option<PathBuf>) -> EepromController {
|
||||
let mut detect = true;
|
||||
|
||||
let eeprom_type = if let Some(path) = &path {
|
||||
let metadata = fs::metadata(&path).unwrap();
|
||||
let eeprom_type = match metadata.len() {
|
||||
512 => EepromType::Eeprom512,
|
||||
8192 => EepromType::Eeprom8k,
|
||||
_ => panic!("invalid file size ({}) for eeprom save", metadata.len()),
|
||||
};
|
||||
detect = false;
|
||||
eeprom_type
|
||||
} else {
|
||||
EepromType::Eeprom512
|
||||
};
|
||||
|
||||
let mut result = EepromController::new_with_type(path, eeprom_type);
|
||||
result.detect = detect;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn new_with_type(path: Option<PathBuf>, eeprom_type: EepromType) -> EepromController {
|
||||
let memory = BackupFile::new(eeprom_type.size(), path);
|
||||
EepromController {
|
||||
chip: RefCell::new(EepromChip::new(eeprom_type, memory)),
|
||||
detect: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_half(&mut self, address: u32, value: u16) {
|
||||
assert!(!self.detect);
|
||||
self.chip.borrow_mut().clock_data_in(address, value as u8);
|
||||
}
|
||||
|
||||
pub fn read_half(&self, address: u32) -> u16 {
|
||||
assert!(!self.detect);
|
||||
let mut chip = self.chip.borrow_mut();
|
||||
chip.clock_data_out(address) as u16
|
||||
}
|
||||
|
||||
pub fn on_dma3_transfer(&mut self, src: u32, dst: u32, count: usize) {
|
||||
use EepromType::*;
|
||||
if self.detect {
|
||||
match (src, dst) {
|
||||
// DMA to EEPROM
|
||||
(_, 0x0d000000..0x0dffffff) => {
|
||||
debug!("caught eeprom dma transfer src={:#x} dst={:#x} count={}", src, dst, count);
|
||||
let eeprom_type = match count {
|
||||
// Read(11) + 6bit address + stop bit
|
||||
9 => Eeprom512,
|
||||
// Read(11) + 14bit address + stop bit
|
||||
17 => Eeprom8k,
|
||||
// Write(11) + 6bit address + 64bit value + stop bit
|
||||
73 => Eeprom512,
|
||||
// Write(11) + 14bit address + 64bit value + stop bit
|
||||
81 => Eeprom8k,
|
||||
_ => panic!("unexpected bit count ({}) when detecting eeprom size", count)
|
||||
};
|
||||
info!("detected eeprom type: {:?}", eeprom_type);
|
||||
self.chip.borrow_mut().set_type(eeprom_type);
|
||||
self.detect = false;
|
||||
}
|
||||
// EEPROM to DMA
|
||||
(0x0d000000..0x0dffffff, _) => {
|
||||
panic!("reading from eeprom when real size is not detected yet is not supported by this emulator")
|
||||
}
|
||||
_ => {/* Not a eeprom dma, doing nothing */}
|
||||
}
|
||||
} else {
|
||||
// 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)
|
||||
let mut chip = self.chip.borrow_mut();
|
||||
if !chip.is_transmitting() {
|
||||
chip.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::super::EEPROM_BASE_ADDR;
|
||||
use super::super::BackupMemoryInterface;
|
||||
use super::super::BackupFile;
|
||||
use super::*;
|
||||
|
||||
use bit::BitIndex;
|
||||
|
||||
#[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]
|
||||
}
|
||||
}
|
||||
|
||||
impl SpiController<MockMemory> {
|
||||
impl EepromController {
|
||||
fn consume_dummy_cycles(&self) {
|
||||
// ignore the dummy bits
|
||||
self.read_half(EEPROM_BASE_ADDR);
|
||||
|
@ -310,17 +394,6 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
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];
|
||||
|
@ -373,37 +446,22 @@ mod tests {
|
|||
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(EEPROM_BASE_ADDR, half);
|
||||
}
|
||||
|
||||
spi.consume_dummy_cycles();
|
||||
|
||||
assert!(spi.chip.borrow().is_transmitting());
|
||||
|
||||
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 mut spi = EepromController::new_with_type(None, EepromType::Eeprom512);
|
||||
// hacky way to initialize the backup file with contents.
|
||||
// TODO - implement EepromController initialization with data buffer and not files
|
||||
{
|
||||
let mut chip = spi.chip.borrow_mut();
|
||||
let mut bytes = chip.memory.bytes_mut();
|
||||
bytes[16] = 'T' as u8;
|
||||
bytes[17] = 'E' as u8;
|
||||
bytes[18] = 'S' as u8;
|
||||
bytes[19] = 'T' as u8;
|
||||
bytes[20] = '!' as u8;
|
||||
drop(bytes);
|
||||
chip.memory.flush();
|
||||
}
|
||||
|
||||
let expected = "Work.".as_bytes();
|
||||
|
||||
|
@ -437,7 +495,7 @@ mod tests {
|
|||
|
||||
{
|
||||
let chip = spi.chip.borrow();
|
||||
assert_eq!(expected, &chip.memory.buffer[0x10..0x15]);
|
||||
assert_eq!(expected, &chip.memory.bytes()[0x10..0x15]);
|
||||
assert_eq!(SpiState::RxInstruction, chip.state);
|
||||
assert_eq!(0, chip.rx_count);
|
||||
assert_eq!(0, chip.tx_count);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::fmt;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
|
||||
mod backup_file;
|
||||
pub use backup_file::BackupFile;
|
||||
|
@ -27,7 +27,7 @@ impl TryFrom<&str> for BackupType {
|
|||
"flash128k" => Ok(Flash1M),
|
||||
"flash64k" => Ok(Flash512),
|
||||
"eeprom" => Ok(Eeprom),
|
||||
_ => Err(format!("{} is not a valid save type", s))
|
||||
_ => Err(format!("{} is not a valid save type", s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,4 +35,5 @@ impl TryFrom<&str> for BackupType {
|
|||
pub trait BackupMemoryInterface: Sized + fmt::Debug {
|
||||
fn write(&mut self, offset: usize, value: u8);
|
||||
fn read(&self, offset: usize) -> u8;
|
||||
fn resize(&mut self, new_size: usize);
|
||||
}
|
||||
|
|
|
@ -125,9 +125,7 @@ fn create_backup(backup_type: BackupType, rom_path: Option<PathBuf>) -> BackupMe
|
|||
}
|
||||
BackupType::Flash1M => BackupMedia::Flash(Flash::new(backup_path, FlashSize::Flash128k)),
|
||||
BackupType::Sram => BackupMedia::Sram(BackupFile::new(0x8000, backup_path)),
|
||||
BackupType::Eeprom => {
|
||||
BackupMedia::Eeprom(SpiController::new(BackupFile::new(0x200, backup_path)))
|
||||
}
|
||||
BackupType::Eeprom => BackupMedia::Eeprom(EepromController::new(backup_path)),
|
||||
BackupType::AutoDetect => panic!("called create_backup with backup_type==AutoDetect"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,10 @@ mod header;
|
|||
use header::CartridgeHeader;
|
||||
|
||||
mod backup;
|
||||
use backup::eeprom::SpiController;
|
||||
use backup::eeprom::EepromController;
|
||||
use backup::flash::Flash;
|
||||
use backup::{BackupFile, BackupMemoryInterface};
|
||||
pub use backup::BackupType;
|
||||
use backup::{BackupFile, BackupMemoryInterface};
|
||||
|
||||
mod builder;
|
||||
pub use builder::GamepakBuilder;
|
||||
|
@ -18,7 +18,7 @@ pub use builder::GamepakBuilder;
|
|||
pub enum BackupMedia {
|
||||
Sram(BackupFile),
|
||||
Flash(Flash),
|
||||
Eeprom(SpiController<BackupFile>),
|
||||
Eeprom(EepromController),
|
||||
Undetected,
|
||||
}
|
||||
|
||||
|
|
|
@ -130,15 +130,6 @@ 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 {
|
||||
|
@ -147,6 +138,17 @@ impl DmaChannel {
|
|||
},
|
||||
_ => self.internal.count,
|
||||
};
|
||||
|
||||
if self.id == 3 && word_size == 2 {
|
||||
if let BackupMedia::Eeprom(eeprom) = &mut sb.cartridge.backup {
|
||||
eeprom.on_dma3_transfer(
|
||||
self.internal.src_addr,
|
||||
self.internal.dst_addr,
|
||||
count as usize,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let fifo_mode = self.fifo_mode;
|
||||
|
||||
if fifo_mode {
|
||||
|
|
Reference in a new issue