GNU Linux-libre 4.14.290-gnu1
[releases.git] / arch / powerpc / platforms / pseries / suspend.c
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
19 #include <linux/cpu.h>
20 #include <linux/delay.h>
21 #include <linux/suspend.h>
22 #include <linux/stat.h>
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>
28 #include <asm/topology.h>
29
30 static u64 stream_id;
31 static struct device suspend_dev;
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_enable_irqs
83  *
84  * Post suspend configuration updates
85  *
86  **/
87 static void pseries_suspend_enable_irqs(void)
88 {
89         /*
90          * Update configuration which can be modified based on device tree
91          * changes during resume.
92          */
93         post_mobility_fixup();
94 }
95
96 /**
97  * pseries_suspend_enter - Final phase of hibernation
98  *
99  * Return value:
100  *      0 on success / other on failure
101  **/
102 static int pseries_suspend_enter(suspend_state_t state)
103 {
104         int rc = rtas_suspend_last_cpu(&suspend_data);
105
106         atomic_set(&suspending, 0);
107         atomic_set(&suspend_data.done, 1);
108         return rc;
109 }
110
111 /**
112  * pseries_prepare_late - Prepare to suspend all other CPUs
113  *
114  * Return value:
115  *      0 on success / other on failure
116  **/
117 static int pseries_prepare_late(void)
118 {
119         atomic_set(&suspending, 1);
120         atomic_set(&suspend_data.working, 0);
121         atomic_set(&suspend_data.done, 0);
122         atomic_set(&suspend_data.error, 0);
123         suspend_data.complete = &suspend_work;
124         reinit_completion(&suspend_work);
125         return 0;
126 }
127
128 /**
129  * store_hibernate - Initiate partition hibernation
130  * @dev:                subsys root device
131  * @attr:               device attribute struct
132  * @buf:                buffer
133  * @count:              buffer size
134  *
135  * Write the stream ID received from the HMC to this file
136  * to trigger hibernating the partition
137  *
138  * Return value:
139  *      number of bytes printed to buffer / other on failure
140  **/
141 static ssize_t store_hibernate(struct device *dev,
142                                struct device_attribute *attr,
143                                const char *buf, size_t count)
144 {
145         cpumask_var_t offline_mask;
146         int rc;
147
148         if (!capable(CAP_SYS_ADMIN))
149                 return -EPERM;
150
151         if (!alloc_cpumask_var(&offline_mask, GFP_KERNEL))
152                 return -ENOMEM;
153
154         stream_id = simple_strtoul(buf, NULL, 16);
155
156         do {
157                 rc = pseries_suspend_begin(PM_SUSPEND_MEM);
158                 if (rc == -EAGAIN)
159                         ssleep(1);
160         } while (rc == -EAGAIN);
161
162         if (!rc) {
163                 /* All present CPUs must be online */
164                 cpumask_andnot(offline_mask, cpu_present_mask,
165                                 cpu_online_mask);
166                 rc = rtas_online_cpus_mask(offline_mask);
167                 if (rc) {
168                         pr_err("%s: Could not bring present CPUs online.\n",
169                                         __func__);
170                         goto out;
171                 }
172
173                 stop_topology_update();
174                 rc = pm_suspend(PM_SUSPEND_MEM);
175                 start_topology_update();
176
177                 /* Take down CPUs not online prior to suspend */
178                 if (!rtas_offline_cpus_mask(offline_mask))
179                         pr_warn("%s: Could not restore CPUs to offline "
180                                         "state.\n", __func__);
181         }
182
183         stream_id = 0;
184
185         if (!rc)
186                 rc = count;
187 out:
188         free_cpumask_var(offline_mask);
189         return rc;
190 }
191
192 #define USER_DT_UPDATE  0
193 #define KERN_DT_UPDATE  1
194
195 /**
196  * show_hibernate - Report device tree update responsibilty
197  * @dev:                subsys root device
198  * @attr:               device attribute struct
199  * @buf:                buffer
200  *
201  * Report whether a device tree update is performed by the kernel after a
202  * resume, or if drmgr must coordinate the update from user space.
203  *
204  * Return value:
205  *      0 if drmgr is to initiate update, and 1 otherwise
206  **/
207 static ssize_t show_hibernate(struct device *dev,
208                               struct device_attribute *attr,
209                               char *buf)
210 {
211         return sprintf(buf, "%d\n", KERN_DT_UPDATE);
212 }
213
214 static DEVICE_ATTR(hibernate, S_IWUSR | S_IRUGO,
215                    show_hibernate, store_hibernate);
216
217 static struct bus_type suspend_subsys = {
218         .name = "power",
219         .dev_name = "power",
220 };
221
222 static const struct platform_suspend_ops pseries_suspend_ops = {
223         .valid          = suspend_valid_only_mem,
224         .prepare_late   = pseries_prepare_late,
225         .enter          = pseries_suspend_enter,
226 };
227
228 /**
229  * pseries_suspend_sysfs_register - Register with sysfs
230  *
231  * Return value:
232  *      0 on success / other on failure
233  **/
234 static int pseries_suspend_sysfs_register(struct device *dev)
235 {
236         int rc;
237
238         if ((rc = subsys_system_register(&suspend_subsys, NULL)))
239                 return rc;
240
241         dev->id = 0;
242         dev->bus = &suspend_subsys;
243
244         if ((rc = device_create_file(suspend_subsys.dev_root, &dev_attr_hibernate)))
245                 goto subsys_unregister;
246
247         return 0;
248
249 subsys_unregister:
250         bus_unregister(&suspend_subsys);
251         return rc;
252 }
253
254 /**
255  * pseries_suspend_init - initcall for pSeries suspend
256  *
257  * Return value:
258  *      0 on success / other on failure
259  **/
260 static int __init pseries_suspend_init(void)
261 {
262         int rc;
263
264         if (!firmware_has_feature(FW_FEATURE_LPAR))
265                 return 0;
266
267         suspend_data.token = rtas_token("ibm,suspend-me");
268         if (suspend_data.token == RTAS_UNKNOWN_SERVICE)
269                 return 0;
270
271         if ((rc = pseries_suspend_sysfs_register(&suspend_dev)))
272                 return rc;
273
274         ppc_md.suspend_disable_cpu = pseries_suspend_cpu;
275         ppc_md.suspend_enable_irqs = pseries_suspend_enable_irqs;
276         suspend_set_ops(&pseries_suspend_ops);
277         return 0;
278 }
279 machine_device_initcall(pseries, pseries_suspend_init);