riscv: kernel: Add support for hibernate/suspend to disk
[platform/kernel/linux-starfive.git] / arch / riscv / kernel / hibernate.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*:
3  * Hibernate support specific for RISCV
4  *
5  * Copyright (C) 2022 Shanghai StarFive Technology Co., Ltd.
6  *
7  * Author: Jee Heng Sia <jeeheng.sia@starfivetech.com>
8  *
9  */
10
11 #include <linux/cpu.h>
12 #include <linux/pm.h>
13 #include <linux/sched.h>
14 #include <linux/suspend.h>
15 #include <linux/utsname.h>
16
17 #include <asm/barrier.h>
18 #include <asm/cacheflush.h>
19 #include <asm/irqflags.h>
20 #include <asm/kexec.h>
21 #include <asm/mmu_context.h>
22 #include <asm/page.h>
23 #include <asm/sections.h>
24 #include <asm/smp.h>
25 #include <asm/suspend.h>
26
27 /*
28  * The logical cpu number we should resume on, initialised to a non-cpu
29  * number.
30  */
31 static int sleep_cpu = -EINVAL;
32
33 /*
34  * Values that may not change over hibernate/resume. We put the build number
35  * and date in here so that we guarantee not to resume with a different
36  * kernel.
37  */
38 struct arch_hibernate_hdr_invariants {
39         char            uts_version[__NEW_UTS_LEN + 1];
40 };
41
42 /* These values need to be known across a hibernate/restore. */
43 static struct arch_hibernate_hdr {
44         struct arch_hibernate_hdr_invariants invariants;
45         u64             hartid;
46 } resume_hdr;
47
48 static inline void arch_hdr_invariants(struct arch_hibernate_hdr_invariants *i)
49 {
50         memset(i, 0, sizeof(*i));
51         memcpy(i->uts_version, init_utsname()->version, sizeof(i->uts_version));
52 }
53
54
55 int pfn_is_nosave(unsigned long pfn)
56 {
57         unsigned long nosave_begin_pfn = sym_to_pfn(&__nosave_begin);
58         unsigned long nosave_end_pfn = sym_to_pfn(&__nosave_end - 1);
59
60         return ((pfn >= nosave_begin_pfn) && (pfn <= nosave_end_pfn));
61 }
62
63 void notrace save_processor_state(void)
64 {
65         WARN_ON(num_online_cpus() != 1);
66 }
67
68 void notrace restore_processor_state(void)
69 {
70 }
71
72
73 int arch_hibernation_header_save(void *addr, unsigned int max_size)
74 {
75         struct arch_hibernate_hdr *hdr = addr;
76
77         if (max_size < sizeof(*hdr))
78                 return -EOVERFLOW;
79
80         arch_hdr_invariants(&hdr->invariants);
81
82         hdr->hartid = cpuid_to_hartid_map(sleep_cpu);
83
84         pr_debug("Hibernating on CPU %x hartid %llx\n", sleep_cpu, hdr->hartid);
85
86         return 0;
87 }
88 EXPORT_SYMBOL(arch_hibernation_header_save);
89
90 int arch_hibernation_header_restore(void *addr)
91 {
92         struct arch_hibernate_hdr_invariants invariants;
93         struct arch_hibernate_hdr *hdr = addr;
94         int ret;
95
96         arch_hdr_invariants(&invariants);
97
98         if (memcmp(&hdr->invariants, &invariants, sizeof(invariants))) {
99                 pr_crit("Hibernate image not generated by this kernel!\n");
100                 return -EINVAL;
101         }
102
103         sleep_cpu = riscv_hartid_to_cpuid(hdr->hartid);
104         if (sleep_cpu < 0) {
105                 pr_crit("Hibernated on a CPU not known to this kernel!\n");
106                 sleep_cpu = -EINVAL;
107                 return -EINVAL;
108         }
109
110         pr_debug("Hibernated on CPU %x hartid %llx\n", sleep_cpu, hdr->hartid);
111
112         ret = bringup_hibernate_cpu(sleep_cpu);
113         if (ret) {
114                 sleep_cpu = -EINVAL;
115                 return ret;
116         }
117
118         resume_hdr = *hdr;
119
120         return 0;
121 }
122 EXPORT_SYMBOL(arch_hibernation_header_restore);
123
124 int swsusp_arch_suspend(void)
125 {
126         return 0;
127 }
128
129 int swsusp_arch_resume(void)
130 {
131         return 0;
132 }
133
134 int hibernate_resume_nonboot_cpu_disable(void)
135 {
136         if (sleep_cpu < 0) {
137                 pr_err("Failing to resume from hibernate on an unknown CPU.\n");
138                 return -ENODEV;
139         }
140
141         return freeze_secondary_cpus(sleep_cpu);
142 }
143