feat(eeprom): Fix eeprom emulation timeout problem, passing the nintendo eeprom tests
Former-commit-id: 5eea390de806d03eee8c043203ae4c57d7355caa
This commit is contained in:
parent
451be2036f
commit
3fb75079a2
|
@ -43,7 +43,7 @@ impl Default for SpiState {
|
|||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
struct EepromChip<M>
|
||||
pub struct EepromChip<M>
|
||||
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<M> EepromChip<M>
|
||||
|
@ -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
|
||||
} else {
|
||||
let result = ((self.tx_buffer >> 63) & 1) as u8;
|
||||
self.tx_buffer = self.tx_buffer << 1;
|
||||
}
|
||||
result
|
||||
}
|
||||
_ => {
|
||||
if self.chip_ready {
|
||||
1
|
||||
} else {
|
||||
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<M>
|
|||
where
|
||||
M: BackupMemoryInterface,
|
||||
{
|
||||
chip: RefCell<EepromChip<M>>,
|
||||
pub(in crate) chip: RefCell<EepromChip<M>>,
|
||||
}
|
||||
|
||||
impl<M> SpiController<M>
|
||||
|
@ -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<MockMemory> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Vec<u8>> {
|
||||
|
@ -199,7 +199,7 @@ fn detect_backup_type(bytes: &[u8]) -> Option<BackupType> {
|
|||
|
||||
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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
Reference in a new issue