Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

powerpc/pseries: Partition hibernation support

Enables support for HMC initiated partition hibernation. This is
a firmware assisted hibernation, since the firmware handles writing
the memory out to disk, along with other partition information,
so we just mimic suspend to ram.

Signed-off-by: Brian King <brking@linux.vnet.ibm.com>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>

authored by

Brian King and committed by
Benjamin Herrenschmidt
32d8ad4e 8fe93f8d

+223 -1
+1 -1
arch/powerpc/Kconfig
··· 219 219 config ARCH_SUSPEND_POSSIBLE 220 220 def_bool y 221 221 depends on ADB_PMU || PPC_EFIKA || PPC_LITE5200 || PPC_83xx || \ 222 - PPC_85xx || PPC_86xx 222 + PPC_85xx || PPC_86xx || PPC_PSERIES 223 223 224 224 config PPC_DCR_NATIVE 225 225 bool
+1
arch/powerpc/include/asm/machdep.h
··· 266 266 void (*suspend_disable_irqs)(void); 267 267 void (*suspend_enable_irqs)(void); 268 268 #endif 269 + int (*suspend_disable_cpu)(void); 269 270 270 271 #ifdef CONFIG_ARCH_CPU_PROBE_RELEASE 271 272 ssize_t (*cpu_probe)(const char *, size_t);
+4
arch/powerpc/platforms/pseries/Makefile
··· 26 26 obj-$(CONFIG_PHYP_DUMP) += phyp_dump.o 27 27 obj-$(CONFIG_CMM) += cmm.o 28 28 obj-$(CONFIG_DTL) += dtl.o 29 + 30 + ifeq ($(CONFIG_PPC_PSERIES),y) 31 + obj-$(CONFIG_SUSPEND) += suspend.o 32 + endif
+3
arch/powerpc/platforms/pseries/hotplug-cpu.c
··· 116 116 117 117 if (get_preferred_offline_state(cpu) == CPU_STATE_INACTIVE) { 118 118 set_cpu_current_state(cpu, CPU_STATE_INACTIVE); 119 + if (ppc_md.suspend_disable_cpu) 120 + ppc_md.suspend_disable_cpu(); 121 + 119 122 cede_latency_hint = 2; 120 123 121 124 get_lppaca()->idle = 1;
+214
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/delay.h> 20 + #include <linux/suspend.h> 21 + #include <asm/firmware.h> 22 + #include <asm/hvcall.h> 23 + #include <asm/machdep.h> 24 + #include <asm/mmu.h> 25 + #include <asm/rtas.h> 26 + 27 + static u64 stream_id; 28 + static struct sys_device suspend_sysdev; 29 + static DECLARE_COMPLETION(suspend_work); 30 + static struct rtas_suspend_me_data suspend_data; 31 + static atomic_t suspending; 32 + 33 + /** 34 + * pseries_suspend_begin - First phase of hibernation 35 + * 36 + * Check to ensure we are in a valid state to hibernate 37 + * 38 + * Return value: 39 + * 0 on success / other on failure 40 + **/ 41 + static int pseries_suspend_begin(suspend_state_t state) 42 + { 43 + long vasi_state, rc; 44 + unsigned long retbuf[PLPAR_HCALL_BUFSIZE]; 45 + 46 + /* Make sure the state is valid */ 47 + rc = plpar_hcall(H_VASI_STATE, retbuf, stream_id); 48 + 49 + vasi_state = retbuf[0]; 50 + 51 + if (rc) { 52 + pr_err("pseries_suspend_begin: vasi_state returned %ld\n",rc); 53 + return rc; 54 + } else if (vasi_state == H_VASI_ENABLED) { 55 + return -EAGAIN; 56 + } else if (vasi_state != H_VASI_SUSPENDING) { 57 + pr_err("pseries_suspend_begin: vasi_state returned state %ld\n", 58 + vasi_state); 59 + return -EIO; 60 + } 61 + 62 + return 0; 63 + } 64 + 65 + /** 66 + * pseries_suspend_cpu - Suspend a single CPU 67 + * 68 + * Makes the H_JOIN call to suspend the CPU 69 + * 70 + **/ 71 + static int pseries_suspend_cpu(void) 72 + { 73 + if (atomic_read(&suspending)) 74 + return rtas_suspend_cpu(&suspend_data); 75 + return 0; 76 + } 77 + 78 + /** 79 + * pseries_suspend_enter - Final phase of hibernation 80 + * 81 + * Return value: 82 + * 0 on success / other on failure 83 + **/ 84 + static int pseries_suspend_enter(suspend_state_t state) 85 + { 86 + int rc = rtas_suspend_last_cpu(&suspend_data); 87 + 88 + atomic_set(&suspending, 0); 89 + atomic_set(&suspend_data.done, 1); 90 + return rc; 91 + } 92 + 93 + /** 94 + * pseries_prepare_late - Prepare to suspend all other CPUs 95 + * 96 + * Return value: 97 + * 0 on success / other on failure 98 + **/ 99 + static int pseries_prepare_late(void) 100 + { 101 + atomic_set(&suspending, 1); 102 + atomic_set(&suspend_data.working, 0); 103 + atomic_set(&suspend_data.done, 0); 104 + atomic_set(&suspend_data.error, 0); 105 + suspend_data.complete = &suspend_work; 106 + INIT_COMPLETION(suspend_work); 107 + return 0; 108 + } 109 + 110 + /** 111 + * store_hibernate - Initiate partition hibernation 112 + * @classdev: sysdev class struct 113 + * @attr: class device attribute struct 114 + * @buf: buffer 115 + * @count: buffer size 116 + * 117 + * Write the stream ID received from the HMC to this file 118 + * to trigger hibernating the partition 119 + * 120 + * Return value: 121 + * number of bytes printed to buffer / other on failure 122 + **/ 123 + static ssize_t store_hibernate(struct sysdev_class *classdev, 124 + struct sysdev_class_attribute *attr, 125 + const char *buf, size_t count) 126 + { 127 + int rc; 128 + 129 + if (!capable(CAP_SYS_ADMIN)) 130 + return -EPERM; 131 + 132 + stream_id = simple_strtoul(buf, NULL, 16); 133 + 134 + do { 135 + rc = pseries_suspend_begin(PM_SUSPEND_MEM); 136 + if (rc == -EAGAIN) 137 + ssleep(1); 138 + } while (rc == -EAGAIN); 139 + 140 + if (!rc) 141 + rc = pm_suspend(PM_SUSPEND_MEM); 142 + 143 + stream_id = 0; 144 + 145 + if (!rc) 146 + rc = count; 147 + return rc; 148 + } 149 + 150 + static SYSDEV_CLASS_ATTR(hibernate, S_IWUSR, NULL, store_hibernate); 151 + 152 + static struct sysdev_class suspend_sysdev_class = { 153 + .name = "power", 154 + }; 155 + 156 + static struct platform_suspend_ops pseries_suspend_ops = { 157 + .valid = suspend_valid_only_mem, 158 + .begin = pseries_suspend_begin, 159 + .prepare_late = pseries_prepare_late, 160 + .enter = pseries_suspend_enter, 161 + }; 162 + 163 + /** 164 + * pseries_suspend_sysfs_register - Register with sysfs 165 + * 166 + * Return value: 167 + * 0 on success / other on failure 168 + **/ 169 + static int pseries_suspend_sysfs_register(struct sys_device *sysdev) 170 + { 171 + int rc; 172 + 173 + if ((rc = sysdev_class_register(&suspend_sysdev_class))) 174 + return rc; 175 + 176 + sysdev->id = 0; 177 + sysdev->cls = &suspend_sysdev_class; 178 + 179 + if ((rc = sysdev_class_create_file(&suspend_sysdev_class, &attr_hibernate))) 180 + goto class_unregister; 181 + 182 + return 0; 183 + 184 + class_unregister: 185 + sysdev_class_unregister(&suspend_sysdev_class); 186 + return rc; 187 + } 188 + 189 + /** 190 + * pseries_suspend_init - initcall for pSeries suspend 191 + * 192 + * Return value: 193 + * 0 on success / other on failure 194 + **/ 195 + static int __init pseries_suspend_init(void) 196 + { 197 + int rc; 198 + 199 + if (!machine_is(pseries) || !firmware_has_feature(FW_FEATURE_LPAR)) 200 + return 0; 201 + 202 + suspend_data.token = rtas_token("ibm,suspend-me"); 203 + if (suspend_data.token == RTAS_UNKNOWN_SERVICE) 204 + return 0; 205 + 206 + if ((rc = pseries_suspend_sysfs_register(&suspend_sysdev))) 207 + return rc; 208 + 209 + ppc_md.suspend_disable_cpu = pseries_suspend_cpu; 210 + suspend_set_ops(&pseries_suspend_ops); 211 + return 0; 212 + } 213 + 214 + __initcall(pseries_suspend_init);