]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ARM: 9476/1: mm: fix kexec and hibernation with CONFIG_CPU_TTBR0_PAN
authorFlorian Fainelli <f.fainelli@gmail.com>
Mon, 25 May 2026 16:38:17 +0000 (17:38 +0100)
committerRussell King <rmk+kernel@armlinux.org.uk>
Thu, 11 Jun 2026 13:52:03 +0000 (14:52 +0100)
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 <catalin.marinas@arm.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Signed-off-by: Florian Fainelli <florian.fainelli@broadcom.com>
Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
arch/arm/kernel/hibernate.c
arch/arm/mm/idmap.c

index 38a90a3d12b2cfcc1b96b8999950f9a8f0f244fb..231a76af09a03fc9905cab8b385452865de813df 100644 (file)
@@ -21,6 +21,7 @@
 #include <asm/suspend.h>
 #include <asm/page.h>
 #include <asm/sections.h>
+#include <asm/uaccess.h>
 #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);
index 4a833e89782aa250f07f7cc5ac273e29840c54dd..70403e968d2aeb2095c5e7e9558b7f4a449d678f 100644 (file)
@@ -11,6 +11,7 @@
 #include <asm/pgalloc.h>
 #include <asm/sections.h>
 #include <asm/system_info.h>
+#include <asm/uaccess.h>
 
 /*
  * 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();