Commit | Line | Data |
---|---|---|
32d8ad4e BK |
1 | /* |
2 | * Copyright (C) 2010 Brian King IBM Corporation | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License | |
15 | * along with this program; if not, write to the Free Software | |
16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
17 | */ | |
18 | ||
120496ac | 19 | #include <linux/cpu.h> |
32d8ad4e BK |
20 | #include <linux/delay.h> |
21 | #include <linux/suspend.h> | |
b56eade5 | 22 | #include <linux/stat.h> |
32d8ad4e BK |
23 | #include <asm/firmware.h> |
24 | #include <asm/hvcall.h> | |
25 | #include <asm/machdep.h> | |
26 | #include <asm/mmu.h> | |
27 | #include <asm/rtas.h> | |
444080d1 | 28 | #include <asm/topology.h> |
32d8ad4e BK |
29 | |
30 | static u64 stream_id; | |
86ba41d0 | 31 | static struct device suspend_dev; |
32d8ad4e BK |
32 | static DECLARE_COMPLETION(suspend_work); |
33 | static struct rtas_suspend_me_data suspend_data; | |
34 | static atomic_t suspending; | |
35 | ||
36 | /** | |
37 | * pseries_suspend_begin - First phase of hibernation | |
38 | * | |
39 | * Check to ensure we are in a valid state to hibernate | |
40 | * | |
41 | * Return value: | |
42 | * 0 on success / other on failure | |
43 | **/ | |
44 | static int pseries_suspend_begin(suspend_state_t state) | |
45 | { | |
46 | long vasi_state, rc; | |
47 | unsigned long retbuf[PLPAR_HCALL_BUFSIZE]; | |
48 | ||
49 | /* Make sure the state is valid */ | |
50 | rc = plpar_hcall(H_VASI_STATE, retbuf, stream_id); | |
51 | ||
52 | vasi_state = retbuf[0]; | |
53 | ||
54 | if (rc) { | |
55 | pr_err("pseries_suspend_begin: vasi_state returned %ld\n",rc); | |
56 | return rc; | |
57 | } else if (vasi_state == H_VASI_ENABLED) { | |
58 | return -EAGAIN; | |
59 | } else if (vasi_state != H_VASI_SUSPENDING) { | |
60 | pr_err("pseries_suspend_begin: vasi_state returned state %ld\n", | |
61 | vasi_state); | |
62 | return -EIO; | |
63 | } | |
64 | ||
65 | return 0; | |
66 | } | |
67 | ||
68 | /** | |
69 | * pseries_suspend_cpu - Suspend a single CPU | |
70 | * | |
71 | * Makes the H_JOIN call to suspend the CPU | |
72 | * | |
73 | **/ | |
74 | static int pseries_suspend_cpu(void) | |
75 | { | |
76 | if (atomic_read(&suspending)) | |
77 | return rtas_suspend_cpu(&suspend_data); | |
78 | return 0; | |
79 | } | |
80 | ||
81 | /** | |
82 | * pseries_suspend_enter - Final phase of hibernation | |
83 | * | |
84 | * Return value: | |
85 | * 0 on success / other on failure | |
86 | **/ | |
87 | static int pseries_suspend_enter(suspend_state_t state) | |
88 | { | |
89 | int rc = rtas_suspend_last_cpu(&suspend_data); | |
90 | ||
91 | atomic_set(&suspending, 0); | |
92 | atomic_set(&suspend_data.done, 1); | |
93 | return rc; | |
94 | } | |
95 | ||
96 | /** | |
97 | * pseries_prepare_late - Prepare to suspend all other CPUs | |
98 | * | |
99 | * Return value: | |
100 | * 0 on success / other on failure | |
101 | **/ | |
102 | static int pseries_prepare_late(void) | |
103 | { | |
104 | atomic_set(&suspending, 1); | |
105 | atomic_set(&suspend_data.working, 0); | |
106 | atomic_set(&suspend_data.done, 0); | |
107 | atomic_set(&suspend_data.error, 0); | |
108 | suspend_data.complete = &suspend_work; | |
109 | INIT_COMPLETION(suspend_work); | |
110 | return 0; | |
111 | } | |
112 | ||
113 | /** | |
114 | * store_hibernate - Initiate partition hibernation | |
86ba41d0 KS |
115 | * @dev: subsys root device |
116 | * @attr: device attribute struct | |
32d8ad4e BK |
117 | * @buf: buffer |
118 | * @count: buffer size | |
119 | * | |
120 | * Write the stream ID received from the HMC to this file | |
121 | * to trigger hibernating the partition | |
122 | * | |
123 | * Return value: | |
124 | * number of bytes printed to buffer / other on failure | |
125 | **/ | |
86ba41d0 KS |
126 | static ssize_t store_hibernate(struct device *dev, |
127 | struct device_attribute *attr, | |
32d8ad4e BK |
128 | const char *buf, size_t count) |
129 | { | |
120496ac | 130 | cpumask_var_t offline_mask; |
32d8ad4e BK |
131 | int rc; |
132 | ||
133 | if (!capable(CAP_SYS_ADMIN)) | |
134 | return -EPERM; | |
135 | ||
120496ac RJ |
136 | if (!alloc_cpumask_var(&offline_mask, GFP_TEMPORARY)) |
137 | return -ENOMEM; | |
138 | ||
32d8ad4e BK |
139 | stream_id = simple_strtoul(buf, NULL, 16); |
140 | ||
141 | do { | |
142 | rc = pseries_suspend_begin(PM_SUSPEND_MEM); | |
143 | if (rc == -EAGAIN) | |
144 | ssleep(1); | |
145 | } while (rc == -EAGAIN); | |
146 | ||
444080d1 | 147 | if (!rc) { |
120496ac RJ |
148 | /* All present CPUs must be online */ |
149 | cpumask_andnot(offline_mask, cpu_present_mask, | |
150 | cpu_online_mask); | |
151 | rc = rtas_online_cpus_mask(offline_mask); | |
152 | if (rc) { | |
153 | pr_err("%s: Could not bring present CPUs online.\n", | |
154 | __func__); | |
155 | goto out; | |
156 | } | |
157 | ||
444080d1 | 158 | stop_topology_update(); |
32d8ad4e | 159 | rc = pm_suspend(PM_SUSPEND_MEM); |
444080d1 | 160 | start_topology_update(); |
120496ac RJ |
161 | |
162 | /* Take down CPUs not online prior to suspend */ | |
163 | if (!rtas_offline_cpus_mask(offline_mask)) | |
164 | pr_warn("%s: Could not restore CPUs to offline " | |
165 | "state.\n", __func__); | |
444080d1 | 166 | } |
32d8ad4e BK |
167 | |
168 | stream_id = 0; | |
169 | ||
170 | if (!rc) | |
171 | rc = count; | |
120496ac RJ |
172 | out: |
173 | free_cpumask_var(offline_mask); | |
32d8ad4e BK |
174 | return rc; |
175 | } | |
176 | ||
86ba41d0 | 177 | static DEVICE_ATTR(hibernate, S_IWUSR, NULL, store_hibernate); |
32d8ad4e | 178 | |
86ba41d0 | 179 | static struct bus_type suspend_subsys = { |
32d8ad4e | 180 | .name = "power", |
86ba41d0 | 181 | .dev_name = "power", |
32d8ad4e BK |
182 | }; |
183 | ||
2f55ac07 | 184 | static const struct platform_suspend_ops pseries_suspend_ops = { |
32d8ad4e BK |
185 | .valid = suspend_valid_only_mem, |
186 | .begin = pseries_suspend_begin, | |
187 | .prepare_late = pseries_prepare_late, | |
188 | .enter = pseries_suspend_enter, | |
189 | }; | |
190 | ||
191 | /** | |
192 | * pseries_suspend_sysfs_register - Register with sysfs | |
193 | * | |
194 | * Return value: | |
195 | * 0 on success / other on failure | |
196 | **/ | |
86ba41d0 | 197 | static int pseries_suspend_sysfs_register(struct device *dev) |
32d8ad4e BK |
198 | { |
199 | int rc; | |
200 | ||
86ba41d0 | 201 | if ((rc = subsys_system_register(&suspend_subsys, NULL))) |
32d8ad4e BK |
202 | return rc; |
203 | ||
86ba41d0 KS |
204 | dev->id = 0; |
205 | dev->bus = &suspend_subsys; | |
32d8ad4e | 206 | |
86ba41d0 KS |
207 | if ((rc = device_create_file(suspend_subsys.dev_root, &dev_attr_hibernate))) |
208 | goto subsys_unregister; | |
32d8ad4e BK |
209 | |
210 | return 0; | |
211 | ||
86ba41d0 KS |
212 | subsys_unregister: |
213 | bus_unregister(&suspend_subsys); | |
32d8ad4e BK |
214 | return rc; |
215 | } | |
216 | ||
217 | /** | |
218 | * pseries_suspend_init - initcall for pSeries suspend | |
219 | * | |
220 | * Return value: | |
221 | * 0 on success / other on failure | |
222 | **/ | |
223 | static int __init pseries_suspend_init(void) | |
224 | { | |
225 | int rc; | |
226 | ||
227 | if (!machine_is(pseries) || !firmware_has_feature(FW_FEATURE_LPAR)) | |
228 | return 0; | |
229 | ||
230 | suspend_data.token = rtas_token("ibm,suspend-me"); | |
231 | if (suspend_data.token == RTAS_UNKNOWN_SERVICE) | |
232 | return 0; | |
233 | ||
86ba41d0 | 234 | if ((rc = pseries_suspend_sysfs_register(&suspend_dev))) |
32d8ad4e BK |
235 | return rc; |
236 | ||
237 | ppc_md.suspend_disable_cpu = pseries_suspend_cpu; | |
238 | suspend_set_ops(&pseries_suspend_ops); | |
239 | return 0; | |
240 | } | |
241 | ||
242 | __initcall(pseries_suspend_init); |