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 6549dca20efe25dbfc3a782a429cd92a16b17b96..6d753e7df9f643305f79f649b73e25a4ab673c9a 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 109c97e991a6391f243045451907cd9bf680e830..b3e5ff0125fe48ce7398c60e14a241492aa9660d 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 8be391c2aecb8c412d0b5a1cbf1820b7d04e2f94..9a5880c452b8303b089423f99058760ac261e706 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 4320629c07c3031f4f89f698743f0e406fda36ec..601603382411b5da08a6196eef5c5f49c1fa9cb2 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);
+}
+