linux/arch/powerpc/platforms/pseries/pseries_energy.c
<<
>>
Prefs
   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/sysdev.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
  31static 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
  41static 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
  71static 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
  96err_of_node_put:
  97        of_node_put(dn);
  98err:
  99        if (rc)
 100                printk(KERN_WARNING "cpu_to_drc_index(%d) failed", cpu);
 101        return ret;
 102}
 103
 104static 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
 130err_of_node_put:
 131        of_node_put(dn);
 132err:
 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
 148static 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
 187static ssize_t get_best_energy_data(struct sys_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
 210static ssize_t cpu_activate_hint_list_show(struct sysdev_class *class,
 211                        struct sysdev_class_attribute *attr, char *page)
 212{
 213        return get_best_energy_list(page, 1);
 214}
 215
 216static ssize_t cpu_deactivate_hint_list_show(struct sysdev_class *class,
 217                        struct sysdev_class_attribute *attr, char *page)
 218{
 219        return get_best_energy_list(page, 0);
 220}
 221
 222static ssize_t percpu_activate_hint_show(struct sys_device *dev,
 223                        struct sysdev_attribute *attr, char *page)
 224{
 225        return get_best_energy_data(dev, page, 1);
 226}
 227
 228static ssize_t percpu_deactivate_hint_show(struct sys_device *dev,
 229                        struct sysdev_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
 244struct sysdev_class_attribute attr_cpu_activate_hint_list =
 245                _SYSDEV_CLASS_ATTR(pseries_activate_hint_list, 0444,
 246                cpu_activate_hint_list_show, NULL);
 247
 248struct sysdev_class_attribute attr_cpu_deactivate_hint_list =
 249                _SYSDEV_CLASS_ATTR(pseries_deactivate_hint_list, 0444,
 250                cpu_deactivate_hint_list_show, NULL);
 251
 252struct sysdev_attribute attr_percpu_activate_hint =
 253                _SYSDEV_ATTR(pseries_activate_hint, 0444,
 254                percpu_activate_hint_show, NULL);
 255
 256struct sysdev_attribute attr_percpu_deactivate_hint =
 257                _SYSDEV_ATTR(pseries_deactivate_hint, 0444,
 258                percpu_deactivate_hint_show, NULL);
 259
 260static int __init pseries_energy_init(void)
 261{
 262        int cpu, err;
 263        struct sys_device *cpu_sys_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 = sysfs_create_file(&cpu_sysdev_class.kset.kobj,
 271                                &attr_cpu_activate_hint_list.attr);
 272        if (!err)
 273                err = sysfs_create_file(&cpu_sysdev_class.kset.kobj,
 274                                &attr_cpu_deactivate_hint_list.attr);
 275
 276        if (err)
 277                return err;
 278        for_each_possible_cpu(cpu) {
 279                cpu_sys_dev = get_cpu_sysdev(cpu);
 280                err = sysfs_create_file(&cpu_sys_dev->kobj,
 281                                &attr_percpu_activate_hint.attr);
 282                if (err)
 283                        break;
 284                err = sysfs_create_file(&cpu_sys_dev->kobj,
 285                                &attr_percpu_deactivate_hint.attr);
 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
 298static void __exit pseries_energy_cleanup(void)
 299{
 300        int cpu;
 301        struct sys_device *cpu_sys_dev;
 302
 303        if (!sysfs_entries)
 304                return;
 305
 306        /* Remove the sysfs files */
 307        sysfs_remove_file(&cpu_sysdev_class.kset.kobj,
 308                                &attr_cpu_activate_hint_list.attr);
 309
 310        sysfs_remove_file(&cpu_sysdev_class.kset.kobj,
 311                                &attr_cpu_deactivate_hint_list.attr);
 312
 313        for_each_possible_cpu(cpu) {
 314                cpu_sys_dev = get_cpu_sysdev(cpu);
 315                sysfs_remove_file(&cpu_sys_dev->kobj,
 316                                &attr_percpu_activate_hint.attr);
 317                sysfs_remove_file(&cpu_sys_dev->kobj,
 318                                &attr_percpu_deactivate_hint.attr);
 319        }
 320}
 321
 322module_init(pseries_energy_init);
 323module_exit(pseries_energy_cleanup);
 324MODULE_DESCRIPTION("Driver for pSeries platform energy management");
 325MODULE_AUTHOR("Vaidyanathan Srinivasan");
 326MODULE_LICENSE("GPL");
 327