riscv: kernel: Add support for hibernate/suspend to disk
authorSia Jee Heng <jeeheng.sia@starfivetech.com>
Thu, 1 Sep 2022 07:47:11 +0000 (15:47 +0800)
committermason.huo <mason.huo@starfivetech.com>
Thu, 5 Jan 2023 05:24:40 +0000 (13:24 +0800)
The implementation assumes that exactly the same kernel is booted on the
same hardware.

We save the build number and date to the swap header so that we guarantee
not to resume with a different kernel upon booted up the hibernated image.

swsusp_arch_resume() and swsusp_arch_suspend() are coded as dummy
functions for now and shall complete in the subsequent patches.

Signed-off-by: Sia Jee Heng <jeeheng.sia@starfivetech.com>
arch/riscv/Kconfig
arch/riscv/include/asm/page.h
arch/riscv/include/asm/suspend.h
arch/riscv/kernel/Makefile
arch/riscv/kernel/hibernate.c [new file with mode: 0644]

index 6549dca..6d753e7 100644 (file)
@@ -557,6 +557,16 @@ config XIP_PHYS_ADDR
          be linked for and stored to.  This address is dependent on your
          own flash usage.
 
+config ARCH_SUSPEND_POSSIBLE
+        def_bool y
+
+config ARCH_HIBERNATION_POSSIBLE
+       def_bool y
+
+config ARCH_HIBERNATION_HEADER
+       def_bool y
+       depends on HIBERNATION
+
 endmenu
 
 config BUILTIN_DTB
index 109c97e..b3e5ff0 100644 (file)
@@ -157,6 +157,8 @@ extern phys_addr_t __phys_addr_symbol(unsigned long x);
 #define page_to_bus(page)      (page_to_phys(page))
 #define phys_to_page(paddr)    (pfn_to_page(phys_to_pfn(paddr)))
 
+#define sym_to_pfn(x)           __phys_to_pfn(__pa_symbol(x))
+
 #ifdef CONFIG_FLATMEM
 #define pfn_valid(pfn) \
        (((pfn) >= ARCH_PFN_OFFSET) && (((pfn) - ARCH_PFN_OFFSET) < max_mapnr))
index 8be391c..9a5880c 100644 (file)
@@ -33,4 +33,13 @@ int cpu_suspend(unsigned long arg,
 /* Low-level CPU resume entry function */
 int __cpu_resume_enter(unsigned long hartid, unsigned long context);
 
+/* Low-level API to support hibernation */
+int swsusp_arch_suspend(void);
+int swsusp_arch_resume(void);
+int arch_hibernation_header_save(void *addr, unsigned int max_size);
+int arch_hibernation_header_restore(void *addr);
+
+/* Used to resume on the CPU we hibernated on */
+int hibernate_resume_nonboot_cpu_disable(void);
+
 #endif
index 4320629..6016033 100644 (file)
@@ -48,7 +48,7 @@ obj-$(CONFIG_MODULES)         += module.o
 obj-$(CONFIG_MODULE_SECTIONS)  += module-sections.o
 
 obj-$(CONFIG_CPU_PM)           += suspend_entry.o suspend.o
-
+obj-$(CONFIG_HIBERNATION)      += hibernate.o
 obj-$(CONFIG_FUNCTION_TRACER)  += mcount.o ftrace.o
 obj-$(CONFIG_DYNAMIC_FTRACE)   += mcount-dyn.o
 
diff --git a/arch/riscv/kernel/hibernate.c b/arch/riscv/kernel/hibernate.c
new file mode 100644 (file)
index 0000000..83471b7
--- /dev/null
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*:
+ * Hibernate support specific for RISCV
+ *
+ * Copyright (C) 2022 Shanghai StarFive Technology Co., Ltd.
+ *
+ * Author: Jee Heng Sia <jeeheng.sia@starfivetech.com>
+ *
+ */
+
+#include <linux/cpu.h>
+#include <linux/pm.h>
+#include <linux/sched.h>
+#include <linux/suspend.h>
+#include <linux/utsname.h>
+
+#include <asm/barrier.h>
+#include <asm/cacheflush.h>
+#include <asm/irqflags.h>
+#include <asm/kexec.h>
+#include <asm/mmu_context.h>
+#include <asm/page.h>
+#include <asm/sections.h>
+#include <asm/smp.h>
+#include <asm/suspend.h>
+
+/*
+ * The logical cpu number we should resume on, initialised to a non-cpu
+ * number.
+ */
+static int sleep_cpu = -EINVAL;
+
+/*
+ * Values that may not change over hibernate/resume. We put the build number
+ * and date in here so that we guarantee not to resume with a different
+ * kernel.
+ */
+struct arch_hibernate_hdr_invariants {
+       char            uts_version[__NEW_UTS_LEN + 1];
+};
+
+/* These values need to be known across a hibernate/restore. */
+static struct arch_hibernate_hdr {
+       struct arch_hibernate_hdr_invariants invariants;
+       u64             hartid;
+} resume_hdr;
+
+static inline void arch_hdr_invariants(struct arch_hibernate_hdr_invariants *i)
+{
+       memset(i, 0, sizeof(*i));
+       memcpy(i->uts_version, init_utsname()->version, sizeof(i->uts_version));
+}
+
+
+int pfn_is_nosave(unsigned long pfn)
+{
+       unsigned long nosave_begin_pfn = sym_to_pfn(&__nosave_begin);
+       unsigned long nosave_end_pfn = sym_to_pfn(&__nosave_end - 1);
+
+       return ((pfn >= nosave_begin_pfn) && (pfn <= nosave_end_pfn));
+}
+
+void notrace save_processor_state(void)
+{
+       WARN_ON(num_online_cpus() != 1);
+}
+
+void notrace restore_processor_state(void)
+{
+}
+
+
+int arch_hibernation_header_save(void *addr, unsigned int max_size)
+{
+       struct arch_hibernate_hdr *hdr = addr;
+
+       if (max_size < sizeof(*hdr))
+               return -EOVERFLOW;
+
+       arch_hdr_invariants(&hdr->invariants);
+
+       hdr->hartid = cpuid_to_hartid_map(sleep_cpu);
+
+       pr_debug("Hibernating on CPU %x hartid %llx\n", sleep_cpu, hdr->hartid);
+
+       return 0;
+}
+EXPORT_SYMBOL(arch_hibernation_header_save);
+
+int arch_hibernation_header_restore(void *addr)
+{
+       struct arch_hibernate_hdr_invariants invariants;
+       struct arch_hibernate_hdr *hdr = addr;
+       int ret;
+
+       arch_hdr_invariants(&invariants);
+
+       if (memcmp(&hdr->invariants, &invariants, sizeof(invariants))) {
+               pr_crit("Hibernate image not generated by this kernel!\n");
+               return -EINVAL;
+       }
+
+       sleep_cpu = riscv_hartid_to_cpuid(hdr->hartid);
+       if (sleep_cpu < 0) {
+               pr_crit("Hibernated on a CPU not known to this kernel!\n");
+               sleep_cpu = -EINVAL;
+               return -EINVAL;
+       }
+
+       pr_debug("Hibernated on CPU %x hartid %llx\n", sleep_cpu, hdr->hartid);
+
+       ret = bringup_hibernate_cpu(sleep_cpu);
+       if (ret) {
+               sleep_cpu = -EINVAL;
+               return ret;
+       }
+
+       resume_hdr = *hdr;
+
+       return 0;
+}
+EXPORT_SYMBOL(arch_hibernation_header_restore);
+
+int swsusp_arch_suspend(void)
+{
+       return 0;
+}
+
+int swsusp_arch_resume(void)
+{
+       return 0;
+}
+
+int hibernate_resume_nonboot_cpu_disable(void)
+{
+       if (sleep_cpu < 0) {
+               pr_err("Failing to resume from hibernate on an unknown CPU.\n");
+               return -ENODEV;
+       }
+
+       return freeze_secondary_cpus(sleep_cpu);
+}
+