Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Intel Performance and Energy Bias Hint support.
0004  *
0005  * Copyright (C) 2019 Intel Corporation
0006  *
0007  * Author:
0008  *  Rafael J. Wysocki <rafael.j.wysocki@intel.com>
0009  */
0010 
0011 #include <linux/cpuhotplug.h>
0012 #include <linux/cpu.h>
0013 #include <linux/device.h>
0014 #include <linux/kernel.h>
0015 #include <linux/string.h>
0016 #include <linux/syscore_ops.h>
0017 #include <linux/pm.h>
0018 
0019 #include <asm/cpu_device_id.h>
0020 #include <asm/cpufeature.h>
0021 #include <asm/msr.h>
0022 
0023 /**
0024  * DOC: overview
0025  *
0026  * The Performance and Energy Bias Hint (EPB) allows software to specify its
0027  * preference with respect to the power-performance tradeoffs present in the
0028  * processor.  Generally, the EPB is expected to be set by user space (directly
0029  * via sysfs or with the help of the x86_energy_perf_policy tool), but there are
0030  * two reasons for the kernel to update it.
0031  *
0032  * First, there are systems where the platform firmware resets the EPB during
0033  * system-wide transitions from sleep states back into the working state
0034  * effectively causing the previous EPB updates by user space to be lost.
0035  * Thus the kernel needs to save the current EPB values for all CPUs during
0036  * system-wide transitions to sleep states and restore them on the way back to
0037  * the working state.  That can be achieved by saving EPB for secondary CPUs
0038  * when they are taken offline during transitions into system sleep states and
0039  * for the boot CPU in a syscore suspend operation, so that it can be restored
0040  * for the boot CPU in a syscore resume operation and for the other CPUs when
0041  * they are brought back online.  However, CPUs that are already offline when
0042  * a system-wide PM transition is started are not taken offline again, but their
0043  * EPB values may still be reset by the platform firmware during the transition,
0044  * so in fact it is necessary to save the EPB of any CPU taken offline and to
0045  * restore it when the given CPU goes back online at all times.
0046  *
0047  * Second, on many systems the initial EPB value coming from the platform
0048  * firmware is 0 ('performance') and at least on some of them that is because
0049  * the platform firmware does not initialize EPB at all with the assumption that
0050  * the OS will do that anyway.  That sometimes is problematic, as it may cause
0051  * the system battery to drain too fast, for example, so it is better to adjust
0052  * it on CPU bring-up and if the initial EPB value for a given CPU is 0, the
0053  * kernel changes it to 6 ('normal').
0054  */
0055 
0056 static DEFINE_PER_CPU(u8, saved_epb);
0057 
0058 #define EPB_MASK    0x0fULL
0059 #define EPB_SAVED   0x10ULL
0060 #define MAX_EPB     EPB_MASK
0061 
0062 enum energy_perf_value_index {
0063     EPB_INDEX_PERFORMANCE,
0064     EPB_INDEX_BALANCE_PERFORMANCE,
0065     EPB_INDEX_NORMAL,
0066     EPB_INDEX_BALANCE_POWERSAVE,
0067     EPB_INDEX_POWERSAVE,
0068 };
0069 
0070 static u8 energ_perf_values[] = {
0071     [EPB_INDEX_PERFORMANCE] = ENERGY_PERF_BIAS_PERFORMANCE,
0072     [EPB_INDEX_BALANCE_PERFORMANCE] = ENERGY_PERF_BIAS_BALANCE_PERFORMANCE,
0073     [EPB_INDEX_NORMAL] = ENERGY_PERF_BIAS_NORMAL,
0074     [EPB_INDEX_BALANCE_POWERSAVE] = ENERGY_PERF_BIAS_BALANCE_POWERSAVE,
0075     [EPB_INDEX_POWERSAVE] = ENERGY_PERF_BIAS_POWERSAVE,
0076 };
0077 
0078 static int intel_epb_save(void)
0079 {
0080     u64 epb;
0081 
0082     rdmsrl(MSR_IA32_ENERGY_PERF_BIAS, epb);
0083     /*
0084      * Ensure that saved_epb will always be nonzero after this write even if
0085      * the EPB value read from the MSR is 0.
0086      */
0087     this_cpu_write(saved_epb, (epb & EPB_MASK) | EPB_SAVED);
0088 
0089     return 0;
0090 }
0091 
0092 static void intel_epb_restore(void)
0093 {
0094     u64 val = this_cpu_read(saved_epb);
0095     u64 epb;
0096 
0097     rdmsrl(MSR_IA32_ENERGY_PERF_BIAS, epb);
0098     if (val) {
0099         val &= EPB_MASK;
0100     } else {
0101         /*
0102          * Because intel_epb_save() has not run for the current CPU yet,
0103          * it is going online for the first time, so if its EPB value is
0104          * 0 ('performance') at this point, assume that it has not been
0105          * initialized by the platform firmware and set it to 6
0106          * ('normal').
0107          */
0108         val = epb & EPB_MASK;
0109         if (val == ENERGY_PERF_BIAS_PERFORMANCE) {
0110             val = energ_perf_values[EPB_INDEX_NORMAL];
0111             pr_warn_once("ENERGY_PERF_BIAS: Set to 'normal', was 'performance'\n");
0112         }
0113     }
0114     wrmsrl(MSR_IA32_ENERGY_PERF_BIAS, (epb & ~EPB_MASK) | val);
0115 }
0116 
0117 static struct syscore_ops intel_epb_syscore_ops = {
0118     .suspend = intel_epb_save,
0119     .resume = intel_epb_restore,
0120 };
0121 
0122 static const char * const energy_perf_strings[] = {
0123     [EPB_INDEX_PERFORMANCE] = "performance",
0124     [EPB_INDEX_BALANCE_PERFORMANCE] = "balance-performance",
0125     [EPB_INDEX_NORMAL] = "normal",
0126     [EPB_INDEX_BALANCE_POWERSAVE] = "balance-power",
0127     [EPB_INDEX_POWERSAVE] = "power",
0128 };
0129 
0130 static ssize_t energy_perf_bias_show(struct device *dev,
0131                      struct device_attribute *attr,
0132                      char *buf)
0133 {
0134     unsigned int cpu = dev->id;
0135     u64 epb;
0136     int ret;
0137 
0138     ret = rdmsrl_on_cpu(cpu, MSR_IA32_ENERGY_PERF_BIAS, &epb);
0139     if (ret < 0)
0140         return ret;
0141 
0142     return sprintf(buf, "%llu\n", epb);
0143 }
0144 
0145 static ssize_t energy_perf_bias_store(struct device *dev,
0146                       struct device_attribute *attr,
0147                       const char *buf, size_t count)
0148 {
0149     unsigned int cpu = dev->id;
0150     u64 epb, val;
0151     int ret;
0152 
0153     ret = __sysfs_match_string(energy_perf_strings,
0154                    ARRAY_SIZE(energy_perf_strings), buf);
0155     if (ret >= 0)
0156         val = energ_perf_values[ret];
0157     else if (kstrtou64(buf, 0, &val) || val > MAX_EPB)
0158         return -EINVAL;
0159 
0160     ret = rdmsrl_on_cpu(cpu, MSR_IA32_ENERGY_PERF_BIAS, &epb);
0161     if (ret < 0)
0162         return ret;
0163 
0164     ret = wrmsrl_on_cpu(cpu, MSR_IA32_ENERGY_PERF_BIAS,
0165                 (epb & ~EPB_MASK) | val);
0166     if (ret < 0)
0167         return ret;
0168 
0169     return count;
0170 }
0171 
0172 static DEVICE_ATTR_RW(energy_perf_bias);
0173 
0174 static struct attribute *intel_epb_attrs[] = {
0175     &dev_attr_energy_perf_bias.attr,
0176     NULL
0177 };
0178 
0179 static const struct attribute_group intel_epb_attr_group = {
0180     .name = power_group_name,
0181     .attrs =  intel_epb_attrs
0182 };
0183 
0184 static int intel_epb_online(unsigned int cpu)
0185 {
0186     struct device *cpu_dev = get_cpu_device(cpu);
0187 
0188     intel_epb_restore();
0189     if (!cpuhp_tasks_frozen)
0190         sysfs_merge_group(&cpu_dev->kobj, &intel_epb_attr_group);
0191 
0192     return 0;
0193 }
0194 
0195 static int intel_epb_offline(unsigned int cpu)
0196 {
0197     struct device *cpu_dev = get_cpu_device(cpu);
0198 
0199     if (!cpuhp_tasks_frozen)
0200         sysfs_unmerge_group(&cpu_dev->kobj, &intel_epb_attr_group);
0201 
0202     intel_epb_save();
0203     return 0;
0204 }
0205 
0206 static const struct x86_cpu_id intel_epb_normal[] = {
0207     X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, 7),
0208     {}
0209 };
0210 
0211 static __init int intel_epb_init(void)
0212 {
0213     const struct x86_cpu_id *id = x86_match_cpu(intel_epb_normal);
0214     int ret;
0215 
0216     if (!boot_cpu_has(X86_FEATURE_EPB))
0217         return -ENODEV;
0218 
0219     if (id)
0220         energ_perf_values[EPB_INDEX_NORMAL] = id->driver_data;
0221 
0222     ret = cpuhp_setup_state(CPUHP_AP_X86_INTEL_EPB_ONLINE,
0223                 "x86/intel/epb:online", intel_epb_online,
0224                 intel_epb_offline);
0225     if (ret < 0)
0226         goto err_out_online;
0227 
0228     register_syscore_ops(&intel_epb_syscore_ops);
0229     return 0;
0230 
0231 err_out_online:
0232     cpuhp_remove_state(CPUHP_AP_X86_INTEL_EPB_ONLINE);
0233     return ret;
0234 }
0235 subsys_initcall(intel_epb_init);