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)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
struct EepromChip<M>
|
pub struct EepromChip<M>
|
||||||
where
|
where
|
||||||
M: BackupMemoryInterface,
|
M: BackupMemoryInterface,
|
||||||
{
|
{
|
||||||
|
@ -57,6 +57,10 @@ where
|
||||||
tx_buffer: u64,
|
tx_buffer: u64,
|
||||||
|
|
||||||
address: usize,
|
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>
|
impl<M> EepromChip<M>
|
||||||
|
@ -75,6 +79,8 @@ where
|
||||||
tx_buffer: 0,
|
tx_buffer: 0,
|
||||||
|
|
||||||
address: 0,
|
address: 0,
|
||||||
|
|
||||||
|
chip_ready: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,12 +104,12 @@ where
|
||||||
self.tx_count = 0;
|
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 SpiInstruction::*;
|
||||||
use SpiState::*;
|
use SpiState::*;
|
||||||
|
|
||||||
// Read the si signal into the rx_buffer
|
// 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_buffer = (self.rx_buffer << 1) | (if si & 1 != 0 { 1 } else { 0 });
|
||||||
self.rx_count += 1;
|
self.rx_count += 1;
|
||||||
|
|
||||||
|
@ -124,15 +130,17 @@ where
|
||||||
RxAddress(insn) => {
|
RxAddress(insn) => {
|
||||||
if self.rx_count == 6 {
|
if self.rx_count == 6 {
|
||||||
self.address = (self.rx_buffer as usize) * 8;
|
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 {
|
match insn {
|
||||||
Read => {
|
Read => {
|
||||||
self.reset_rx_buffer();
|
next_state = Some(StopBit(Read));
|
||||||
self.reset_tx_buffer();
|
|
||||||
next_state = Some(StopBit(insn));
|
|
||||||
}
|
}
|
||||||
Write => {
|
Write => {
|
||||||
next_state = Some(RxData);
|
next_state = Some(RxData);
|
||||||
|
self.chip_ready = false;
|
||||||
self.reset_rx_buffer();
|
self.reset_rx_buffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,7 +154,7 @@ where
|
||||||
RxData => {
|
RxData => {
|
||||||
if self.rx_count == 64 {
|
if self.rx_count == 64 {
|
||||||
let mut data = self.rx_buffer;
|
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 {
|
for i in 0..8 {
|
||||||
self.memory
|
self.memory
|
||||||
.write(self.address + (7 - i), (data & 0xff) as u8);
|
.write(self.address + (7 - i), (data & 0xff) as u8);
|
||||||
|
@ -157,6 +165,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StopBit(Write) => {
|
StopBit(Write) => {
|
||||||
|
self.chip_ready = true;
|
||||||
self.state = RxInstruction;
|
self.state = RxInstruction;
|
||||||
self.reset_rx_buffer();
|
self.reset_rx_buffer();
|
||||||
self.reset_tx_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::*;
|
use SpiState::*;
|
||||||
|
|
||||||
let mut next_state = None;
|
let mut next_state = None;
|
||||||
|
@ -183,22 +192,26 @@ where
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
TxData => {
|
TxData => {
|
||||||
|
let result = ((self.tx_buffer >> 63) & 1) as u8;
|
||||||
|
self.tx_buffer = self.tx_buffer.wrapping_shl(1);
|
||||||
self.tx_count += 1;
|
self.tx_count += 1;
|
||||||
if self.tx_count == 64 {
|
if self.tx_count == 64 {
|
||||||
self.reset_rx_buffer();
|
|
||||||
self.reset_tx_buffer();
|
self.reset_tx_buffer();
|
||||||
|
self.reset_rx_buffer();
|
||||||
next_state = Some(RxInstruction);
|
next_state = Some(RxInstruction);
|
||||||
0
|
}
|
||||||
} else {
|
|
||||||
let result = ((self.tx_buffer >> 63) & 1) as u8;
|
|
||||||
self.tx_buffer = self.tx_buffer << 1;
|
|
||||||
result
|
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 {
|
if let Some(next_state) = next_state {
|
||||||
self.state = next_state;
|
self.state = next_state;
|
||||||
}
|
}
|
||||||
|
@ -206,13 +219,19 @@ where
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data_available(&self) -> bool {
|
pub(in crate) fn is_transmitting(&self) -> bool {
|
||||||
if let SpiState::TxData = self.state {
|
use SpiState::*;
|
||||||
true
|
match self.state {
|
||||||
} else {
|
TxData | TxDummy => true,
|
||||||
false
|
_ => 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
|
/// The Eeprom controller is usually mapped to the top 256 bytes of the cartridge memory
|
||||||
|
@ -222,7 +241,7 @@ pub struct SpiController<M>
|
||||||
where
|
where
|
||||||
M: BackupMemoryInterface,
|
M: BackupMemoryInterface,
|
||||||
{
|
{
|
||||||
chip: RefCell<EepromChip<M>>,
|
pub(in crate) chip: RefCell<EepromChip<M>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M> SpiController<M>
|
impl<M> SpiController<M>
|
||||||
|
@ -235,53 +254,23 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_half(&mut self, value: u16) {
|
pub fn write_half(&mut self, address: u32, value: u16) {
|
||||||
self.chip.borrow_mut().clock_data_in(value as u8);
|
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 mut chip = self.chip.borrow_mut();
|
||||||
let bit = chip.clock_data_out() as u16;
|
chip.clock_data_out(address) 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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::super::super::EEPROM_BASE_ADDR;
|
||||||
use super::super::BackupMemoryInterface;
|
use super::super::BackupMemoryInterface;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use bit::BitIndex;
|
use bit::BitIndex;
|
||||||
use hexdump;
|
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct MockMemory {
|
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 {
|
fn make_mock_memory() -> MockMemory {
|
||||||
let mut buffer = vec![0; 512];
|
let mut buffer = vec![0; 512];
|
||||||
buffer[16] = 'T' as u8;
|
buffer[16] = 'T' as u8;
|
||||||
|
@ -369,12 +381,12 @@ mod tests {
|
||||||
// 1 bit "0" - stop bit
|
// 1 bit "0" - stop bit
|
||||||
let stream = make_spi_read_request(2);
|
let stream = make_spi_read_request(2);
|
||||||
for half in stream.into_iter() {
|
for half in stream.into_iter() {
|
||||||
spi.write_half(half);
|
spi.write_half(EEPROM_BASE_ADDR, half);
|
||||||
}
|
}
|
||||||
|
|
||||||
spi.consume_dummy_cycles();
|
spi.consume_dummy_cycles();
|
||||||
|
|
||||||
assert!(spi.chip.borrow().data_available());
|
assert!(spi.chip.borrow().is_transmitting());
|
||||||
|
|
||||||
let data = spi.rx_data();
|
let data = spi.rx_data();
|
||||||
|
|
||||||
|
@ -398,7 +410,7 @@ mod tests {
|
||||||
// First, lets test a read request
|
// First, lets test a read request
|
||||||
let stream = make_spi_read_request(2);
|
let stream = make_spi_read_request(2);
|
||||||
for half in stream.into_iter() {
|
for half in stream.into_iter() {
|
||||||
spi.write_half(half);
|
spi.write_half(EEPROM_BASE_ADDR, half);
|
||||||
}
|
}
|
||||||
spi.consume_dummy_cycles();
|
spi.consume_dummy_cycles();
|
||||||
let data = spi.rx_data();
|
let data = spi.rx_data();
|
||||||
|
@ -420,7 +432,7 @@ mod tests {
|
||||||
bytes[4] = expected[4];
|
bytes[4] = expected[4];
|
||||||
let stream = make_spi_write_request(2, bytes);
|
let stream = make_spi_write_request(2, bytes);
|
||||||
for half in stream.into_iter() {
|
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
|
// Also lets again read the result
|
||||||
let stream = make_spi_read_request(2);
|
let stream = make_spi_read_request(2);
|
||||||
for half in stream.into_iter() {
|
for half in stream.into_iter() {
|
||||||
spi.write_half(half);
|
spi.write_half(EEPROM_BASE_ADDR, half);
|
||||||
}
|
}
|
||||||
spi.consume_dummy_cycles();
|
spi.consume_dummy_cycles();
|
||||||
let data = spi.rx_data();
|
let data = spi.rx_data();
|
||||||
assert_eq!(expected, &data[0..5]);
|
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,
|
pub header: CartridgeHeader,
|
||||||
bytes: Box<[u8]>,
|
bytes: Box<[u8]>,
|
||||||
size: usize,
|
size: usize,
|
||||||
backup: BackupMedia,
|
pub(in crate) backup: BackupMedia,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_rom(path: &Path) -> GBAResult<Vec<u8>> {
|
fn load_rom(path: &Path) -> GBAResult<Vec<u8>> {
|
||||||
|
@ -199,7 +199,7 @@ fn detect_backup_type(bytes: &[u8]) -> Option<BackupType> {
|
||||||
|
|
||||||
use super::sysbus::consts::*;
|
use super::sysbus::consts::*;
|
||||||
|
|
||||||
const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00;
|
pub 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 {
|
||||||
|
@ -221,9 +221,11 @@ impl Bus for Cartridge {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_16(&self, addr: u32) -> u16 {
|
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 {
|
if let BackupMedia::Eeprom(spi) = &self.backup {
|
||||||
return spi.read_half();
|
return spi.read_half(addr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.default_read_16(addr)
|
self.default_read_16(addr)
|
||||||
|
@ -236,14 +238,16 @@ impl Bus for Cartridge {
|
||||||
BackupMedia::Sram(memory) => memory.write((addr & 0x7FFF) as usize, value),
|
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) {
|
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 {
|
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);
|
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::iodev::consts::{REG_FIFO_A, REG_FIFO_B};
|
||||||
use super::sysbus::SysBus;
|
use super::sysbus::SysBus;
|
||||||
use super::{Bus, Interrupt, IrqBitmask};
|
use super::{Bus, Interrupt, IrqBitmask};
|
||||||
|
@ -129,6 +130,15 @@ impl DmaChannel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn xfer(&mut self, sb: &mut SysBus, irqs: &mut IrqBitmask) {
|
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 word_size = if self.ctrl.is_32bit() { 4 } else { 2 };
|
||||||
let count = match self.internal.count {
|
let count = match self.internal.count {
|
||||||
0 => match self.id {
|
0 => match self.id {
|
||||||
|
|
|
@ -115,7 +115,7 @@ pub struct SysBus {
|
||||||
bios: BoxedMemory,
|
bios: BoxedMemory,
|
||||||
onboard_work_ram: BoxedMemory,
|
onboard_work_ram: BoxedMemory,
|
||||||
internal_work_ram: BoxedMemory,
|
internal_work_ram: BoxedMemory,
|
||||||
cartridge: Cartridge,
|
pub cartridge: Cartridge,
|
||||||
dummy: DummyBus,
|
dummy: DummyBus,
|
||||||
|
|
||||||
pub trace_access: bool,
|
pub trace_access: bool,
|
||||||
|
|
Reference in a new issue