1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * POWER platform energy management driver 4 * Copyright (C) 2010 IBM Corporation 5 * 6 * This pseries platform device driver provides access to 7 * platform energy management capabilities. 8 */ 9 10 #include <linux/module.h> 11 #include <linux/types.h> 12 #include <linux/errno.h> 13 #include <linux/init.h> 14 #include <linux/seq_file.h> 15 #include <linux/sysfs.h> 16 #include <linux/device.h> 17 #include <linux/cpu.h> 18 #include <linux/of.h> 19 #include <asm/cputhreads.h> 20 #include <asm/page.h> 21 #include <asm/hvcall.h> 22 #include <asm/firmware.h> 23 #include <asm/prom.h> 24 25 26 #define MODULE_VERS "1.0" 27 #define MODULE_NAME "pseries_energy" 28 29 /* Driver flags */ 30 31 static int sysfs_entries; 32 33 /* Helper routines */ 34 35 /* Helper Routines to convert between drc_index to cpu numbers */ 36 37 static u32 cpu_to_drc_index(int cpu) 38 { 39 struct device_node *dn = NULL; 40 struct property *info; 41 int thread_index; 42 int rc = 1; 43 u32 ret = 0; 44 45 dn = of_find_node_by_path("/cpus"); 46 if (dn == NULL) 47 goto err; 48 49 /* Convert logical cpu number to core number */ 50 thread_index = cpu_core_index_of_thread(cpu); 51 52 info = of_find_property(dn, "ibm,drc-info", NULL); 53 if (info) { 54 struct of_drc_info drc; 55 int j; 56 u32 num_set_entries; 57 const __be32 *value; 58 59 value = of_prop_next_u32(info, NULL, &num_set_entries); 60 if (!value) 61 goto err_of_node_put; 62 else 63 value++; 64 65 for (j = 0; j < num_set_entries; j++) { 66 67 of_read_drc_info_cell(&info, &value, &drc); 68 if (strncmp(drc.drc_type, "CPU", 3)) 69 goto err; 70 71 if (thread_index < drc.last_drc_index) 72 break; 73 } 74 75 ret = drc.drc_index_start + (thread_index * drc.sequential_inc); 76 } else { 77 u32 nr_drc_indexes, thread_drc_index; 78 79 /* 80 * The first element of ibm,drc-indexes array is the 81 * number of drc_indexes returned in the list. Hence 82 * thread_index+1 will get the drc_index corresponding 83 * to core number thread_index. 84 */ 85 rc = of_property_read_u32_index(dn, "ibm,drc-indexes", 86 0, &nr_drc_indexes); 87 if (rc) 88 goto err_of_node_put; 89 90 WARN_ON_ONCE(thread_index > nr_drc_indexes); 91 rc = of_property_read_u32_index(dn, "ibm,drc-indexes", 92 thread_index + 1, 93 &thread_drc_index); 94 if (rc) 95 goto err_of_node_put; 96 97 ret = thread_drc_index; 98 } 99 100 rc = 0; 101 102 err_of_node_put: 103 of_node_put(dn); 104 err: 105 if (rc) 106 printk(KERN_WARNING "cpu_to_drc_index(%d) failed", cpu); 107 return ret; 108 } 109 110 static int drc_index_to_cpu(u32 drc_index) 111 { 112 struct device_node *dn = NULL; 113 struct property *info; 114 const int *indexes; 115 int thread_index = 0, cpu = 0; 116 int rc = 1; 117 118 dn = of_find_node_by_path("/cpus"); 119 if (dn == NULL) 120 goto err; 121 info = of_find_property(dn, "ibm,drc-info", NULL); 122 if (info) { 123 struct of_drc_info drc; 124 int j; 125 u32 num_set_entries; 126 const __be32 *value; 127 128 value = of_prop_next_u32(info, NULL, &num_set_entries); 129 if (!value) 130 goto err_of_node_put; 131 else 132 value++; 133 134 for (j = 0; j < num_set_entries; j++) { 135 136 of_read_drc_info_cell(&info, &value, &drc); 137 if (strncmp(drc.drc_type, "CPU", 3)) 138 goto err; 139 140 if (drc_index > drc.last_drc_index) { 141 cpu += drc.num_sequential_elems; 142 continue; 143 } 144 cpu += ((drc_index - drc.drc_index_start) / 145 drc.sequential_inc); 146 147 thread_index = cpu_first_thread_of_core(cpu); 148 rc = 0; 149 break; 150 } 151 } else { 152 unsigned long int i; 153 154 indexes = of_get_property(dn, "ibm,drc-indexes", NULL); 155 if (indexes == NULL) 156 goto err_of_node_put; 157 /* 158 * First element in the array is the number of drc_indexes 159 * returned. Search through the list to find the matching 160 * drc_index and get the core number 161 */ 162 for (i = 0; i < indexes[0]; i++) { 163 if (indexes[i + 1] == drc_index) 164 break; 165 } 166 /* Convert core number to logical cpu number */ 167 thread_index = cpu_first_thread_of_core(i); 168 rc = 0; 169 } 170 171 err_of_node_put: 172 of_node_put(dn); 173 err: 174 if (rc) 175 printk(KERN_WARNING "drc_index_to_cpu(%d) failed", drc_index); 176 return thread_index; 177 } 178 179 /* 180 * pseries hypervisor call H_BEST_ENERGY provides hints to OS on 181 * preferred logical cpus to activate or deactivate for optimized 182 * energy consumption. 183 */ 184 185 #define FLAGS_MODE1 0x004E200000080E01UL 186 #define FLAGS_MODE2 0x004E200000080401UL 187 #define FLAGS_ACTIVATE 0x100 188 189 static ssize_t get_best_energy_list(char *page, int activate) 190 { 191 int rc, cnt, i, cpu; 192 unsigned long retbuf[PLPAR_HCALL9_BUFSIZE]; 193 unsigned long flags = 0; 194 u32 *buf_page; 195 char *s = page; 196 197 buf_page = (u32 *) get_zeroed_page(GFP_KERNEL); 198 if (!buf_page) 199 return -ENOMEM; 200 201 flags = FLAGS_MODE1; 202 if (activate) 203 flags |= FLAGS_ACTIVATE; 204 205 rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags, 0, __pa(buf_page), 206 0, 0, 0, 0, 0, 0); 207 if (rc != H_SUCCESS) { 208 free_page((unsigned long) buf_page); 209 return -EINVAL; 210 } 211 212 cnt = retbuf[0]; 213 for (i = 0; i < cnt; i++) { 214 cpu = drc_index_to_cpu(buf_page[2*i+1]); 215 if ((cpu_online(cpu) && !activate) || 216 (!cpu_online(cpu) && activate)) 217 s += sprintf(s, "%d,", cpu); 218 } 219 if (s > page) { /* Something to show */ 220 s--; /* Suppress last comma */ 221 s += sprintf(s, "\n"); 222 } 223 224 free_page((unsigned long) buf_page); 225 return s-page; 226 } 227 228 static ssize_t get_best_energy_data(struct device *dev, 229 char *page, int activate) 230 { 231 int rc; 232 unsigned long retbuf[PLPAR_HCALL9_BUFSIZE]; 233 unsigned long flags = 0; 234 235 flags = FLAGS_MODE2; 236 if (activate) 237 flags |= FLAGS_ACTIVATE; 238 239 rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags, 240 cpu_to_drc_index(dev->id), 241 0, 0, 0, 0, 0, 0, 0); 242 243 if (rc != H_SUCCESS) 244 return -EINVAL; 245 246 return sysfs_emit(page, "%lu\n", retbuf[1] >> 32); 247 } 248 249 /* Wrapper functions */ 250 251 static ssize_t cpu_activate_hint_list_show(struct device *dev, 252 struct device_attribute *attr, char *page) 253 { 254 return get_best_energy_list(page, 1); 255 } 256 257 static ssize_t cpu_deactivate_hint_list_show(struct device *dev, 258 struct device_attribute *attr, char *page) 259 { 260 return get_best_energy_list(page, 0); 261 } 262 263 static ssize_t percpu_activate_hint_show(struct device *dev, 264 struct device_attribute *attr, char *page) 265 { 266 return get_best_energy_data(dev, page, 1); 267 } 268 269 static ssize_t percpu_deactivate_hint_show(struct device *dev, 270 struct device_attribute *attr, char *page) 271 { 272 return get_best_energy_data(dev, page, 0); 273 } 274 275 /* 276 * Create sysfs interface: 277 * /sys/devices/system/cpu/pseries_activate_hint_list 278 * /sys/devices/system/cpu/pseries_deactivate_hint_list 279 * Comma separated list of cpus to activate or deactivate 280 * /sys/devices/system/cpu/cpuN/pseries_activate_hint 281 * /sys/devices/system/cpu/cpuN/pseries_deactivate_hint 282 * Per-cpu value of the hint 283 */ 284 285 static struct device_attribute attr_cpu_activate_hint_list = 286 __ATTR(pseries_activate_hint_list, 0444, 287 cpu_activate_hint_list_show, NULL); 288 289 static struct device_attribute attr_cpu_deactivate_hint_list = 290 __ATTR(pseries_deactivate_hint_list, 0444, 291 cpu_deactivate_hint_list_show, NULL); 292 293 static struct device_attribute attr_percpu_activate_hint = 294 __ATTR(pseries_activate_hint, 0444, 295 percpu_activate_hint_show, NULL); 296 297 static struct device_attribute attr_percpu_deactivate_hint = 298 __ATTR(pseries_deactivate_hint, 0444, 299 percpu_deactivate_hint_show, NULL); 300 301 static int __init pseries_energy_init(void) 302 { 303 int cpu, err; 304 struct device *cpu_dev, *dev_root; 305 306 if (!firmware_has_feature(FW_FEATURE_BEST_ENERGY)) 307 return 0; /* H_BEST_ENERGY hcall not supported */ 308 309 /* Create the sysfs files */ 310 dev_root = bus_get_dev_root(&cpu_subsys); 311 if (dev_root) { 312 err = device_create_file(dev_root, &attr_cpu_activate_hint_list); 313 if (!err) 314 err = device_create_file(dev_root, &attr_cpu_deactivate_hint_list); 315 put_device(dev_root); 316 if (err) 317 return err; 318 } 319 320 for_each_possible_cpu(cpu) { 321 cpu_dev = get_cpu_device(cpu); 322 err = device_create_file(cpu_dev, 323 &attr_percpu_activate_hint); 324 if (err) 325 break; 326 err = device_create_file(cpu_dev, 327 &attr_percpu_deactivate_hint); 328 if (err) 329 break; 330 } 331 332 if (err) 333 return err; 334 335 sysfs_entries = 1; /* Removed entries on cleanup */ 336 return 0; 337 338 } 339 340 static void __exit pseries_energy_cleanup(void) 341 { 342 int cpu; 343 struct device *cpu_dev, *dev_root; 344 345 if (!sysfs_entries) 346 return; 347 348 /* Remove the sysfs files */ 349 dev_root = bus_get_dev_root(&cpu_subsys); 350 if (dev_root) { 351 device_remove_file(dev_root, &attr_cpu_activate_hint_list); 352 device_remove_file(dev_root, &attr_cpu_deactivate_hint_list); 353 put_device(dev_root); 354 } 355 356 for_each_possible_cpu(cpu) { 357 cpu_dev = get_cpu_device(cpu); 358 sysfs_remove_file(&cpu_dev->kobj, 359 &attr_percpu_activate_hint.attr); 360 sysfs_remove_file(&cpu_dev->kobj, 361 &attr_percpu_deactivate_hint.attr); 362 } 363 } 364 365 module_init(pseries_energy_init); 366 module_exit(pseries_energy_cleanup); 367 MODULE_DESCRIPTION("Driver for pSeries platform energy management"); 368 MODULE_AUTHOR("Vaidyanathan Srinivasan"); 369 MODULE_LICENSE("GPL"); 370