xref: /openbmc/linux/arch/x86/kernel/cpu/intel_epb.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
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