]>
Commit | Line | Data |
---|---|---|
1a59d1b8 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
32d8ad4e BK |
2 | /* |
3 | * Copyright (C) 2010 Brian King IBM Corporation | |
32d8ad4e BK |
4 | */ |
5 | ||
120496ac | 6 | #include <linux/cpu.h> |
32d8ad4e BK |
7 | #include <linux/delay.h> |
8 | #include <linux/suspend.h> | |
b56eade5 | 9 | #include <linux/stat.h> |
32d8ad4e BK |
10 | #include <asm/firmware.h> |
11 | #include <asm/hvcall.h> | |
12 | #include <asm/machdep.h> | |
13 | #include <asm/mmu.h> | |
14 | #include <asm/rtas.h> | |
444080d1 | 15 | #include <asm/topology.h> |
6b36ba84 | 16 | #include "../../kernel/cacheinfo.h" |
32d8ad4e BK |
17 | |
18 | static u64 stream_id; | |
86ba41d0 | 19 | static struct device suspend_dev; |
32d8ad4e BK |
20 | static DECLARE_COMPLETION(suspend_work); |
21 | static struct rtas_suspend_me_data suspend_data; | |
22 | static atomic_t suspending; | |
23 | ||
24 | /** | |
25 | * pseries_suspend_begin - First phase of hibernation | |
26 | * | |
27 | * Check to ensure we are in a valid state to hibernate | |
28 | * | |
29 | * Return value: | |
30 | * 0 on success / other on failure | |
31 | **/ | |
32 | static int pseries_suspend_begin(suspend_state_t state) | |
33 | { | |
34 | long vasi_state, rc; | |
35 | unsigned long retbuf[PLPAR_HCALL_BUFSIZE]; | |
36 | ||
37 | /* Make sure the state is valid */ | |
38 | rc = plpar_hcall(H_VASI_STATE, retbuf, stream_id); | |
39 | ||
40 | vasi_state = retbuf[0]; | |
41 | ||
42 | if (rc) { | |
43 | pr_err("pseries_suspend_begin: vasi_state returned %ld\n",rc); | |
44 | return rc; | |
45 | } else if (vasi_state == H_VASI_ENABLED) { | |
46 | return -EAGAIN; | |
47 | } else if (vasi_state != H_VASI_SUSPENDING) { | |
48 | pr_err("pseries_suspend_begin: vasi_state returned state %ld\n", | |
49 | vasi_state); | |
50 | return -EIO; | |
51 | } | |
52 | ||
53 | return 0; | |
54 | } | |
55 | ||
56 | /** | |
57 | * pseries_suspend_cpu - Suspend a single CPU | |
58 | * | |
59 | * Makes the H_JOIN call to suspend the CPU | |
60 | * | |
61 | **/ | |
62 | static int pseries_suspend_cpu(void) | |
63 | { | |
64 | if (atomic_read(&suspending)) | |
65 | return rtas_suspend_cpu(&suspend_data); | |
66 | return 0; | |
67 | } | |
68 | ||
6b36ba84 HM |
69 | /** |
70 | * pseries_suspend_enable_irqs | |
71 | * | |
72 | * Post suspend configuration updates | |
73 | * | |
74 | **/ | |
75 | static void pseries_suspend_enable_irqs(void) | |
76 | { | |
77 | /* | |
78 | * Update configuration which can be modified based on device tree | |
79 | * changes during resume. | |
80 | */ | |
81 | cacheinfo_cpu_offline(smp_processor_id()); | |
82 | post_mobility_fixup(); | |
83 | cacheinfo_cpu_online(smp_processor_id()); | |
84 | } | |
85 | ||
32d8ad4e BK |
86 | /** |
87 | * pseries_suspend_enter - Final phase of hibernation | |
88 | * | |
89 | * Return value: | |
90 | * 0 on success / other on failure | |
91 | **/ | |
92 | static int pseries_suspend_enter(suspend_state_t state) | |
93 | { | |
94 | int rc = rtas_suspend_last_cpu(&suspend_data); | |
95 | ||
96 | atomic_set(&suspending, 0); | |
97 | atomic_set(&suspend_data.done, 1); | |
98 | return rc; | |
99 | } | |
100 | ||
101 | /** | |
102 | * pseries_prepare_late - Prepare to suspend all other CPUs | |
103 | * | |
104 | * Return value: | |
105 | * 0 on success / other on failure | |
106 | **/ | |
107 | static int pseries_prepare_late(void) | |
108 | { | |
109 | atomic_set(&suspending, 1); | |
110 | atomic_set(&suspend_data.working, 0); | |
111 | atomic_set(&suspend_data.done, 0); | |
112 | atomic_set(&suspend_data.error, 0); | |
113 | suspend_data.complete = &suspend_work; | |
16735d02 | 114 | reinit_completion(&suspend_work); |
32d8ad4e BK |
115 | return 0; |
116 | } | |
117 | ||
118 | /** | |
119 | * store_hibernate - Initiate partition hibernation | |
86ba41d0 KS |
120 | * @dev: subsys root device |
121 | * @attr: device attribute struct | |
32d8ad4e BK |
122 | * @buf: buffer |
123 | * @count: buffer size | |
124 | * | |
125 | * Write the stream ID received from the HMC to this file | |
126 | * to trigger hibernating the partition | |
127 | * | |
128 | * Return value: | |
129 | * number of bytes printed to buffer / other on failure | |
130 | **/ | |
86ba41d0 KS |
131 | static ssize_t store_hibernate(struct device *dev, |
132 | struct device_attribute *attr, | |
32d8ad4e BK |
133 | const char *buf, size_t count) |
134 | { | |
120496ac | 135 | cpumask_var_t offline_mask; |
32d8ad4e BK |
136 | int rc; |
137 | ||
138 | if (!capable(CAP_SYS_ADMIN)) | |
139 | return -EPERM; | |
140 | ||
0ee931c4 | 141 | if (!alloc_cpumask_var(&offline_mask, GFP_KERNEL)) |
120496ac RJ |
142 | return -ENOMEM; |
143 | ||
32d8ad4e BK |
144 | stream_id = simple_strtoul(buf, NULL, 16); |
145 | ||
146 | do { | |
147 | rc = pseries_suspend_begin(PM_SUSPEND_MEM); | |
148 | if (rc == -EAGAIN) | |
149 | ssleep(1); | |
150 | } while (rc == -EAGAIN); | |
151 | ||
444080d1 | 152 | if (!rc) { |
120496ac RJ |
153 | /* All present CPUs must be online */ |
154 | cpumask_andnot(offline_mask, cpu_present_mask, | |
155 | cpu_online_mask); | |
156 | rc = rtas_online_cpus_mask(offline_mask); | |
157 | if (rc) { | |
158 | pr_err("%s: Could not bring present CPUs online.\n", | |
159 | __func__); | |
160 | goto out; | |
161 | } | |
162 | ||
444080d1 | 163 | stop_topology_update(); |
32d8ad4e | 164 | rc = pm_suspend(PM_SUSPEND_MEM); |
444080d1 | 165 | start_topology_update(); |
120496ac RJ |
166 | |
167 | /* Take down CPUs not online prior to suspend */ | |
168 | if (!rtas_offline_cpus_mask(offline_mask)) | |
169 | pr_warn("%s: Could not restore CPUs to offline " | |
170 | "state.\n", __func__); | |
444080d1 | 171 | } |
32d8ad4e BK |
172 | |
173 | stream_id = 0; | |
174 | ||
175 | if (!rc) | |
176 | rc = count; | |
120496ac RJ |
177 | out: |
178 | free_cpumask_var(offline_mask); | |
32d8ad4e BK |
179 | return rc; |
180 | } | |
181 | ||
9da34892 TD |
182 | #define USER_DT_UPDATE 0 |
183 | #define KERN_DT_UPDATE 1 | |
184 | ||
185 | /** | |
186 | * show_hibernate - Report device tree update responsibilty | |
187 | * @dev: subsys root device | |
188 | * @attr: device attribute struct | |
189 | * @buf: buffer | |
190 | * | |
191 | * Report whether a device tree update is performed by the kernel after a | |
192 | * resume, or if drmgr must coordinate the update from user space. | |
193 | * | |
194 | * Return value: | |
195 | * 0 if drmgr is to initiate update, and 1 otherwise | |
196 | **/ | |
197 | static ssize_t show_hibernate(struct device *dev, | |
198 | struct device_attribute *attr, | |
199 | char *buf) | |
200 | { | |
201 | return sprintf(buf, "%d\n", KERN_DT_UPDATE); | |
202 | } | |
203 | ||
57ad583f | 204 | static DEVICE_ATTR(hibernate, 0644, show_hibernate, store_hibernate); |
32d8ad4e | 205 | |
86ba41d0 | 206 | static struct bus_type suspend_subsys = { |
32d8ad4e | 207 | .name = "power", |
86ba41d0 | 208 | .dev_name = "power", |
32d8ad4e BK |
209 | }; |
210 | ||
2f55ac07 | 211 | static const struct platform_suspend_ops pseries_suspend_ops = { |
32d8ad4e BK |
212 | .valid = suspend_valid_only_mem, |
213 | .begin = pseries_suspend_begin, | |
214 | .prepare_late = pseries_prepare_late, | |
215 | .enter = pseries_suspend_enter, | |
216 | }; | |
217 | ||
218 | /** | |
219 | * pseries_suspend_sysfs_register - Register with sysfs | |
220 | * | |
221 | * Return value: | |
222 | * 0 on success / other on failure | |
223 | **/ | |
86ba41d0 | 224 | static int pseries_suspend_sysfs_register(struct device *dev) |
32d8ad4e BK |
225 | { |
226 | int rc; | |
227 | ||
86ba41d0 | 228 | if ((rc = subsys_system_register(&suspend_subsys, NULL))) |
32d8ad4e BK |
229 | return rc; |
230 | ||
86ba41d0 KS |
231 | dev->id = 0; |
232 | dev->bus = &suspend_subsys; | |
32d8ad4e | 233 | |
86ba41d0 KS |
234 | if ((rc = device_create_file(suspend_subsys.dev_root, &dev_attr_hibernate))) |
235 | goto subsys_unregister; | |
32d8ad4e BK |
236 | |
237 | return 0; | |
238 | ||
86ba41d0 KS |
239 | subsys_unregister: |
240 | bus_unregister(&suspend_subsys); | |
32d8ad4e BK |
241 | return rc; |
242 | } | |
243 | ||
244 | /** | |
245 | * pseries_suspend_init - initcall for pSeries suspend | |
246 | * | |
247 | * Return value: | |
248 | * 0 on success / other on failure | |
249 | **/ | |
250 | static int __init pseries_suspend_init(void) | |
251 | { | |
252 | int rc; | |
253 | ||
8e83e905 | 254 | if (!firmware_has_feature(FW_FEATURE_LPAR)) |
32d8ad4e BK |
255 | return 0; |
256 | ||
257 | suspend_data.token = rtas_token("ibm,suspend-me"); | |
258 | if (suspend_data.token == RTAS_UNKNOWN_SERVICE) | |
259 | return 0; | |
260 | ||
86ba41d0 | 261 | if ((rc = pseries_suspend_sysfs_register(&suspend_dev))) |
32d8ad4e BK |
262 | return rc; |
263 | ||
264 | ppc_md.suspend_disable_cpu = pseries_suspend_cpu; | |
6b36ba84 | 265 | ppc_md.suspend_enable_irqs = pseries_suspend_enable_irqs; |
32d8ad4e BK |
266 | suspend_set_ops(&pseries_suspend_ops); |
267 | return 0; | |
268 | } | |
8e83e905 | 269 | machine_device_initcall(pseries, pseries_suspend_init); |