Skip to content

Deliver ARM9 IPC FIFO words to the ARM7 at their send time (fixes boot-time white screens with a generated firmware) - #34

Merged
beebono merged 1 commit into
beebono:mainfrom
Squirbie:ipc-fifo-arm9-send-time
Oct 29, 2026
Merged

beebono merged 1 commit into
beebono:mainfrom
Squirbie:ipc-fifo-arm9-send-time

Conversation

@Squirbie

Copy link
Copy Markdown
Contributor

Summary

The ARM9 runs each scheduling slice first and the ARM7 catches up afterwards, so a word the ARM9 writes to IPCFIFOSEND in the middle of a slice is pushed into the FIFO at once and the ARM7 sees it up to a slice (2048 cycles) before the ARM9 actually sent it. This exposes a boot-time race in the NitroSDK and can leave some titles on a white screen.

Symptom

With the generated firmware (no firmware dump), some titles hang on a white screen right after boot; we saw it with Picross DS (Korean release). Stock v3.0.3 reproduces it headlessly (real BIOS, generated firmware, --rtc-host, JIT). It is a cycle-level timing race: JIT vs. interpreter, moving vs. fixed RTC, fast vs. exact and similar choices decide which runs hang (8 of 48 combinations hung in a sweep before this fix), so it is easy to miss when a firmware dump is present.

Cause

Korean titles read the firmware user area from the ARM9 through PXI NVRAM (tag 4) right after boot. In the NitroSDK ARM7 SPI_Init, the PXI receive callbacks are registered before the message queue and its entry ring exist. When the ARM7 sees the word early it handles it before the queue exists: SendMessage fails with EXCLUSIVE, the ARM9 retries, the retry lands in the queue but is then wiped by the ring clearing and answered as a touch-panel result (wrong tag), so the ARM9 waits forever.

Fix

When the ARM9 (running, not a DMA) writes IPCFIFOSEND, hold the word in Io::ipc9_pending, end the ARM9 slice there (Scheduler::yield), and push it into the FIFO from Scheduler::fire_due() once the ARM7 has caught up (Io::ipc_deliver()), raising the IRQ then. The ARM9's own view of its send FIFO (empty/full flags, send-empty IRQ) counts the held words. ARM7→ARM9 traffic, IPCSYNC and DSi are unchanged. The save-state format is unchanged (held words are flushed before saving; a slice boundary never has any). DS_IPC_DEFER=0 restores the old behaviour.

Results

These were measured on v3.0.3 with the same change:

  • 48-condition sweep (JIT/interpreter × moving/fixed RTC × fast/exact × idle-skip modes × firmware language), once with no firmware file and once with a generated firmware passed as a file: white screens 8/48 → 0/48 in both; DS_IPC_DEFER=0 brings them back. Super Mario 64 DS (Korean) also booted in all 48 conditions.
  • Normal conditions (JIT, fixed RTC, fast): the 1800-frame screen hash is bit-identical to before; median frame time 0.43 ms vs 0.47 ms. Cost: about 4.3 extra slice ends per frame (7,826 ARM9 FIFO sends in 1800 frames).
  • ctest passed (32 tests) on aarch64 Linux. The change has also been running on two Anbernic RG35XX Pro handhelds in daily use since late October.

On current main (e04b806) I rebased it and compile-checked the changed files with the project's own build flags (macOS, clang); I have not repeated the sweep on main.

🤖 Generated with Claude Code

The ARM9 runs each slice first and the ARM7 catches up afterwards, so a
word the ARM9 wrote to IPCFIFOSEND mid-slice was pushed into the FIFO at
once and the ARM7 could see it up to a slice (2048 cycles) before it was
sent. That exposes a boot-time race in the NitroSDK ARM7 SPI_Init (PXI
callbacks are registered before the message queue exists): a title that
reads the firmware user area through PXI NVRAM right after boot can have
its request lost and wait forever on a white screen, seen with Picross
DS (Korean release) and a generated firmware.

When the running ARM9 (not a DMA) sends a word, hold it in
Io::ipc9_pending, end the ARM9 slice there (Scheduler::yield), and push
it from Scheduler::fire_due() once the ARM7 has caught up
(Io::ipc_deliver), raising the IRQ then. The ARM9's own view of its send
FIFO (empty/full flags, send-empty IRQ) counts the held words. ARM7 to
ARM9 traffic, IPCSYNC and DSi mode are unchanged, and so is the state
format (held words are flushed before saving). DS_IPC_DEFER=0 restores
the old behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@beebono
beebono merged commit ee976ce into beebono:main Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants