1 /* 2 * POWER platform energy management driver 3 * Copyright (C) 2010 IBM Corporation 4 * 5 * This program is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU General Public License 7 * version 2 as published by the Free Software Foundation. 8 * 9 * This pseries platform device driver provides access to 10 * platform energy management capabilities. 11 */ 12 13 #include <linux/module.h> 14 #include <linux/types.h> 15 #include <linux/errno.h> 16 #include <linux/init.h> 17 #include <linux/seq_file.h> 18 #include <linux/device.h> 19 #include <linux/cpu.h> 20 #include <linux/of.h> 21 #include <asm/cputhreads.h> 22 #include <asm/page.h> 23 #include <asm/hvcall.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 /* 36 * Routine to detect firmware support for hcall 37 * return 1 if H_BEST_ENERGY is supported 38 * else return 0 39 */ 40 41 static int check_for_h_best_energy(void) 42 { 43 struct device_node *rtas = NULL; 44 const char *hypertas, *s; 45 int length; 46 int rc = 0; 47 48 rtas = of_find_node_by_path("/rtas"); 49 if (!rtas) 50 return 0; 51 52 hypertas = of_get_property(rtas, "ibm,hypertas-functions", &length); 53 if (!hypertas) { 54 of_node_put(rtas); 55 return 0; 56 } 57 58 /* hypertas will have list of strings with hcall names */ 59 for (s = hypertas; s < hypertas + length; s += strlen(s) + 1) { 60 if (!strncmp("hcall-best-energy-1", s, 19)) { 61 rc = 1; /* Found the string */ 62 break; 63 } 64 } 65 of_node_put(rtas); 66 return rc; 67 } 68 69 /* Helper Routines to convert between drc_index to cpu numbers */ 70 71 static u32 cpu_to_drc_index(int cpu) 72 { 73 struct device_node *dn = NULL; 74 const int *indexes; 75 int i; 76 int rc = 1; 77 u32 ret = 0; 78 79 dn = of_find_node_by_path("/cpus"); 80 if (dn == NULL) 81 goto err; 82 indexes = of_get_property(dn, "ibm,drc-indexes", NULL); 83 if (indexes == NULL) 84 goto err_of_node_put; 85 /* Convert logical cpu number to core number */ 86 i = cpu_core_index_of_thread(cpu); 87 /* 88 * The first element indexes[0] is the number of drc_indexes 89 * returned in the list. Hence i+1 will get the drc_index 90 * corresponding to core number i. 91 */ 92 WARN_ON(i > indexes[0]); 93 ret = indexes[i + 1]; 94 rc = 0; 95 96 err_of_node_put: 97 of_node_put(dn); 98 err: 99 if (rc) 100 printk(KERN_WARNING "cpu_to_drc_index(%d) failed", cpu); 101 return ret; 102 } 103 104 static int drc_index_to_cpu(u32 drc_index) 105 { 106 struct device_node *dn = NULL; 107 const int *indexes; 108 int i, cpu = 0; 109 int rc = 1; 110 111 dn = of_find_node_by_path("/cpus"); 112 if (dn == NULL) 113 goto err; 114 indexes = of_get_property(dn, "ibm,drc-indexes", NULL); 115 if (indexes == NULL) 116 goto err_of_node_put; 117 /* 118 * First element in the array is the number of drc_indexes 119 * returned. Search through the list to find the matching 120 * drc_index and get the core number 121 */ 122 for (i = 0; i < indexes[0]; i++) { 123 if (indexes[i + 1] == drc_index) 124 break; 125 } 126 /* Convert core number to logical cpu number */ 127 cpu = cpu_first_thread_of_core(i); 128 rc = 0; 129 130 err_of_node_put: 131 of_node_put(dn); 132 err: 133 if (rc) 134 printk(KERN_WARNING "drc_index_to_cpu(%d) failed", drc_index); 135 return cpu; 136 } 137 138 /* 139 * pseries hypervisor call H_BEST_ENERGY provides hints to OS on 140 * preferred logical cpus to activate or deactivate for optimized 141 * energy consumption. 142 */ 143 144 #define FLAGS_MODE1 0x004E200000080E01 145 #define FLAGS_MODE2 0x004E200000080401 146 #define FLAGS_ACTIVATE 0x100 147 148 static ssize_t get_best_energy_list(char *page, int activate) 149 { 150 int rc, cnt, i, cpu; 151 unsigned long retbuf[PLPAR_HCALL9_BUFSIZE]; 152 unsigned long flags = 0; 153 u32 *buf_page; 154 char *s = page; 155 156 buf_page = (u32 *) get_zeroed_page(GFP_KERNEL); 157 if (!buf_page) 158 return -ENOMEM; 159 160 flags = FLAGS_MODE1; 161 if (activate) 162 flags |= FLAGS_ACTIVATE; 163 164 rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags, 0, __pa(buf_page), 165 0, 0, 0, 0, 0, 0); 166 if (rc != H_SUCCESS) { 167 free_page((unsigned long) buf_page); 168 return -EINVAL; 169 } 170 171 cnt = retbuf[0]; 172 for (i = 0; i < cnt; i++) { 173 cpu = drc_index_to_cpu(buf_page[2*i+1]); 174 if ((cpu_online(cpu) && !activate) || 175 (!cpu_online(cpu) && activate)) 176 s += sprintf(s, "%d,", cpu); 177 } 178 if (s > page) { /* Something to show */ 179 s--; /* Suppress last comma */ 180 s += sprintf(s, "\n"); 181 } 182 183 free_page((unsigned long) buf_page); 184 return s-page; 185 } 186 187 static ssize_t get_best_energy_data(struct device *dev, 188 char *page, int activate) 189 { 190 int rc; 191 unsigned long retbuf[PLPAR_HCALL9_BUFSIZE]; 192 unsigned long flags = 0; 193 194 flags = FLAGS_MODE2; 195 if (activate) 196 flags |= FLAGS_ACTIVATE; 197 198 rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags, 199 cpu_to_drc_index(dev->id), 200 0, 0, 0, 0, 0, 0, 0); 201 202 if (rc != H_SUCCESS) 203 return -EINVAL; 204 205 return sprintf(page, "%lu\n", retbuf[1] >> 32); 206 } 207 208 /* Wrapper functions */ 209 210 static ssize_t cpu_activate_hint_list_show(struct device *dev, 211 struct device_attribute *attr, char *page) 212 { 213 return get_best_energy_list(page, 1); 214 } 215 216 static ssize_t cpu_deactivate_hint_list_show(struct device *dev, 217 struct device_attribute *attr, char *page) 218 { 219 return get_best_energy_list(page, 0); 220 } 221 222 static ssize_t percpu_activate_hint_show(struct device *dev, 223 struct device_attribute *attr, char *page) 224 { 225 return get_best_energy_data(dev, page, 1); 226 } 227 228 static ssize_t percpu_deactivate_hint_show(struct device *dev, 229 struct device_attribute *attr, char *page) 230 { 231 return get_best_energy_data(dev, page, 0); 232 } 233 234 /* 235 * Create sysfs interface: 236 * /sys/devices/system/cpu/pseries_activate_hint_list 237 * /sys/devices/system/cpu/pseries_deactivate_hint_list 238 * Comma separated list of cpus to activate or deactivate 239 * /sys/devices/system/cpu/cpuN/pseries_activate_hint 240 * /sys/devices/system/cpu/cpuN/pseries_deactivate_hint 241 * Per-cpu value of the hint 242 */ 243 244 struct device_attribute attr_cpu_activate_hint_list = 245 __ATTR(pseries_activate_hint_list, 0444, 246 cpu_activate_hint_list_show, NULL); 247 248 struct device_attribute attr_cpu_deactivate_hint_list = 249 __ATTR(pseries_deactivate_hint_list, 0444, 250 cpu_deactivate_hint_list_show, NULL); 251 252 struct device_attribute attr_percpu_activate_hint = 253 __ATTR(pseries_activate_hint, 0444, 254 percpu_activate_hint_show, NULL); 255 256 struct device_attribute attr_percpu_deactivate_hint = 257 __ATTR(pseries_deactivate_hint, 0444, 258 percpu_deactivate_hint_show, NULL); 259 260 static int __init pseries_energy_init(void) 261 { 262 int cpu, err; 263 struct device *cpu_dev; 264 265 if (!check_for_h_best_energy()) { 266 printk(KERN_INFO "Hypercall H_BEST_ENERGY not supported\n"); 267 return 0; 268 } 269 /* Create the sysfs files */ 270 err = device_create_file(cpu_subsys.dev_root, 271 &attr_cpu_activate_hint_list); 272 if (!err) 273 err = device_create_file(cpu_subsys.dev_root, 274 &attr_cpu_deactivate_hint_list); 275 276 if (err) 277 return err; 278 for_each_possible_cpu(cpu) { 279 cpu_dev = get_cpu_device(cpu); 280 err = device_create_file(cpu_dev, 281 &attr_percpu_activate_hint); 282 if (err) 283 break; 284 err = device_create_file(cpu_dev, 285 &attr_percpu_deactivate_hint); 286 if (err) 287 break; 288 } 289 290 if (err) 291 return err; 292 293 sysfs_entries = 1; /* Removed entries on cleanup */ 294 return 0; 295 296 } 297 298 static void __exit pseries_energy_cleanup(void) 299 { 300 int cpu; 301 struct device *cpu_dev; 302 303 if (!sysfs_entries) 304 return; 305 306 /* Remove the sysfs files */ 307 device_remove_file(cpu_subsys.dev_root, &attr_cpu_activate_hint_list); 308 device_remove_file(cpu_subsys.dev_root, &attr_cpu_deactivate_hint_list); 309 310 for_each_possible_cpu(cpu) { 311 cpu_dev = get_cpu_device(cpu); 312 sysfs_remove_file(&cpu_dev->kobj, 313 &attr_percpu_activate_hint.attr); 314 sysfs_remove_file(&cpu_dev->kobj, 315 &attr_percpu_deactivate_hint.attr); 316 } 317 } 318 319 module_init(pseries_energy_init); 320 module_exit(pseries_energy_cleanup); 321 MODULE_DESCRIPTION("Driver for pSeries platform energy management"); 322 MODULE_AUTHOR("Vaidyanathan Srinivasan"); 323 MODULE_LICENSE("GPL"); 324