feat(serial): support disconnected Joybus configuration and local registers
This commit is contained in:
parent
8b3b7ae62d
commit
7546d17630
15 changed files with 462 additions and 62 deletions
142
crates/gba-core/tests/serial_joybus.rs
Normal file
142
crates/gba-core/tests/serial_joybus.rs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
//! Original disconnected Joybus probes. No external commands or linked transfers.
|
||||
use gba_core::{
|
||||
cpu::Cpu,
|
||||
dma::DMA_BASE,
|
||||
io::{HALTCNT, IE, IF, IME, JOYCNT, JOYSTAT, JOY_RECV, JOY_TRANS, RCNT, SIOCNT, SIODATA8},
|
||||
machine::{Machine, StepKind},
|
||||
memory::{Memory, MemoryError, ROM_START},
|
||||
};
|
||||
|
||||
fn memory() -> Memory {
|
||||
Memory::new(0xeaff_fffe_u32.to_le_bytes().to_vec()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_register_masks_do_not_fabricate_remote_events_in_any_mode() {
|
||||
let mut bus = memory();
|
||||
for mode in [0, 0x8000, 0xc000, 0xc100, 0x4100] {
|
||||
bus.write16(RCNT, mode).unwrap();
|
||||
for value in 0..=255 {
|
||||
bus.write16(JOYCNT, 0xff00 | value).unwrap();
|
||||
bus.write16(JOYSTAT, 0xff00 | value).unwrap();
|
||||
assert_eq!(bus.read16(JOYCNT).unwrap(), value & 0x40);
|
||||
assert_eq!(bus.read16(JOYSTAT).unwrap(), value & 0x30);
|
||||
bus.write8(JOYCNT + 1, 0xff).unwrap();
|
||||
bus.write8(JOYSTAT + 1, 0xff).unwrap();
|
||||
assert_eq!(bus.read16(JOYCNT).unwrap(), value & 0x40);
|
||||
assert_eq!(bus.read16(JOYSTAT).unwrap(), value & 0x30);
|
||||
}
|
||||
bus.advance_cycles(1000000);
|
||||
assert_eq!(bus.read16(IF).unwrap(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_lanes_retain_words_and_transmit_writes_set_pending_not_completion() {
|
||||
for mode in [0, 0x8000, 0xc000] {
|
||||
for base in [JOY_RECV, JOY_TRANS] {
|
||||
let mut bus = memory();
|
||||
bus.write16(RCNT, mode).unwrap();
|
||||
for lane in 0..4 {
|
||||
bus.write8(base + lane, (0x12 + lane * 0x22) as u8).unwrap();
|
||||
}
|
||||
assert_eq!(bus.read32(base).unwrap(), 0x78563412);
|
||||
bus.write16(base + 2, 0xabcd).unwrap();
|
||||
assert_eq!(bus.read32(base).unwrap(), 0xabcd3412);
|
||||
bus.write32(base, 0x98765432).unwrap();
|
||||
assert_eq!(bus.read16(base).unwrap(), 0x5432);
|
||||
assert_eq!(bus.read16(base + 2).unwrap(), 0x9876);
|
||||
let pending = if base == JOY_TRANS { 2 } else { 0 };
|
||||
assert_eq!(bus.read16(JOYSTAT).unwrap(), pending);
|
||||
bus.write16(JOYSTAT, 0xffff).unwrap();
|
||||
assert_eq!(bus.read16(JOYSTAT).unwrap(), pending | 0x30);
|
||||
bus.write16(JOYSTAT, 0).unwrap(); // Software cannot clear transmit pending.
|
||||
bus.write16(JOYCNT, 0x47).unwrap();
|
||||
bus.read32(JOY_RECV).unwrap();
|
||||
bus.advance_cycles(1000000);
|
||||
assert_eq!(bus.read16(JOYSTAT).unwrap(), pending);
|
||||
assert_eq!(bus.read16(JOYCNT).unwrap(), 0x40);
|
||||
assert_eq!(bus.read16(IF).unwrap(), 0);
|
||||
bus.write16(RCNT, 0x8000).unwrap();
|
||||
bus.write16(RCNT, 0xc100).unwrap();
|
||||
assert_eq!(bus.read32(base).unwrap(), 0x98765432);
|
||||
assert_eq!(bus.read16(JOYSTAT).unwrap(), pending);
|
||||
}
|
||||
}
|
||||
// Writing zero also makes a reply available; zero is not a reset strobe.
|
||||
let mut bus = memory();
|
||||
bus.write8(JOY_TRANS + 3, 0).unwrap();
|
||||
assert_eq!(bus.read16(JOYSTAT).unwrap(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn joybus_does_not_run_normal_clocks_or_wake_halt_or_stop() {
|
||||
for stop in [false, true] {
|
||||
let mut bus = memory();
|
||||
bus.write16(RCNT, 0xc100).unwrap(); // Bit 8 is inactive, not a GPIO IRQ enable.
|
||||
bus.write16(SIOCNT, 0x4083).unwrap(); // Unused in Joybus, even with internal start retained.
|
||||
bus.write8(SIODATA8, 0x12).unwrap();
|
||||
bus.write16(JOYCNT, 0x40).unwrap();
|
||||
bus.write32(JOY_TRANS, 0xaabbccdd).unwrap();
|
||||
bus.write16(IE, 0x80).unwrap();
|
||||
bus.write16(IME, 1).unwrap();
|
||||
bus.write8(HALTCNT, if stop { 0x80 } else { 0 }).unwrap();
|
||||
bus.advance_cycles(1000000);
|
||||
assert_eq!(bus.stopped(), stop);
|
||||
assert_eq!(bus.halted(), !stop);
|
||||
assert_eq!(bus.read8(SIODATA8).unwrap(), 0x12);
|
||||
assert_eq!(bus.read16(SIOCNT).unwrap(), 0x4083);
|
||||
assert_eq!(bus.read16(JOYCNT).unwrap(), 0x40);
|
||||
assert_eq!(bus.read16(JOYSTAT).unwrap(), 2);
|
||||
assert_eq!(bus.read16(IF).unwrap(), 0);
|
||||
assert_eq!(bus.read8(RCNT), Err(MemoryError::Unmapped(RCNT)));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_normal_transfer_requires_completion_or_explicit_cancellation_before_joybus() {
|
||||
let mut bus = memory();
|
||||
bus.write16(SIOCNT, 0x4083).unwrap();
|
||||
for elapsed in [0, 8] {
|
||||
bus.advance_cycles(elapsed);
|
||||
let error = bus.write16(RCNT, 0xc100).unwrap_err();
|
||||
assert!(error.to_string().contains("serial reconfiguration"));
|
||||
assert_eq!(bus.write16(RCNT, 0xc100), Err(error));
|
||||
assert_eq!(bus.read8(RCNT + 1).unwrap(), 0);
|
||||
}
|
||||
bus.write16(SIOCNT, 0).unwrap();
|
||||
bus.write16(RCNT, 0xc100).unwrap();
|
||||
bus.advance_cycles(1000000);
|
||||
assert_eq!(bus.read16(IF).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn joy_control_and_status_word_padding_fail_without_partial_side_effects() {
|
||||
let mut bus = memory();
|
||||
for address in [JOYCNT, JOYSTAT] {
|
||||
assert_eq!(
|
||||
bus.write32(address, u32::MAX),
|
||||
Err(MemoryError::Unmapped(address + 2))
|
||||
);
|
||||
assert_eq!(bus.read32(address), Err(MemoryError::Unmapped(address + 2)));
|
||||
assert_eq!(bus.read16(address).unwrap(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dma_transmit_write_has_only_dma_completion_irq() {
|
||||
let mut bus = memory();
|
||||
bus.write16(RCNT, 0xc000).unwrap();
|
||||
bus.write16(JOYCNT, 0x40).unwrap();
|
||||
bus.write32(0x02000000, 0x12345678).unwrap();
|
||||
bus.write32(DMA_BASE, 0x02000000).unwrap();
|
||||
bus.write32(DMA_BASE + 4, JOY_TRANS).unwrap();
|
||||
bus.write32(DMA_BASE + 8, 0xc4000001).unwrap();
|
||||
let mut machine = Machine::new(Cpu::new(ROM_START), bus);
|
||||
assert_eq!(machine.step().unwrap(), StepKind::Dma { channel: 0 });
|
||||
assert_eq!(machine.memory().read32(JOY_TRANS).unwrap(), 0x12345678);
|
||||
assert_eq!(machine.memory().read16(JOYSTAT).unwrap(), 2);
|
||||
assert_eq!(machine.memory().read16(IF).unwrap(), 0x100);
|
||||
machine.memory_mut().advance_cycles(1000000);
|
||||
assert_eq!(machine.memory().read16(IF).unwrap(), 0x100);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue