Path: blob/master/arch/powerpc/platforms/pseries/pseries_energy.c
26481 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* POWER platform energy management driver3* Copyright (C) 2010 IBM Corporation4*5* This pseries platform device driver provides access to6* platform energy management capabilities.7*/89#include <linux/module.h>10#include <linux/types.h>11#include <linux/errno.h>12#include <linux/init.h>13#include <linux/seq_file.h>14#include <linux/device.h>15#include <linux/cpu.h>16#include <linux/of.h>17#include <asm/cputhreads.h>18#include <asm/page.h>19#include <asm/hvcall.h>20#include <asm/firmware.h>21#include <asm/prom.h>222324#define MODULE_VERS "1.0"25#define MODULE_NAME "pseries_energy"2627/* Driver flags */2829static int sysfs_entries;3031/* Helper routines */3233/* Helper Routines to convert between drc_index to cpu numbers */3435static u32 cpu_to_drc_index(int cpu)36{37struct device_node *dn = NULL;38struct property *info;39int thread_index;40int rc = 1;41u32 ret = 0;4243dn = of_find_node_by_path("/cpus");44if (dn == NULL)45goto err;4647/* Convert logical cpu number to core number */48thread_index = cpu_core_index_of_thread(cpu);4950info = of_find_property(dn, "ibm,drc-info", NULL);51if (info) {52struct of_drc_info drc;53int j;54u32 num_set_entries;55const __be32 *value;5657value = of_prop_next_u32(info, NULL, &num_set_entries);58if (!value)59goto err_of_node_put;60else61value++;6263for (j = 0; j < num_set_entries; j++) {6465of_read_drc_info_cell(&info, &value, &drc);66if (strncmp(drc.drc_type, "CPU", 3))67goto err;6869if (thread_index < drc.last_drc_index)70break;71}7273ret = drc.drc_index_start + (thread_index * drc.sequential_inc);74} else {75u32 nr_drc_indexes, thread_drc_index;7677/*78* The first element of ibm,drc-indexes array is the79* number of drc_indexes returned in the list. Hence80* thread_index+1 will get the drc_index corresponding81* to core number thread_index.82*/83rc = of_property_read_u32_index(dn, "ibm,drc-indexes",840, &nr_drc_indexes);85if (rc)86goto err_of_node_put;8788WARN_ON_ONCE(thread_index > nr_drc_indexes);89rc = of_property_read_u32_index(dn, "ibm,drc-indexes",90thread_index + 1,91&thread_drc_index);92if (rc)93goto err_of_node_put;9495ret = thread_drc_index;96}9798rc = 0;99100err_of_node_put:101of_node_put(dn);102err:103if (rc)104printk(KERN_WARNING "cpu_to_drc_index(%d) failed", cpu);105return ret;106}107108static int drc_index_to_cpu(u32 drc_index)109{110struct device_node *dn = NULL;111struct property *info;112const int *indexes;113int thread_index = 0, cpu = 0;114int rc = 1;115116dn = of_find_node_by_path("/cpus");117if (dn == NULL)118goto err;119info = of_find_property(dn, "ibm,drc-info", NULL);120if (info) {121struct of_drc_info drc;122int j;123u32 num_set_entries;124const __be32 *value;125126value = of_prop_next_u32(info, NULL, &num_set_entries);127if (!value)128goto err_of_node_put;129else130value++;131132for (j = 0; j < num_set_entries; j++) {133134of_read_drc_info_cell(&info, &value, &drc);135if (strncmp(drc.drc_type, "CPU", 3))136goto err;137138if (drc_index > drc.last_drc_index) {139cpu += drc.num_sequential_elems;140continue;141}142cpu += ((drc_index - drc.drc_index_start) /143drc.sequential_inc);144145thread_index = cpu_first_thread_of_core(cpu);146rc = 0;147break;148}149} else {150unsigned long int i;151152indexes = of_get_property(dn, "ibm,drc-indexes", NULL);153if (indexes == NULL)154goto err_of_node_put;155/*156* First element in the array is the number of drc_indexes157* returned. Search through the list to find the matching158* drc_index and get the core number159*/160for (i = 0; i < indexes[0]; i++) {161if (indexes[i + 1] == drc_index)162break;163}164/* Convert core number to logical cpu number */165thread_index = cpu_first_thread_of_core(i);166rc = 0;167}168169err_of_node_put:170of_node_put(dn);171err:172if (rc)173printk(KERN_WARNING "drc_index_to_cpu(%d) failed", drc_index);174return thread_index;175}176177/*178* pseries hypervisor call H_BEST_ENERGY provides hints to OS on179* preferred logical cpus to activate or deactivate for optimized180* energy consumption.181*/182183#define FLAGS_MODE1 0x004E200000080E01UL184#define FLAGS_MODE2 0x004E200000080401UL185#define FLAGS_ACTIVATE 0x100186187static ssize_t get_best_energy_list(char *page, int activate)188{189int rc, cnt, i, cpu;190unsigned long retbuf[PLPAR_HCALL9_BUFSIZE];191unsigned long flags = 0;192u32 *buf_page;193char *s = page;194195buf_page = (u32 *) get_zeroed_page(GFP_KERNEL);196if (!buf_page)197return -ENOMEM;198199flags = FLAGS_MODE1;200if (activate)201flags |= FLAGS_ACTIVATE;202203rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags, 0, __pa(buf_page),2040, 0, 0, 0, 0, 0);205if (rc != H_SUCCESS) {206free_page((unsigned long) buf_page);207return -EINVAL;208}209210cnt = retbuf[0];211for (i = 0; i < cnt; i++) {212cpu = drc_index_to_cpu(buf_page[2*i+1]);213if ((cpu_online(cpu) && !activate) ||214(!cpu_online(cpu) && activate))215s += sprintf(s, "%d,", cpu);216}217if (s > page) { /* Something to show */218s--; /* Suppress last comma */219s += sprintf(s, "\n");220}221222free_page((unsigned long) buf_page);223return s-page;224}225226static ssize_t get_best_energy_data(struct device *dev,227char *page, int activate)228{229int rc;230unsigned long retbuf[PLPAR_HCALL9_BUFSIZE];231unsigned long flags = 0;232233flags = FLAGS_MODE2;234if (activate)235flags |= FLAGS_ACTIVATE;236237rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags,238cpu_to_drc_index(dev->id),2390, 0, 0, 0, 0, 0, 0);240241if (rc != H_SUCCESS)242return -EINVAL;243244return sprintf(page, "%lu\n", retbuf[1] >> 32);245}246247/* Wrapper functions */248249static ssize_t cpu_activate_hint_list_show(struct device *dev,250struct device_attribute *attr, char *page)251{252return get_best_energy_list(page, 1);253}254255static ssize_t cpu_deactivate_hint_list_show(struct device *dev,256struct device_attribute *attr, char *page)257{258return get_best_energy_list(page, 0);259}260261static ssize_t percpu_activate_hint_show(struct device *dev,262struct device_attribute *attr, char *page)263{264return get_best_energy_data(dev, page, 1);265}266267static ssize_t percpu_deactivate_hint_show(struct device *dev,268struct device_attribute *attr, char *page)269{270return get_best_energy_data(dev, page, 0);271}272273/*274* Create sysfs interface:275* /sys/devices/system/cpu/pseries_activate_hint_list276* /sys/devices/system/cpu/pseries_deactivate_hint_list277* Comma separated list of cpus to activate or deactivate278* /sys/devices/system/cpu/cpuN/pseries_activate_hint279* /sys/devices/system/cpu/cpuN/pseries_deactivate_hint280* Per-cpu value of the hint281*/282283static struct device_attribute attr_cpu_activate_hint_list =284__ATTR(pseries_activate_hint_list, 0444,285cpu_activate_hint_list_show, NULL);286287static struct device_attribute attr_cpu_deactivate_hint_list =288__ATTR(pseries_deactivate_hint_list, 0444,289cpu_deactivate_hint_list_show, NULL);290291static struct device_attribute attr_percpu_activate_hint =292__ATTR(pseries_activate_hint, 0444,293percpu_activate_hint_show, NULL);294295static struct device_attribute attr_percpu_deactivate_hint =296__ATTR(pseries_deactivate_hint, 0444,297percpu_deactivate_hint_show, NULL);298299static int __init pseries_energy_init(void)300{301int cpu, err;302struct device *cpu_dev, *dev_root;303304if (!firmware_has_feature(FW_FEATURE_BEST_ENERGY))305return 0; /* H_BEST_ENERGY hcall not supported */306307/* Create the sysfs files */308dev_root = bus_get_dev_root(&cpu_subsys);309if (dev_root) {310err = device_create_file(dev_root, &attr_cpu_activate_hint_list);311if (!err)312err = device_create_file(dev_root, &attr_cpu_deactivate_hint_list);313put_device(dev_root);314if (err)315return err;316}317318for_each_possible_cpu(cpu) {319cpu_dev = get_cpu_device(cpu);320err = device_create_file(cpu_dev,321&attr_percpu_activate_hint);322if (err)323break;324err = device_create_file(cpu_dev,325&attr_percpu_deactivate_hint);326if (err)327break;328}329330if (err)331return err;332333sysfs_entries = 1; /* Removed entries on cleanup */334return 0;335336}337338static void __exit pseries_energy_cleanup(void)339{340int cpu;341struct device *cpu_dev, *dev_root;342343if (!sysfs_entries)344return;345346/* Remove the sysfs files */347dev_root = bus_get_dev_root(&cpu_subsys);348if (dev_root) {349device_remove_file(dev_root, &attr_cpu_activate_hint_list);350device_remove_file(dev_root, &attr_cpu_deactivate_hint_list);351put_device(dev_root);352}353354for_each_possible_cpu(cpu) {355cpu_dev = get_cpu_device(cpu);356sysfs_remove_file(&cpu_dev->kobj,357&attr_percpu_activate_hint.attr);358sysfs_remove_file(&cpu_dev->kobj,359&attr_percpu_deactivate_hint.attr);360}361}362363module_init(pseries_energy_init);364module_exit(pseries_energy_cleanup);365MODULE_DESCRIPTION("Driver for pSeries platform energy management");366MODULE_AUTHOR("Vaidyanathan Srinivasan");367MODULE_LICENSE("GPL");368369370