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