feat(cartridge): add explicit Flash identification and banked reads

This commit is contained in:
0xrsydn 2026-10-07 06:13:39 +07:00
commit 8b3b7ae62d
23 changed files with 1042 additions and 52 deletions

View file

@ -9,7 +9,8 @@ Unsupported operations and invalid calendar payloads return diagnostics.
## Selection
`Memory::set_cartridge_hardware(CartridgeHardware::Rtc)` attaches the bounded interface with initial state.
`CartridgeHardware::None` is the default. Selecting either option replaces peripheral state, without changing ROM bytes.
`CartridgeHardware::None` is the default. Selecting either option replaces GPIO/RTC state, without changing ROM bytes.
The separately selected [Flash device](saves.md) and its array remain unchanged.
Call this API between machine steps. The core does not inspect filenames, game headers, or library signatures to select hardware.
The desktop enables this interface with `--rtc`, in either ROM execution mode:

View file

@ -33,14 +33,18 @@ It does not decode archives or ELF executables. Supply a raw binary with an ARM
Headerless original programs are accepted.
Reads past supplied cartridge bytes remain explicit memory errors, not open-bus values.
All three Game Pak ROM windows use the same supplied bytes and existing wait-state rules.
Ordinary cartridge bytes remain read-only. Save devices remain unsupported.
Ordinary cartridge bytes remain read-only.
Optional `--save-type flash64` or `--save-type flash128` enables [Flash identification and array reads](saves.md).
The desktop starts an erased device and does not load or write save files. Programming and erase remain diagnostic.
Optional `--rtc` selection enables the bounded [Game Pak GPIO and RTC calendar interface](cartridge-gpio.md).
It supports date/time reads, complete writes, reset, and explicit elapsed-time updates. RTC interrupts and persistence remain unsupported.
The desktop seeds UTC once, then supplies monotonic host elapsed seconds independently of GBA cycles and STOP.
Both modes call `bios::boot` with the loaded bytes.
With `--rtc`, the host then selects the cartridge peripheral before the first machine step.
Without that option, no cartridge peripheral is attached. Duplicate `--rtc` options are rejected.
Without that option, no RTC is attached. Duplicate `--rtc` options are rejected.
Save-device selection is independent of RTC selection. Omitting `--save-type` leaves save memory unmapped.
Unknown save types and duplicate options fail before file access. No ROM-based hardware detection is used.
The [original BIOS replacement](bios.md) executes its minimal boot before entering `0x08000000` in ARM System mode.
There is no command-line option for external BIOS files.
Unsupported services remain diagnostics. RegisterRamReset now accepts sound/serial reset flags, including `r0=0xff`, within the initialization subset.

View file

@ -351,6 +351,8 @@ Palette and video RAM cost 1 cycle for byte/halfword accesses and 2 for words, w
ROM costs include one cycle plus the configured wait states for each 16-bit transfer.
A ROM word uses two halfword accesses; its second halfword always uses sequential timing.
Accesses at 128 KiB ROM boundaries force non-sequential timing for the first halfword.
Supported save-byte accesses use WAITCNT bits 0–1: one cycle plus 4, 3, 2, or 8 wait states.
Sequential and non-sequential save requests use the same nominal cost. Flash data accesses cancel the opcode prefetch queue.
`WAITCNT` resets to zero and supports byte, halfword, and word access.
The writable mask is `0x5fff`; the Game Pak type flag reads as GBA, and the upper halfword reads as zero.
@ -421,6 +423,7 @@ Important timing limits:
- Refill cost calculation does not read target bytes. ARM/Thumb instruction-buffer and local IWRAM bus-history samples add no extra cycles.
- Target-pair sampling cannot fail the branch early. An invalid branch target still fails on the following instruction fetch.
- Game Pak prefetch is nominal. Full-buffer restart, page boundaries, cancellation, and live WAITCNT changes need independent hardware validation.
- PHI and SRAM wait fields are stored; PHI output and SRAM mapping are not implemented.
- Save wait fields drive the supported Flash byte-access costs. PHI output and SRAM mapping remain unimplemented.
See [Flash access restrictions](saves.md#bus-behavior); wider save transfers and physical bus timing remain unsupported or unverified.
- External work RAM timing is fixed. The undocumented memory-control register is not implemented.
- Exact DMA startup/resumption delays, display-bus contention, and timer startup/register-write delays remain unmodeled.

122
docs/hardware/saves.md Normal file
View file

@ -0,0 +1,122 @@
# Cartridge Flash identification and read subset
The core supports explicit Macronix Flash selection, chip identification, array reads, and 128 KiB bank selection.
Programming, erase, busy polling, and host save-file persistence remain unimplemented.
This is not working game-save support. Unsupported writes fail instead of reporting a completed save.
SRAM, EEPROM, and other manufacturers' protocols remain separate work.
## Selection and initial data
The desktop accepts either `--save-type flash64` or `--save-type flash128` in ROM terminal/window modes.
The option is independent of `--rtc`. Omitting it leaves save memory unmapped.
Unknown values and duplicate options fail before opening the ROM.
```sh
direnv exec . cargo run --locked --release -- --rom roms/pokemon-emerald.gba --rtc --save-type flash128 --steps 3000000
direnv exec . cargo run --locked --release -- --rom roms/pokemon-emerald.gba --rtc --save-type flash128 --window --frames 60
```
Selection is explicit, not inferred from a filename, ROM signature, or the first write.
A 64 KiB device cannot silently become a 128 KiB device.
The desktop starts each selected device with erased `0xff` bytes and reports the read-only subset's limits.
It does not look for, read, create, or overwrite a save file. Existing game saves remain untouched.
The ROM suite schema still uses no save device by default and has no save-selection field yet.
Core APIs on `Memory`:
- `set_save_device(SaveDevice)` replaces save hardware and initializes its array to erased bytes.
`None`, `Flash64`, and `Flash128` are available. The call preserves GPIO/RTC state.
- `load_save_image(&[u8])` supplies an exact-capacity image and resets command state, ID mode, and bank selection.
A wrong length or missing device changes nothing. This is host setup, not an emulated program operation.
- `save_image()` inspects the complete array, independently of ID mode or selected bank.
It returns `None` when no save device is selected.
Configuration and image replacement occur between machine steps. No filesystem operations belong to the core.
Selecting GPIO/RTC hardware does not replace an attached save device or its bytes.
This feature can inspect caller-supplied images but cannot modify their data through Flash commands.
## Bus behavior
Only `0x0e000000` through `0x0e00ffff` are mapped when a device is selected.
The Flash interface uses byte accesses. Halfword/word transfers, DMA, and instruction fetches remain diagnostic.
Unverified aliases outside this window remain unmapped rather than returning mirrored data.
Ordinary ROM writes remain read-only.
CPU Flash reads must execute from external or internal work RAM, as required by GBATEK's access description.
A read from ROM/BIOS code reports a diagnostic rather than inventing a value for an unsupported bus sequence.
Host byte inspection has no CPU execution restriction. CPU command writes may execute from ROM.
WAITCNT bits 0–1 select save wait states 4, 3, 2, or 8.
The nominal byte-access costs are therefore 5, 4, 3, or 9 cycles, for both sequential and non-sequential requests.
Save waits do not alter ROM wait fields. Flash data accesses cancel Game Pak opcode prefetch through the existing nominal model.
Changing only save wait bits preserves the queue until a cartridge access cancels it.
These costs are source-backed estimates, not physical timing measurements.
## Commands
Use these byte addresses and values for the unlock prefix:
1. Write `0xaa` to `0x0e005555`.
2. Write `0x55` to `0x0e002aaa`.
3. Write a command byte to `0x0e005555`.
Supported commands after the prefix:
| Command | Behavior |
| --- | --- |
| `0x90` | Enter chip-identification mode |
| `0xf0` | Exit identification mode and return to array reads |
| `0xb0` | On Flash128 only, accept a bank byte next at `0x0e000000` |
In ID mode, byte zero is the manufacturer and byte one is the device:
| Selection | Manufacturer | Device | Combined ID |
| --- | --- | --- | --- |
| Flash64 | `0xc2` | `0x1c` | `0x1cc2` |
| Flash128 | `0xc2` | `0x09` | `0x09c2` |
Other ID-mode addresses remain diagnostic because their values are not established by this subset.
Array mode reads bytes from the selected 64 KiB bank. Flash64 always uses bank zero.
Flash128 accepts bank values zero and one only. Bank changes do not alter stored data.
Entering and leaving ID mode preserves the selected bank.
Reads during an incomplete unlock retain the previous read mode and bank.
For the documented 64 KiB Macronix device, a direct `0xf0` write at `0x0e005555` resets command/ID state.
For Flash128, the same write is accepted only as a redundant reset when already idle in array mode.
This bounded no-op matches the inspected emulators' idle behavior. It does not establish direct ID exit or busy-command cancellation.
Flash128 still requires the full unlock prefix to exit ID mode. Direct reset during an incomplete unlock remains diagnostic.
No matching manufacturer datasheet or physical measurement was used to claim broader 128 KiB reset behavior.
`0xa0` byte-program setup and `0x80` erase setup remain diagnostic at the command write.
No operation starts, data changes, busy state, completion flag, or interrupt is fabricated.
Unknown commands, invalid prefixes, and invalid bank writes are also diagnostic.
Rejected accesses retain the previous command state and bytes, so retrying the same access gives the same result.
This atomic diagnostic policy does not reproduce hardware handling of every malformed sequence.
## Transactions and validation
The small Flash controller state joins the existing cartridge machine-step transaction.
The full save image stays outside the copied controller state; this read-only subset never mutates it during emulation.
Failed CPU or DMA steps discard speculative controller changes and timing.
Block-store preflight rejects a wide Flash destination before committing earlier RAM or device writes.
HALT/STOP and elapsed cycles cannot create Flash completion events. There is no pending program or erase operation to finish.
Original tests cover both device IDs, every byte in both banks, exact image lengths, erased state, invalid sequences, and unchanged ROM/save data.
They distinguish redundant idle reset from unsupported Flash128 ID/partial-command reset.
ARM/Thumb probes verify ROM command writes, RAM byte reads, wait settings, restrictions, and retryable failed steps.
Additional tests cover staged ID reads, block rollback, DMA rejection, GPIO/RTC independence, and prefetch cancellation.
Desktop tests execute an original IWRAM byte reader with explicit device selection in terminal and native-window modes.
No game code, save file, or copyrighted assets are included in these tests.
## Remaining work
Implement and validate program/erase commands, busy polling, cancellation, and byte-program bit transitions before claiming save functionality.
Then add explicit host save loading and safe persistence, with exact-size validation and restart tests.
Do not overwrite existing files or add immediate-success responses merely to pass a game check.
## References
- [GBATEK backup Flash](https://problemkaputt.de/gbatek-gba-cart-backup-flash-rom.htm): capacities, IDs, unlock sequences, bank selection, 64 KiB Macronix reset, byte-only access, and RAM-execution requirements.
- [GBATEK](https://mgba-emu.github.io/gbatek/), WAITCNT section: save wait fields and one-cycle-plus-wait timing.
- [mGBA savedata at 3a5e34be](https://github.com/mgba-emu/mgba/blob/3a5e34be33dc7f8f707e5bc9db69e8a430046f21/src/gba/savedata.c): comparison for controller phases, bank addressing, ID overlays, and idle reset handling. Its default chip IDs differ; its write/erase timing and busy approximation are not copied.
- [ares Flash at 9408cb43](https://github.com/ares-emulator/ares/blob/9408cb43d4948fc3ea6e152a307a34348df3fe04/ares/gba/cartridge/flash.cpp): comparison for IDs, banking, unlocked ID exit, and unchanged idle state after an unrecognized direct reset. Its immediate program/erase behavior and permissive unlock handling are not adopted.