xref: /linux-6.15/arch/x86/kernel/cpu/intel_epb.c (revision 5861381d)
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 <[email protected]>
9  */
10 
11 #include <linux/cpuhotplug.h>
12 #include <linux/kernel.h>
13 #include <linux/syscore_ops.h>
14 
15 #include <asm/cpufeature.h>
16 #include <asm/msr.h>
17 
18 /**
19  * DOC: overview
20  *
21  * The Performance and Energy Bias Hint (EPB) allows software to specify its
22  * preference with respect to the power-performance tradeoffs present in the
23  * processor.  Generally, the EPB is expected to be set by user space through
24  * the generic MSR interface (with the help of the x86_energy_perf_policy tool),
25  * but there are two reasons for the kernel to touch it.
26  *
27  * First, there are systems where the platform firmware resets the EPB during
28  * system-wide transitions from sleep states back into the working state
29  * effectively causing the previous EPB updates by user space to be lost.
30  * Thus the kernel needs to save the current EPB values for all CPUs during
31  * system-wide transitions to sleep states and restore them on the way back to
32  * the working state.  That can be achieved by saving EPB for secondary CPUs
33  * when they are taken offline during transitions into system sleep states and
34  * for the boot CPU in a syscore suspend operation, so that it can be restored
35  * for the boot CPU in a syscore resume operation and for the other CPUs when
36  * they are brought back online.  However, CPUs that are already offline when
37  * a system-wide PM transition is started are not taken offline again, but their
38  * EPB values may still be reset by the platform firmware during the transition,
39  * so in fact it is necessary to save the EPB of any CPU taken offline and to
40  * restore it when the given CPU goes back online at all times.
41  *
42  * Second, on many systems the initial EPB value coming from the platform
43  * firmware is 0 ('performance') and at least on some of them that is because
44  * the platform firmware does not initialize EPB at all with the assumption that
45  * the OS will do that anyway.  That sometimes is problematic, as it may cause
46  * the system battery to drain too fast, for example, so it is better to adjust
47  * it on CPU bring-up and if the initial EPB value for a given CPU is 0, the
48  * kernel changes it to 6 ('normal').
49  */
50 
51 static DEFINE_PER_CPU(u8, saved_epb);
52 
53 #define EPB_MASK	0x0fULL
54 #define EPB_SAVED	0x10ULL
55 
56 static int intel_epb_save(void)
57 {
58 	u64 epb;
59 
60 	rdmsrl(MSR_IA32_ENERGY_PERF_BIAS, epb);
61 	/*
62 	 * Ensure that saved_epb will always be nonzero after this write even if
63 	 * the EPB value read from the MSR is 0.
64 	 */
65 	this_cpu_write(saved_epb, (epb & EPB_MASK) | EPB_SAVED);
66 
67 	return 0;
68 }
69 
70 static void intel_epb_restore(void)
71 {
72 	u64 val = this_cpu_read(saved_epb);
73 	u64 epb;
74 
75 	rdmsrl(MSR_IA32_ENERGY_PERF_BIAS, epb);
76 	if (val) {
77 		val &= EPB_MASK;
78 	} else {
79 		/*
80 		 * Because intel_epb_save() has not run for the current CPU yet,
81 		 * it is going online for the first time, so if its EPB value is
82 		 * 0 ('performance') at this point, assume that it has not been
83 		 * initialized by the platform firmware and set it to 6
84 		 * ('normal').
85 		 */
86 		val = epb & EPB_MASK;
87 		if (val == ENERGY_PERF_BIAS_PERFORMANCE) {
88 			val = ENERGY_PERF_BIAS_NORMAL;
89 			pr_warn_once("ENERGY_PERF_BIAS: Set to 'normal', was 'performance'\n");
90 		}
91 	}
92 	wrmsrl(MSR_IA32_ENERGY_PERF_BIAS, (epb & ~EPB_MASK) | val);
93 }
94 
95 static struct syscore_ops intel_epb_syscore_ops = {
96 	.suspend = intel_epb_save,
97 	.resume = intel_epb_restore,
98 };
99 
100 static int intel_epb_online(unsigned int cpu)
101 {
102 	intel_epb_restore();
103 	return 0;
104 }
105 
106 static int intel_epb_offline(unsigned int cpu)
107 {
108 	return intel_epb_save();
109 }
110 
111 static __init int intel_epb_init(void)
112 {
113 	int ret;
114 
115 	if (!boot_cpu_has(X86_FEATURE_EPB))
116 		return -ENODEV;
117 
118 	ret = cpuhp_setup_state(CPUHP_AP_X86_INTEL_EPB_ONLINE,
119 				"x86/intel/epb:online", intel_epb_online,
120 				intel_epb_offline);
121 	if (ret < 0)
122 		goto err_out_online;
123 
124 	register_syscore_ops(&intel_epb_syscore_ops);
125 	return 0;
126 
127 err_out_online:
128 	cpuhp_remove_state(CPUHP_AP_X86_INTEL_EPB_ONLINE);
129 	return ret;
130 }
131 subsys_initcall(intel_epb_init);
132