Snapshot the full word at (executing PC+4)&~3 for Thumb BIOS execution, using strict image reads and incoming instruction state. Keep ARM PC+8 open bus separate and unchanged. Commit snapshots only after successful steps; preserve diagnostic behavior for unavailable lookahead. Add original regression coverage for both code alignments, load widths and lanes, signed and unaligned values, processor modes, sequential fetches, ARM/Thumb transitions, SWI, RAM-backed POP exits, BIOS boundaries, failure retention, and nominal device progression. Record source evidence and explicit pipeline/hardware-validation limits. Darwin arm64 validation: 878 workspace tests and 809 core/demo tests in each of debug and release; 39 Python tests; native ROM window tests in both profiles; ten window/graphics smoke modes; fmt, clippy, and rustdoc. Public ARM, Thumb, memory, and BIOS reports match prior passing reports and match between profiles. No physical-hardware validation claimed.
99 lines
6.6 KiB
Markdown
99 lines
6.6 KiB
Markdown
# BIOS protected-read mismatch: root cause and fix
|
|
|
|
## Finding
|
|
|
|
The public BIOS failure came from the replacement firmware's exit layout, not its protected-load byte lanes.
|
|
The memory bus correctly returned the replacement image's retained ARM PC+8 word.
|
|
Before the layout fix, that word differed from the documented value left by the standard GBA firmware.
|
|
Matching the bus mechanism alone does not make replacement firmware's observable outputs compatible.
|
|
|
|
Before the firmware-layout fix, at the initial failing checkpoint:
|
|
|
|
- The unmodified public ROM expects `0xe129f000` after boot.
|
|
- Our replacement's final boot instruction is at `0x60`; its PC+8 word at `0x68` is `0xe59f1320`.
|
|
- The returned value matches that image word, rather than the reset vector or ROM caller's prefetch.
|
|
- The ROM sets r12 to 1 and reaches its real evaluation checkpoint. This is an assertion failure, not a CPU diagnostic.
|
|
|
|
The original regression `original_boot_protected_read_matches_its_actual_last_bios_snapshot` verifies this image-derived behavior.
|
|
It computes the expected word from the executed boot trace, not from a Nintendo opcode constant.
|
|
|
|
## Evidence
|
|
|
|
[GBATEK](https://problemkaputt.de/gbatek-gba-unpredictable-things.htm) documents PC-dependent BIOS protection and the most recently fetched BIOS opcode.
|
|
For standard ARM firmware exits, the observed word comes from the exit instruction's PC+8 location.
|
|
The [pinned public test](https://github.com/jsmolka/gba-tests/blob/a7113b67e63f83a9b321696ddd7042ccfad6c881/bios/bios.asm) checks these lifecycle values:
|
|
|
|
| Firmware boundary | Standard source address | Protected-read word |
|
|
| --- | --- | --- |
|
|
| Boot / SoftReset exit | `0x0dc + 8` | `0xe129f000` |
|
|
| SWI return | `0x188 + 8` | `0xe3a02004` |
|
|
| Inside external IRQ callback | `0x134 + 8` | `0xe25ef004` |
|
|
| IRQ return | `0x13c + 8` | `0xe55ec002` |
|
|
|
|
The addresses describe standard firmware. Our replacement does not need the same internal addresses to expose the same values.
|
|
|
|
[Smolka's Progress Report #6](https://www.smolka.dev/posts/progress-report-6) directly compares original and replacement BIOS return prefetch.
|
|
It traces a Pokémon Ruby/Sapphire null-pointer read to this firmware-dependent value.
|
|
The report notes that Emerald fixed that particular game bug. It does not establish Emerald compatibility for our emulator.
|
|
The report also shows that different firmware values can be valid under the bus rule while affecting software behavior.
|
|
|
|
[mGBA's software BIOS implementation](https://github.com/mgba-emu/mgba/blob/master/src/gba/bios.c) explicitly sets `biosPrefetch` to `0xe3a02004` after a returning SWI.
|
|
Its [memory implementation](https://github.com/mgba-emu/mgba/blob/master/src/gba/memory.c) instead retains fetched BIOS data when executing a mapped BIOS image.
|
|
These are different implementation paths for the same observable behavior. Our core uses executed ARM firmware, not intercepted SWIs.
|
|
|
|
The [Cult-of-GBA replacement BIOS](https://github.com/Cult-of-GBA/BIOS/tree/a30e9a96df083628b650724b7d4d7112b4070b98) was also reviewed.
|
|
Its original assembly and MIT license illustrate the separate roles of firmware routines and their surrounding image layout.
|
|
No replacement BIOS code, binary, artwork, or Nintendo BIOS image was copied into this repository.
|
|
|
|
## Implementation decision
|
|
|
|
Make the original generated firmware expose the documented lifecycle readback values through its real image layout.
|
|
Do not add a bus override, game identifier, ROM hash check, result-register write, or public-test patch.
|
|
Do not change caller-supplied BIOS images or their generic protection behavior.
|
|
|
|
Reserve two words immediately after each relevant non-fallthrough exit:
|
|
|
|
1. Put an intentional diagnostic trap at exit PC+4.
|
|
2. Put the documented compatibility word at exit PC+8.
|
|
|
|
The exit skips both words. The normal ARM prefetch snapshot observes the second word.
|
|
These words are explicit compatibility data in the generated image, not executable copies of firmware routines.
|
|
The builder's normal fixups relocate branches and literals around them.
|
|
Cold boot and SoftReset need the reset word. Returning SWIs and IRQs need their respective words.
|
|
|
|
The IRQ callback path already has the real `SUBS pc, lr, #4` instruction at the callback branch's PC+8.
|
|
Keep that executed return instruction in place and test the layout invariant separately.
|
|
|
|
This corrects the earlier overly broad claim that reproducing any documented opcode value would merely force a test pass.
|
|
Arbitrarily overriding a read to satisfy an assertion would be wrong.
|
|
Reproducing documented firmware boundary outputs for all callers is a legitimate compatibility feature, provided its scope is explicit.
|
|
|
|
## Implemented result
|
|
|
|
`ArmImage::prefetch_tail` adds skipped compatibility data after boot, SoftReset, SWI return, and IRQ return.
|
|
The change adds 32 image bytes without adding normally executed instructions.
|
|
The callback path retains its actual `SUBS pc,lr,#4` at the branch's PC+8.
|
|
No CPU or memory-bus implementation changed.
|
|
|
|
Original regressions in `crates/gba-core/tests/bios/readback.rs` reproduced the boundary failures before the fix.
|
|
They now pass for ARM/Thumb callers, load lanes and widths, User/System state, both reset targets, and interrupt waits.
|
|
A supplied-image variant keeps its own changed word, proving that no compatibility constant overrides image data.
|
|
Builder regressions verify branch and literal relocation around the skipped data.
|
|
|
|
The [unchanged public BIOS ROM](../public-bios-tests.md) passes in debug and release on Darwin arm64.
|
|
It reaches its checkpoint with r12 = 0 and one IRQ entry. Public ARM, Thumb, and memory results remain passing.
|
|
Workspace tests, preparation tests, lint checks, rustdoc, native ROM windows, and graphics smoke tests also pass.
|
|
|
|
## Validation requirements and limits
|
|
|
|
- Run the pinned BIOS ROM unchanged, with its original budget, checkpoint, and r12 assertion.
|
|
- Test boot, SoftReset, returning SWIs, IRQ callbacks, and IRQ return with original programs.
|
|
- Verify byte lanes, load widths, caller state, banked stacks, and ARM/Thumb returns.
|
|
- Verify that the compatibility words are never executed during normal exit paths.
|
|
- Keep synthetic caller-supplied images image-derived, including images with different exit words.
|
|
- Rerun public ARM, Thumb, and memory suites and native Darwin regressions.
|
|
|
|
The firmware-layout fix did not implement a persistent instruction pipeline, Thumb BIOS fetch history, or exact firmware timing.
|
|
A subsequent [Thumb BIOS snapshot change](thumb-bios-prefetch.md) adds bounded aligned-word history, with separate tests and explicit limits.
|
|
It does not prove complete BIOS compatibility or commercial-game support.
|
|
The public BIOS ROM checks these protected-read values; it is not a comprehensive BIOS service suite.
|