core: More RTC work

Former-commit-id: be2955b08d65c547d206d637ae499dbad892a87a
Former-commit-id: 19f0e4e6f9c598c35fb8172d93a1519f921eb27f
This commit is contained in:
Michel Heily 2020-05-17 20:12:31 +03:00 committed by MishMish
parent 304ac31c02
commit 8e1ba1117c
9 changed files with 801 additions and 130 deletions

View file

@ -24,6 +24,10 @@ args:
- flash64k - flash64k
- eeprom - eeprom
- autodetect - autodetect
- rtc:
long: rtc
help: Force cartridge to have RTC
required: false
- skip_bios: - skip_bios:
long: skip-bios long: skip-bios
help: Skip running bios and start from the ROM instead help: Skip running bios and start from the ROM instead

View file

@ -245,12 +245,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut rom_name = Path::new(&rom_path).file_name().unwrap().to_str().unwrap(); 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( .save_type(BackupType::try_from(
matches.value_of("save_type").unwrap(), matches.value_of("save_type").unwrap(),
)?) )?)
.file(Path::new(&rom_path)) .file(Path::new(&rom_path));
.build()?;
if matches.occurrences_of("rtc") != 0 {
builder = builder.with_rtc();
}
let gamepak = builder.build()?;
let mut gba = GameBoyAdvance::new( let mut gba = GameBoyAdvance::new(
bios_bin.into_boxed_slice(), bios_bin.into_boxed_slice(),

View file

@ -5,13 +5,14 @@ authors = ["Michel Heily <michelheily@gmail.com>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
serde = { version = "1.0.104", features = ["derive"] } serde = { version = "1.0.104", features = ["derive", "rc"] }
bincode = "1.2.1" bincode = "1.2.1"
byteorder = "1" byteorder = "1"
num = "0.2.1" num = "0.2.1"
num-traits = "0.2" num-traits = "0.2"
enum-primitive-derive = "^0.1" enum-primitive-derive = "^0.1"
bit = "^0.1" bit = "^0.1"
chrono = "0.4"
colored = "1.9" colored = "1.9"
ansi_term = "0.12.1" ansi_term = "0.12.1"
hexdump = "0.1.0" hexdump = "0.1.0"
@ -36,6 +37,7 @@ gdbstub = { version = "0.1.2", optional = true, features = ["std"] }
ringbuf = "0.2.1" ringbuf = "0.2.1"
goblin = { version = "0.2", optional = true } goblin = { version = "0.2", optional = true }
fuzzy-matcher = { version = "0.3.4", optional = true } fuzzy-matcher = { version = "0.3.4", optional = true }
bit_reverse = "0.1.8"
[target.'cfg(target_arch="wasm32")'.dependencies] [target.'cfg(target_arch="wasm32")'.dependencies]
instant = { version = "0.1.2", features = ["wasm-bindgen"] } instant = { version = "0.1.2", features = ["wasm-bindgen"] }

View file

@ -14,12 +14,21 @@ use super::Cartridge;
use super::loader::{load_from_bytes, load_from_file, LoadRom}; use super::loader::{load_from_bytes, load_from_file, LoadRom};
#[derive(Debug)]
pub enum GpioDeviceType {
Rtc,
SolarSensor,
Gyro,
None,
}
#[derive(Debug)] #[derive(Debug)]
pub struct GamepakBuilder { pub struct GamepakBuilder {
path: Option<PathBuf>, path: Option<PathBuf>,
bytes: Option<Box<[u8]>>, bytes: Option<Box<[u8]>>,
save_path: Option<PathBuf>, save_path: Option<PathBuf>,
save_type: BackupType, save_type: BackupType,
gpio_device: GpioDeviceType,
create_backup_file: bool, create_backup_file: bool,
} }
@ -30,6 +39,7 @@ impl GamepakBuilder {
path: None, path: None,
save_path: None, save_path: None,
bytes: None, bytes: None,
gpio_device: GpioDeviceType::None,
create_backup_file: true, create_backup_file: true,
} }
} }
@ -84,6 +94,11 @@ impl GamepakBuilder {
self self
} }
pub fn with_rtc(mut self) -> Self {
self.gpio_device = GpioDeviceType::Rtc;
self
}
pub fn build(mut self) -> GBAResult<Cartridge> { pub fn build(mut self) -> GBAResult<Cartridge> {
let (bytes, symbols) = if let Some(bytes) = self.bytes { let (bytes, symbols) = if let Some(bytes) = self.bytes {
match load_from_bytes(bytes.to_vec())? { match load_from_bytes(bytes.to_vec())? {
@ -127,7 +142,14 @@ impl GamepakBuilder {
let backup = create_backup(self.save_type, self.save_path); 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(); let size = bytes.len();
Ok(Cartridge { Ok(Cartridge {

View file

@ -4,44 +4,73 @@ use super::{GPIO_PORT_CONTROL, GPIO_PORT_DATA, GPIO_PORT_DIRECTION};
use bit::BitIndex; use bit::BitIndex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::cell::RefCell; /// 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)] #[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
enum GpioDirection { pub enum GpioDirection {
/// GPIO to GBA
In = 0, In = 0,
/// GBA to GPIO
Out = 1, Out = 1,
} }
pub type GpioState = [GpioDirection; 4];
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)] #[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
enum GpioPortControl { enum GpioPortControl {
WriteOnly = 0, WriteOnly = 0,
ReadWrite = 1, ReadWrite = 1,
} }
trait GpioDevice: Sized { pub trait GpioDevice: Sized {
fn write(&mut self); fn write(&mut self, gpio_state: &GpioState, data: u16);
fn read(&mut self); fn read(&self, gpio_state: &GpioState) -> u16;
}
enum GpioDeviceType {
Rtc,
SolarSensor,
Gyro,
None,
} }
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Gpio { pub struct Gpio {
rtc: Option<RefCell<Rtc>>, rtc: Option<Rtc>,
direction: [GpioDirection; 4], direction: GpioState,
control: GpioPortControl, control: GpioPortControl,
} }
impl Gpio { impl Gpio {
pub fn new() -> Self { pub fn new_none() -> Self {
Gpio { Gpio {
rtc: None, rtc: None,
direction: [GpioDirection::In; 4], direction: [GpioDirection::Out; 4],
control: GpioPortControl::WriteOnly,
}
}
pub fn new_rtc() -> Self {
Gpio {
rtc: Some(Rtc::new()),
direction: [GpioDirection::Out; 4],
control: GpioPortControl::WriteOnly, control: GpioPortControl::WriteOnly,
} }
} }
@ -52,7 +81,13 @@ impl Gpio {
pub fn read(&self, addr: u32) -> u16 { pub fn read(&self, addr: u32) -> u16 {
match addr { match addr {
GPIO_PORT_DATA => unimplemented!(), GPIO_PORT_DATA => {
if let Some(rtc) = &self.rtc {
rtc.read(&self.direction)
} else {
0
}
}
GPIO_PORT_DIRECTION => { GPIO_PORT_DIRECTION => {
let mut direction = 0u16; let mut direction = 0u16;
for i in 0..4 { for i in 0..4 {
@ -65,17 +100,30 @@ impl Gpio {
} }
} }
pub fn write(&mut self, addr: u32) -> u16 { pub fn write(&mut self, addr: u32, value: u16) {
match addr { match addr {
GPIO_PORT_DATA => unimplemented!(), GPIO_PORT_DATA => {
if let Some(rtc) = &mut self.rtc {
rtc.write(&self.direction, value);
}
}
GPIO_PORT_DIRECTION => { GPIO_PORT_DIRECTION => {
let mut direction = 0u16;
for i in 0..4 { for i in 0..4 {
direction.set_bit(i, self.direction[i] == GpioDirection::Out); if value.bit(i) {
self.direction[i] = GpioDirection::Out;
} else {
self.direction[i] = GpioDirection::In;
} }
direction
} }
GPIO_PORT_CONTROL => self.control as u16, }
GPIO_PORT_CONTROL => {
info!("GPIO port control: {:?}", self.control);
self.control = if value != 0 {
GpioPortControl::ReadWrite
} else {
GpioPortControl::WriteOnly
};
}
_ => unreachable!(), _ => unreachable!(),
} }
} }

View file

@ -21,9 +21,9 @@ mod builder;
mod loader; mod loader;
pub use builder::GamepakBuilder; pub use builder::GamepakBuilder;
pub const GPIO_PORT_DATA: u32 = 0x0800_00C4; pub const GPIO_PORT_DATA: u32 = 0xC4;
pub const GPIO_PORT_DIRECTION: u32 = 0x0800_00C6; pub const GPIO_PORT_DIRECTION: u32 = 0xC6;
pub const GPIO_PORT_CONTROL: u32 = 0x0800_00C8; pub const GPIO_PORT_CONTROL: u32 = 0xC8;
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub enum BackupMedia { pub enum BackupMedia {
@ -56,7 +56,7 @@ use super::sysbus::consts::*;
pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00; pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00;
fn is_gpio_access(addr: u32) -> bool { fn is_gpio_access(addr: u32) -> bool {
match addr { match addr & 0x1ff_ffff {
GPIO_PORT_DATA | GPIO_PORT_DIRECTION | GPIO_PORT_CONTROL => true, GPIO_PORT_DATA | GPIO_PORT_DIRECTION | GPIO_PORT_CONTROL => true,
_ => false, _ => false,
} }
@ -82,8 +82,11 @@ impl Bus for Cartridge {
} }
fn read_16(&self, addr: u32) -> u16 { fn read_16(&self, addr: u32) -> u16 {
if is_gpio_access(addr) && self.gpio.is_readable() { if is_gpio_access(addr) {
return self.gpio.read(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 if addr & 0xff000000 == GAMEPAK_WS2_HI
@ -109,7 +112,7 @@ impl Bus for Cartridge {
fn write_16(&mut self, addr: u32, value: u16) { fn write_16(&mut self, addr: u32, value: u16) {
if is_gpio_access(addr) { if is_gpio_access(addr) {
self.gpio.write(addr); self.gpio.write(addr & 0x1ff_ffff, value);
return; return;
} }

View file

@ -1,6 +1,30 @@
use bit::BitIndex;
use bit_reverse::LookupReverse;
use chrono::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug)] 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 { enum Port {
#[doc("Serial Clock")] #[doc("Serial Clock")]
Sck = 0, Sck = 0,
@ -10,18 +34,580 @@ enum Port {
Cs = 2, 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<u8> {
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<u8> {
let result = self.value();
self.reset();
result
}
}
/// Model of the S3511 8pin RTC /// Model of the S3511 8pin RTC
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Rtc {} pub struct Rtc {
state: State,
sck: GpioPort,
sio: GpioPort,
cs: GpioPort,
status: registers::StatusRegister,
serial_buffer: SerialBuffer,
internal_buffer: [u8; 8],
}
impl Rtc { impl Rtc {
pub fn new() -> Self { pub fn new() -> Self {
Rtc {} 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],
}
} }
pub fn read_port(port: usize) -> u8 { fn serial_read(&mut self) {
0 self.serial_buffer.push_bit(self.sio.high());
} }
pub fn write_port(port: usize) {} 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> = 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> = 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> = 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));
}
} }

View file

@ -64,6 +64,7 @@ impl GameBoyAdvance {
let sound_controller = Box::new(SoundController::new( let sound_controller = Box::new(SoundController::new(
audio_device.borrow().get_sample_rate() as f32, audio_device.borrow().get_sample_rate() as f32,
)); ));
let io = IoDevices::new(gpu, sound_controller); let io = IoDevices::new(gpu, sound_controller);
let sysbus = Box::new(SysBus::new(io, bios_rom, gamepak)); let sysbus = Box::new(SysBus::new(io, bios_rom, gamepak));

View file

@ -282,7 +282,7 @@ macro_rules! memory_map {
$sb.io.$write_fn(addr, $value) $sb.io.$write_fn(addr, $value)
} }
PALRAM_ADDR | VRAM_ADDR | OAM_ADDR => $sb.io.gpu.$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), GAMEPAK_WS2_HI => $sb.cartridge.$write_fn($addr, $value),
SRAM_LO | SRAM_HI => $sb.cartridge.$write_fn($addr, $value), SRAM_LO | SRAM_HI => $sb.cartridge.$write_fn($addr, $value),
_ => { _ => {