Skip to content

physmem, dart: flush another CPU's TLB on that CPU (port from MakrSas/Inferno) - #57

Merged
dylanneve1 merged 1 commit into
masterfrom
port/mttcg-iommu-flush
Oct 22, 2026
Merged

dylanneve1 merged 1 commit into
masterfrom
port/mttcg-iommu-flush

Conversation

@dylanneve1

@dylanneve1 dylanneve1 commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Ported from MakrSas/Inferno ios branch, commit 80b488f ("tcg: flush another CPU's TLB on that CPU"), by Makr (GPL/AGPL fork of Inferno, same licences as the files touched). Commit authorship is kept.

What it fixes

tcg_iommu_unmap_notify (system/physmem.c) runs on whichever thread wrote to the IOMMU. On t8030 that is usually an application core tearing down a DART mapping that the SEP core (or another app core) has cached in its softmmu TLB. It called tlb_flush(notifier->cpu) directly from that thread, but Pyre's tlb_flush_by_mmuidx must run on the owning CPU (assert_cpu_is_self, which is compiled out in release builds). The flush frees or resizes the TLB tables while their owner keeps reading them without a lock, which is a use-after-free. MakrSas saw it as malloc spinning in a corrupted heap, dirty bitmaps full of ASCII, and "Bad ram pointer".

  • A flush for another CPU is now queued with async_run_on_cpu, the same way tcg_commit hands over a memory-map change. A flush for the calling CPU stays synchronous.
  • apple_dart_notify_remap no longer replays the whole page table for notifiers that only asked for UNMAP events (TCG's per-CPU notifiers). The UNMAP event sent just before the replay already flushed those notifiers, so for them the walk did nothing.

Possibly relevant to Scion-Project/scion#89 (SEP panic at -smp 7). The SEP vCPU translates through DARTs that app cores modify, and more app cores means more concurrent remaps. This is not verified: ChefKissInc#89 was never reproduced on the Mac, and the same sars panic has also been seen after a hard quit (stale anti-replay state, see #24).

No changes in accel/tcg, tcg/ or target/arm.

Testing

Android cross build, run on the Android emulator (emulator-5554). Tested as one combined build with #58 against master d9118068e5:

  • Restore boot (tools/restore_bench.py, iOS 14.0 18A5351d restore ramdisk, real SEP ROM and firmware, -smp 4): every run reached waiting_host with no panic. Timings, from two interleaved batches against master, 11 runs each, all reaching waiting_host.
batch master waiting_host (median) this + #58 (median) host CPU user (median, master / this)
quiet emulator, 6 + 6 runs 9.0 s [8.5–16.0] 8.9 s [8.5–12.8] 5.35 / 5.35 s
busy emulator, 5 + 5 runs 12.6 s [9.6–26.1] 13.7 s [11.8–19.0] 8.2 / 9.6 s

There is no measurable change. The spread in the busy batch is host load on the emulator, not a difference between the builds.

  • patch-check (tools/emu_test.py patch-check, 14.8 / 26.6.2 / 27.0.1 research kernels): JSON identical to master's. The only difference from the scratch baseline.json is the "IOMFB: always detect the simulator system type" row, which master kernel_patches: fix ~3% iOS 14.8 boot panic (IOMFB 'bandwidth values not found') #41 added and master shows too.
  • a13-test (hw/arm/tests/run-a13-tests.py, Android binary on the emulator): 29/29 passed.

🤖 Generated with Claude Code

…/Inferno)

A DART mapping is revoked on whichever thread wrote to the IOMMU. On the
t8030 that is seldom the thread of the CPU whose TLB holds the translation:
an application core tears down a mapping that the SEP core, or another
application core, has been translating through.

tcg_iommu_unmap_notify called tlb_flush(notifier->cpu) from that foreign
thread. Pyre's tlb_flush_by_mmuidx documents that it must run on the owning
CPU (assert_cpu_is_self, compiled out in release builds). It frees and
reallocates the tables it resizes while the owner keeps reading them without
a lock, so the owner can read freed memory as TLB entries and write guest
data wherever those point. Upstream MakrSas saw this as a heap that malloc
spun in forever, dirty bitmaps holding ASCII where pointers belonged, and
"Bad ram pointer". A flush for another CPU is now queued to that CPU with
async_run_on_cpu, the way tcg_commit hands over a changed memory map. The
owner is kicked and flushes at its next exit. Until then it can still use
the stale translation, which is the same as a few cycles of DART latency.

apple_dart_notify_remap also replayed the whole page table for every
notifier after each remap. Only a notifier that asked for MAP events gets
anything from a replay. TCG registers one notifier per CPU for UNMAP alone,
and the UNMAP event just before it already did that notifier's work, so the
replay is now skipped for UNMAP-only notifiers.

Ported from MakrSas/Inferno commit 80b488f ("tcg: flush another CPU's
TLB on that CPU", branch ios), under the same licences as the files it
touches. Original author: Makr <ngrom33@gmail.com>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dylanneve1
dylanneve1 merged commit 5372ab1 into master Oct 22, 2026
5 checks passed
@dylanneve1
dylanneve1 deleted the port/mttcg-iommu-flush branch November 2, 2026 01:56
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