1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Intel Performance and Energy Bias Hint support. 4 * 5 * Copyright (C) 2019 Intel Corporation 6 * 7 * Author: 8 * Rafael J. Wysocki <rafael.j.wysocki@intel.com> 9 */ 10 11 #include <linux/cpuhotplug.h> 12 #include <linux/cpu.h> 13 #include <linux/device.h> 14 #include <linux/kernel.h> 15 #include <linux/string.h> 16 #include <linux/syscore_ops.h> 17 #include <linux/pm.h> 18 19 #include <asm/cpufeature.h> 20 #include <asm/msr.h> 21 22 /** 23 * DOC: overview 24 * 25 * The Performance and Energy Bias Hint (EPB) allows software to specify its 26 * preference with respect to the power-performance tradeoffs present in the 27 * processor. Generally, the EPB is expected to be set by user space (directly 28 * via sysfs or with the help of the x86_energy_perf_policy tool), but there are 29 * two reasons for the kernel to update it. 30 * 31 * First, there are systems where the platform firmware resets the EPB during 32 * system-wide transitions from sleep states back into the working state 33 * effectively causing the previous EPB updates by user space to be lost. 34 * Thus the kernel needs to save the current EPB values for all CPUs during 35 * system-wide transitions to sleep states and restore them on the way back to 36 * the working state. That can be achieved by saving EPB for secondary CPUs 37 * when they are taken offline during transitions into system sleep states and 38 * for the boot CPU in a syscore suspend operation, so that it can be restored 39 * for the boot CPU in a syscore resume operation and for the other CPUs when 40 * they are brought back online. However, CPUs that are already offline when 41 * a system-wide PM transition is started are not taken offline again, but their 42 * EPB values may still be reset by the platform firmware during the transition, 43 * so in fact it is necessary to save the EPB of any CPU taken offline and to 44 * restore it when the given CPU goes back online at all times. 45 * 46 * Second, on many systems the initial EPB value coming from the platform 47 * firmware is 0 ('performance') and at least on some of them that is because 48 * the platform firmware does not initialize EPB at all with the assumption that 49 * the OS will do that anyway. That sometimes is problematic, as it may cause 50 * the system battery to drain too fast, for example, so it is better to adjust 51 * it on CPU bring-up and if the initial EPB value for a given CPU is 0, the 52 * kernel changes it to 6 ('normal'). 53 */ 54 55 static DEFINE_PER_CPU(u8, saved_epb); 56 57 #define EPB_MASK 0x0fULL 58 #define EPB_SAVED 0x10ULL 59 #define MAX_EPB EPB_MASK 60 61 static int intel_epb_save(void) 62 { 63 u64 epb; 64 65 rdmsrl(MSR_IA32_ENERGY_PERF_BIAS, epb); 66 /* 67 * Ensure that saved_epb will always be nonzero after this write even if 68 * the EPB value read from the MSR is 0. 69 */ 70 this_cpu_write(saved_epb, (epb & EPB_MASK) | EPB_SAVED); 71 72 return 0; 73 } 74 75 static void intel_epb_restore(void) 76 { 77 u64 val = this_cpu_read(saved_epb); 78 u64 epb; 79 80 rdmsrl(MSR_IA32_ENERGY_PERF_BIAS, epb); 81 if (val) { 82 val &= EPB_MASK; 83 } else { 84 /* 85 * Because intel_epb_save() has not run for the current CPU yet, 86 * it is going online for the first time, so if its EPB value is 87 * 0 ('performance') at this point, assume that it has not been 88 * initialized by the platform firmware and set it to 6 89 * ('normal'). 90 */ 91 val = epb & EPB_MASK; 92 if (val == ENERGY_PERF_BIAS_PERFORMANCE) { 93 val = ENERGY_PERF_BIAS_NORMAL; 94 pr_warn_once("ENERGY_PERF_BIAS: Set to 'normal', was 'performance'\n"); 95 } 96 } 97 wrmsrl(MSR_IA32_ENERGY_PERF_BIAS, (epb & ~EPB_MASK) | val); 98 } 99 100 #ifdef CONFIG_PM 101 static struct syscore_ops intel_epb_syscore_ops = { 102 .suspend = intel_epb_save, 103 .resume = intel_epb_restore, 104 }; 105 106 static const char * const energy_perf_strings[] = { 107 "performance", 108 "balance-performance", 109 "normal", 110 "balance-power", 111 "power" 112 }; 113 static const u8 energ_perf_values[] = { 114 ENERGY_PERF_BIAS_PERFORMANCE, 115 ENERGY_PERF_BIAS_BALANCE_PERFORMANCE, 116 ENERGY_PERF_BIAS_NORMAL, 117 ENERGY_PERF_BIAS_BALANCE_POWERSAVE, 118 ENERGY_PERF_BIAS_POWERSAVE 119 }; 120 121 static ssize_t energy_perf_bias_show(struct device *dev, 122 struct device_attribute *attr, 123 char *buf) 124 { 125 unsigned int cpu = dev->id; 126 u64 epb; 127 int ret; 128 129 ret = rdmsrl_on_cpu(cpu, MSR_IA32_ENERGY_PERF_BIAS, &epb); 130 if (ret < 0) 131 return ret; 132 133 return sprintf(buf, "%llu\n", epb); 134 } 135 136 static ssize_t energy_perf_bias_store(struct device *dev, 137 struct device_attribute *attr, 138 const char *buf, size_t count) 139 { 140 unsigned int cpu = dev->id; 141 u64 epb, val; 142 int ret; 143 144 ret = __sysfs_match_string(energy_perf_strings, 145 ARRAY_SIZE(energy_perf_strings), buf); 146 if (ret >= 0) 147 val = energ_perf_values[ret]; 148 else if (kstrtou64(buf, 0, &val) || val > MAX_EPB) 149 return -EINVAL; 150 151 ret = rdmsrl_on_cpu(cpu, MSR_IA32_ENERGY_PERF_BIAS, &epb); 152 if (ret < 0) 153 return ret; 154 155 ret = wrmsrl_on_cpu(cpu, MSR_IA32_ENERGY_PERF_BIAS, 156 (epb & ~EPB_MASK) | val); 157 if (ret < 0) 158 return ret; 159 160 return count; 161 } 162 163 static DEVICE_ATTR_RW(energy_perf_bias); 164 165 static struct attribute *intel_epb_attrs[] = { 166 &dev_attr_energy_perf_bias.attr, 167 NULL 168 }; 169 170 static const struct attribute_group intel_epb_attr_group = { 171 .name = power_group_name, 172 .attrs = intel_epb_attrs 173 }; 174 175 static int intel_epb_online(unsigned int cpu) 176 { 177 struct device *cpu_dev = get_cpu_device(cpu); 178 179 intel_epb_restore(); 180 if (!cpuhp_tasks_frozen) 181 sysfs_merge_group(&cpu_dev->kobj, &intel_epb_attr_group); 182 183 return 0; 184 } 185 186 static int intel_epb_offline(unsigned int cpu) 187 { 188 struct device *cpu_dev = get_cpu_device(cpu); 189 190 if (!cpuhp_tasks_frozen) 191 sysfs_unmerge_group(&cpu_dev->kobj, &intel_epb_attr_group); 192 193 intel_epb_save(); 194 return 0; 195 } 196 197 static inline void register_intel_ebp_syscore_ops(void) 198 { 199 register_syscore_ops(&intel_epb_syscore_ops); 200 } 201 #else /* !CONFIG_PM */ 202 static int intel_epb_online(unsigned int cpu) 203 { 204 intel_epb_restore(); 205 return 0; 206 } 207 208 static int intel_epb_offline(unsigned int cpu) 209 { 210 return intel_epb_save(); 211 } 212 213 static inline void register_intel_ebp_syscore_ops(void) {} 214 #endif 215 216 static __init int intel_epb_init(void) 217 { 218 int ret; 219 220 if (!boot_cpu_has(X86_FEATURE_EPB)) 221 return -ENODEV; 222 223 ret = cpuhp_setup_state(CPUHP_AP_X86_INTEL_EPB_ONLINE, 224 "x86/intel/epb:online", intel_epb_online, 225 intel_epb_offline); 226 if (ret < 0) 227 goto err_out_online; 228 229 register_intel_ebp_syscore_ops(); 230 return 0; 231 232 err_out_online: 233 cpuhp_remove_state(CPUHP_AP_X86_INTEL_EPB_ONLINE); 234 return ret; 235 } 236 subsys_initcall(intel_epb_init); 237