]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
LoongArch: Use polling play_dead() when resuming from hibernation
authorHuacai Chen <chenhuacai@loongson.cn>
Sat, 8 Mar 2025 05:51:32 +0000 (13:51 +0800)
committerHuacai Chen <chenhuacai@loongson.cn>
Sat, 8 Mar 2025 05:51:32 +0000 (13:51 +0800)
When CONFIG_RANDOM_KMALLOC_CACHES or other randomization infrastructrue
enabled, the idle_task's stack may different between the booting kernel
and target kernel. So when resuming from hibernation, an ACTION_BOOT_CPU
IPI wakeup the idle instruction in arch_cpu_idle_dead() and jump to the
interrupt handler. But since the stack pointer is changed, the interrupt
handler cannot restore correct context.

So rename the current arch_cpu_idle_dead() to idle_play_dead(), make it
as the default version of play_dead(), and the new arch_cpu_idle_dead()
call play_dead() directly. For hibernation, implement an arch-specific
hibernate_resume_nonboot_cpu_disable() to use the polling version (idle
instruction is replace by nop, and irq is disabled) of play_dead(), i.e.
poll_play_dead(), to avoid IPI handler corrupting the idle_task's stack
when resuming from hibernation.

This solution is a little similar to commit 406f992e4a372dafbe3c ("x86 /
hibernate: Use hlt_play_dead() when resuming from hibernation").

Cc: stable@vger.kernel.org
Tested-by: Erpeng Xu <xuerpeng@uniontech.com>
Tested-by: Yuli Wang <wangyuli@uniontech.com>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
arch/loongarch/kernel/smp.c

index fbf747447f13fd356edb056cc3f5dea502e813b7..4b24589c0b5653658a7174266bba35e0ef07f720 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/smp.h>
 #include <linux/threads.h>
 #include <linux/export.h>
+#include <linux/suspend.h>
 #include <linux/syscore_ops.h>
 #include <linux/time.h>
 #include <linux/tracepoint.h>
@@ -423,7 +424,7 @@ void loongson_cpu_die(unsigned int cpu)
        mb();
 }
 
-void __noreturn arch_cpu_idle_dead(void)
+static void __noreturn idle_play_dead(void)
 {
        register uint64_t addr;
        register void (*init_fn)(void);
@@ -447,6 +448,50 @@ void __noreturn arch_cpu_idle_dead(void)
        BUG();
 }
 
+#ifdef CONFIG_HIBERNATION
+static void __noreturn poll_play_dead(void)
+{
+       register uint64_t addr;
+       register void (*init_fn)(void);
+
+       idle_task_exit();
+       __this_cpu_write(cpu_state, CPU_DEAD);
+
+       __smp_mb();
+       do {
+               __asm__ __volatile__("nop\n\t");
+               addr = iocsr_read64(LOONGARCH_IOCSR_MBUF0);
+       } while (addr == 0);
+
+       init_fn = (void *)TO_CACHE(addr);
+       iocsr_write32(0xffffffff, LOONGARCH_IOCSR_IPI_CLEAR);
+
+       init_fn();
+       BUG();
+}
+#endif
+
+static void (*play_dead)(void) = idle_play_dead;
+
+void __noreturn arch_cpu_idle_dead(void)
+{
+       play_dead();
+       BUG(); /* play_dead() doesn't return */
+}
+
+#ifdef CONFIG_HIBERNATION
+int hibernate_resume_nonboot_cpu_disable(void)
+{
+       int ret;
+
+       play_dead = poll_play_dead;
+       ret = suspend_disable_secondary_cpus();
+       play_dead = idle_play_dead;
+
+       return ret;
+}
+#endif
+
 #endif
 
 /*