From baeedc5009fad5d19885c39c0a360b21fb126a4e Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Thu, 8 Oct 2026 00:23:47 +0200 Subject: [PATCH] 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 --- arch/arm64/src/common/arm64_mmu.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/arm64/src/common/arm64_mmu.h b/arch/arm64/src/common/arm64_mmu.h index 68862f4fc6c..f910f660b65 100644 --- a/arch/arm64/src/common/arm64_mmu.h +++ b/arch/arm64/src/common/arm64_mmu.h @@ -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(); }