15861381dSRafael J. Wysocki // SPDX-License-Identifier: GPL-2.0
25861381dSRafael J. Wysocki /*
35861381dSRafael J. Wysocki * Intel Performance and Energy Bias Hint support.
45861381dSRafael J. Wysocki *
55861381dSRafael J. Wysocki * Copyright (C) 2019 Intel Corporation
65861381dSRafael J. Wysocki *
75861381dSRafael J. Wysocki * Author:
85861381dSRafael J. Wysocki * Rafael J. Wysocki <rafael.j.wysocki@intel.com>
95861381dSRafael J. Wysocki */
105861381dSRafael J. Wysocki
115861381dSRafael J. Wysocki #include <linux/cpuhotplug.h>
12b9c273baSRafael J. Wysocki #include <linux/cpu.h>
13b9c273baSRafael J. Wysocki #include <linux/device.h>
145861381dSRafael J. Wysocki #include <linux/kernel.h>
15b9c273baSRafael J. Wysocki #include <linux/string.h>
165861381dSRafael J. Wysocki #include <linux/syscore_ops.h>
17b9c273baSRafael J. Wysocki #include <linux/pm.h>
185861381dSRafael J. Wysocki
194ecc933bSSrinivas Pandruvada #include <asm/cpu_device_id.h>
205861381dSRafael J. Wysocki #include <asm/cpufeature.h>
215861381dSRafael J. Wysocki #include <asm/msr.h>
225861381dSRafael J. Wysocki
235861381dSRafael J. Wysocki /**
245861381dSRafael J. Wysocki * DOC: overview
255861381dSRafael J. Wysocki *
265861381dSRafael J. Wysocki * The Performance and Energy Bias Hint (EPB) allows software to specify its
275861381dSRafael J. Wysocki * preference with respect to the power-performance tradeoffs present in the
28b9c273baSRafael J. Wysocki * processor. Generally, the EPB is expected to be set by user space (directly
29b9c273baSRafael J. Wysocki * via sysfs or with the help of the x86_energy_perf_policy tool), but there are
30b9c273baSRafael J. Wysocki * two reasons for the kernel to update it.
315861381dSRafael J. Wysocki *
325861381dSRafael J. Wysocki * First, there are systems where the platform firmware resets the EPB during
335861381dSRafael J. Wysocki * system-wide transitions from sleep states back into the working state
345861381dSRafael J. Wysocki * effectively causing the previous EPB updates by user space to be lost.
355861381dSRafael J. Wysocki * Thus the kernel needs to save the current EPB values for all CPUs during
365861381dSRafael J. Wysocki * system-wide transitions to sleep states and restore them on the way back to
375861381dSRafael J. Wysocki * the working state. That can be achieved by saving EPB for secondary CPUs
385861381dSRafael J. Wysocki * when they are taken offline during transitions into system sleep states and
395861381dSRafael J. Wysocki * for the boot CPU in a syscore suspend operation, so that it can be restored
405861381dSRafael J. Wysocki * for the boot CPU in a syscore resume operation and for the other CPUs when
415861381dSRafael J. Wysocki * they are brought back online. However, CPUs that are already offline when
425861381dSRafael J. Wysocki * a system-wide PM transition is started are not taken offline again, but their
435861381dSRafael J. Wysocki * EPB values may still be reset by the platform firmware during the transition,
445861381dSRafael J. Wysocki * so in fact it is necessary to save the EPB of any CPU taken offline and to
455861381dSRafael J. Wysocki * restore it when the given CPU goes back online at all times.
465861381dSRafael J. Wysocki *
475861381dSRafael J. Wysocki * Second, on many systems the initial EPB value coming from the platform
485861381dSRafael J. Wysocki * firmware is 0 ('performance') and at least on some of them that is because
495861381dSRafael J. Wysocki * the platform firmware does not initialize EPB at all with the assumption that
505861381dSRafael J. Wysocki * the OS will do that anyway. That sometimes is problematic, as it may cause
515861381dSRafael J. Wysocki * the system battery to drain too fast, for example, so it is better to adjust
525861381dSRafael J. Wysocki * it on CPU bring-up and if the initial EPB value for a given CPU is 0, the
535861381dSRafael J. Wysocki * kernel changes it to 6 ('normal').
545861381dSRafael J. Wysocki */
555861381dSRafael J. Wysocki
565861381dSRafael J. Wysocki static DEFINE_PER_CPU(u8, saved_epb);
575861381dSRafael J. Wysocki
585861381dSRafael J. Wysocki #define EPB_MASK 0x0fULL
595861381dSRafael J. Wysocki #define EPB_SAVED 0x10ULL
60b9c273baSRafael J. Wysocki #define MAX_EPB EPB_MASK
615861381dSRafael J. Wysocki
624ecc933bSSrinivas Pandruvada enum energy_perf_value_index {
634ecc933bSSrinivas Pandruvada EPB_INDEX_PERFORMANCE,
644ecc933bSSrinivas Pandruvada EPB_INDEX_BALANCE_PERFORMANCE,
654ecc933bSSrinivas Pandruvada EPB_INDEX_NORMAL,
664ecc933bSSrinivas Pandruvada EPB_INDEX_BALANCE_POWERSAVE,
674ecc933bSSrinivas Pandruvada EPB_INDEX_POWERSAVE,
684ecc933bSSrinivas Pandruvada };
694ecc933bSSrinivas Pandruvada
704ecc933bSSrinivas Pandruvada static u8 energ_perf_values[] = {
714ecc933bSSrinivas Pandruvada [EPB_INDEX_PERFORMANCE] = ENERGY_PERF_BIAS_PERFORMANCE,
724ecc933bSSrinivas Pandruvada [EPB_INDEX_BALANCE_PERFORMANCE] = ENERGY_PERF_BIAS_BALANCE_PERFORMANCE,
734ecc933bSSrinivas Pandruvada [EPB_INDEX_NORMAL] = ENERGY_PERF_BIAS_NORMAL,
744ecc933bSSrinivas Pandruvada [EPB_INDEX_BALANCE_POWERSAVE] = ENERGY_PERF_BIAS_BALANCE_POWERSAVE,
754ecc933bSSrinivas Pandruvada [EPB_INDEX_POWERSAVE] = ENERGY_PERF_BIAS_POWERSAVE,
764ecc933bSSrinivas Pandruvada };
774ecc933bSSrinivas Pandruvada
intel_epb_save(void)785861381dSRafael J. Wysocki static int intel_epb_save(void)
795861381dSRafael J. Wysocki {
805861381dSRafael J. Wysocki u64 epb;
815861381dSRafael J. Wysocki
825861381dSRafael J. Wysocki rdmsrl(MSR_IA32_ENERGY_PERF_BIAS, epb);
835861381dSRafael J. Wysocki /*
845861381dSRafael J. Wysocki * Ensure that saved_epb will always be nonzero after this write even if
855861381dSRafael J. Wysocki * the EPB value read from the MSR is 0.
865861381dSRafael J. Wysocki */
875861381dSRafael J. Wysocki this_cpu_write(saved_epb, (epb & EPB_MASK) | EPB_SAVED);
885861381dSRafael J. Wysocki
895861381dSRafael J. Wysocki return 0;
905861381dSRafael J. Wysocki }
915861381dSRafael J. Wysocki
intel_epb_restore(void)925861381dSRafael J. Wysocki static void intel_epb_restore(void)
935861381dSRafael J. Wysocki {
945861381dSRafael J. Wysocki u64 val = this_cpu_read(saved_epb);
955861381dSRafael J. Wysocki u64 epb;
965861381dSRafael J. Wysocki
975861381dSRafael J. Wysocki rdmsrl(MSR_IA32_ENERGY_PERF_BIAS, epb);
985861381dSRafael J. Wysocki if (val) {
995861381dSRafael J. Wysocki val &= EPB_MASK;
1005861381dSRafael J. Wysocki } else {
1015861381dSRafael J. Wysocki /*
1025861381dSRafael J. Wysocki * Because intel_epb_save() has not run for the current CPU yet,
1035861381dSRafael J. Wysocki * it is going online for the first time, so if its EPB value is
1045861381dSRafael J. Wysocki * 0 ('performance') at this point, assume that it has not been
1055861381dSRafael J. Wysocki * initialized by the platform firmware and set it to 6
1065861381dSRafael J. Wysocki * ('normal').
1075861381dSRafael J. Wysocki */
1085861381dSRafael J. Wysocki val = epb & EPB_MASK;
1095861381dSRafael J. Wysocki if (val == ENERGY_PERF_BIAS_PERFORMANCE) {
1104ecc933bSSrinivas Pandruvada val = energ_perf_values[EPB_INDEX_NORMAL];
1115861381dSRafael J. Wysocki pr_warn_once("ENERGY_PERF_BIAS: Set to 'normal', was 'performance'\n");
1125861381dSRafael J. Wysocki }
1135861381dSRafael J. Wysocki }
1145861381dSRafael J. Wysocki wrmsrl(MSR_IA32_ENERGY_PERF_BIAS, (epb & ~EPB_MASK) | val);
1155861381dSRafael J. Wysocki }
1165861381dSRafael J. Wysocki
1175861381dSRafael J. Wysocki static struct syscore_ops intel_epb_syscore_ops = {
1185861381dSRafael J. Wysocki .suspend = intel_epb_save,
1195861381dSRafael J. Wysocki .resume = intel_epb_restore,
1205861381dSRafael J. Wysocki };
1215861381dSRafael J. Wysocki
122b9c273baSRafael J. Wysocki static const char * const energy_perf_strings[] = {
1234ecc933bSSrinivas Pandruvada [EPB_INDEX_PERFORMANCE] = "performance",
1244ecc933bSSrinivas Pandruvada [EPB_INDEX_BALANCE_PERFORMANCE] = "balance-performance",
1254ecc933bSSrinivas Pandruvada [EPB_INDEX_NORMAL] = "normal",
1264ecc933bSSrinivas Pandruvada [EPB_INDEX_BALANCE_POWERSAVE] = "balance-power",
1274ecc933bSSrinivas Pandruvada [EPB_INDEX_POWERSAVE] = "power",
128b9c273baSRafael J. Wysocki };
129b9c273baSRafael J. Wysocki
energy_perf_bias_show(struct device * dev,struct device_attribute * attr,char * buf)130b9c273baSRafael J. Wysocki static ssize_t energy_perf_bias_show(struct device *dev,
131b9c273baSRafael J. Wysocki struct device_attribute *attr,
132b9c273baSRafael J. Wysocki char *buf)
133b9c273baSRafael J. Wysocki {
134b9c273baSRafael J. Wysocki unsigned int cpu = dev->id;
135b9c273baSRafael J. Wysocki u64 epb;
136b9c273baSRafael J. Wysocki int ret;
137b9c273baSRafael J. Wysocki
138b9c273baSRafael J. Wysocki ret = rdmsrl_on_cpu(cpu, MSR_IA32_ENERGY_PERF_BIAS, &epb);
139b9c273baSRafael J. Wysocki if (ret < 0)
140b9c273baSRafael J. Wysocki return ret;
141b9c273baSRafael J. Wysocki
142b9c273baSRafael J. Wysocki return sprintf(buf, "%llu\n", epb);
143b9c273baSRafael J. Wysocki }
144b9c273baSRafael J. Wysocki
energy_perf_bias_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)145b9c273baSRafael J. Wysocki static ssize_t energy_perf_bias_store(struct device *dev,
146b9c273baSRafael J. Wysocki struct device_attribute *attr,
147b9c273baSRafael J. Wysocki const char *buf, size_t count)
148b9c273baSRafael J. Wysocki {
149b9c273baSRafael J. Wysocki unsigned int cpu = dev->id;
150b9c273baSRafael J. Wysocki u64 epb, val;
151b9c273baSRafael J. Wysocki int ret;
152b9c273baSRafael J. Wysocki
153b9c273baSRafael J. Wysocki ret = __sysfs_match_string(energy_perf_strings,
154b9c273baSRafael J. Wysocki ARRAY_SIZE(energy_perf_strings), buf);
155b9c273baSRafael J. Wysocki if (ret >= 0)
156b9c273baSRafael J. Wysocki val = energ_perf_values[ret];
157b9c273baSRafael J. Wysocki else if (kstrtou64(buf, 0, &val) || val > MAX_EPB)
158b9c273baSRafael J. Wysocki return -EINVAL;
159b9c273baSRafael J. Wysocki
160b9c273baSRafael J. Wysocki ret = rdmsrl_on_cpu(cpu, MSR_IA32_ENERGY_PERF_BIAS, &epb);
161b9c273baSRafael J. Wysocki if (ret < 0)
162b9c273baSRafael J. Wysocki return ret;
163b9c273baSRafael J. Wysocki
164b9c273baSRafael J. Wysocki ret = wrmsrl_on_cpu(cpu, MSR_IA32_ENERGY_PERF_BIAS,
165b9c273baSRafael J. Wysocki (epb & ~EPB_MASK) | val);
166b9c273baSRafael J. Wysocki if (ret < 0)
167b9c273baSRafael J. Wysocki return ret;
168b9c273baSRafael J. Wysocki
169b9c273baSRafael J. Wysocki return count;
170b9c273baSRafael J. Wysocki }
171b9c273baSRafael J. Wysocki
172b9c273baSRafael J. Wysocki static DEVICE_ATTR_RW(energy_perf_bias);
173b9c273baSRafael J. Wysocki
174b9c273baSRafael J. Wysocki static struct attribute *intel_epb_attrs[] = {
175b9c273baSRafael J. Wysocki &dev_attr_energy_perf_bias.attr,
176b9c273baSRafael J. Wysocki NULL
177b9c273baSRafael J. Wysocki };
178b9c273baSRafael J. Wysocki
179b9c273baSRafael J. Wysocki static const struct attribute_group intel_epb_attr_group = {
180b9c273baSRafael J. Wysocki .name = power_group_name,
181b9c273baSRafael J. Wysocki .attrs = intel_epb_attrs
182b9c273baSRafael J. Wysocki };
183b9c273baSRafael J. Wysocki
intel_epb_online(unsigned int cpu)1845861381dSRafael J. Wysocki static int intel_epb_online(unsigned int cpu)
1855861381dSRafael J. Wysocki {
186b9c273baSRafael J. Wysocki struct device *cpu_dev = get_cpu_device(cpu);
187b9c273baSRafael J. Wysocki
1885861381dSRafael J. Wysocki intel_epb_restore();
189b9c273baSRafael J. Wysocki if (!cpuhp_tasks_frozen)
190b9c273baSRafael J. Wysocki sysfs_merge_group(&cpu_dev->kobj, &intel_epb_attr_group);
191b9c273baSRafael J. Wysocki
1925861381dSRafael J. Wysocki return 0;
1935861381dSRafael J. Wysocki }
1945861381dSRafael J. Wysocki
intel_epb_offline(unsigned int cpu)1955861381dSRafael J. Wysocki static int intel_epb_offline(unsigned int cpu)
1965861381dSRafael J. Wysocki {
197b9c273baSRafael J. Wysocki struct device *cpu_dev = get_cpu_device(cpu);
198b9c273baSRafael J. Wysocki
199b9c273baSRafael J. Wysocki if (!cpuhp_tasks_frozen)
200b9c273baSRafael J. Wysocki sysfs_unmerge_group(&cpu_dev->kobj, &intel_epb_attr_group);
201b9c273baSRafael J. Wysocki
202b9c273baSRafael J. Wysocki intel_epb_save();
203b9c273baSRafael J. Wysocki return 0;
2045861381dSRafael J. Wysocki }
2055861381dSRafael J. Wysocki
2064ecc933bSSrinivas Pandruvada static const struct x86_cpu_id intel_epb_normal[] = {
2077420ae3bSSrinivas Pandruvada X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L,
2087420ae3bSSrinivas Pandruvada ENERGY_PERF_BIAS_NORMAL_POWERSAVE),
209*882cdb06SPeter Zijlstra X86_MATCH_INTEL_FAM6_MODEL(ATOM_GRACEMONT,
2107420ae3bSSrinivas Pandruvada ENERGY_PERF_BIAS_NORMAL_POWERSAVE),
2117420ae3bSSrinivas Pandruvada X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE_P,
2127420ae3bSSrinivas Pandruvada ENERGY_PERF_BIAS_NORMAL_POWERSAVE),
2134ecc933bSSrinivas Pandruvada {}
2144ecc933bSSrinivas Pandruvada };
2154ecc933bSSrinivas Pandruvada
intel_epb_init(void)2165861381dSRafael J. Wysocki static __init int intel_epb_init(void)
2175861381dSRafael J. Wysocki {
2184ecc933bSSrinivas Pandruvada const struct x86_cpu_id *id = x86_match_cpu(intel_epb_normal);
2195861381dSRafael J. Wysocki int ret;
2205861381dSRafael J. Wysocki
2215861381dSRafael J. Wysocki if (!boot_cpu_has(X86_FEATURE_EPB))
2225861381dSRafael J. Wysocki return -ENODEV;
2235861381dSRafael J. Wysocki
2244ecc933bSSrinivas Pandruvada if (id)
2254ecc933bSSrinivas Pandruvada energ_perf_values[EPB_INDEX_NORMAL] = id->driver_data;
2264ecc933bSSrinivas Pandruvada
2275861381dSRafael J. Wysocki ret = cpuhp_setup_state(CPUHP_AP_X86_INTEL_EPB_ONLINE,
2285861381dSRafael J. Wysocki "x86/intel/epb:online", intel_epb_online,
2295861381dSRafael J. Wysocki intel_epb_offline);
2305861381dSRafael J. Wysocki if (ret < 0)
2315861381dSRafael J. Wysocki goto err_out_online;
2325861381dSRafael J. Wysocki
233be1fcde6SRafael J. Wysocki register_syscore_ops(&intel_epb_syscore_ops);
2345861381dSRafael J. Wysocki return 0;
2355861381dSRafael J. Wysocki
2365861381dSRafael J. Wysocki err_out_online:
2375861381dSRafael J. Wysocki cpuhp_remove_state(CPUHP_AP_X86_INTEL_EPB_ONLINE);
2385861381dSRafael J. Wysocki return ret;
2395861381dSRafael J. Wysocki }
2405861381dSRafael J. Wysocki subsys_initcall(intel_epb_init);
241