gameboy-rust/src/bios/reset.rs
0xrsydn a8944d1d5f feat(bios): implement selective RegisterRamReset service
Execute SWI 0x01 through original ARM code. Clear selected work RAM, palette RAM, VRAM, and OAM; force blank; reset supported display, DMA, timer, and interrupt registers through normal bus writes. Preserve caller state and excluded BIOS work RAM apart from the common SWI frame.

Reject serial/sound reset flags before display or reset writes. Document the supported subset, timer counter behavior, DMA ordering, and RAM caller hazards. Add fifteen tests covering memory flags and boundaries, I/O defaults, rendering, register preservation, hidden device latches, diagnostics, and device progress.

Validated on Darwin arm64: 720 default and 709 core-only tests in debug and release; formatting, Clippy, rustdoc, ten native window smoke tests, and CPU/timer demos pass.
2026-10-05 15:54:07 +07:00

119 lines
4.7 KiB
Rust

//! Original ARM SoftReset and selective RegisterRamReset services.
//! Serial/sound reset requests are diagnostics until those devices are modeled.
use super::{ArmImage, IRQ_STACK, ROM_START, SVC_STACK, SYSTEM_STACK};
use crate::{
dma::DMA_BASE,
io::{DISPCNT, IE, IME, TIMER_BASE},
};
pub(super) fn emit(a: &mut ArmImage) {
a.label("soft_reset");
// Read the byte before clearing its containing RAM and the common SWI frame.
// No stack accesses follow: this service never returns to its caller.
a.literal(0, 0x0300_7ffa);
a.emit(0xe5d0_2000); // LDRB r2,[r0] (zero: ROM, nonzero: EWRAM)
a.literal(0, 0x0300_7e00);
a.emit(0xe3a0_1080); // MOV r1,#128 (512 bytes)
a.emit(0xe3a0_3000); // MOV r3,#0
a.label("soft_reset_clear");
a.emit(0xe480_3004); // STR r3,[r0],#4
a.emit(0xe251_1001); // SUBS r1,r1,#1
a.branch(1, "soft_reset_clear");
a.emit(0xe169_f003); // MSR SPSR_fc,r3 (SVC saved status = 0)
a.emit(0xe1a0_e003); // MOV lr,r3 (SVC link = 0)
a.literal(13, SVC_STACK);
a.emit(0xe321_f0d2); // MSR CPSR_c,#IRQ|I|F
a.emit(0xe169_f003); // MSR SPSR_fc,r3 (IRQ saved status = 0)
a.emit(0xe1a0_e003); // MOV lr,r3 (IRQ link = 0)
a.literal(13, IRQ_STACK);
a.emit(0xe321_f0df); // MSR CPSR_c,#System|I|F
a.literal(13, SYSTEM_STACK);
a.emit(0xe352_0000); // CMP r2,#0
a.literal(14, ROM_START);
a.emit(0x13a0_e402); // MOVNE lr,#0x02000000
for register in 0..=12 {
a.emit(0xe3a0_0000 | register << 12); // MOV rN,#0
}
// Deterministic subset policy: clear NZCV and F, keep I set. Do not change
// IME, IE, IF, devices, or unrelated CPU banks. IRQ stays masked throughout.
a.emit(0xe329_f09f); // MSR CPSR_fc,#System|I
a.emit(0xe12f_ff1e); // BX lr (ARM target, no return through erased SWI frame)
emit_ram_reset(a);
}
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)
for (flag, start, bytes, next) in [
(1, 0x0200_0000, 0x40000, "ram_reset_iwram"),
(2, 0x0300_0000, 0x7e00, "ram_reset_palette"),
(4, 0x0500_0000, 0x400, "ram_reset_vram"),
(8, 0x0600_0000, 0x18000, "ram_reset_oam"),
(16, 0x0700_0000, 0x400, "ram_reset_io"),
] {
a.emit(0xe310_0000 | flag); // TST r0,#flag
a.branch(0, next);
clear_words(a, start, bytes);
a.label(next);
}
a.emit(0xe310_0080); // TST r0,#other registers
a.branch(0, "return");
// IRQ remains CPU-masked throughout. Disable DMA/timers before clearing
// their address/count/reload registers. These are bus writes, not host resets.
a.literal(1, IME);
store_halfword(a, 0); // IME = 0
a.literal(1, DMA_BASE);
for offset in [10, 22, 34, 46] {
store_halfword(a, offset); // Disable each channel before clearing its registers.
}
clear_words(a, DMA_BASE, 48);
a.literal(1, TIMER_BASE);
for offset in [2, 6, 10, 14] {
store_halfword(a, offset); // Stop each timer; the stopped counter remains latched.
}
clear_words(a, TIMER_BASE, 16); // Clear reload/control, not hidden counter/clock state.
clear_words(a, DISPCNT + 4, 0x54); // Display controls, offsets, origins, windows/effects.
a.literal(1, DISPCNT);
a.emit(0xe3a0_3c01); // MOV r3,#256 (identity affine PA/PD for BG2 and BG3)
for offset in [0x20, 0x26, 0x30, 0x36] {
store_halfword(a, offset);
}
a.emit(0xe3a0_3000); // MOV r3,#0
a.literal(1, IE);
store_halfword(a, 0); // IE = 0
store_halfword(a, 4); // WAITCNT = 0
a.literal(3, 0xffff);
store_halfword(a, 2); // Acknowledge IF after stopping timer/display IRQ sources.
a.branch(14, "return");
// Leaf routine; only r1/r2 change. The common 28-byte SWI frame saves lr.
a.label("ram_reset_words");
a.emit(0xe481_3004); // STR r3,[r1],#4
a.emit(0xe252_2004); // SUBS r2,r2,#4
a.branch(1, "ram_reset_words");
a.emit(0xe12f_ff1e); // BX lr
}
fn clear_words(a: &mut ArmImage, address: u32, bytes: u32) {
debug_assert!(bytes != 0 && bytes % 4 == 0);
a.literal(1, address);
a.literal(2, bytes);
a.call("ram_reset_words");
}
// Store r3 through r1 with an ARM immediate halfword offset.
fn store_halfword(a: &mut ArmImage, offset: u32) {
debug_assert!(offset <= 255 && offset % 2 == 0);
a.emit(0xe1c1_30b0 | (offset & 0xf0) << 4 | (offset & 15));
}