
Solves #168 Former-commit-id: 09104a6ebefdaf2e33ae22eb08860d9711ecb66b Former-commit-id: e07219e40fa187f516dd266ab753a67f3c7e796b
529 lines
16 KiB
Rust
529 lines
16 KiB
Rust
use super::{BackupFile, BackupMemoryInterface};
|
|
|
|
use bytesize;
|
|
use num::FromPrimitive;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use std::cell::RefCell;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum EepromType {
|
|
Eeprom512,
|
|
Eeprom8k,
|
|
}
|
|
|
|
#[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 {
|
|
EepromAddressBits::Eeprom6bit => 6,
|
|
EepromAddressBits::Eeprom14bit => 14,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Primitive, PartialEq, Copy, Clone)]
|
|
enum SpiInstruction {
|
|
Read = 0b011,
|
|
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,
|
|
RxAddress(SpiInstruction),
|
|
StopBit(SpiInstruction),
|
|
TxDummy,
|
|
TxData,
|
|
RxData,
|
|
}
|
|
|
|
impl Default for SpiState {
|
|
fn default() -> SpiState {
|
|
SpiState::RxInstruction
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
pub struct EepromChip {
|
|
memory: BackupFile,
|
|
addr_bits: EepromAddressBits,
|
|
|
|
state: SpiState,
|
|
rx_count: usize,
|
|
rx_buffer: u64,
|
|
|
|
tx_count: usize,
|
|
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 {
|
|
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,
|
|
rx_buffer: 0,
|
|
|
|
tx_count: 0,
|
|
tx_buffer: 0,
|
|
|
|
address: 0,
|
|
|
|
chip_ready: false,
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
fn reset_tx_buffer(&mut self) {
|
|
self.tx_buffer = 0;
|
|
self.tx_count = 0;
|
|
}
|
|
|
|
fn fill_tx_buffer(&mut self) {
|
|
let mut tx_buffer = 0u64;
|
|
for i in 0..8 {
|
|
tx_buffer = tx_buffer << 8;
|
|
tx_buffer |= self.memory.read(self.address + i) as u64;
|
|
}
|
|
self.tx_buffer = tx_buffer;
|
|
self.tx_count = 0;
|
|
}
|
|
|
|
fn clock_data_in(&mut self, address: u32, si: u8) {
|
|
use SpiInstruction::*;
|
|
use SpiState::*;
|
|
|
|
// Read the si signal into the rx_buffer
|
|
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;
|
|
|
|
let mut next_state: Option<SpiState> = None;
|
|
|
|
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
|
|
));
|
|
next_state = Some(RxAddress(insn));
|
|
self.reset_rx_buffer();
|
|
}
|
|
}
|
|
RxAddress(insn) => {
|
|
if self.rx_count == self.addr_bits.into() {
|
|
self.address = (self.rx_buffer as usize) * 8;
|
|
trace!(
|
|
"{:?} mode , recvd address = {:#x} (rx_buffer={:#x})",
|
|
insn,
|
|
self.address,
|
|
self.rx_buffer
|
|
);
|
|
match insn {
|
|
Read => {
|
|
next_state = Some(StopBit(Read));
|
|
}
|
|
Write => {
|
|
next_state = Some(RxData);
|
|
self.chip_ready = false;
|
|
self.reset_rx_buffer();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
StopBit(Read) => {
|
|
next_state = Some(TxDummy);
|
|
self.reset_rx_buffer();
|
|
self.reset_tx_buffer();
|
|
}
|
|
RxData => {
|
|
if self.rx_count == 64 {
|
|
let mut data = self.rx_buffer;
|
|
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);
|
|
data = data >> 8;
|
|
}
|
|
next_state = Some(StopBit(Write));
|
|
self.reset_rx_buffer();
|
|
}
|
|
}
|
|
StopBit(Write) => {
|
|
self.chip_ready = true;
|
|
self.state = RxInstruction;
|
|
self.reset_rx_buffer();
|
|
self.reset_tx_buffer();
|
|
}
|
|
_ => {}
|
|
}
|
|
if let Some(next_state) = next_state {
|
|
self.state = next_state;
|
|
}
|
|
}
|
|
|
|
fn clock_data_out(&mut self, address: u32) -> u8 {
|
|
use SpiState::*;
|
|
|
|
let mut next_state = None;
|
|
let result = match self.state {
|
|
TxDummy => {
|
|
self.tx_count += 1;
|
|
if self.tx_count == 4 {
|
|
next_state = Some(TxData);
|
|
self.fill_tx_buffer();
|
|
trace!("transmitting data bits, tx_buffer = {:#x}", self.tx_buffer);
|
|
}
|
|
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_tx_buffer();
|
|
self.reset_rx_buffer();
|
|
next_state = Some(RxInstruction);
|
|
}
|
|
result
|
|
}
|
|
_ => {
|
|
if self.chip_ready {
|
|
1
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
};
|
|
|
|
trace!("({:?}) addr={:#x} TX bit {}", self.state, address, result);
|
|
if let Some(next_state) = next_state {
|
|
self.state = next_state;
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
pub(crate) fn is_transmitting(&self) -> bool {
|
|
use SpiState::*;
|
|
match self.state {
|
|
TxData | TxDummy => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn reset(&mut self) {
|
|
self.state = SpiState::RxInstruction;
|
|
self.reset_rx_buffer();
|
|
self.reset_tx_buffer();
|
|
}
|
|
}
|
|
|
|
// #[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 EepromController {
|
|
pub(crate) chip: RefCell<EepromChip>,
|
|
detect: bool,
|
|
}
|
|
|
|
impl EepromController {
|
|
pub fn new(path: Option<PathBuf>) -> EepromController {
|
|
let mut detect = true;
|
|
let mut eeprom_type = EepromType::Eeprom512;
|
|
if let Some(path) = &path {
|
|
if let Ok(metadata) = fs::metadata(&path) {
|
|
let human_size = bytesize::ByteSize::b(metadata.len());
|
|
let assumed_type = match metadata.len() {
|
|
512 => EepromType::Eeprom512,
|
|
8192 => EepromType::Eeprom8k,
|
|
_ => panic!("invalid file size ({}) for eeprom save", human_size),
|
|
};
|
|
detect = false;
|
|
info!(
|
|
"save file is size {}, assuming eeprom type is {:?}",
|
|
human_size, assumed_type
|
|
);
|
|
eeprom_type = assumed_type;
|
|
}
|
|
}
|
|
|
|
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::*;
|
|
|
|
use bit::BitIndex;
|
|
|
|
impl EepromController {
|
|
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_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_write() {
|
|
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 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();
|
|
|
|
// First, lets test a read request
|
|
let stream = make_spi_read_request(2);
|
|
for half in stream.into_iter() {
|
|
spi.write_half(EEPROM_BASE_ADDR, 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(EEPROM_BASE_ADDR, half);
|
|
}
|
|
|
|
{
|
|
let chip = spi.chip.borrow();
|
|
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);
|
|
}
|
|
|
|
// Also lets again read the result
|
|
let stream = make_spi_read_request(2);
|
|
for half in stream.into_iter() {
|
|
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);
|
|
}
|
|
}
|
|
}
|