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#include <asm/firmware.h> 25 26 27#define MODULE_VERS "1.0" 28#define MODULE_NAME "pseries_energy" 29 30/* Driver flags */ 31 32static int sysfs_entries; 33 34/* Helper routines */ 35 36/* Helper Routines to convert between drc_index to cpu numbers */ 37 38static u32 cpu_to_drc_index(int cpu) 39{ 40 struct device_node *dn = NULL; 41 const int *indexes; 42 int i; 43 int rc = 1; 44 u32 ret = 0; 45 46 dn = of_find_node_by_path("/cpus"); 47 if (dn == NULL) 48 goto err; 49 indexes = of_get_property(dn, "ibm,drc-indexes", NULL); 50 if (indexes == NULL) 51 goto err_of_node_put; 52 /* Convert logical cpu number to core number */ 53 i = cpu_core_index_of_thread(cpu); 54 /* 55 * The first element indexes[0] is the number of drc_indexes 56 * returned in the list. Hence i+1 will get the drc_index 57 * corresponding to core number i. 58 */ 59 WARN_ON(i > indexes[0]); 60 ret = indexes[i + 1]; 61 rc = 0; 62 63err_of_node_put: 64 of_node_put(dn); 65err: 66 if (rc) 67 printk(KERN_WARNING "cpu_to_drc_index(%d) failed", cpu); 68 return ret; 69} 70 71static int drc_index_to_cpu(u32 drc_index) 72{ 73 struct device_node *dn = NULL; 74 const int *indexes; 75 int i, cpu = 0; 76 int rc = 1; 77 78 dn = of_find_node_by_path("/cpus"); 79 if (dn == NULL) 80 goto err; 81 indexes = of_get_property(dn, "ibm,drc-indexes", NULL); 82 if (indexes == NULL) 83 goto err_of_node_put; 84 /* 85 * First element in the array is the number of drc_indexes 86 * returned. Search through the list to find the matching 87 * drc_index and get the core number 88 */ 89 for (i = 0; i < indexes[0]; i++) { 90 if (indexes[i + 1] == drc_index) 91 break; 92 } 93 /* Convert core number to logical cpu number */ 94 cpu = cpu_first_thread_of_core(i); 95 rc = 0; 96 97err_of_node_put: 98 of_node_put(dn); 99err: 100 if (rc) 101 printk(KERN_WARNING "drc_index_to_cpu(%d) failed", drc_index); 102 return cpu; 103} 104 105/* 106 * pseries hypervisor call H_BEST_ENERGY provides hints to OS on 107 * preferred logical cpus to activate or deactivate for optimized 108 * energy consumption. 109 */ 110 111#define FLAGS_MODE1 0x004E200000080E01UL 112#define FLAGS_MODE2 0x004E200000080401UL 113#define FLAGS_ACTIVATE 0x100 114 115static ssize_t get_best_energy_list(char *page, int activate) 116{ 117 int rc, cnt, i, cpu; 118 unsigned long retbuf[PLPAR_HCALL9_BUFSIZE]; 119 unsigned long flags = 0; 120 u32 *buf_page; 121 char *s = page; 122 123 buf_page = (u32 *) get_zeroed_page(GFP_KERNEL); 124 if (!buf_page) 125 return -ENOMEM; 126 127 flags = FLAGS_MODE1; 128 if (activate) 129 flags |= FLAGS_ACTIVATE; 130 131 rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags, 0, __pa(buf_page), 132 0, 0, 0, 0, 0, 0); 133 if (rc != H_SUCCESS) { 134 free_page((unsigned long) buf_page); 135 return -EINVAL; 136 } 137 138 cnt = retbuf[0]; 139 for (i = 0; i < cnt; i++) { 140 cpu = drc_index_to_cpu(buf_page[2*i+1]); 141 if ((cpu_online(cpu) && !activate) || 142 (!cpu_online(cpu) && activate)) 143 s += sprintf(s, "%d,", cpu); 144 } 145 if (s > page) { /* Something to show */ 146 s--; /* Suppress last comma */ 147 s += sprintf(s, "\n"); 148 } 149 150 free_page((unsigned long) buf_page); 151 return s-page; 152} 153 154static ssize_t get_best_energy_data(struct device *dev, 155 char *page, int activate) 156{ 157 int rc; 158 unsigned long retbuf[PLPAR_HCALL9_BUFSIZE]; 159 unsigned long flags = 0; 160 161 flags = FLAGS_MODE2; 162 if (activate) 163 flags |= FLAGS_ACTIVATE; 164 165 rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags, 166 cpu_to_drc_index(dev->id), 167 0, 0, 0, 0, 0, 0, 0); 168 169 if (rc != H_SUCCESS) 170 return -EINVAL; 171 172 return sprintf(page, "%lu\n", retbuf[1] >> 32); 173} 174 175/* Wrapper functions */ 176 177static ssize_t cpu_activate_hint_list_show(struct device *dev, 178 struct device_attribute *attr, char *page) 179{ 180 return get_best_energy_list(page, 1); 181} 182 183static ssize_t cpu_deactivate_hint_list_show(struct device *dev, 184 struct device_attribute *attr, char *page) 185{ 186 return get_best_energy_list(page, 0); 187} 188 189static ssize_t percpu_activate_hint_show(struct device *dev, 190 struct device_attribute *attr, char *page) 191{ 192 return get_best_energy_data(dev, page, 1); 193} 194 195static ssize_t percpu_deactivate_hint_show(struct device *dev, 196 struct device_attribute *attr, char *page) 197{ 198 return get_best_energy_data(dev, page, 0); 199} 200 201/* 202 * Create sysfs interface: 203 * /sys/devices/system/cpu/pseries_activate_hint_list 204 * /sys/devices/system/cpu/pseries_deactivate_hint_list 205 * Comma separated list of cpus to activate or deactivate 206 * /sys/devices/system/cpu/cpuN/pseries_activate_hint 207 * /sys/devices/system/cpu/cpuN/pseries_deactivate_hint 208 * Per-cpu value of the hint 209 */ 210 211struct device_attribute attr_cpu_activate_hint_list = 212 __ATTR(pseries_activate_hint_list, 0444, 213 cpu_activate_hint_list_show, NULL); 214 215struct device_attribute attr_cpu_deactivate_hint_list = 216 __ATTR(pseries_deactivate_hint_list, 0444, 217 cpu_deactivate_hint_list_show, NULL); 218 219struct device_attribute attr_percpu_activate_hint = 220 __ATTR(pseries_activate_hint, 0444, 221 percpu_activate_hint_show, NULL); 222 223struct device_attribute attr_percpu_deactivate_hint = 224 __ATTR(pseries_deactivate_hint, 0444, 225 percpu_deactivate_hint_show, NULL); 226 227static int __init pseries_energy_init(void) 228{ 229 int cpu, err; 230 struct device *cpu_dev; 231 232 if (!firmware_has_feature(FW_FEATURE_BEST_ENERGY)) { 233 printk(KERN_INFO "Hypercall H_BEST_ENERGY not supported\n"); 234 return 0; 235 } 236 /* Create the sysfs files */ 237 err = device_create_file(cpu_subsys.dev_root, 238 &attr_cpu_activate_hint_list); 239 if (!err) 240 err = device_create_file(cpu_subsys.dev_root, 241 &attr_cpu_deactivate_hint_list); 242 243 if (err) 244 return err; 245 for_each_possible_cpu(cpu) { 246 cpu_dev = get_cpu_device(cpu); 247 err = device_create_file(cpu_dev, 248 &attr_percpu_activate_hint); 249 if (err) 250 break; 251 err = device_create_file(cpu_dev, 252 &attr_percpu_deactivate_hint); 253 if (err) 254 break; 255 } 256 257 if (err) 258 return err; 259 260 sysfs_entries = 1; /* Removed entries on cleanup */ 261 return 0; 262 263} 264 265static void __exit pseries_energy_cleanup(void) 266{ 267 int cpu; 268 struct device *cpu_dev; 269 270 if (!sysfs_entries) 271 return; 272 273 /* Remove the sysfs files */ 274 device_remove_file(cpu_subsys.dev_root, &attr_cpu_activate_hint_list); 275 device_remove_file(cpu_subsys.dev_root, &attr_cpu_deactivate_hint_list); 276 277 for_each_possible_cpu(cpu) { 278 cpu_dev = get_cpu_device(cpu); 279 sysfs_remove_file(&cpu_dev->kobj, 280 &attr_percpu_activate_hint.attr); 281 sysfs_remove_file(&cpu_dev->kobj, 282 &attr_percpu_deactivate_hint.attr); 283 } 284} 285 286module_init(pseries_energy_init); 287module_exit(pseries_energy_cleanup); 288MODULE_DESCRIPTION("Driver for pSeries platform energy management"); 289MODULE_AUTHOR("Vaidyanathan Srinivasan"); 290MODULE_LICENSE("GPL"); 291