Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * POWER platform energy management driver
0004  * Copyright (C) 2010 IBM Corporation
0005  *
0006  * This pseries platform device driver provides access to
0007  * platform energy management capabilities.
0008  */
0009 
0010 #include <linux/module.h>
0011 #include <linux/types.h>
0012 #include <linux/errno.h>
0013 #include <linux/init.h>
0014 #include <linux/seq_file.h>
0015 #include <linux/device.h>
0016 #include <linux/cpu.h>
0017 #include <linux/of.h>
0018 #include <asm/cputhreads.h>
0019 #include <asm/page.h>
0020 #include <asm/hvcall.h>
0021 #include <asm/firmware.h>
0022 #include <asm/prom.h>
0023 
0024 
0025 #define MODULE_VERS "1.0"
0026 #define MODULE_NAME "pseries_energy"
0027 
0028 /* Driver flags */
0029 
0030 static int sysfs_entries;
0031 
0032 /* Helper routines */
0033 
0034 /* Helper Routines to convert between drc_index to cpu numbers */
0035 
0036 static u32 cpu_to_drc_index(int cpu)
0037 {
0038     struct device_node *dn = NULL;
0039     struct property *info;
0040     int thread_index;
0041     int rc = 1;
0042     u32 ret = 0;
0043 
0044     dn = of_find_node_by_path("/cpus");
0045     if (dn == NULL)
0046         goto err;
0047 
0048     /* Convert logical cpu number to core number */
0049     thread_index = cpu_core_index_of_thread(cpu);
0050 
0051     info = of_find_property(dn, "ibm,drc-info", NULL);
0052     if (info) {
0053         struct of_drc_info drc;
0054         int j;
0055         u32 num_set_entries;
0056         const __be32 *value;
0057 
0058         value = of_prop_next_u32(info, NULL, &num_set_entries);
0059         if (!value)
0060             goto err_of_node_put;
0061         else
0062             value++;
0063 
0064         for (j = 0; j < num_set_entries; j++) {
0065 
0066             of_read_drc_info_cell(&info, &value, &drc);
0067             if (strncmp(drc.drc_type, "CPU", 3))
0068                 goto err;
0069 
0070             if (thread_index < drc.last_drc_index)
0071                 break;
0072         }
0073 
0074         ret = drc.drc_index_start + (thread_index * drc.sequential_inc);
0075     } else {
0076         u32 nr_drc_indexes, thread_drc_index;
0077 
0078         /*
0079          * The first element of ibm,drc-indexes array is the
0080          * number of drc_indexes returned in the list.  Hence
0081          * thread_index+1 will get the drc_index corresponding
0082          * to core number thread_index.
0083          */
0084         rc = of_property_read_u32_index(dn, "ibm,drc-indexes",
0085                         0, &nr_drc_indexes);
0086         if (rc)
0087             goto err_of_node_put;
0088 
0089         WARN_ON_ONCE(thread_index > nr_drc_indexes);
0090         rc = of_property_read_u32_index(dn, "ibm,drc-indexes",
0091                         thread_index + 1,
0092                         &thread_drc_index);
0093         if (rc)
0094             goto err_of_node_put;
0095 
0096         ret = thread_drc_index;
0097     }
0098 
0099     rc = 0;
0100 
0101 err_of_node_put:
0102     of_node_put(dn);
0103 err:
0104     if (rc)
0105         printk(KERN_WARNING "cpu_to_drc_index(%d) failed", cpu);
0106     return ret;
0107 }
0108 
0109 static int drc_index_to_cpu(u32 drc_index)
0110 {
0111     struct device_node *dn = NULL;
0112     struct property *info;
0113     const int *indexes;
0114     int thread_index = 0, cpu = 0;
0115     int rc = 1;
0116 
0117     dn = of_find_node_by_path("/cpus");
0118     if (dn == NULL)
0119         goto err;
0120     info = of_find_property(dn, "ibm,drc-info", NULL);
0121     if (info) {
0122         struct of_drc_info drc;
0123         int j;
0124         u32 num_set_entries;
0125         const __be32 *value;
0126 
0127         value = of_prop_next_u32(info, NULL, &num_set_entries);
0128         if (!value)
0129             goto err_of_node_put;
0130         else
0131             value++;
0132 
0133         for (j = 0; j < num_set_entries; j++) {
0134 
0135             of_read_drc_info_cell(&info, &value, &drc);
0136             if (strncmp(drc.drc_type, "CPU", 3))
0137                 goto err;
0138 
0139             if (drc_index > drc.last_drc_index) {
0140                 cpu += drc.num_sequential_elems;
0141                 continue;
0142             }
0143             cpu += ((drc_index - drc.drc_index_start) /
0144                 drc.sequential_inc);
0145 
0146             thread_index = cpu_first_thread_of_core(cpu);
0147             rc = 0;
0148             break;
0149         }
0150     } else {
0151         unsigned long int i;
0152 
0153         indexes = of_get_property(dn, "ibm,drc-indexes", NULL);
0154         if (indexes == NULL)
0155             goto err_of_node_put;
0156         /*
0157          * First element in the array is the number of drc_indexes
0158          * returned.  Search through the list to find the matching
0159          * drc_index and get the core number
0160          */
0161         for (i = 0; i < indexes[0]; i++) {
0162             if (indexes[i + 1] == drc_index)
0163                 break;
0164         }
0165         /* Convert core number to logical cpu number */
0166         thread_index = cpu_first_thread_of_core(i);
0167         rc = 0;
0168     }
0169 
0170 err_of_node_put:
0171     of_node_put(dn);
0172 err:
0173     if (rc)
0174         printk(KERN_WARNING "drc_index_to_cpu(%d) failed", drc_index);
0175     return thread_index;
0176 }
0177 
0178 /*
0179  * pseries hypervisor call H_BEST_ENERGY provides hints to OS on
0180  * preferred logical cpus to activate or deactivate for optimized
0181  * energy consumption.
0182  */
0183 
0184 #define FLAGS_MODE1 0x004E200000080E01UL
0185 #define FLAGS_MODE2 0x004E200000080401UL
0186 #define FLAGS_ACTIVATE  0x100
0187 
0188 static ssize_t get_best_energy_list(char *page, int activate)
0189 {
0190     int rc, cnt, i, cpu;
0191     unsigned long retbuf[PLPAR_HCALL9_BUFSIZE];
0192     unsigned long flags = 0;
0193     u32 *buf_page;
0194     char *s = page;
0195 
0196     buf_page = (u32 *) get_zeroed_page(GFP_KERNEL);
0197     if (!buf_page)
0198         return -ENOMEM;
0199 
0200     flags = FLAGS_MODE1;
0201     if (activate)
0202         flags |= FLAGS_ACTIVATE;
0203 
0204     rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags, 0, __pa(buf_page),
0205                 0, 0, 0, 0, 0, 0);
0206     if (rc != H_SUCCESS) {
0207         free_page((unsigned long) buf_page);
0208         return -EINVAL;
0209     }
0210 
0211     cnt = retbuf[0];
0212     for (i = 0; i < cnt; i++) {
0213         cpu = drc_index_to_cpu(buf_page[2*i+1]);
0214         if ((cpu_online(cpu) && !activate) ||
0215             (!cpu_online(cpu) && activate))
0216             s += sprintf(s, "%d,", cpu);
0217     }
0218     if (s > page) { /* Something to show */
0219         s--; /* Suppress last comma */
0220         s += sprintf(s, "\n");
0221     }
0222 
0223     free_page((unsigned long) buf_page);
0224     return s-page;
0225 }
0226 
0227 static ssize_t get_best_energy_data(struct device *dev,
0228                     char *page, int activate)
0229 {
0230     int rc;
0231     unsigned long retbuf[PLPAR_HCALL9_BUFSIZE];
0232     unsigned long flags = 0;
0233 
0234     flags = FLAGS_MODE2;
0235     if (activate)
0236         flags |= FLAGS_ACTIVATE;
0237 
0238     rc = plpar_hcall9(H_BEST_ENERGY, retbuf, flags,
0239                 cpu_to_drc_index(dev->id),
0240                 0, 0, 0, 0, 0, 0, 0);
0241 
0242     if (rc != H_SUCCESS)
0243         return -EINVAL;
0244 
0245     return sprintf(page, "%lu\n", retbuf[1] >> 32);
0246 }
0247 
0248 /* Wrapper functions */
0249 
0250 static ssize_t cpu_activate_hint_list_show(struct device *dev,
0251             struct device_attribute *attr, char *page)
0252 {
0253     return get_best_energy_list(page, 1);
0254 }
0255 
0256 static ssize_t cpu_deactivate_hint_list_show(struct device *dev,
0257             struct device_attribute *attr, char *page)
0258 {
0259     return get_best_energy_list(page, 0);
0260 }
0261 
0262 static ssize_t percpu_activate_hint_show(struct device *dev,
0263             struct device_attribute *attr, char *page)
0264 {
0265     return get_best_energy_data(dev, page, 1);
0266 }
0267 
0268 static ssize_t percpu_deactivate_hint_show(struct device *dev,
0269             struct device_attribute *attr, char *page)
0270 {
0271     return get_best_energy_data(dev, page, 0);
0272 }
0273 
0274 /*
0275  * Create sysfs interface:
0276  * /sys/devices/system/cpu/pseries_activate_hint_list
0277  * /sys/devices/system/cpu/pseries_deactivate_hint_list
0278  *  Comma separated list of cpus to activate or deactivate
0279  * /sys/devices/system/cpu/cpuN/pseries_activate_hint
0280  * /sys/devices/system/cpu/cpuN/pseries_deactivate_hint
0281  *  Per-cpu value of the hint
0282  */
0283 
0284 static struct device_attribute attr_cpu_activate_hint_list =
0285         __ATTR(pseries_activate_hint_list, 0444,
0286         cpu_activate_hint_list_show, NULL);
0287 
0288 static struct device_attribute attr_cpu_deactivate_hint_list =
0289         __ATTR(pseries_deactivate_hint_list, 0444,
0290         cpu_deactivate_hint_list_show, NULL);
0291 
0292 static struct device_attribute attr_percpu_activate_hint =
0293         __ATTR(pseries_activate_hint, 0444,
0294         percpu_activate_hint_show, NULL);
0295 
0296 static struct device_attribute attr_percpu_deactivate_hint =
0297         __ATTR(pseries_deactivate_hint, 0444,
0298         percpu_deactivate_hint_show, NULL);
0299 
0300 static int __init pseries_energy_init(void)
0301 {
0302     int cpu, err;
0303     struct device *cpu_dev;
0304 
0305     if (!firmware_has_feature(FW_FEATURE_BEST_ENERGY))
0306         return 0; /* H_BEST_ENERGY hcall not supported */
0307 
0308     /* Create the sysfs files */
0309     err = device_create_file(cpu_subsys.dev_root,
0310                 &attr_cpu_activate_hint_list);
0311     if (!err)
0312         err = device_create_file(cpu_subsys.dev_root,
0313                 &attr_cpu_deactivate_hint_list);
0314 
0315     if (err)
0316         return err;
0317     for_each_possible_cpu(cpu) {
0318         cpu_dev = get_cpu_device(cpu);
0319         err = device_create_file(cpu_dev,
0320                 &attr_percpu_activate_hint);
0321         if (err)
0322             break;
0323         err = device_create_file(cpu_dev,
0324                 &attr_percpu_deactivate_hint);
0325         if (err)
0326             break;
0327     }
0328 
0329     if (err)
0330         return err;
0331 
0332     sysfs_entries = 1; /* Removed entries on cleanup */
0333     return 0;
0334 
0335 }
0336 
0337 static void __exit pseries_energy_cleanup(void)
0338 {
0339     int cpu;
0340     struct device *cpu_dev;
0341 
0342     if (!sysfs_entries)
0343         return;
0344 
0345     /* Remove the sysfs files */
0346     device_remove_file(cpu_subsys.dev_root, &attr_cpu_activate_hint_list);
0347     device_remove_file(cpu_subsys.dev_root, &attr_cpu_deactivate_hint_list);
0348 
0349     for_each_possible_cpu(cpu) {
0350         cpu_dev = get_cpu_device(cpu);
0351         sysfs_remove_file(&cpu_dev->kobj,
0352                 &attr_percpu_activate_hint.attr);
0353         sysfs_remove_file(&cpu_dev->kobj,
0354                 &attr_percpu_deactivate_hint.attr);
0355     }
0356 }
0357 
0358 module_init(pseries_energy_init);
0359 module_exit(pseries_energy_cleanup);
0360 MODULE_DESCRIPTION("Driver for pSeries platform energy management");
0361 MODULE_AUTHOR("Vaidyanathan Srinivasan");
0362 MODULE_LICENSE("GPL");