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 sys_device suspend_sysdev; 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 * @classdev: sysdev class struct 114 * @attr: class 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 sysdev_class *classdev, 125 struct sysdev_class_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 SYSDEV_CLASS_ATTR(hibernate, S_IWUSR, NULL, store_hibernate); 152 153 static struct sysdev_class suspend_sysdev_class = { 154 .name = "power", 155 }; 156 157 static const struct platform_suspend_ops pseries_suspend_ops = { 158 .valid = suspend_valid_only_mem, 159 .begin = pseries_suspend_begin, 160 .prepare_late = pseries_prepare_late, 161 .enter = pseries_suspend_enter, 162 }; 163 164 /** 165 * pseries_suspend_sysfs_register - Register with sysfs 166 * 167 * Return value: 168 * 0 on success / other on failure 169 **/ 170 static int pseries_suspend_sysfs_register(struct sys_device *sysdev) 171 { 172 int rc; 173 174 if ((rc = sysdev_class_register(&suspend_sysdev_class))) 175 return rc; 176 177 sysdev->id = 0; 178 sysdev->cls = &suspend_sysdev_class; 179 180 if ((rc = sysdev_class_create_file(&suspend_sysdev_class, &attr_hibernate))) 181 goto class_unregister; 182 183 return 0; 184 185 class_unregister: 186 sysdev_class_unregister(&suspend_sysdev_class); 187 return rc; 188 } 189 190 /** 191 * pseries_suspend_init - initcall for pSeries suspend 192 * 193 * Return value: 194 * 0 on success / other on failure 195 **/ 196 static int __init pseries_suspend_init(void) 197 { 198 int rc; 199 200 if (!machine_is(pseries) || !firmware_has_feature(FW_FEATURE_LPAR)) 201 return 0; 202 203 suspend_data.token = rtas_token("ibm,suspend-me"); 204 if (suspend_data.token == RTAS_UNKNOWN_SERVICE) 205 return 0; 206 207 if ((rc = pseries_suspend_sysfs_register(&suspend_sysdev))) 208 return rc; 209 210 ppc_md.suspend_disable_cpu = pseries_suspend_cpu; 211 suspend_set_ops(&pseries_suspend_ops); 212 return 0; 213 } 214 215 __initcall(pseries_suspend_init); 216