feat(bios): support sound and serial register reset
This commit is contained in:
parent
cb2772cef8
commit
d6a47a3e12
14 changed files with 278 additions and 47 deletions
|
|
@ -52,8 +52,9 @@ pub fn boot(rom: Vec<u8>) -> Result<Machine, MemoryError> {
|
|||
/// and registers other than each arithmetic service's documented outputs.
|
||||
/// SoftReset instead clears BIOS work RAM, resets specified CPU banks, and enters
|
||||
/// ROM or RAM in ARM System mode with IRQ masked. It does not reset devices.
|
||||
/// RegisterRamReset clears selected RAM and supported I/O registers. Serial/sound
|
||||
/// reset flags reach a diagnostic before display or reset writes.
|
||||
/// RegisterRamReset clears selected RAM and supported I/O registers, including
|
||||
/// disabled sound and disconnected serial reset state. Active audio/link operations
|
||||
/// remain diagnostic in the device model.
|
||||
/// IRQ callbacks must preserve r4–r11, acknowledge IF, update IRQ_FLAGS for wait
|
||||
/// services, and return with BX lr. Nested IRQs and SWIs from callbacks are unsupported.
|
||||
/// Service routines use the Supervisor stack; exact firmware stack layout and
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
//! Original ARM SoftReset and selective RegisterRamReset services.
|
||||
//! Serial/sound reset requests are diagnostics until those devices are modeled.
|
||||
//! Sound and serial reset use the disabled/disconnected I/O subset.
|
||||
|
||||
use super::{ArmImage, IRQ_STACK, ROM_START, SVC_STACK, SYSTEM_STACK};
|
||||
use crate::{
|
||||
dma::DMA_BASE,
|
||||
io::{DISPCNT, IE, IME, TIMER_BASE},
|
||||
io::{
|
||||
DISPCNT, IE, IME, JOYCNT, JOY_RECV, JOY_TRANS, RCNT, SIOCNT, SIODATA32, SIODATA8,
|
||||
SOUNDBIAS, SOUNDCNT_H, SOUNDCNT_X, TIMER_BASE, WAVE_RAM,
|
||||
},
|
||||
};
|
||||
|
||||
pub(super) fn emit(a: &mut ArmImage) {
|
||||
|
|
@ -47,14 +50,15 @@ pub(super) fn emit(a: &mut ArmImage) {
|
|||
|
||||
fn emit_ram_reset(a: &mut ArmImage) {
|
||||
a.label("register_ram_reset");
|
||||
// Validate unsupported devices before forced blank or any reset writes.
|
||||
// Higher flag bits are ignored, as in the documented low-byte flag format.
|
||||
a.emit(0xe310_0060); // TST r0,#serial|sound
|
||||
a.branch(1, "invalid_argument");
|
||||
a.literal(1, DISPCNT);
|
||||
a.emit(0xe3a0_2080); // MOV r2,#forced blank (also clear other DISPCNT bits)
|
||||
a.emit(0xe1c1_20b0); // STRH r2,[r1]
|
||||
a.emit(0xe3a0_3000); // MOV r3,#0 (shared fill value)
|
||||
|
||||
// Documented BIOS side effect occurs even when serial reset is not selected.
|
||||
a.literal(1, SIODATA32);
|
||||
store_halfword(a, 0);
|
||||
for (flag, start, bytes, next) in [
|
||||
(1, 0x0200_0000, 0x40000, "ram_reset_iwram"),
|
||||
(2, 0x0300_0000, 0x7e00, "ram_reset_palette"),
|
||||
|
|
@ -67,6 +71,35 @@ fn emit_ram_reset(a: &mut ArmImage) {
|
|||
clear_words(a, start, bytes);
|
||||
a.label(next);
|
||||
}
|
||||
a.emit(0xe310_0020); // TST r0,#serial
|
||||
a.branch(0, "ram_reset_sound");
|
||||
a.literal(1, SIOCNT);
|
||||
store_halfword(a, 0);
|
||||
a.literal(1, SIODATA8);
|
||||
store_halfword(a, 0);
|
||||
a.literal(1, RCNT);
|
||||
a.emit(0xe3a0_3902); // MOV r3,#0x8000 (GPIO, all pins inputs)
|
||||
store_halfword(a, 0);
|
||||
a.emit(0xe3a0_3000); // MOV r3,#0
|
||||
a.literal(1, JOYCNT);
|
||||
store_halfword(a, 0);
|
||||
clear_words(a, JOY_RECV, 4);
|
||||
clear_words(a, JOY_TRANS, 4);
|
||||
|
||||
a.label("ram_reset_sound");
|
||||
a.emit(0xe310_0040); // TST r0,#sound
|
||||
a.branch(0, "ram_reset_other");
|
||||
a.literal(1, SOUNDCNT_X);
|
||||
store_halfword(a, 0); // Disable sound; PSG register state is held in reset.
|
||||
a.literal(1, SOUNDCNT_H);
|
||||
store_halfword(a, 0);
|
||||
a.literal(1, SOUNDBIAS);
|
||||
a.emit(0xe3a0_3c02); // MOV r3,#0x200 (bias default)
|
||||
store_halfword(a, 0);
|
||||
a.emit(0xe3a0_3000); // MOV r3,#0
|
||||
clear_words(a, WAVE_RAM, 16); // Accessible inactive bank; other bank cannot be enabled yet.
|
||||
|
||||
a.label("ram_reset_other");
|
||||
a.emit(0xe310_0080); // TST r0,#other registers
|
||||
a.branch(0, "return");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use super::*;
|
||||
use gba_core::{
|
||||
bios::INVALID_ARGUMENT_TRAP,
|
||||
dma::{DmaError, DMA_BASE, DMA_STRIDE},
|
||||
input::Buttons,
|
||||
io::{
|
||||
|
|
@ -185,34 +184,57 @@ fn internal_ram_clear_preserves_bios_work_area_except_common_swi_frame() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn all_serial_sound_requests_fail_before_forced_blank_or_memory_clear() {
|
||||
fn serial_sound_reset_flags_are_selective_and_all_devices_return() {
|
||||
use gba_core::io::{
|
||||
RCNT, SIOCNT, SIODATA32, SIODATA8, SOUNDBIAS, SOUNDCNT_H, SOUNDCNT_X, WAVE_RAM,
|
||||
};
|
||||
for thumb in [false, true] {
|
||||
for flags in (0..256).filter(|flags| flags & 0x60 != 0) {
|
||||
let (mut m, _) = prepare(flags, thumb, 0x1f);
|
||||
for flags in [
|
||||
0, 0x20, 0x40, 0x60, 0x80, 0xff, 0xffffff20, 0xffffff40, 0xffffffff,
|
||||
] {
|
||||
let (mut m, pc) = prepare(flags, thumb, 0x1f);
|
||||
seed_samples(&mut m);
|
||||
m.memory_mut().write16(DISPCNT, 0x0403).unwrap();
|
||||
for _ in 0..1000 {
|
||||
let before = m.cpu().clone();
|
||||
let cycles = m.cycles();
|
||||
let timing = m.last_timing();
|
||||
if let Err(error) = m.step() {
|
||||
assert!(matches!(
|
||||
error,
|
||||
MachineError::Cpu(CpuError::UnsupportedInstruction {
|
||||
instruction: INVALID_ARGUMENT_TRAP,
|
||||
..
|
||||
})
|
||||
));
|
||||
assert_eq!(m.cpu(), &before);
|
||||
assert_eq!(m.cycles(), cycles);
|
||||
assert_eq!(m.last_timing(), timing);
|
||||
assert_eq!(m.step(), Err(error));
|
||||
break;
|
||||
}
|
||||
let bus = m.memory_mut();
|
||||
bus.write32(SIODATA32, MARKER).unwrap();
|
||||
bus.write16(SIODATA8, 0x55).unwrap();
|
||||
bus.write16(SIOCNT, 0x500b).unwrap();
|
||||
bus.write16(RCNT, 0x80fa).unwrap();
|
||||
bus.write16(SOUNDCNT_H, 0xffff).unwrap();
|
||||
bus.write16(SOUNDBIAS, 0xffff).unwrap();
|
||||
for offset in 0..16 {
|
||||
bus.write8(WAVE_RAM + offset, 0x5a).unwrap();
|
||||
}
|
||||
complete(&mut m, pc);
|
||||
check_samples(&m, flags);
|
||||
let bus = m.memory();
|
||||
assert_eq!(bus.read32(SIODATA32).unwrap(), MARKER & 0xffff0000); // Unconditional low-halfword clear.
|
||||
assert_eq!(
|
||||
bus.read16(SIODATA8).unwrap(),
|
||||
if flags & 0x20 != 0 { 0 } else { 0x55 }
|
||||
);
|
||||
assert_eq!(
|
||||
bus.read16(SIOCNT).unwrap(),
|
||||
if flags & 0x20 != 0 { 4 } else { 0x500b }
|
||||
);
|
||||
assert_eq!(
|
||||
bus.read16(RCNT).unwrap(),
|
||||
if flags & 0x20 != 0 { 0x800f } else { 0x80fa }
|
||||
);
|
||||
assert_eq!(
|
||||
bus.read16(SOUNDCNT_H).unwrap(),
|
||||
if flags & 0x40 != 0 { 0 } else { 0x770f }
|
||||
);
|
||||
assert_eq!(
|
||||
bus.read16(SOUNDBIAS).unwrap(),
|
||||
if flags & 0x40 != 0 { 0x200 } else { 0xc3fe }
|
||||
);
|
||||
assert_eq!(bus.read16(SOUNDCNT_X).unwrap(), 0);
|
||||
for offset in 0..16 {
|
||||
assert_eq!(
|
||||
bus.read8(WAVE_RAM + offset).unwrap(),
|
||||
if flags & 0x40 != 0 { 0 } else { 0x5a }
|
||||
);
|
||||
}
|
||||
assert!(m.step().is_err());
|
||||
assert_eq!(m.memory().read16(DISPCNT).unwrap(), 0x0403);
|
||||
check_samples(&m, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue