gameboy-rust/docs/public-bios-tests.md
0xrsydn efdc288db6 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.
2026-10-06 00:45:28 +07:00

100 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Pinned public BIOS read-protection test
The headless runner executes the unmodified BIOS test ROM from [jsmolka/gba-tests](https://github.com/jsmolka/gba-tests).
It passes its unchanged protected-read assertions on Darwin arm64 in debug and release builds.
This ROM tests protected BIOS reads against Nintendo firmware opcode values, not the complete BIOS service set.
Our runner uses the original replacement BIOS. It neither supplies nor loads Nintendo firmware.
## Prepare and run
```sh
direnv exec . python3 tools/prepare_gba_tests.py /tmp/gba-public-bios --suite bios
direnv exec . cargo run --locked --release -- --test-suite /tmp/gba-public-bios/suite.json > /tmp/gba-public-bios-report.json
```
`bios/bios.gba` is a public test cartridge, not a BIOS image.
Use a new directory outside the repository or under ignored `roms/`. Its parent directory must exist.
The adapter checks every selected file's size and SHA-256 digest before creating output.
It retains the upstream license, source, ROM, `suite.json`, and `source-lock.json` without modifying them.
Downloaded binaries and header sources must not enter tracked files.
Offline preparation uses the same checks:
```sh
direnv exec . python3 tools/prepare_gba_tests.py /tmp/gba-public-bios-offline --suite bios --source-dir /path/to/gba-tests
```
ARM remains the default suite. The Thumb and memory selections remain unchanged.
Preparation is not a passing result. A successful BIOS run exits zero and reports `r12 == 0` at the verified checkpoint.
## Provenance and checkpoint
- Repository: `jsmolka/gba-tests`.
- Revision: `a7113b67e63f83a9b321696ddd7042ccfad6c881`.
- Upstream project license: MIT, copyright Julian Smolka; retained with downloaded files.
- Test ROM: `bios/bios.gba`, 1,908 bytes.
- SHA-256: `9d7b369fa1aa661ff03692b3d79c6f644b623d72983d0fc890e6d87a0409a3c9`.
- Selected file hashes: [`tools/gba-tests-bios.lock.json`](../tools/gba-tests-bios.lock.json).
The adapter uses the upstream committed binary. No FASMARM rebuild or source-to-binary reproduction was performed on Darwin.
The source initializes r12 to zero. Each failure sets r12 to its test number and branches to `eval`.
Successful execution reaches the same label with r12 still zero.
The source exits and binary ARM branch targets were checked against `eval` at `0x08000248`.
Its first instruction is `0xe92d0003`, the shared `m_vsync` register save.
There is another `m_vsync` inside test 3 at `0x08000194`; that is not the completion checkpoint.
The manifest requires ARM state at `0x08000248` and `r12 == 0`, within 1,000,000 successful machine steps.
The budget includes original BIOS boot. All test setup executes normally; only final result rendering is excluded.
Test 3's VBlank wait and IRQ callback are part of the test and are not bypassed.
Preparation verifies the checkpoint word in addition to whole-file hashes.
Timeouts, diagnostics, STOP, and nonzero r12 remain failures under the [normal runner rules](rom-tests.md).
## Observed result on Darwin arm64
### Historical failures
Before BIOS read protection:
- Both builds reach `eval` after 64 successful steps and 543 nominal cycles.
- The CPU is in System/ARM state, with CPSR `0x2000001f` and r12 = 1.
- The report says `reason: assertion_failed`, with no emulator diagnostic.
- Test 1 reads address zero after boot. It expects `0xe129f000`, a Nintendo BIOS opcode.
- The emulator instead exposes the replacement BIOS reset-vector instruction at address zero.
- The later post-SWI and IRQ checks are not reached.
After the bounded ARM BIOS read-protection implementation:
- Both builds still report test 1 as failed after 64 steps and 543 nominal cycles.
- The protected read now returns `0xe59f1320`, sampled from the original BIOS at its final boot instruction's PC+8.
- It no longer exposes address zero's reset-vector word, `0xea000006`.
- The test still expects `0xe129f000`. No assertion, checkpoint, ROM byte, or returned constant was changed to force a pass.
- Tests 2–4 remain unreached. Their SWI and IRQ cases have not passed independently.
### Current passing result
After correcting the replacement firmware's exit layout:
- Both builds reach `eval` at `0x08000248` after 17,606 successful steps and 197,456 nominal cycles.
- The CPU is in System/ARM state, with CPSR `0x6000001f` and r12 = 0.
- The report says `reason: checkpoint`, with no emulator diagnostic and one IRQ entry.
- Boot, returning Sqrt, external IRQ callback, and IRQ return checks all execute and pass.
- The ROM, checkpoint, r12 assertion, and step budget remain unchanged.
- Debug and release reports match exactly for the same fixture path.
The initial failure exposed both a missing bus-protection rule and a firmware layout difference.
The protected-read bus still derives its retained word from the actual supplied image.
Our generated firmware now places documented compatibility words at its exit instructions' PC+8 locations.
These words are skipped data, not copied firmware routines. No bus override or ROM-specific condition was added.
The IRQ callback uses its existing real return instruction as the readback word.
See the [root-cause research](research/bios-readback.md) and [CPU BIOS read-protection limits](hardware/bios.md#cpu-bios-read-protection).
The ARM, Thumb, and memory suites remain separate passing results.
This BIOS result covers boundary readback values, not complete BIOS services, exact fetch history, or commercial-game compatibility.
Thumb BIOS execution, full pipeline behavior, and exact firmware timing remain unverified.
## References
- [Pinned BIOS test source and expected opcodes](https://github.com/jsmolka/gba-tests/blob/a7113b67e63f83a9b321696ddd7042ccfad6c881/bios/bios.asm).
- [Shared initialization and result macros](https://github.com/jsmolka/gba-tests/blob/a7113b67e63f83a9b321696ddd7042ccfad6c881/lib/macros.inc).
- [GBATEK BIOS read protection](https://problemkaputt.de/gbatek-gba-unpredictable-things.htm).
- [Pinned license](https://github.com/jsmolka/gba-tests/blob/a7113b67e63f83a9b321696ddd7042ccfad6c881/LICENSE).