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/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