196 lines
6.8 KiB
Rust
196 lines
6.8 KiB
Rust
use super::*;
|
|
use gba_core::{
|
|
cartridge::{RtcDateTime, RtcError},
|
|
io::{HALTCNT, IE, IME},
|
|
};
|
|
|
|
fn setup(parts: [u8; 7]) -> Memory {
|
|
let mut bus = memory();
|
|
bus.write16(GPIO_CONTROL, 1).unwrap();
|
|
bus.set_rtc_datetime(RtcDateTime::new(parts).unwrap())
|
|
.unwrap();
|
|
bus
|
|
}
|
|
fn read_byte(bus: &mut Memory) -> u8 {
|
|
let mut byte = 0;
|
|
for bit in 0..8 {
|
|
bus.write16(GPIO_DATA, 4).unwrap();
|
|
let data = bus.read16(GPIO_DATA).unwrap() & 2;
|
|
bus.write16(GPIO_DATA, 5).unwrap();
|
|
assert_eq!(bus.read16(GPIO_DATA).unwrap() & 2, data);
|
|
byte |= (data as u8 >> 1) << bit;
|
|
}
|
|
byte
|
|
}
|
|
fn read(bus: &mut Memory, cmd: u8, len: usize) -> Vec<u8> {
|
|
start(bus);
|
|
command(bus, cmd, true).unwrap();
|
|
bus.write16(GPIO_DIRECTION, 5).unwrap();
|
|
let bytes = (0..len).map(|_| read_byte(bus)).collect();
|
|
bus.write16(GPIO_DATA, 1).unwrap();
|
|
bytes
|
|
}
|
|
fn write(bus: &mut Memory, cmd: u8, bytes: &[u8]) -> Result<(), MemoryError> {
|
|
start(bus);
|
|
command(bus, cmd, true)?;
|
|
for &byte in bytes {
|
|
parameter(bus, byte)?;
|
|
}
|
|
bus.write16(GPIO_DATA, 1)
|
|
}
|
|
|
|
#[test]
|
|
fn calendar_and_time_reads_use_bcd_and_gba_pm_bit() {
|
|
let mut bus = setup([24, 2, 29, 4, 23, 58, 59]);
|
|
assert_eq!(
|
|
read(&mut bus, 0x65, 7),
|
|
[0x24, 2, 0x29, 4, 0xa3, 0x58, 0x59]
|
|
);
|
|
assert_eq!(read(&mut bus, 0x67, 3), [0xa3, 0x58, 0x59]);
|
|
write(&mut bus, 0x62, &[0]).unwrap(); // 12-hour presentation preserves the actual hour.
|
|
assert_eq!(read(&mut bus, 0x67, 3), [0x91, 0x58, 0x59]);
|
|
bus.advance_rtc_seconds(61).unwrap();
|
|
assert_eq!(read(&mut bus, 0x65, 7), [0x24, 3, 1, 5, 0, 0, 0]);
|
|
assert_eq!(bus.read16(IF).unwrap(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn complete_writes_commit_calendar_or_time_and_mask_unused_bits() {
|
|
let mut bus = setup([0, 1, 1, 0, 0, 0, 0]);
|
|
write(&mut bus, 0x64, &[0x24, 0xe2, 0xe9, 0xfc, 0x63, 0xd8, 0xd9]).unwrap();
|
|
assert_eq!(
|
|
bus.rtc_datetime().unwrap().components(),
|
|
[24, 2, 29, 4, 23, 58, 59]
|
|
);
|
|
write(&mut bus, 0x66, &[0x80, 0x12, 0x34]).unwrap(); // PM ignored in 24-hour writes.
|
|
assert_eq!(
|
|
bus.rtc_datetime().unwrap().components(),
|
|
[24, 2, 29, 4, 0, 12, 34]
|
|
);
|
|
write(&mut bus, 0x62, &[0]).unwrap();
|
|
write(&mut bus, 0x66, &[0x80, 0, 0]).unwrap(); // Noon is 00 with PM, not 12.
|
|
assert_eq!(
|
|
bus.rtc_datetime().unwrap().components(),
|
|
[24, 2, 29, 4, 12, 0, 0]
|
|
);
|
|
assert_eq!(read(&mut bus, 0x67, 3), [0x80, 0, 0]);
|
|
}
|
|
|
|
#[test]
|
|
fn read_snapshot_survives_mid_transfer_tick_and_host_reseed() {
|
|
let mut bus = setup([24, 12, 31, 2, 23, 59, 59]);
|
|
start(&mut bus);
|
|
command(&mut bus, 0x65, true).unwrap();
|
|
bus.write16(GPIO_DIRECTION, 5).unwrap();
|
|
assert_eq!(read_byte(&mut bus), 0x24);
|
|
bus.advance_rtc_seconds(1).unwrap();
|
|
assert_eq!(
|
|
bus.rtc_datetime().unwrap().components(),
|
|
[25, 1, 1, 3, 0, 0, 0]
|
|
);
|
|
bus.set_rtc_datetime(RtcDateTime::new([50, 6, 7, 1, 8, 9, 10]).unwrap())
|
|
.unwrap();
|
|
let tail: Vec<_> = (0..6).map(|_| read_byte(&mut bus)).collect();
|
|
assert_eq!(tail, [0x12, 0x31, 2, 0xa3, 0x59, 0x59]);
|
|
bus.write16(GPIO_DATA, 1).unwrap();
|
|
assert_eq!(read(&mut bus, 0x65, 7), [0x50, 6, 7, 1, 8, 9, 0x10]);
|
|
}
|
|
|
|
#[test]
|
|
fn partial_payloads_abort_without_calendar_changes_and_time_writes_preserve_live_date() {
|
|
let original = [24, 2, 28, 6, 23, 59, 59];
|
|
for bits in 0..56 {
|
|
let mut bus = setup(original);
|
|
start(&mut bus);
|
|
command(&mut bus, 0x64, true).unwrap();
|
|
send_bits(&mut bus, (0..bits).map(|_| false)).unwrap();
|
|
bus.write16(GPIO_DATA, 1).unwrap();
|
|
assert_eq!(bus.rtc_datetime().unwrap().components(), original);
|
|
}
|
|
let mut bus = setup(original);
|
|
start(&mut bus);
|
|
command(&mut bus, 0x66, true).unwrap();
|
|
parameter(&mut bus, 0x12).unwrap();
|
|
bus.advance_rtc_seconds(1).unwrap();
|
|
parameter(&mut bus, 0x34).unwrap();
|
|
parameter(&mut bus, 0x56).unwrap();
|
|
bus.write16(GPIO_DATA, 1).unwrap();
|
|
assert_eq!(
|
|
bus.rtc_datetime().unwrap().components(),
|
|
[24, 2, 29, 0, 12, 34, 56]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_calendar_payload_is_diagnostic_retryable_and_cannot_commit() {
|
|
for payload in [
|
|
[0xfa, 1, 1, 0, 0, 0, 0],
|
|
[0x23, 2, 0x29, 0, 0, 0, 0],
|
|
[0, 0, 1, 0, 0, 0, 0],
|
|
[0, 1, 1, 7, 0, 0, 0],
|
|
[0, 1, 1, 0, 0x24, 0, 0],
|
|
[0, 1, 1, 0, 0, 0x60, 0],
|
|
[0, 1, 1, 0, 0, 0, 0x6a],
|
|
] {
|
|
let mut bus = setup([24, 5, 6, 3, 7, 8, 9]);
|
|
let before = bus.rtc_datetime();
|
|
let error = write(&mut bus, 0x64, &payload).unwrap_err();
|
|
assert!(error.to_string().contains("invalid calendar data"));
|
|
let pins = bus.read16(GPIO_DATA).unwrap();
|
|
assert_eq!(pins & 1, 0);
|
|
assert_eq!(bus.write16(GPIO_DATA, pins | 1), Err(error));
|
|
assert_eq!(bus.rtc_datetime(), before);
|
|
bus.write16(GPIO_DATA, 1).unwrap();
|
|
assert_eq!(read(&mut bus, 0x65, 7), [0x24, 5, 6, 3, 7, 8, 9]);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn reset_affects_calendar_and_mode_but_not_the_gpio_overlay() {
|
|
for cmd in [0x60, 0x61] {
|
|
let mut bus = setup([99, 12, 31, 6, 23, 59, 59]);
|
|
write(&mut bus, cmd, &[]).unwrap();
|
|
assert_eq!(bus.rtc_datetime(), Some(RtcDateTime::default()));
|
|
assert_eq!(control(&mut bus, true), 0);
|
|
assert_eq!(read(&mut bus, 0x65, 7), [0, 1, 1, 0, 0, 0, 0]);
|
|
assert_eq!(bus.read16(GPIO_CONTROL).unwrap(), 1);
|
|
bus.advance_rtc_seconds(43200).unwrap();
|
|
assert_eq!(read(&mut bus, 0x67, 3), [0x80, 0, 0]);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn battery_clock_is_independent_of_cpu_cycles_halt_stop_and_device_presence() {
|
|
let mut absent = Memory::new(vec![0; 4]).unwrap();
|
|
assert_eq!(absent.rtc_datetime(), None);
|
|
assert_eq!(
|
|
absent.set_rtc_datetime(RtcDateTime::default()),
|
|
Err(RtcError::NotAttached)
|
|
);
|
|
assert_eq!(absent.advance_rtc_seconds(0), Err(RtcError::NotAttached));
|
|
for stop in [false, true] {
|
|
let mut bus = setup([0, 1, 1, 0, 0, 0, 0]);
|
|
bus.write16(IE, 0x2080).unwrap();
|
|
bus.write16(IME, 1).unwrap();
|
|
bus.write8(HALTCNT, if stop { 128 } else { 0 }).unwrap();
|
|
let mut machine = Machine::new(Cpu::new(ROM_START), bus);
|
|
for _ in 0..20 {
|
|
machine.step().unwrap();
|
|
}
|
|
machine.memory_mut().advance_cycles(16777216);
|
|
assert_eq!(
|
|
machine.memory().rtc_datetime(),
|
|
Some(RtcDateTime::default())
|
|
);
|
|
let cycles = machine.cycles();
|
|
machine.memory_mut().advance_rtc_seconds(86401).unwrap();
|
|
assert_eq!(
|
|
machine.memory().rtc_datetime().unwrap().components(),
|
|
[0, 1, 2, 1, 0, 0, 1]
|
|
);
|
|
assert_eq!(machine.cycles(), cycles);
|
|
assert_eq!(machine.memory().read16(IF).unwrap(), 0);
|
|
assert_eq!(machine.halted(), !stop);
|
|
assert_eq!(machine.stopped(), stop);
|
|
}
|
|
}
|