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 <linux/stat.h> 22 #include <asm/firmware.h> 23 #include <asm/hvcall.h> 24 #include <asm/machdep.h> 25 #include <asm/mmu.h> 26 #include <asm/rtas.h> 27 28 static u64 stream_id; 29 static struct device suspend_dev; 30 static DECLARE_COMPLETION(suspend_work); 31 static struct rtas_suspend_me_data suspend_data; 32 static atomic_t suspending; 33 34 /** 35 * pseries_suspend_begin - First phase of hibernation 36 * 37 * Check to ensure we are in a valid state to hibernate 38 * 39 * Return value: 40 * 0 on success / other on failure 41 **/ 42 static int pseries_suspend_begin(suspend_state_t state) 43 { 44 long vasi_state, rc; 45 unsigned long retbuf[PLPAR_HCALL_BUFSIZE]; 46 47 /* Make sure the state is valid */ 48 rc = plpar_hcall(H_VASI_STATE, retbuf, stream_id); 49 50 vasi_state = retbuf[0]; 51 52 if (rc) { 53 pr_err("pseries_suspend_begin: vasi_state returned %ld\n",rc); 54 return rc; 55 } else if (vasi_state == H_VASI_ENABLED) { 56 return -EAGAIN; 57 } else if (vasi_state != H_VASI_SUSPENDING) { 58 pr_err("pseries_suspend_begin: vasi_state returned state %ld\n", 59 vasi_state); 60 return -EIO; 61 } 62 63 return 0; 64 } 65 66 /** 67 * pseries_suspend_cpu - Suspend a single CPU 68 * 69 * Makes the H_JOIN call to suspend the CPU 70 * 71 **/ 72 static int pseries_suspend_cpu(void) 73 { 74 if (atomic_read(&suspending)) 75 return rtas_suspend_cpu(&suspend_data); 76 return 0; 77 } 78 79 /** 80 * pseries_suspend_enter - Final phase of hibernation 81 * 82 * Return value: 83 * 0 on success / other on failure 84 **/ 85 static int pseries_suspend_enter(suspend_state_t state) 86 { 87 int rc = rtas_suspend_last_cpu(&suspend_data); 88 89 atomic_set(&suspending, 0); 90 atomic_set(&suspend_data.done, 1); 91 return rc; 92 } 93 94 /** 95 * pseries_prepare_late - Prepare to suspend all other CPUs 96 * 97 * Return value: 98 * 0 on success / other on failure 99 **/ 100 static int pseries_prepare_late(void) 101 { 102 atomic_set(&suspending, 1); 103 atomic_set(&suspend_data.working, 0); 104 atomic_set(&suspend_data.done, 0); 105 atomic_set(&suspend_data.error, 0); 106 suspend_data.complete = &suspend_work; 107 INIT_COMPLETION(suspend_work); 108 return 0; 109 } 110 111 /** 112 * store_hibernate - Initiate partition hibernation 113 * @dev: subsys root device 114 * @attr: device attribute struct 115 * @buf: buffer 116 * @count: buffer size 117 * 118 * Write the stream ID received from the HMC to this file 119 * to trigger hibernating the partition 120 * 121 * Return value: 122 * number of bytes printed to buffer / other on failure 123 **/ 124 static ssize_t store_hibernate(struct device *dev, 125 struct device_attribute *attr, 126 const char *buf, size_t count) 127 { 128 int rc; 129 130 if (!capable(CAP_SYS_ADMIN)) 131 return -EPERM; 132 133 stream_id = simple_strtoul(buf, NULL, 16); 134 135 do { 136 rc = pseries_suspend_begin(PM_SUSPEND_MEM); 137 if (rc == -EAGAIN) 138 ssleep(1); 139 } while (rc == -EAGAIN); 140 141 if (!rc) 142 rc = pm_suspend(PM_SUSPEND_MEM); 143 144 stream_id = 0; 145 146 if (!rc) 147 rc = count; 148 return rc; 149 } 150 151 static DEVICE_ATTR(hibernate, S_IWUSR, NULL, store_hibernate); 152 153 static struct bus_type suspend_subsys = { 154 .name = "power", 155 .dev_name = "power", 156 }; 157 158 static const struct platform_suspend_ops pseries_suspend_ops = { 159 .valid = suspend_valid_only_mem, 160 .begin = pseries_suspend_begin, 161 .prepare_late = pseries_prepare_late, 162 .enter = pseries_suspend_enter, 163 }; 164 165 /** 166 * pseries_suspend_sysfs_register - Register with sysfs 167 * 168 * Return value: 169 * 0 on success / other on failure 170 **/ 171 static int pseries_suspend_sysfs_register(struct device *dev) 172 { 173 int rc; 174 175 if ((rc = subsys_system_register(&suspend_subsys, NULL))) 176 return rc; 177 178 dev->id = 0; 179 dev->bus = &suspend_subsys; 180 181 if ((rc = device_create_file(suspend_subsys.dev_root, &dev_attr_hibernate))) 182 goto subsys_unregister; 183 184 return 0; 185 186 subsys_unregister: 187 bus_unregister(&suspend_subsys); 188 return rc; 189 } 190 191 /** 192 * pseries_suspend_init - initcall for pSeries suspend 193 * 194 * Return value: 195 * 0 on success / other on failure 196 **/ 197 static int __init pseries_suspend_init(void) 198 { 199 int rc; 200 201 if (!machine_is(pseries) || !firmware_has_feature(FW_FEATURE_LPAR)) 202 return 0; 203 204 suspend_data.token = rtas_token("ibm,suspend-me"); 205 if (suspend_data.token == RTAS_UNKNOWN_SERVICE) 206 return 0; 207 208 if ((rc = pseries_suspend_sysfs_register(&suspend_dev))) 209 return rc; 210 211 ppc_md.suspend_disable_cpu = pseries_suspend_cpu; 212 suspend_set_ops(&pseries_suspend_ops); 213 return 0; 214 } 215 216 __initcall(pseries_suspend_init); 217