core: Finish up RTC!

Pokemon Emerald no longer has the "Battery ran dry" message, and RTC
functions work.


Former-commit-id: b05115af9797b9d754e20d98b1dbd5dac5389518
Former-commit-id: b10a70ec02d0b8775e1e39e9d64b41860d3b1274
This commit is contained in:
Michel Heily 2020-05-21 22:10:18 +03:00 committed by MishMish
parent 58c9d10360
commit 3839b8eb02
5 changed files with 142 additions and 129 deletions

View file

@ -16,6 +16,7 @@ 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)] #[derive(Debug)]
#[allow(dead_code)]
pub enum GpioDeviceType { pub enum GpioDeviceType {
Rtc, Rtc,
SolarSensor, SolarSensor,
@ -136,7 +137,10 @@ impl GamepakBuilder {
let mut gpio_device = self.gpio_device; let mut gpio_device = self.gpio_device;
if let Some(overrides) = overrides::get_game_overrides(&header.game_code) { if let Some(overrides) = overrides::get_game_overrides(&header.game_code) {
info!("Found game overrides for {}: {:#?}", header.game_code, overrides); info!(
"Found game overrides for {}: {:#?}",
header.game_code, overrides
);
if let Some(override_save_type) = overrides.save_type() { if let Some(override_save_type) = overrides.save_type() {
if override_save_type != save_type && save_type != BackupType::AutoDetect { if override_save_type != save_type && save_type != BackupType::AutoDetect {
warn!( warn!(
@ -150,7 +154,7 @@ impl GamepakBuilder {
if overrides.force_rtc() { if overrides.force_rtc() {
match gpio_device { match gpio_device {
GpioDeviceType::None => gpio_device = GpioDeviceType::Rtc, GpioDeviceType::None => gpio_device = GpioDeviceType::Rtc,
GpioDeviceType::Rtc => {}, GpioDeviceType::Rtc => {}
_ => { _ => {
warn!( warn!(
"Can't use RTC due to forced gpio device type {:?}", "Can't use RTC due to forced gpio device type {:?}",

View file

@ -4,32 +4,6 @@ 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};
/// 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)]
pub enum GpioDirection { pub enum GpioDirection {
/// GPIO to GBA /// GPIO to GBA
@ -53,7 +27,7 @@ pub trait GpioDevice: Sized {
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Gpio { pub struct Gpio {
rtc: Option<Rtc>, pub(in crate) rtc: Option<Rtc>,
direction: GpioState, direction: GpioState,
control: GpioPortControl, control: GpioPortControl,
} }
@ -117,7 +91,6 @@ impl Gpio {
} }
} }
GPIO_PORT_CONTROL => { GPIO_PORT_CONTROL => {
info!("GPIO port control: {:?}", self.control);
self.control = if value != 0 { self.control = if value != 0 {
GpioPortControl::ReadWrite GpioPortControl::ReadWrite
} else { } else {
@ -128,11 +101,3 @@ impl Gpio {
} }
} }
} }
#[cfg(test)]
mod tests {
#[test]
fn test_gpio() {
unimplemented!();
}
}

View file

@ -49,6 +49,9 @@ impl Cartridge {
pub fn get_symbols(&self) -> &Option<SymbolTable> { pub fn get_symbols(&self) -> &Option<SymbolTable> {
&self.symbols &self.symbols
} }
pub fn get_gpio(&self) -> &Gpio {
&self.gpio
}
} }
use super::sysbus::consts::*; use super::sysbus::consts::*;

View file

@ -7,7 +7,7 @@ use num::FromPrimitive;
use std::cmp; use std::cmp;
use super::gpio::{GpioDevice, GpioDirection, GpioPort, GpioState, GPIO_LOW}; use super::gpio::{GpioDevice, GpioDirection, GpioState};
fn num2bcd(mut num: u8) -> u8 { fn num2bcd(mut num: u8) -> u8 {
num = cmp::min(num, 99); num = cmp::min(num, 99);
@ -41,6 +41,30 @@ impl Port {
} }
} }
/// Struct holding the logical state of a serial port
#[repr(transparent)]
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
struct PortValue(u16);
#[allow(dead_code)]
impl PortValue {
fn get(&self) -> u16 {
self.0
}
fn set(&mut self, value: u16) {
self.0 = value & 1;
}
fn high(&self) -> bool {
self.0 != 0
}
fn low(&self) -> bool {
self.0 == 0
}
}
/// RTC Registers codes in the GBA /// RTC Registers codes in the GBA
#[derive(Primitive, Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] #[derive(Primitive, Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
enum RegisterKind { enum RegisterKind {
@ -67,9 +91,9 @@ impl RegisterKind {
} }
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
enum State { enum RtcState {
Idle, Idle,
WaitForChipSelect, WaitForChipSelectHigh,
GetCommandByte, GetCommandByte,
RxFromMaster { RxFromMaster {
reg: RegisterKind, reg: RegisterKind,
@ -107,14 +131,14 @@ impl SerialBuffer {
} }
#[inline] #[inline]
fn pop_bit(&mut self) -> bool { fn pop_bit(&mut self) -> Option<bool> {
if self.counter > 0 { if self.counter > 0 {
let result = self.byte.bit(0); let result = self.byte.bit(0);
self.byte = self.byte.wrapping_shr(1); self.byte = self.byte.wrapping_shr(1);
self.counter -= 1; self.counter -= 1;
result Some(result)
} else { } else {
false None
} }
} }
@ -163,10 +187,10 @@ impl SerialBuffer {
/// 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, state: RtcState,
sck: GpioPort, sck: PortValue,
sio: GpioPort, sio: PortValue,
cs: GpioPort, cs: PortValue,
status: registers::StatusRegister, status: registers::StatusRegister,
serial_buffer: SerialBuffer, serial_buffer: SerialBuffer,
internal_buffer: [u8; 8], internal_buffer: [u8; 8],
@ -175,14 +199,11 @@ pub struct Rtc {
impl Rtc { impl Rtc {
pub fn new() -> Self { pub fn new() -> Self {
Rtc { Rtc {
state: State::Idle, state: RtcState::Idle,
sck: GPIO_LOW, sck: PortValue(0),
sio: GPIO_LOW, sio: PortValue(0),
cs: GPIO_LOW, cs: PortValue(0),
status: registers::StatusRegister { status: registers::StatusRegister(0x82),
mode_24h: true,
..Default::default()
},
serial_buffer: SerialBuffer::new(), serial_buffer: SerialBuffer::new(),
internal_buffer: [0; 8], internal_buffer: [0; 8],
} }
@ -194,9 +215,18 @@ impl Rtc {
fn force_reset(&mut self) { fn force_reset(&mut self) {
self.serial_buffer.reset(); self.serial_buffer.reset();
self.state = State::Idle; self.state = RtcState::Idle;
self.status.write(0); self.status.write(0);
// TODO according to the datasheet, the date time registers should reset to 0-0-0-0.. // TODO according to the S3511 datasheet,
// the date time registers should be reset to 0-0-0-0..
}
fn serial_transfer_in_progress(&self) -> bool {
use RtcState::*;
match self.state {
Idle | WaitForChipSelectHigh => false,
_ => true,
}
} }
/// Loads a register contents into an internal buffer /// Loads a register contents into an internal buffer
@ -206,9 +236,9 @@ impl Rtc {
RegisterKind::DateTime => { RegisterKind::DateTime => {
let local: DateTime<Local> = Local::now(); let local: DateTime<Local> = Local::now();
let year = local.year(); let year = local.year();
assert!(year >= 2000 && year <= 2099); // Wonder if I will leave to see this assert fail assert!(year >= 2000 && year <= 2099); // Wonder if I will live to see this one fail
let hour = if self.status.mode_24h { let hour = if self.status.mode_24h() {
local.hour() local.hour()
} else { } else {
let (_, hour12) = local.hour12(); let (_, hour12) = local.hour12();
@ -222,15 +252,10 @@ impl Rtc {
self.internal_buffer[4] = num2bcd(hour as u8); self.internal_buffer[4] = num2bcd(hour as u8);
self.internal_buffer[5] = num2bcd(local.minute() as u8); self.internal_buffer[5] = num2bcd(local.minute() as u8);
self.internal_buffer[6] = num2bcd(local.second() as u8); self.internal_buffer[6] = num2bcd(local.second() as u8);
println!(
"DateTime result: {:x?} dt={:?}",
self.internal_buffer, local
);
} }
RegisterKind::Time => { RegisterKind::Time => {
let local: DateTime<Local> = Local::now(); let local: DateTime<Local> = Local::now();
let hour = if self.status.mode_24h { let hour = if self.status.mode_24h() {
local.hour() local.hour()
} else { } else {
let (_, hour12) = local.hour12(); let (_, hour12) = local.hour12();
@ -246,7 +271,6 @@ impl Rtc {
fn store_register(&mut self, r: RegisterKind) { fn store_register(&mut self, r: RegisterKind) {
use RegisterKind::*; use RegisterKind::*;
info!("write register {:?} {:?}", r, self.internal_buffer);
match r { match r {
Status => self.status.write(self.internal_buffer[0]), Status => self.status.write(self.internal_buffer[0]),
ForceReset => self.force_reset(), ForceReset => self.force_reset(),
@ -266,7 +290,6 @@ impl GpioDevice for Rtc {
self.sck.set(data.bit(Port::Sck.index()) as u16); self.sck.set(data.bit(Port::Sck.index()) as u16);
self.cs.set(data.bit(Port::Cs.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(); let sck_falling_edge = old_sck.high() && self.sck.low();
if sck_falling_edge && gpio_state[Port::Sio.index()] == GpioDirection::Out { if sck_falling_edge && gpio_state[Port::Sio.index()] == GpioDirection::Out {
@ -274,17 +297,32 @@ impl GpioDevice for Rtc {
} }
if self.cs.high() && old_cs.low() { if self.cs.high() && old_cs.low() {
trace!("RTC CS went from low to high!"); trace!("RTC: CS went from low to high!");
}
use RtcState::*;
if self.cs.low() && self.serial_transfer_in_progress() {
debug!(
"RTC: CS set low from state {:?}, resetting state",
self.state
);
self.serial_buffer.reset();
self.state = Idle;
return;
} }
use State::*;
match self.state { match self.state {
Idle => { Idle => {
if self.sck.high() && self.cs.low() { if self.sck.high() && self.cs.low() {
self.state = WaitForChipSelect; if self.cs.low() {
self.state = WaitForChipSelectHigh;
} else {
self.state = GetCommandByte;
}
} }
} }
WaitForChipSelect => { WaitForChipSelectHigh => {
if self.sck.high() && self.cs.high() { if self.sck.high() && self.cs.high() {
self.state = GetCommandByte; self.state = GetCommandByte;
self.serial_buffer.reset(); self.serial_buffer.reset();
@ -319,10 +357,11 @@ impl GpioDevice for Rtc {
let is_read_operation = command.bit(7); let is_read_operation = command.bit(7);
info!( debug!(
"got command: {} {:?}", "RTC: got command: {} {:?} args len: {}",
if is_read_operation { "READ" } else { "WRITE" }, if is_read_operation { "READ" } else { "WRITE" },
reg reg,
byte_count
); );
if byte_count != 0 { if byte_count != 0 {
@ -332,20 +371,21 @@ impl GpioDevice for Rtc {
byte_count, byte_count,
byte_index: 0, byte_index: 0,
}; };
self.serial_buffer.reset();
} else { } else {
self.state = RxFromMaster { self.state = RxFromMaster {
reg, reg,
byte_count, byte_count,
byte_index: 0, byte_index: 0,
}; };
self.serial_buffer.reset();
} }
} else { } else {
assert!(!is_read_operation); assert!(!is_read_operation);
self.store_register(reg); self.store_register(reg);
self.state = Idle; self.state = Idle;
self.serial_buffer.reset();
} }
self.serial_buffer.reset();
} }
TxToMaster { TxToMaster {
byte_count, byte_count,
@ -355,24 +395,29 @@ impl GpioDevice for Rtc {
return; return;
} }
let new_byte_index = if self.serial_buffer.is_empty() { let mut new_byte_index = byte_index;
byte_index + 1 let bit = if let Some(bit) = self.serial_buffer.pop_bit() {
} else { bit
byte_index } else if byte_index < byte_count {
};
if byte_count == new_byte_index {
self.state = Idle;
return;
}
if byte_index != new_byte_index {
self.serial_buffer self.serial_buffer
.load_byte(self.internal_buffer[byte_index as usize]); .load_byte(self.internal_buffer[byte_index as usize]);
} new_byte_index += 1;
self.serial_buffer.pop_bit().unwrap()
} else {
self.state = Idle;
self.serial_buffer.reset();
return;
};
trace!("RTC TX BIT {}", bit);
assert_eq!(gpio_state[Port::Sio.index()], GpioDirection::In); assert_eq!(gpio_state[Port::Sio.index()], GpioDirection::In);
self.sio.set(self.serial_buffer.pop_bit() as u16); self.sio.set(bit as u16);
if self.serial_buffer.is_empty() && new_byte_index == byte_count {
self.state = Idle;
self.serial_buffer.reset();
return;
}
self.state = TxToMaster { self.state = TxToMaster {
byte_count, byte_count,
@ -423,28 +468,27 @@ impl GpioDevice for Rtc {
} }
mod registers { mod registers {
use super::*;
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Default)] bitfield! {
pub(super) struct StatusRegister { #[derive(Serialize, Deserialize, Clone)]
pub(super) per_minute_irq: bool, pub struct StatusRegister(u8);
pub(super) mode_24h: bool, impl Debug;
pub(super) power_off: bool, u8;
pub intfe, set_intfe : 1; // unimplemented
pub intme, set_intme : 3; // unimplemented
pub intae, set_intae : 5; // unimplemented
pub mode_24h, set_mode_24h : 6;
pub power_fail, set_power_fail : 7;
} }
impl StatusRegister { impl StatusRegister {
const IGNORED_MASK: u8 = 0b1001_0101;
pub(super) fn read(&self) -> u8 { pub(super) fn read(&self) -> u8 {
let mut result = 0; self.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) { pub(super) fn write(&mut self, value: u8) {
self.per_minute_irq = value.bit(3); self.0 = !Self::IGNORED_MASK & value;
self.mode_24h = value.bit(6);
self.power_off = value.bit(7);
} }
} }
} }
@ -478,15 +522,15 @@ mod tests {
} }
fn start_serial_transfer(rtc: &mut Rtc, gpio_state: &GpioState) { fn start_serial_transfer(rtc: &mut Rtc, gpio_state: &GpioState) {
assert_eq!(rtc.state, State::Idle); assert_eq!(rtc.state, RtcState::Idle);
// set CS low, // set CS low,
rtc.write(&gpio_state, 0b0001); rtc.write(&gpio_state, 0b0001);
assert_eq!(rtc.state, State::WaitForChipSelect); assert_eq!(rtc.state, RtcState::WaitForChipSelectHigh);
// set CS high, SCK rising edge // set CS high, SCK rising edge
rtc.write(&gpio_state, 0b0101); rtc.write(&gpio_state, 0b0101);
assert_eq!(rtc.state, State::GetCommandByte); assert_eq!(rtc.state, RtcState::GetCommandByte);
} }
#[test] #[test]
@ -514,7 +558,7 @@ mod tests {
macro_rules! setup_rtc { macro_rules! setup_rtc {
($rtc:ident, $gpio_state:ident) => { ($rtc:ident, $gpio_state:ident) => {
let mut $rtc = Rtc::new(Rc::new(Cell::new(false))); let mut $rtc = Rtc::new();
#[allow(unused_mut)] #[allow(unused_mut)]
let mut $gpio_state = [ let mut $gpio_state = [
GpioDirection::Out, /* SCK */ GpioDirection::Out, /* SCK */
@ -529,37 +573,31 @@ mod tests {
fn test_rtc_status() { fn test_rtc_status() {
setup_rtc!(rtc, gpio_state); setup_rtc!(rtc, gpio_state);
rtc.status.mode_24h = false; rtc.status.set_mode_24h(false);
start_serial_transfer(&mut rtc, &mut gpio_state); start_serial_transfer(&mut rtc, &mut gpio_state);
// write Status register command // write Status register command
transmit_bits(&mut rtc, &gpio_state, &[0, 1, 1, 0, 0, 0, 1, 0]); transmit_bits(&mut rtc, &gpio_state, &[0, 1, 1, 0, 0, 0, 1, 0]);
assert_eq!( assert_eq!(
rtc.state, rtc.state,
State::RxFromMaster { RtcState::RxFromMaster {
reg: RegisterKind::Status, reg: RegisterKind::Status,
byte_count: 1, byte_count: 1,
byte_index: 0, byte_index: 0,
} }
); );
assert_eq!(rtc.status.mode_24h, false); assert_eq!(rtc.status.mode_24h(), false);
let mut serial_buffer = SerialBuffer::new(); let mut serial_buffer = SerialBuffer::new();
serial_buffer.load_byte( serial_buffer.load_byte(1 << 6);
registers::StatusRegister {
mode_24h: true,
..Default::default()
}
.read(),
);
while !serial_buffer.is_empty() { while let Some(bit) = serial_buffer.pop_bit() {
transmit(&mut rtc, &gpio_state, serial_buffer.pop_bit() as u8); transmit(&mut rtc, &gpio_state, bit as u8);
} }
assert!(rtc.serial_buffer.is_empty()); assert!(rtc.serial_buffer.is_empty());
assert_eq!(rtc.status.mode_24h, true); assert_eq!(rtc.status.mode_24h(), true);
start_serial_transfer(&mut rtc, &mut gpio_state); start_serial_transfer(&mut rtc, &mut gpio_state);
@ -567,7 +605,7 @@ mod tests {
transmit_bits(&mut rtc, &gpio_state, &[0, 1, 1, 0, 0, 0, 1, 1]); transmit_bits(&mut rtc, &gpio_state, &[0, 1, 1, 0, 0, 0, 1, 1]);
assert_eq!( assert_eq!(
rtc.state, rtc.state,
State::TxToMaster { RtcState::TxToMaster {
byte_count: 1, byte_count: 1,
byte_index: 0, byte_index: 0,
} }
@ -579,10 +617,8 @@ mod tests {
let mut bytes = [0]; let mut bytes = [0];
receive_bytes(&mut rtc, &gpio_state, &mut bytes); receive_bytes(&mut rtc, &gpio_state, &mut bytes);
let mut status = registers::StatusRegister::default(); let mut read_status = registers::StatusRegister(bytes[0]);
status.write(bytes[0]); assert_eq!(read_status.mode_24h(), true);
assert_eq!(status.mode_24h, true);
} }
#[test] #[test]
@ -593,7 +629,7 @@ mod tests {
transmit_bits(&mut rtc, &gpio_state, &[0, 1, 1, 0, 0, 1, 0, 1]); transmit_bits(&mut rtc, &gpio_state, &[0, 1, 1, 0, 0, 1, 0, 1]);
assert_eq!( assert_eq!(
rtc.state, rtc.state,
State::TxToMaster { RtcState::TxToMaster {
byte_count: 7, byte_count: 7,
byte_index: 0 byte_index: 0
} }
@ -602,7 +638,7 @@ mod tests {
gpio_state[Port::Sio.index()] = GpioDirection::In; gpio_state[Port::Sio.index()] = GpioDirection::In;
let mut bytes = [0; 7]; let mut bytes = [0; 7];
receive_bytes(&mut rtc, &gpio_state, &mut bytes); receive_bytes(&mut rtc, &gpio_state, &mut bytes);
assert_eq!(rtc.state, State::Idle); assert_eq!(rtc.state, RtcState::Idle);
println!("{:x?}", bytes); println!("{:x?}", bytes);
let local: DateTime<Local> = Local::now(); let local: DateTime<Local> = Local::now();

View file

@ -46,6 +46,7 @@ bitflags! {
pub enum Command { pub enum Command {
Info, Info,
GpuInfo, GpuInfo,
GpioInfo,
Step(usize), Step(usize),
Continue, Continue,
Frame(usize), Frame(usize),
@ -78,6 +79,9 @@ impl Debugger {
println!("IF={:#?}", self.gba.sysbus.io.intc.interrupt_flags); println!("IF={:#?}", self.gba.sysbus.io.intc.interrupt_flags);
} }
GpuInfo => println!("GPU: {:#?}", self.gba.sysbus.io.gpu), GpuInfo => println!("GPU: {:#?}", self.gba.sysbus.io.gpu),
GpioInfo => {
println!("GPIO: {:#?}", self.gba.sysbus.cartridge.get_gpio());
}
Step(count) => { Step(count) => {
for _ in 0..count { for _ in 0..count {
self.gba.cpu.step(&mut self.gba.sysbus); self.gba.cpu.step(&mut self.gba.sysbus);
@ -288,6 +292,7 @@ impl Debugger {
match command.as_ref() { match command.as_ref() {
"i" | "info" => Ok(Command::Info), "i" | "info" => Ok(Command::Info),
"gpuinfo" => Ok(Command::GpuInfo), "gpuinfo" => Ok(Command::GpuInfo),
"gpio" => Ok(Command::GpioInfo),
"s" | "step" => { "s" | "step" => {
let count = match args.len() { let count = match args.len() {
0 => 1, 0 => 1,