arch/arm64: Synchronize the TTBR0 write before the TLB invalidation.

mmu_write_ttbr0() writes TTBR0_EL1 and then invalidates the TLB.  A write
to TTBR0_EL1 takes effect only at the next context synchronization event,
so until the ISB at the end of the invalidation, a table walk can still
use the old table.  The instruction fetches of the invalidation sequence
itself do such walks.  An entry that they cache after the TLBI completes
stays valid: walk cache entries are not tagged with the table base, and
the kernel and every process use ASID 0.

A kernel build then translates a user address of the new process through
a level 0 entry of the old table, and gets a level 1 translation fault.
Under QEMU with HVF on Apple silicon this happens on every boot of
qemu-armv8a:knsh: up_addrenv_va_to_pa() fails for the first user buffer,
and virtio gets a descriptor with address 0.  TCG has no walk caches, so
it does not show the problem.

Add an ISB after the write, as the Arm ARM sequence for a TTBR change
without an ASID change requires: write, ISB, TLBI, DSB, ISB.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
This commit is contained in:
Marco Casaroli 2026-10-08 00:23:47 +02:00 • committed by Alan C. Assis
parent f3721d83d0
commit baeedc5009

View file

@ -456,6 +456,12 @@ static inline void mmu_invalidate_tlbs(void)
static inline void mmu_write_ttbr0(uintptr_t reg)
{
write_sysreg(reg, ttbr0_el1);
/* Until this ISB, walks can still use the old table and cache its
* entries after the TLB invalidation below.
*/
UP_ISB();
mmu_invalidate_tlbs();
}