Skip to content
Snippets Groups Projects
Commit c9117434 authored by Huacai Chen's avatar Huacai Chen
Browse files

LoongArch: Use polling play_dead() when resuming from hibernation


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 406f992e ("x86 /
hibernate: Use hlt_play_dead() when resuming from hibernation").

Cc: stable@vger.kernel.org
Tested-by: default avatarErpeng Xu <xuerpeng@uniontech.com>
Tested-by: default avatarYuli Wang <wangyuli@uniontech.com>
Signed-off-by: default avatarHuacai Chen <chenhuacai@loongson.cn>
parent a0d3c8bc
No related branches found
No related tags found
No related merge requests found
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include <linux/smp.h> #include <linux/smp.h>
#include <linux/threads.h> #include <linux/threads.h>
#include <linux/export.h> #include <linux/export.h>
#include <linux/suspend.h>
#include <linux/syscore_ops.h> #include <linux/syscore_ops.h>
#include <linux/time.h> #include <linux/time.h>
#include <linux/tracepoint.h> #include <linux/tracepoint.h>
...@@ -423,7 +424,7 @@ void loongson_cpu_die(unsigned int cpu) ...@@ -423,7 +424,7 @@ void loongson_cpu_die(unsigned int cpu)
mb(); mb();
} }
void __noreturn arch_cpu_idle_dead(void) static void __noreturn idle_play_dead(void)
{ {
register uint64_t addr; register uint64_t addr;
register void (*init_fn)(void); register void (*init_fn)(void);
...@@ -447,6 +448,50 @@ void __noreturn arch_cpu_idle_dead(void) ...@@ -447,6 +448,50 @@ void __noreturn arch_cpu_idle_dead(void)
BUG(); 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 #endif
/* /*
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment