fix(bios): preserve documented protected-read words at firmware exits
Place skipped compatibility data at boot, SoftReset, SWI, and IRQ exit PC+8 locations. Preserve the real IRQ callback return instruction as its readback word. Keep CPU and memory behavior image-derived, including caller-supplied images. Add original boundary, load-lane, caller-state, wait, supplied-image, and builder-fixup regressions. Document the historical failures, research decision, passing public BIOS checkpoint, and remaining fetch-history limits. Validated on Darwin arm64: 868 workspace tests in debug and release; 799 core/demo tests separately in both profiles; 39 preparation tests; native ROM window tests in both profiles; ten graphics/window smoke modes; fmt, clippy, and rustdoc. Hash-verified public ARM, Thumb, memory, and BIOS suites pass unchanged with identical debug/release reports. BIOS: r12=0, 17606 steps, 197456 nominal cycles, one IRQ entry.
This commit is contained in:
parent
b16588aa4c
commit
efdc288db6
15 changed files with 387 additions and 34 deletions
|
|
@ -45,7 +45,8 @@ pub fn boot(rom: Vec<u8>) -> Result<Machine, MemoryError> {
|
|||
}
|
||||
|
||||
/// Build a deterministic 16 KiB image from original ARM instructions and a
|
||||
/// mathematically generated sine table. No Nintendo firmware bytes are included.
|
||||
/// mathematically generated sine table. Documented protected-read compatibility
|
||||
/// words are explicit exit data; no Nintendo firmware routines are included.
|
||||
/// SWIs use bits 16–23 of the ARM immediate, or the Thumb immediate byte.
|
||||
/// Services support User/System callers. Returning services preserve caller status
|
||||
/// and registers other than each arithmetic service's documented outputs.
|
||||
|
|
@ -90,6 +91,7 @@ pub fn image() -> Vec<u8> {
|
|||
a.emit(0xe5c1_0000); // STRB r0,[r1]
|
||||
a.emit(0xe321_f01f); // MSR CPSR_c,#System
|
||||
a.literal(15, ROM_START); // LDR pc,=ROM_START
|
||||
a.prefetch_tail(0xe129_f000); // Boot / SoftReset protected-read word.
|
||||
|
||||
a.label("irq");
|
||||
a.emit(0xe92d_500f); // STMDB sp!,{r0-r3,r12,lr}
|
||||
|
|
@ -103,7 +105,10 @@ pub fn image() -> Vec<u8> {
|
|||
a.emit(0xe1a0_e00f); // MOV lr,pc (return after BX)
|
||||
a.emit(0xe12f_ff11); // BX r1
|
||||
a.emit(0xe8bd_500f); // LDMIA sp!,{r0-r3,r12,lr}
|
||||
|
||||
// This instruction is also the callback branch's PC+8 readback word.
|
||||
a.emit(0xe25e_f004); // SUBS pc,lr,#4
|
||||
a.prefetch_tail(0xe55e_c002); // IRQ return protected-read word.
|
||||
|
||||
a.label("swi");
|
||||
a.emit(0xe92d_500f); // STMDB sp!,{r0-r3,r12,lr}
|
||||
|
|
@ -268,6 +273,7 @@ pub fn image() -> Vec<u8> {
|
|||
a.emit(0xe169_f003); // MSR SPSR_fc,r3
|
||||
a.emit(0xe8bd_500f); // LDMIA sp!,{r0-r3,r12,lr}
|
||||
a.emit(0xe1b0_f00e); // MOVS pc,lr
|
||||
a.prefetch_tail(0xe3a0_2004); // SWI return protected-read word.
|
||||
a.label("unsupported");
|
||||
a.emit(UNSUPPORTED_TRAP);
|
||||
a.label("invalid_argument");
|
||||
|
|
@ -331,6 +337,14 @@ impl ArmImage {
|
|||
self.code.push(instruction);
|
||||
}
|
||||
|
||||
/// Place only after a non-fallthrough exit. Neither word is executed.
|
||||
/// The normal ARM PC+8 snapshot exposes this documented compatibility data.
|
||||
/// Keep the bus image-derived, including for caller-supplied BIOS images.
|
||||
fn prefetch_tail(&mut self, value: u32) {
|
||||
self.emit(UNSUPPORTED_TRAP); // PC+4: diagnose accidental fallthrough.
|
||||
self.emit(value); // PC+8: retained BIOS word after the exit.
|
||||
}
|
||||
|
||||
fn label(&mut self, name: &'static str) {
|
||||
assert!(
|
||||
self.labels.insert(name, self.code.len()).is_none(),
|
||||
|
|
@ -418,6 +432,32 @@ mod image_tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefetch_tail_keeps_branch_and_literal_fixups_outside_compatibility_data() {
|
||||
let mut a = ArmImage::default();
|
||||
a.literal(0, 0x1122_3344);
|
||||
a.branch(14, "after_tail");
|
||||
a.prefetch_tail(0xe129_f000);
|
||||
a.label("after_tail");
|
||||
a.emit(0xe12f_ff1e); // BX lr
|
||||
let image = a.finish(BIOS_SIZE);
|
||||
let actual: Vec<_> = image[..24]
|
||||
.chunks_exact(4)
|
||||
.map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap()))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
actual,
|
||||
[
|
||||
0xe59f_000c, // Literal at 20, relative to PC+8.
|
||||
0xea00_0001, // Branch at 4 skips both tail words to 16.
|
||||
UNSUPPORTED_TRAP,
|
||||
0xe129_f000,
|
||||
0xe12f_ff1e,
|
||||
0x1122_3344,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "BIOS code overlaps reserved data")]
|
||||
fn code_cannot_overlap_reserved_tables() {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ pub(super) fn emit(a: &mut ArmImage) {
|
|||
// 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)
|
||||
a.prefetch_tail(0xe129_f000); // Same protected-read word as cold boot.
|
||||
|
||||
emit_ram_reset(a);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue