Path: blob/master/arch/powerpc/platforms/pseries/pseries_energy.c
10818 views
/*1* POWER platform energy management driver2* Copyright (C) 2010 IBM Corporation3*4* This program is free software; you can redistribute it and/or5* modify it under the terms of the GNU General Public License6* version 2 as published by the Free Software Foundation.7*8* This pseries platform device driver provides access to9* platform energy management capabilities.10*/1112#include <linux/module.h>13#include <linux/types.h>14#include <linux/errno.h>15#include <linux/init.h>16#include <linux/seq_file.h>17#include <linux/sysdev.h>18#include <linux/cpu.h>19#include <linux/of.h>20#include <asm/cputhreads.h>21#include <asm/page.h>22#include <asm/hvcall.h>232425#define MODULE_VERS "1.0"26#define MODULE_NAME "pseries_energy"2728/* Driver flags */2930static int sysfs_entries;3132/* Helper routines */3334/*35* Routine to detect firmware support for hcall36* return 1 if H_BEST_ENERGY is supported37* else return 038*/3940static int check_for_h_best_energy(void)41{42struct device_node *rtas = NULL;43const char *hypertas, *s;44int length;45int rc = 0;4647rtas = of_find_node_by_path("/rtas");48if (!rtas)49return 0;5051hypertas = of_get_property(rtas, "ibm,hypertas-functions", &length);52if (!hypertas) {53of_node_put(rtas);54return 0;55}5657/* hypertas will have list of strings with hcall names */58for (s = hypertas; s < hypertas + length; s += strlen(s) + 1) {59if (!strncmp("hcall-best-energy-1", s, 19)) {60rc = 1; /* Found the string */61break;62}63}64of_node_put(rtas);65return rc;66}6768/* Helper Routines to convert between drc_index to cpu numbers */6970static u32 cpu_to_drc_index(int cpu)71{72struct device_node *dn = NULL;73const int *indexes;74int i;75int rc = 1;76u32 ret = 0;7778dn = of_find_node_by_path("/cpus");79if (dn == NULL)80goto err;81indexes = of_get_property(dn, "ibm,drc-indexes", NULL);82if (indexes == NULL)83goto err_of_node_put;84/* Convert logical cpu number to core number */85i = cpu_core_index_of_thread(cpu);86/*87* The first element indexes[0] is the number of drc_indexes88* returned in the list. Hence i+1 will get the drc_index89* corresponding to core number i.90*/91WARN_ON(i > indexes[0]);92ret = indexes[i + 1];93rc = 0;9495err_of_node_put:96of_node_put(dn);97err:98if (rc)99printk(KERN_WARNING "cpu_to_drc_index(%d) failed", cpu);100return ret;101}102103static int drc_index_to_cpu(u32 drc_index)104{105struct device_node *dn = NULL;106const int *indexes;107int i, cpu = 0;108int rc = 1;109110dn = of_find_node_by_path("/cpus");111if (dn == NULL)112goto err;113indexes = of_get_property(dn, "ibm,drc-indexes", NULL);114if (indexes == NULL)115goto err_of_node_put;116/*117* First element in the array is the number of drc_indexes118* returned. Search through the list to find the matching119* drc_index and get the core number120*/121for (i = 0; i < indexes[0]; i++) {122if (indexes[i + 1] == drc_index)123break;124}125/* Convert core number to logical cpu number */126cpu = cpu_first_thread_of_core(i);127rc = 0;128129err_of_node_put:130of_node_put(dn);131err:132if (rc)133printk(KERN_WARNING "drc_index_to_cpu(%d) failed", drc_index);134return cpu;135}136137/*138* pseries hypervisor call H_BEST_ENERGY provides hints to OS on139* preferred logical cpus to activate or deactivate for optimized140* energy consumption.141*/142143#define FLAGS_MODE1 0x004E200000080E01144#define FLAGS_MODE2 0x004E200000080401145#define FLAGS_ACTIVATE 0x100146147static ssize_t get_best_energy_list(char *page, int activate)148{149int rc, cnt, i, cpu;150unsigned long retbuf[PLPAR_HCALL9_BUFSIZE];151unsigned long flags = 0;152u32 *buf_page;153char *s = page;154155buf_page = (u32 *) get_zeroed_page(GFP_KERNEL);156if (!buf_page)157return -ENOMEM;158159flags = FLAGS_MODE1;160if (activate)161flags |= FLAGS_ACTIVATE;162163rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags, 0, __pa(buf_page),1640, 0, 0, 0, 0, 0);165if (rc != H_SUCCESS) {166free_page((unsigned long) buf_page);167return -EINVAL;168}169170cnt = retbuf[0];171for (i = 0; i < cnt; i++) {172cpu = drc_index_to_cpu(buf_page[2*i+1]);173if ((cpu_online(cpu) && !activate) ||174(!cpu_online(cpu) && activate))175s += sprintf(s, "%d,", cpu);176}177if (s > page) { /* Something to show */178s--; /* Suppress last comma */179s += sprintf(s, "\n");180}181182free_page((unsigned long) buf_page);183return s-page;184}185186static ssize_t get_best_energy_data(struct sys_device *dev,187char *page, int activate)188{189int rc;190unsigned long retbuf[PLPAR_HCALL9_BUFSIZE];191unsigned long flags = 0;192193flags = FLAGS_MODE2;194if (activate)195flags |= FLAGS_ACTIVATE;196197rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags,198cpu_to_drc_index(dev->id),1990, 0, 0, 0, 0, 0, 0);200201if (rc != H_SUCCESS)202return -EINVAL;203204return sprintf(page, "%lu\n", retbuf[1] >> 32);205}206207/* Wrapper functions */208209static ssize_t cpu_activate_hint_list_show(struct sysdev_class *class,210struct sysdev_class_attribute *attr, char *page)211{212return get_best_energy_list(page, 1);213}214215static ssize_t cpu_deactivate_hint_list_show(struct sysdev_class *class,216struct sysdev_class_attribute *attr, char *page)217{218return get_best_energy_list(page, 0);219}220221static ssize_t percpu_activate_hint_show(struct sys_device *dev,222struct sysdev_attribute *attr, char *page)223{224return get_best_energy_data(dev, page, 1);225}226227static ssize_t percpu_deactivate_hint_show(struct sys_device *dev,228struct sysdev_attribute *attr, char *page)229{230return get_best_energy_data(dev, page, 0);231}232233/*234* Create sysfs interface:235* /sys/devices/system/cpu/pseries_activate_hint_list236* /sys/devices/system/cpu/pseries_deactivate_hint_list237* Comma separated list of cpus to activate or deactivate238* /sys/devices/system/cpu/cpuN/pseries_activate_hint239* /sys/devices/system/cpu/cpuN/pseries_deactivate_hint240* Per-cpu value of the hint241*/242243struct sysdev_class_attribute attr_cpu_activate_hint_list =244_SYSDEV_CLASS_ATTR(pseries_activate_hint_list, 0444,245cpu_activate_hint_list_show, NULL);246247struct sysdev_class_attribute attr_cpu_deactivate_hint_list =248_SYSDEV_CLASS_ATTR(pseries_deactivate_hint_list, 0444,249cpu_deactivate_hint_list_show, NULL);250251struct sysdev_attribute attr_percpu_activate_hint =252_SYSDEV_ATTR(pseries_activate_hint, 0444,253percpu_activate_hint_show, NULL);254255struct sysdev_attribute attr_percpu_deactivate_hint =256_SYSDEV_ATTR(pseries_deactivate_hint, 0444,257percpu_deactivate_hint_show, NULL);258259static int __init pseries_energy_init(void)260{261int cpu, err;262struct sys_device *cpu_sys_dev;263264if (!check_for_h_best_energy()) {265printk(KERN_INFO "Hypercall H_BEST_ENERGY not supported\n");266return 0;267}268/* Create the sysfs files */269err = sysfs_create_file(&cpu_sysdev_class.kset.kobj,270&attr_cpu_activate_hint_list.attr);271if (!err)272err = sysfs_create_file(&cpu_sysdev_class.kset.kobj,273&attr_cpu_deactivate_hint_list.attr);274275if (err)276return err;277for_each_possible_cpu(cpu) {278cpu_sys_dev = get_cpu_sysdev(cpu);279err = sysfs_create_file(&cpu_sys_dev->kobj,280&attr_percpu_activate_hint.attr);281if (err)282break;283err = sysfs_create_file(&cpu_sys_dev->kobj,284&attr_percpu_deactivate_hint.attr);285if (err)286break;287}288289if (err)290return err;291292sysfs_entries = 1; /* Removed entries on cleanup */293return 0;294295}296297static void __exit pseries_energy_cleanup(void)298{299int cpu;300struct sys_device *cpu_sys_dev;301302if (!sysfs_entries)303return;304305/* Remove the sysfs files */306sysfs_remove_file(&cpu_sysdev_class.kset.kobj,307&attr_cpu_activate_hint_list.attr);308309sysfs_remove_file(&cpu_sysdev_class.kset.kobj,310&attr_cpu_deactivate_hint_list.attr);311312for_each_possible_cpu(cpu) {313cpu_sys_dev = get_cpu_sysdev(cpu);314sysfs_remove_file(&cpu_sys_dev->kobj,315&attr_percpu_activate_hint.attr);316sysfs_remove_file(&cpu_sys_dev->kobj,317&attr_percpu_deactivate_hint.attr);318}319}320321module_init(pseries_energy_init);322module_exit(pseries_energy_cleanup);323MODULE_DESCRIPTION("Driver for pSeries platform energy management");324MODULE_AUTHOR("Vaidyanathan Srinivasan");325MODULE_LICENSE("GPL");326327328