From 8e1ba1117c0604aeebcb97e4c402bb69e7f6d755 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Sun, 17 May 2020 20:12:31 +0300 Subject: [PATCH] core: More RTC work Former-commit-id: be2955b08d65c547d206d637ae499dbad892a87a Former-commit-id: 19f0e4e6f9c598c35fb8172d93a1519f921eb27f --- platform/rustboyadvance-sdl2/src/cli.yml | 4 + platform/rustboyadvance-sdl2/src/main.rs | 11 +- rustboyadvance-core/Cargo.toml | 4 +- rustboyadvance-core/src/cartridge/builder.rs | 24 +- rustboyadvance-core/src/cartridge/gpio.rs | 228 ++++--- rustboyadvance-core/src/cartridge/mod.rs | 17 +- rustboyadvance-core/src/cartridge/rtc.rs | 640 ++++++++++++++++++- rustboyadvance-core/src/gba.rs | 1 + rustboyadvance-core/src/sysbus.rs | 2 +- 9 files changed, 801 insertions(+), 130 deletions(-) diff --git a/platform/rustboyadvance-sdl2/src/cli.yml b/platform/rustboyadvance-sdl2/src/cli.yml index 0756d27..88d71da 100644 --- a/platform/rustboyadvance-sdl2/src/cli.yml +++ b/platform/rustboyadvance-sdl2/src/cli.yml @@ -24,6 +24,10 @@ args: - flash64k - eeprom - autodetect + - rtc: + long: rtc + help: Force cartridge to have RTC + required: false - skip_bios: long: skip-bios help: Skip running bios and start from the ROM instead diff --git a/platform/rustboyadvance-sdl2/src/main.rs b/platform/rustboyadvance-sdl2/src/main.rs index aed53b0..b411f69 100644 --- a/platform/rustboyadvance-sdl2/src/main.rs +++ b/platform/rustboyadvance-sdl2/src/main.rs @@ -245,12 +245,17 @@ fn main() -> Result<(), Box> { let mut rom_name = Path::new(&rom_path).file_name().unwrap().to_str().unwrap(); - let gamepak = GamepakBuilder::new() + let mut builder = GamepakBuilder::new() .save_type(BackupType::try_from( matches.value_of("save_type").unwrap(), )?) - .file(Path::new(&rom_path)) - .build()?; + .file(Path::new(&rom_path)); + + if matches.occurrences_of("rtc") != 0 { + builder = builder.with_rtc(); + } + + let gamepak = builder.build()?; let mut gba = GameBoyAdvance::new( bios_bin.into_boxed_slice(), diff --git a/rustboyadvance-core/Cargo.toml b/rustboyadvance-core/Cargo.toml index ee09c8f..7813483 100644 --- a/rustboyadvance-core/Cargo.toml +++ b/rustboyadvance-core/Cargo.toml @@ -5,13 +5,14 @@ authors = ["Michel Heily "] edition = "2018" [dependencies] -serde = { version = "1.0.104", features = ["derive"] } +serde = { version = "1.0.104", features = ["derive", "rc"] } bincode = "1.2.1" byteorder = "1" num = "0.2.1" num-traits = "0.2" enum-primitive-derive = "^0.1" bit = "^0.1" +chrono = "0.4" colored = "1.9" ansi_term = "0.12.1" hexdump = "0.1.0" @@ -36,6 +37,7 @@ gdbstub = { version = "0.1.2", optional = true, features = ["std"] } ringbuf = "0.2.1" goblin = { version = "0.2", optional = true } fuzzy-matcher = { version = "0.3.4", optional = true } +bit_reverse = "0.1.8" [target.'cfg(target_arch="wasm32")'.dependencies] instant = { version = "0.1.2", features = ["wasm-bindgen"] } diff --git a/rustboyadvance-core/src/cartridge/builder.rs b/rustboyadvance-core/src/cartridge/builder.rs index c9e4d1b..1a37f41 100644 --- a/rustboyadvance-core/src/cartridge/builder.rs +++ b/rustboyadvance-core/src/cartridge/builder.rs @@ -14,12 +14,21 @@ use super::Cartridge; use super::loader::{load_from_bytes, load_from_file, LoadRom}; +#[derive(Debug)] +pub enum GpioDeviceType { + Rtc, + SolarSensor, + Gyro, + None, +} + #[derive(Debug)] pub struct GamepakBuilder { path: Option, bytes: Option>, save_path: Option, save_type: BackupType, + gpio_device: GpioDeviceType, create_backup_file: bool, } @@ -30,6 +39,7 @@ impl GamepakBuilder { path: None, save_path: None, bytes: None, + gpio_device: GpioDeviceType::None, create_backup_file: true, } } @@ -84,6 +94,11 @@ impl GamepakBuilder { self } + pub fn with_rtc(mut self) -> Self { + self.gpio_device = GpioDeviceType::Rtc; + self + } + pub fn build(mut self) -> GBAResult { let (bytes, symbols) = if let Some(bytes) = self.bytes { match load_from_bytes(bytes.to_vec())? { @@ -127,7 +142,14 @@ impl GamepakBuilder { let backup = create_backup(self.save_type, self.save_path); - let gpio = Gpio::new(); + let gpio = match self.gpio_device { + GpioDeviceType::None => Gpio::new_none(), + GpioDeviceType::Rtc => { + info!("Emulating RTC!"); + Gpio::new_rtc() + } + _ => unimplemented!("Gpio device {:?} not implemented", self.gpio_device), + }; let size = bytes.len(); Ok(Cartridge { diff --git a/rustboyadvance-core/src/cartridge/gpio.rs b/rustboyadvance-core/src/cartridge/gpio.rs index a7e1c9e..96b2ee4 100644 --- a/rustboyadvance-core/src/cartridge/gpio.rs +++ b/rustboyadvance-core/src/cartridge/gpio.rs @@ -1,90 +1,138 @@ -use super::rtc::Rtc; -use super::{GPIO_PORT_CONTROL, GPIO_PORT_DATA, GPIO_PORT_DIRECTION}; - -use bit::BitIndex; -use serde::{Deserialize, Serialize}; - -use std::cell::RefCell; - -#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)] -enum GpioDirection { - In = 0, - Out = 1, -} - -#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)] -enum GpioPortControl { - WriteOnly = 0, - ReadWrite = 1, -} - -trait GpioDevice: Sized { - fn write(&mut self); - fn read(&mut self); -} - -enum GpioDeviceType { - Rtc, - SolarSensor, - Gyro, - None, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct Gpio { - rtc: Option>, - direction: [GpioDirection; 4], - control: GpioPortControl, -} - -impl Gpio { - pub fn new() -> Self { - Gpio { - rtc: None, - direction: [GpioDirection::In; 4], - control: GpioPortControl::WriteOnly, - } - } - - pub fn is_readable(&self) -> bool { - self.control != GpioPortControl::WriteOnly - } - - pub fn read(&self, addr: u32) -> u16 { - match addr { - GPIO_PORT_DATA => unimplemented!(), - GPIO_PORT_DIRECTION => { - let mut direction = 0u16; - for i in 0..4 { - direction.set_bit(i, self.direction[i] == GpioDirection::Out); - } - direction - } - GPIO_PORT_CONTROL => self.control as u16, - _ => unreachable!(), - } - } - - pub fn write(&mut self, addr: u32) -> u16 { - match addr { - GPIO_PORT_DATA => unimplemented!(), - GPIO_PORT_DIRECTION => { - let mut direction = 0u16; - for i in 0..4 { - direction.set_bit(i, self.direction[i] == GpioDirection::Out); - } - direction - } - GPIO_PORT_CONTROL => self.control as u16, - _ => unreachable!(), - } - } -} - -#[cfg(test)] -mod tests { - #[test] - fn test_gpio() { - unimplemented!(); - } -} +use super::rtc::Rtc; +use super::{GPIO_PORT_CONTROL, GPIO_PORT_DATA, GPIO_PORT_DIRECTION}; + +use bit::BitIndex; +use serde::{Deserialize, Serialize}; + +/// Struct holding the logical state of a serial port +#[repr(transparent)] +#[derive(Serialize, Deserialize, Copy, Clone, Debug)] +pub struct GpioPort(u16); + +impl GpioPort { + pub fn get(&self) -> u16 { + self.0 + } + + pub fn set(&mut self, value: u16) { + self.0 = value & 1; + } + + pub fn high(&self) -> bool { + self.0 != 0 + } + + pub fn low(&self) -> bool { + self.0 == 0 + } +} + +pub const GPIO_LOW: GpioPort = GpioPort(0); +pub const GPIO_HIGH: GpioPort = GpioPort(1); + +#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)] +pub enum GpioDirection { + /// GPIO to GBA + In = 0, + /// GBA to GPIO + Out = 1, +} + +pub type GpioState = [GpioDirection; 4]; + +#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)] +enum GpioPortControl { + WriteOnly = 0, + ReadWrite = 1, +} + +pub trait GpioDevice: Sized { + fn write(&mut self, gpio_state: &GpioState, data: u16); + fn read(&self, gpio_state: &GpioState) -> u16; +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Gpio { + rtc: Option, + direction: GpioState, + control: GpioPortControl, +} + +impl Gpio { + pub fn new_none() -> Self { + Gpio { + rtc: None, + direction: [GpioDirection::Out; 4], + control: GpioPortControl::WriteOnly, + } + } + + pub fn new_rtc() -> Self { + Gpio { + rtc: Some(Rtc::new()), + direction: [GpioDirection::Out; 4], + control: GpioPortControl::WriteOnly, + } + } + + pub fn is_readable(&self) -> bool { + self.control != GpioPortControl::WriteOnly + } + + pub fn read(&self, addr: u32) -> u16 { + match addr { + GPIO_PORT_DATA => { + if let Some(rtc) = &self.rtc { + rtc.read(&self.direction) + } else { + 0 + } + } + GPIO_PORT_DIRECTION => { + let mut direction = 0u16; + for i in 0..4 { + direction.set_bit(i, self.direction[i] == GpioDirection::Out); + } + direction + } + GPIO_PORT_CONTROL => self.control as u16, + _ => unreachable!(), + } + } + + pub fn write(&mut self, addr: u32, value: u16) { + match addr { + GPIO_PORT_DATA => { + if let Some(rtc) = &mut self.rtc { + rtc.write(&self.direction, value); + } + } + GPIO_PORT_DIRECTION => { + for i in 0..4 { + if value.bit(i) { + self.direction[i] = GpioDirection::Out; + } else { + self.direction[i] = GpioDirection::In; + } + } + } + GPIO_PORT_CONTROL => { + info!("GPIO port control: {:?}", self.control); + self.control = if value != 0 { + GpioPortControl::ReadWrite + } else { + GpioPortControl::WriteOnly + }; + } + _ => unreachable!(), + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_gpio() { + unimplemented!(); + } +} diff --git a/rustboyadvance-core/src/cartridge/mod.rs b/rustboyadvance-core/src/cartridge/mod.rs index 4aa1000..9178864 100644 --- a/rustboyadvance-core/src/cartridge/mod.rs +++ b/rustboyadvance-core/src/cartridge/mod.rs @@ -21,9 +21,9 @@ mod builder; mod loader; pub use builder::GamepakBuilder; -pub const GPIO_PORT_DATA: u32 = 0x0800_00C4; -pub const GPIO_PORT_DIRECTION: u32 = 0x0800_00C6; -pub const GPIO_PORT_CONTROL: u32 = 0x0800_00C8; +pub const GPIO_PORT_DATA: u32 = 0xC4; +pub const GPIO_PORT_DIRECTION: u32 = 0xC6; +pub const GPIO_PORT_CONTROL: u32 = 0xC8; #[derive(Serialize, Deserialize, Clone, Debug)] pub enum BackupMedia { @@ -56,7 +56,7 @@ use super::sysbus::consts::*; pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00; fn is_gpio_access(addr: u32) -> bool { - match addr { + match addr & 0x1ff_ffff { GPIO_PORT_DATA | GPIO_PORT_DIRECTION | GPIO_PORT_CONTROL => true, _ => false, } @@ -82,8 +82,11 @@ impl Bus for Cartridge { } fn read_16(&self, addr: u32) -> u16 { - if is_gpio_access(addr) && self.gpio.is_readable() { - return self.gpio.read(addr); + if is_gpio_access(addr) { + if !(self.gpio.is_readable()) { + info!("trying to read GPIO when reads are not allowed"); + } + return self.gpio.read(addr & 0x1ff_ffff); } if addr & 0xff000000 == GAMEPAK_WS2_HI @@ -109,7 +112,7 @@ impl Bus for Cartridge { fn write_16(&mut self, addr: u32, value: u16) { if is_gpio_access(addr) { - self.gpio.write(addr); + self.gpio.write(addr & 0x1ff_ffff, value); return; } diff --git a/rustboyadvance-core/src/cartridge/rtc.rs b/rustboyadvance-core/src/cartridge/rtc.rs index 1c71cfd..6dc0b39 100644 --- a/rustboyadvance-core/src/cartridge/rtc.rs +++ b/rustboyadvance-core/src/cartridge/rtc.rs @@ -1,27 +1,613 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Clone, Debug)] -enum Port { - #[doc("Serial Clock")] - Sck = 0, - #[doc("Serial IO")] - Sio = 1, - #[doc("Chip Select")] - Cs = 2, -} - -/// Model of the S3511 8pin RTC -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct Rtc {} - -impl Rtc { - pub fn new() -> Self { - Rtc {} - } - - pub fn read_port(port: usize) -> u8 { - 0 - } - - pub fn write_port(port: usize) {} -} +use bit::BitIndex; +use bit_reverse::LookupReverse; +use chrono::prelude::*; +use serde::{Deserialize, Serialize}; + +use num::FromPrimitive; + +use std::cmp; + +use super::gpio::{GpioDevice, GpioDirection, GpioPort, GpioState, GPIO_LOW}; + +fn num2bcd(mut num: u8) -> u8 { + num = cmp::min(num, 99); + + let mut bcd = 0; + let mut digit = 1; + + while num > 0 { + let x = num % 10; + bcd += x * digit; + digit = digit << 4; + num /= 10; + } + bcd +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug)] +enum Port { + #[doc("Serial Clock")] + Sck = 0, + #[doc("Serial IO")] + Sio = 1, + #[doc("Chip Select")] + Cs = 2, +} + +impl Port { + #[inline] + pub fn index(self) -> usize { + self as usize + } +} + +/// RTC Registers codes in the GBA +#[derive(Primitive, Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] +enum RegisterKind { + ForceReset = 0, + DateTime = 2, + ForceIrq = 3, + Status = 4, + Time = 6, + Free = 7, +} + +impl RegisterKind { + fn param_count(&self) -> u8 { + use RegisterKind::*; + match self { + ForceReset => 0, + DateTime => 7, + ForceIrq => 0, + Status => 1, + Time => 3, + Free => 0, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] +enum State { + Idle, + WaitForChipSelect, + GetCommandByte, + RxFromMaster { + reg: RegisterKind, + byte_count: u8, + byte_index: u8, + }, + TxToMaster { + byte_count: u8, + byte_index: u8, + }, +} + +/// A Simple LSB-first 8-bit queue +#[derive(Serialize, Deserialize, Clone, DebugStub)] +struct SerialBuffer { + byte: u8, + counter: usize, +} + +impl SerialBuffer { + fn new() -> Self { + SerialBuffer { + byte: 0, + counter: 0, + } + } + + #[inline] + fn push_bit(&mut self, bit: bool) { + if self.counter == 8 { + return; + } + self.byte.set_bit(self.counter, bit); + self.counter += 1; + } + + #[inline] + fn pop_bit(&mut self) -> bool { + if self.counter > 0 { + let result = self.byte.bit(0); + self.byte = self.byte.wrapping_shr(1); + self.counter -= 1; + result + } else { + false + } + } + + fn reset(&mut self) { + self.byte = 0; + self.counter = 0; + } + + fn count(&self) -> usize { + self.counter + } + + fn is_empty(&self) -> bool { + self.count() == 0 + } + + fn is_full(&self) -> bool { + self.count() == 8 + } + + fn value(&self) -> Option { + if self.is_empty() { + None + } else { + let mask = if self.is_full() { + 0b11111111 + } else { + (1 << self.counter) - 1 + }; + Some(self.byte & u8::from(mask)) + } + } + + fn load_byte(&mut self, value: u8) { + self.byte = value; + self.counter = 8; + } + + fn take_byte(&mut self) -> Option { + let result = self.value(); + self.reset(); + result + } +} + +/// Model of the S3511 8pin RTC +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Rtc { + state: State, + sck: GpioPort, + sio: GpioPort, + cs: GpioPort, + status: registers::StatusRegister, + serial_buffer: SerialBuffer, + internal_buffer: [u8; 8], +} + +impl Rtc { + pub fn new() -> Self { + Rtc { + state: State::Idle, + sck: GPIO_LOW, + sio: GPIO_LOW, + cs: GPIO_LOW, + status: registers::StatusRegister { + mode_24h: true, + ..Default::default() + }, + serial_buffer: SerialBuffer::new(), + internal_buffer: [0; 8], + } + } + + fn serial_read(&mut self) { + self.serial_buffer.push_bit(self.sio.high()); + } + + fn force_reset(&mut self) { + self.serial_buffer.reset(); + self.state = State::Idle; + self.status.write(0); + // TODO according to the datasheet, the date time registers should reset to 0-0-0-0.. + } + + /// Loads a register contents into an internal buffer + fn load_register(&mut self, r: RegisterKind) { + match r { + RegisterKind::Status => self.internal_buffer[0] = self.status.read(), + RegisterKind::DateTime => { + let local: DateTime = Local::now(); + let year = local.year(); + assert!(year >= 2000 && year <= 2099); // Wonder if I will leave to see this assert fail + + let hour = if self.status.mode_24h { + local.hour() + } else { + let (_, hour12) = local.hour12(); + hour12 - 1 + }; + + self.internal_buffer[0] = num2bcd((year % 100) as u8); + self.internal_buffer[1] = num2bcd(local.month() as u8); + self.internal_buffer[2] = num2bcd(local.day() as u8); + self.internal_buffer[3] = num2bcd(local.weekday().number_from_monday() as u8); + self.internal_buffer[4] = num2bcd(hour as u8); + self.internal_buffer[5] = num2bcd(local.minute() as u8); + self.internal_buffer[6] = num2bcd(local.second() as u8); + + println!( + "DateTime result: {:x?} dt={:?}", + self.internal_buffer, local + ); + } + RegisterKind::Time => { + let local: DateTime = Local::now(); + let hour = if self.status.mode_24h { + local.hour() + } else { + let (_, hour12) = local.hour12(); + hour12 - 1 + }; + self.internal_buffer[0] = num2bcd(hour as u8); + self.internal_buffer[1] = num2bcd(local.minute() as u8); + self.internal_buffer[2] = num2bcd(local.second() as u8); + } + _ => warn!("RTC: read {:?} not implemented", r), + } + } + + fn store_register(&mut self, r: RegisterKind) { + use RegisterKind::*; + info!("write register {:?} {:?}", r, self.internal_buffer); + match r { + Status => self.status.write(self.internal_buffer[0]), + ForceReset => self.force_reset(), + _ => warn!("RTC: write {:?} not implemented", r), + } + } +} + +impl GpioDevice for Rtc { + fn write(&mut self, gpio_state: &GpioState, data: u16) { + assert_eq!(gpio_state[Port::Sck.index()], GpioDirection::Out); + assert_eq!(gpio_state[Port::Cs.index()], GpioDirection::Out); + + let old_sck = self.sck; + let old_cs = self.cs; + + self.sck.set(data.bit(Port::Sck.index()) as u16); + self.cs.set(data.bit(Port::Cs.index()) as u16); + + let sck_rising_edge = old_sck.low() && self.sck.high(); + let sck_falling_edge = old_sck.high() && self.sck.low(); + + if sck_falling_edge && gpio_state[Port::Sio.index()] == GpioDirection::Out { + self.sio.set(data.bit(Port::Sio.index()) as u16); + } + + if self.cs.high() && old_cs.low() { + trace!("RTC CS went from low to high!"); + } + + use State::*; + match self.state { + Idle => { + if self.sck.high() && self.cs.low() { + self.state = WaitForChipSelect; + } + } + WaitForChipSelect => { + if self.sck.high() && self.cs.high() { + self.state = GetCommandByte; + self.serial_buffer.reset(); + } + } + GetCommandByte => { + if !sck_falling_edge { + return; + } + + // receive bit + self.serial_read(); + if !self.serial_buffer.is_full() { + return; + } + + // finished collecting all the bits + let mut command = self.serial_buffer.value().unwrap(); + self.serial_buffer.reset(); + + let lsb_first = command.bit_range(0..4) == 0b0110; + if !lsb_first && command.bit_range(4..8) != 0b0110 { + panic!("RTC bad command format"); + } + + if !lsb_first { + command = command.swap_bits(); + } + + let reg = RegisterKind::from_u8(command.bit_range(4..7)).expect("RTC bad register"); + let byte_count = reg.param_count(); + + let is_read_operation = command.bit(7); + + info!( + "got command: {} {:?}", + if is_read_operation { "READ" } else { "WRITE" }, + reg + ); + + if byte_count != 0 { + if is_read_operation { + self.load_register(reg); + self.state = TxToMaster { + byte_count, + byte_index: 0, + }; + } else { + self.state = RxFromMaster { + reg, + byte_count, + byte_index: 0, + }; + } + } else { + assert!(!is_read_operation); + self.store_register(reg); + self.state = Idle; + } + + self.serial_buffer.reset(); + } + TxToMaster { + byte_count, + byte_index, + } => { + if !sck_falling_edge { + return; + } + + let new_byte_index = if self.serial_buffer.is_empty() { + byte_index + 1 + } else { + byte_index + }; + + if byte_count == new_byte_index { + self.state = Idle; + return; + } + + if byte_index != new_byte_index { + self.serial_buffer + .load_byte(self.internal_buffer[byte_index as usize]); + } + + assert_eq!(gpio_state[Port::Sio.index()], GpioDirection::In); + self.sio.set(self.serial_buffer.pop_bit() as u16); + + self.state = TxToMaster { + byte_count, + byte_index: new_byte_index, + }; + } + RxFromMaster { + reg, + byte_count, + byte_index, + } => { + if !sck_falling_edge { + return; + } + + assert_eq!(gpio_state[Port::Sio.index()], GpioDirection::Out); + + self.serial_read(); + if !self.serial_buffer.is_full() { + return; + } + + self.internal_buffer[byte_index as usize] = self.serial_buffer.take_byte().unwrap(); + + let byte_index = byte_index + 1; + + if byte_index == byte_count { + self.store_register(reg); + self.state = Idle; + } else { + self.state = RxFromMaster { + reg, + byte_count, + byte_index, + } + } + } + } + } + + fn read(&self, _gpio_state: &GpioState) -> u16 { + let mut result = 0; + result.set_bit(Port::Sck.index(), self.sck.high()); + result.set_bit(Port::Sio.index(), self.sio.high()); + result.set_bit(Port::Cs.index(), self.cs.high()); + result + } +} + +mod registers { + use super::*; + + #[derive(Serialize, Deserialize, Copy, Clone, Debug, Default)] + pub(super) struct StatusRegister { + pub(super) per_minute_irq: bool, + pub(super) mode_24h: bool, + pub(super) power_off: bool, + } + + impl StatusRegister { + pub(super) fn read(&self) -> u8 { + let mut result = 0; + result.set_bit(3, self.per_minute_irq); + result.set_bit(6, self.mode_24h); + result.set_bit(7, self.power_off); + result + } + + pub(super) fn write(&mut self, value: u8) { + self.per_minute_irq = value.bit(3); + self.mode_24h = value.bit(6); + self.power_off = value.bit(7); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::cell::Cell; + use std::rc::Rc; + + fn transmit(rtc: &mut Rtc, gpio_state: &GpioState, bit: u8) { + rtc.write(&gpio_state, 0b0100_u16 | (u16::from(bit) << 1)); + rtc.write(&gpio_state, 0b0101_u16); + } + + fn receive_bytes(rtc: &mut Rtc, gpio_state: &GpioState, bytes: &mut [u8]) { + for byte in bytes.iter_mut() { + for i in 0..8 { + rtc.write(&gpio_state, 0b0100_u16); + let data = rtc.read(&gpio_state); + rtc.write(&gpio_state, 0b0101_u16); + byte.set_bit(i, data.bit(Port::Sio.index())); + } + } + } + + fn transmit_bits(rtc: &mut Rtc, gpio_state: &GpioState, bits: &[u8]) { + for bit in bits.iter() { + transmit(rtc, gpio_state, *bit); + } + } + + fn start_serial_transfer(rtc: &mut Rtc, gpio_state: &GpioState) { + assert_eq!(rtc.state, State::Idle); + + // set CS low, + rtc.write(&gpio_state, 0b0001); + assert_eq!(rtc.state, State::WaitForChipSelect); + + // set CS high, SCK rising edge + rtc.write(&gpio_state, 0b0101); + assert_eq!(rtc.state, State::GetCommandByte); + } + + #[test] + fn test_serial_buffer() { + let mut serial_buffer = SerialBuffer::new(); + assert_eq!(serial_buffer.value(), None); + serial_buffer.push_bit(true); + assert_eq!(serial_buffer.value(), Some(1)); + let _ = serial_buffer.pop_bit(); + assert_eq!(serial_buffer.value(), None); + + serial_buffer.push_bit(false); + serial_buffer.push_bit(true); + serial_buffer.push_bit(true); + serial_buffer.push_bit(false); + assert_eq!(serial_buffer.count(), 4); + + assert_eq!(serial_buffer.value(), Some(6)); + let _ = serial_buffer.pop_bit(); + assert_eq!(serial_buffer.value(), Some(3)); // pops are LSB first + let _ = serial_buffer.pop_bit(); + assert_eq!(serial_buffer.count(), 2); + assert_eq!(serial_buffer.value(), Some(1)); + } + + macro_rules! setup_rtc { + ($rtc:ident, $gpio_state:ident) => { + let mut $rtc = Rtc::new(Rc::new(Cell::new(false))); + #[allow(unused_mut)] + let mut $gpio_state = [ + GpioDirection::Out, /* SCK */ + GpioDirection::Out, /* SIO */ + GpioDirection::Out, /* CS */ + GpioDirection::In, /* dont-care */ + ]; + }; + } + + #[test] + fn test_rtc_status() { + setup_rtc!(rtc, gpio_state); + + rtc.status.mode_24h = false; + start_serial_transfer(&mut rtc, &mut gpio_state); + + // write Status register command + transmit_bits(&mut rtc, &gpio_state, &[0, 1, 1, 0, 0, 0, 1, 0]); + assert_eq!( + rtc.state, + State::RxFromMaster { + reg: RegisterKind::Status, + byte_count: 1, + byte_index: 0, + } + ); + + assert_eq!(rtc.status.mode_24h, false); + + let mut serial_buffer = SerialBuffer::new(); + serial_buffer.load_byte( + registers::StatusRegister { + mode_24h: true, + ..Default::default() + } + .read(), + ); + + while !serial_buffer.is_empty() { + transmit(&mut rtc, &gpio_state, serial_buffer.pop_bit() as u8); + } + + assert!(rtc.serial_buffer.is_empty()); + assert_eq!(rtc.status.mode_24h, true); + + start_serial_transfer(&mut rtc, &mut gpio_state); + + // read Status register command + transmit_bits(&mut rtc, &gpio_state, &[0, 1, 1, 0, 0, 0, 1, 1]); + assert_eq!( + rtc.state, + State::TxToMaster { + byte_count: 1, + byte_index: 0, + } + ); + + // adjust SIO pin + gpio_state[Port::Sio.index()] = GpioDirection::In; + + let mut bytes = [0]; + receive_bytes(&mut rtc, &gpio_state, &mut bytes); + + let mut status = registers::StatusRegister::default(); + status.write(bytes[0]); + + assert_eq!(status.mode_24h, true); + } + + #[test] + fn test_date_time() { + setup_rtc!(rtc, gpio_state); + + start_serial_transfer(&mut rtc, &mut gpio_state); + transmit_bits(&mut rtc, &gpio_state, &[0, 1, 1, 0, 0, 1, 0, 1]); + assert_eq!( + rtc.state, + State::TxToMaster { + byte_count: 7, + byte_index: 0 + } + ); + + gpio_state[Port::Sio.index()] = GpioDirection::In; + let mut bytes = [0; 7]; + receive_bytes(&mut rtc, &gpio_state, &mut bytes); + assert_eq!(rtc.state, State::Idle); + + println!("{:x?}", bytes); + let local: DateTime = Local::now(); + assert_eq!(bytes[0], num2bcd((local.year() % 100) as u8)); + assert_eq!(bytes[1], num2bcd(local.month() as u8)); + assert_eq!(bytes[2], num2bcd(local.day() as u8)); + } +} diff --git a/rustboyadvance-core/src/gba.rs b/rustboyadvance-core/src/gba.rs index 0e30058..2712880 100644 --- a/rustboyadvance-core/src/gba.rs +++ b/rustboyadvance-core/src/gba.rs @@ -64,6 +64,7 @@ impl GameBoyAdvance { let sound_controller = Box::new(SoundController::new( audio_device.borrow().get_sample_rate() as f32, )); + let io = IoDevices::new(gpu, sound_controller); let sysbus = Box::new(SysBus::new(io, bios_rom, gamepak)); diff --git a/rustboyadvance-core/src/sysbus.rs b/rustboyadvance-core/src/sysbus.rs index b3c0ea7..f84395f 100644 --- a/rustboyadvance-core/src/sysbus.rs +++ b/rustboyadvance-core/src/sysbus.rs @@ -282,7 +282,7 @@ macro_rules! memory_map { $sb.io.$write_fn(addr, $value) } PALRAM_ADDR | VRAM_ADDR | OAM_ADDR => $sb.io.gpu.$write_fn($addr, $value), - GAMEPAK_WS0_LO | GAMEPAK_WS0_HI => {} + GAMEPAK_WS0_LO => $sb.cartridge.$write_fn($addr, $value), GAMEPAK_WS2_HI => $sb.cartridge.$write_fn($addr, $value), SRAM_LO | SRAM_HI => $sb.cartridge.$write_fn($addr, $value), _ => {