1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Copyright (C) 2010 Brian King IBM Corporation 4 */ 5 6 #include <linux/cpu.h> 7 #include <linux/delay.h> 8 #include <linux/suspend.h> 9 #include <linux/stat.h> 10 #include <asm/firmware.h> 11 #include <asm/hvcall.h> 12 #include <asm/machdep.h> 13 #include <asm/mmu.h> 14 #include <asm/rtas.h> 15 #include <asm/topology.h> 16 #include "pseries.h" 17 18 static struct device suspend_dev; 19 20 /** 21 * pseries_suspend_begin - First phase of hibernation 22 * 23 * Check to ensure we are in a valid state to hibernate 24 * 25 * Return value: 26 * 0 on success / other on failure 27 **/ 28 static int pseries_suspend_begin(u64 stream_id) 29 { 30 long vasi_state, rc; 31 unsigned long retbuf[PLPAR_HCALL_BUFSIZE]; 32 33 /* Make sure the state is valid */ 34 rc = plpar_hcall(H_VASI_STATE, retbuf, stream_id); 35 36 vasi_state = retbuf[0]; 37 38 if (rc) { 39 pr_err("pseries_suspend_begin: vasi_state returned %ld\n",rc); 40 return rc; 41 } else if (vasi_state == H_VASI_ENABLED) { 42 return -EAGAIN; 43 } else if (vasi_state != H_VASI_SUSPENDING) { 44 pr_err("pseries_suspend_begin: vasi_state returned state %ld\n", 45 vasi_state); 46 return -EIO; 47 } 48 return 0; 49 } 50 51 /** 52 * pseries_suspend_enter - Final phase of hibernation 53 * 54 * Return value: 55 * 0 on success / other on failure 56 **/ 57 static int pseries_suspend_enter(suspend_state_t state) 58 { 59 return rtas_ibm_suspend_me(NULL); 60 } 61 62 /** 63 * store_hibernate - Initiate partition hibernation 64 * @dev: subsys root device 65 * @attr: device attribute struct 66 * @buf: buffer 67 * @count: buffer size 68 * 69 * Write the stream ID received from the HMC to this file 70 * to trigger hibernating the partition 71 * 72 * Return value: 73 * number of bytes printed to buffer / other on failure 74 **/ 75 static ssize_t store_hibernate(struct device *dev, 76 struct device_attribute *attr, 77 const char *buf, size_t count) 78 { 79 u64 stream_id; 80 int rc; 81 82 if (!capable(CAP_SYS_ADMIN)) 83 return -EPERM; 84 85 stream_id = simple_strtoul(buf, NULL, 16); 86 87 do { 88 rc = pseries_suspend_begin(stream_id); 89 if (rc == -EAGAIN) 90 ssleep(1); 91 } while (rc == -EAGAIN); 92 93 if (!rc) 94 rc = pm_suspend(PM_SUSPEND_MEM); 95 96 if (!rc) { 97 rc = count; 98 post_mobility_fixup(); 99 } 100 101 102 return rc; 103 } 104 105 #define USER_DT_UPDATE 0 106 #define KERN_DT_UPDATE 1 107 108 /** 109 * show_hibernate - Report device tree update responsibilty 110 * @dev: subsys root device 111 * @attr: device attribute struct 112 * @buf: buffer 113 * 114 * Report whether a device tree update is performed by the kernel after a 115 * resume, or if drmgr must coordinate the update from user space. 116 * 117 * Return value: 118 * 0 if drmgr is to initiate update, and 1 otherwise 119 **/ 120 static ssize_t show_hibernate(struct device *dev, 121 struct device_attribute *attr, 122 char *buf) 123 { 124 return sprintf(buf, "%d\n", KERN_DT_UPDATE); 125 } 126 127 static DEVICE_ATTR(hibernate, 0644, show_hibernate, store_hibernate); 128 129 static struct bus_type suspend_subsys = { 130 .name = "power", 131 .dev_name = "power", 132 }; 133 134 static const struct platform_suspend_ops pseries_suspend_ops = { 135 .valid = suspend_valid_only_mem, 136 .enter = pseries_suspend_enter, 137 }; 138 139 /** 140 * pseries_suspend_sysfs_register - Register with sysfs 141 * 142 * Return value: 143 * 0 on success / other on failure 144 **/ 145 static int pseries_suspend_sysfs_register(struct device *dev) 146 { 147 struct device *dev_root; 148 int rc; 149 150 if ((rc = subsys_system_register(&suspend_subsys, NULL))) 151 return rc; 152 153 dev->id = 0; 154 dev->bus = &suspend_subsys; 155 156 dev_root = bus_get_dev_root(&suspend_subsys); 157 if (dev_root) { 158 rc = device_create_file(dev_root, &dev_attr_hibernate); 159 put_device(dev_root); 160 if (rc) 161 goto subsys_unregister; 162 } 163 164 return 0; 165 166 subsys_unregister: 167 bus_unregister(&suspend_subsys); 168 return rc; 169 } 170 171 /** 172 * pseries_suspend_init - initcall for pSeries suspend 173 * 174 * Return value: 175 * 0 on success / other on failure 176 **/ 177 static int __init pseries_suspend_init(void) 178 { 179 int rc; 180 181 if (!firmware_has_feature(FW_FEATURE_LPAR)) 182 return 0; 183 184 if ((rc = pseries_suspend_sysfs_register(&suspend_dev))) 185 return rc; 186 187 suspend_set_ops(&pseries_suspend_ops); 188 return 0; 189 } 190 machine_device_initcall(pseries, pseries_suspend_init); 191
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.