From: Florian Fainelli Date: Mon, 25 May 2026 16:38:17 +0000 (+0100) Subject: ARM: 9476/1: mm: fix kexec and hibernation with CONFIG_CPU_TTBR0_PAN X-Git-Tag: v7.1~1^2 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=009b6c6486b94a3aff566b017256b598dc96bf18;p=thirdparty%2Fkernel%2Flinux.git ARM: 9476/1: mm: fix kexec and hibernation with CONFIG_CPU_TTBR0_PAN Commit 7af5b901e847 ("ARM: 9358/2: Implement PAN for LPAE by TTBR0 page table walks disablement") implemented PAN for LPAE kernels by setting TTBCR.EPD0 on every kernel entry, disabling TTBR0 page-table walks while running in kernel mode. The commit correctly updated cpu_suspend() in arch/arm/kernel/suspend.c, but missed two other code paths that switch the CPU to the identity mapping before jumping to low-PA (TTBR0-range) physical addresses: 1. setup_mm_for_reboot() in arch/arm/mm/idmap.c, used by the kexec reboot path. With TTBCR.EPD0 still set, the subsequent branch to the identity-mapped cpu_v7_reset causes a PrefetchAbort because the TTBR0 page-table walk needed to resolve the identity-mapped address is disabled. This manifests as a hard hang or "bad PC value" panic on LPAE kernels booted on CPUs that strictly enforce EPD0 for instruction fetch (e.g. Cortex-A53 in AArch32 mode) while the same image may accidentally work on Cortex-A15 due to microarchitectural differences in EPD0 enforcement. 2. arch_restore_image() in arch/arm/kernel/hibernate.c, which calls cpu_switch_mm(idmap_pgd, &init_mm) directly without going through setup_mm_for_reboot(), leaving TTBCR.EPD0 set while the identity mapping is active. Fix both sites by calling uaccess_save_and_enable() before switching to the identity mapping, mirroring what the original commit did for cpu_suspend(). Assisted-by: Cursor:claude-sonnet-4.6 Fixes: 7af5b901e847 ("ARM: 9358/2: Implement PAN for LPAE by TTBR0 page table walks disablement") Cc: Catalin Marinas Cc: Linus Walleij Reviewed-by: Linus Walleij Signed-off-by: Florian Fainelli Signed-off-by: Russell King --- diff --git a/arch/arm/kernel/hibernate.c b/arch/arm/kernel/hibernate.c index 38a90a3d12b2c..231a76af09a03 100644 --- a/arch/arm/kernel/hibernate.c +++ b/arch/arm/kernel/hibernate.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "reboot.h" int pfn_is_nosave(unsigned long pfn) @@ -82,6 +83,15 @@ static void notrace arch_restore_image(void *unused) { struct pbe *pbe; + /* + * With CONFIG_CPU_TTBR0_PAN enabled, TTBCR.EPD0 is set to block + * TTBR0 page-table walks. The identity mapping used here lives at + * low (user-space) virtual addresses and is only reachable via + * TTBR0, so re-enable those walks before switching page tables. + * On non-PAN kernels this is a no-op. + */ + if (IS_ENABLED(CONFIG_CPU_TTBR0_PAN)) + uaccess_save_and_enable(); cpu_switch_mm(idmap_pgd, &init_mm); for (pbe = restore_pblist; pbe; pbe = pbe->next) copy_page(pbe->orig_address, pbe->address); diff --git a/arch/arm/mm/idmap.c b/arch/arm/mm/idmap.c index 4a833e89782aa..70403e968d2ae 100644 --- a/arch/arm/mm/idmap.c +++ b/arch/arm/mm/idmap.c @@ -11,6 +11,7 @@ #include #include #include +#include /* * Note: accesses outside of the kernel image and the identity map area @@ -133,6 +134,17 @@ early_initcall(init_static_idmap); */ void setup_mm_for_reboot(void) { + /* + * With CONFIG_CPU_TTBR0_PAN enabled, TTBCR.EPD0 is set whenever + * user-space access is disabled in order to block TTBR0 page-table + * walks. The identity mapping lives at low (user-space) virtual + * addresses and can only be reached via TTBR0, so we must re-enable + * those walks before switching page tables. On non-PAN kernels this + * is a no-op. + */ + if (IS_ENABLED(CONFIG_CPU_TTBR0_PAN)) + uaccess_save_and_enable(); + /* Switch to the identity mapping. */ cpu_switch_mm(idmap_pgd, &init_mm); local_flush_bp_all();