xref: /haiku/src/add-ons/kernel/power/cpufreq/intel_pstates/intel_pstates.cpp (revision 4a32f48e70297d9a634646f01e08c2f451ecd6bd)
1 /*
2  * Copyright 2020, Jérôme Duval, jerome.duval@gmail.com.
3  * Copyright 2013, Haiku, Inc. All Rights Reserved.
4  * Distributed under the terms of the MIT License.
5  *
6  * Authors:
7  *		Paweł Dziepak, <pdziepak@quarnos.org>
8  */
9 
10 
11 #include <cpufreq.h>
12 #include <KernelExport.h>
13 
14 #include <arch_cpu.h>
15 #include <cpu.h>
16 #include <smp.h>
17 #include <util/AutoLock.h>
18 
19 
20 #define INTEL_PSTATES_MODULE_NAME	CPUFREQ_MODULES_PREFIX "/intel_pstates/v1"
21 
22 
23 const int kMinimalInterval = 50000;
24 
25 static uint16 sMinPState;
26 static uint16 sMaxPState;
27 static uint16 sBoostPState;
28 static bool sHWPActive;
29 static bool sHWPEPP;
30 static uint8 sHWPLowest;
31 static uint8 sHWPGuaranteed;
32 static uint8 sHWPEfficient;
33 static uint8 sHWPHighest;
34 static bool sHWPPackage;
35 
36 static bool sAvoidBoost;
37 
38 
39 struct CPUEntry {
40 				CPUEntry();
41 
42 	uint16		fCurrentPState;
43 
44 	bigtime_t	fLastUpdate;
45 
46 	uint64		fPrevAperf;
47 	uint64		fPrevMperf;
48 } CACHE_LINE_ALIGN;
49 static CPUEntry* sCPUEntries;
50 
51 
52 CPUEntry::CPUEntry()
53 	:
54 	fCurrentPState(sMinPState - 1),
55 	fLastUpdate(0)
56 {
57 }
58 
59 
60 static void set_normal_pstate(void* /* dummy */, int cpu);
61 
62 
63 static void
64 pstates_set_scheduler_mode(scheduler_mode mode)
65 {
66 	sAvoidBoost = mode == SCHEDULER_MODE_POWER_SAVING;
67 	if (sHWPActive)
68 		call_all_cpus(set_normal_pstate, NULL);
69 }
70 
71 
72 static int
73 measure_pstate(CPUEntry* entry)
74 {
75 	InterruptsLocker locker;
76 
77 	uint64 mperf = x86_read_msr(IA32_MSR_MPERF);
78 	uint64 aperf = x86_read_msr(IA32_MSR_APERF);
79 
80 	locker.Unlock();
81 
82 	if (mperf == 0 || mperf == entry->fPrevMperf)
83 		return sMinPState;
84 
85 	int oldPState = sMaxPState * (aperf - entry->fPrevAperf)
86 		/ (mperf - entry->fPrevMperf);
87 	oldPState = min_c(max_c(oldPState, sMinPState), sBoostPState);
88 	entry->fPrevAperf = aperf;
89 	entry->fPrevMperf = mperf;
90 
91 	return oldPState;
92 }
93 
94 
95 static inline void
96 set_pstate(uint16 pstate)
97 {
98 	CPUEntry* entry = &sCPUEntries[smp_get_current_cpu()];
99 	pstate = min_c(max_c(sMinPState, pstate), sBoostPState);
100 
101 	if (entry->fCurrentPState != pstate) {
102 		entry->fLastUpdate = system_time();
103 		entry->fCurrentPState = pstate;
104 
105 		x86_write_msr(IA32_MSR_PERF_CTL, pstate << 8);
106 	}
107 }
108 
109 
110 static status_t
111 pstates_increase_performance(int delta)
112 {
113 	CPUEntry* entry = &sCPUEntries[smp_get_current_cpu()];
114 
115 	if (sHWPActive)
116 		return B_NOT_SUPPORTED;
117 
118 	if (system_time() - entry->fLastUpdate < kMinimalInterval)
119 		return B_OK;
120 
121 	int pState = measure_pstate(entry);
122 	pState += (sBoostPState - pState) * delta / kCPUPerformanceScaleMax;
123 
124 	if (sAvoidBoost && pState < (sMaxPState + sBoostPState) / 2)
125 		pState = min_c(pState, sMaxPState);
126 
127 	set_pstate(pState);
128 	return B_OK;
129 }
130 
131 
132 static status_t
133 pstates_decrease_performance(int delta)
134 {
135 	CPUEntry* entry = &sCPUEntries[smp_get_current_cpu()];
136 
137 	if (sHWPActive)
138 		return B_NOT_SUPPORTED;
139 
140 	if (system_time() - entry->fLastUpdate < kMinimalInterval)
141 		return B_OK;
142 
143 	int pState = measure_pstate(entry);
144 	pState -= (pState - sMinPState) * delta / kCPUPerformanceScaleMax;
145 
146 	set_pstate(pState);
147 	return B_OK;
148 }
149 
150 
151 static bool
152 is_cpu_model_supported(cpu_ent* cpu)
153 {
154 	uint8 model = cpu->arch.model + (cpu->arch.extended_model << 4);
155 
156 	if (cpu->arch.vendor != VENDOR_INTEL)
157 		return false;
158 
159 	if (cpu->arch.family != 6)
160 		return false;
161 
162 	if (x86_check_feature(IA32_FEATURE_HWP, FEATURE_6_EAX))
163 		return true;
164 
165 	const uint8 kSupportedFamily6Models[] = {
166 		0x2a, 0x2d, 0x3a, 0x3c, 0x3d, 0x3e, 0x3f, 0x45, 0x46, 0x47, 0x4a,
167 		0x4d, 0x4e, 0x4f, 0x55, 0x56, 0x57, 0x5a, 0x5c, 0x5e, 0x5f, 0x75,
168 		0x7a, 0x85
169 	};
170 	/* TODO: support Atom Silvermont and Airmont: 0x37, 0x4c */
171 	const int kSupportedFamily6ModelsCount
172 		= sizeof(kSupportedFamily6Models) / sizeof(uint8);
173 
174 	int i;
175 	for (i = 0; i < kSupportedFamily6ModelsCount; i++) {
176 		if (model == kSupportedFamily6Models[i])
177 			break;
178 	}
179 
180 	return i != kSupportedFamily6ModelsCount;
181 }
182 
183 
184 static void
185 set_normal_pstate(void* /* dummy */, int cpu)
186 {
187 	if (sHWPActive) {
188 		if (x86_check_feature(IA32_FEATURE_HWP_NOTIFY, FEATURE_6_EAX))
189 			x86_write_msr(IA32_MSR_HWP_INTERRUPT, 0);
190 		x86_write_msr(IA32_MSR_PM_ENABLE, 1);
191 		uint64 hwpRequest = x86_read_msr(IA32_MSR_HWP_REQUEST);
192 		uint64 caps = x86_read_msr(IA32_MSR_HWP_CAPABILITIES);
193 		sHWPLowest = IA32_HWP_CAPS_LOWEST_PERFORMANCE(caps);
194 		sHWPEfficient = IA32_HWP_CAPS_EFFICIENT_PERFORMANCE(caps);
195 		sHWPGuaranteed = IA32_HWP_CAPS_GUARANTEED_PERFORMANCE(caps);
196 		sHWPHighest = IA32_HWP_CAPS_HIGHEST_PERFORMANCE(caps);
197 
198 		hwpRequest &= ~IA32_HWP_REQUEST_DESIRED_PERFORMANCE;
199 		hwpRequest &= ~IA32_HWP_REQUEST_ACTIVITY_WINDOW;
200 
201 		hwpRequest &= ~IA32_HWP_REQUEST_MINIMUM_PERFORMANCE;
202 		hwpRequest |= sHWPLowest;
203 
204 		hwpRequest &= ~IA32_HWP_REQUEST_MAXIMUM_PERFORMANCE;
205 		hwpRequest |= sHWPHighest << 8;
206 
207 		if (x86_check_feature(IA32_FEATURE_HWP_EPP, FEATURE_6_EAX)) {
208 			hwpRequest &= ~IA32_HWP_REQUEST_ENERGY_PERFORMANCE_PREFERENCE;
209 			hwpRequest |= (sAvoidBoost ? 0x80ULL : 0x0ULL) << 24;
210 		} else if (x86_check_feature(IA32_FEATURE_EPB, FEATURE_6_ECX)) {
211 			uint64 perfBias = x86_read_msr(IA32_MSR_ENERGY_PERF_BIAS);
212 			perfBias &= ~(0xfULL << 0);
213 			perfBias |= (sAvoidBoost ? 0xfULL : 0x0ULL) << 0;
214 			x86_write_msr(IA32_MSR_ENERGY_PERF_BIAS, perfBias);
215 		}
216 
217 		if (sHWPPackage) {
218 			x86_write_msr(IA32_MSR_HWP_REQUEST, hwpRequest
219 				| IA32_HWP_REQUEST_PACKAGE_CONTROL);
220 			x86_write_msr(IA32_MSR_HWP_REQUEST_PKG, hwpRequest);
221 		} else
222 			x86_write_msr(IA32_MSR_HWP_REQUEST, hwpRequest);
223 	} else {
224 		measure_pstate(&sCPUEntries[cpu]);
225 		set_pstate(sMaxPState);
226 	}
227 }
228 
229 
230 static status_t
231 init_pstates()
232 {
233 	if (!x86_check_feature(IA32_FEATURE_MSR, FEATURE_COMMON))
234 		return B_ERROR;
235 
236 	if (!x86_check_feature(IA32_FEATURE_APERFMPERF, FEATURE_6_ECX))
237 		return B_ERROR;
238 
239 	int32 cpuCount = smp_get_num_cpus();
240 	for (int32 i = 0; i < cpuCount; i++) {
241 		if (!is_cpu_model_supported(&gCPU[i]))
242 			return B_ERROR;
243 	}
244 
245 	uint64 platformInfo = x86_read_msr(IA32_MSR_PLATFORM_INFO);
246 	sHWPEPP = x86_check_feature(IA32_FEATURE_HWP_EPP, FEATURE_6_EAX);
247 	sHWPActive = (x86_check_feature(IA32_FEATURE_HWP, FEATURE_6_EAX)
248 		&& sHWPEPP);
249 	sMinPState = (platformInfo >> 40) & 0xff;
250 	sMaxPState = (platformInfo >> 8) & 0xff;
251 	sBoostPState
252 		= max_c(x86_read_msr(IA32_MSR_TURBO_RATIO_LIMIT) & 0xff, sMaxPState);
253 	/* x86_check_feature(IA32_FEATURE_HWP_PLR, FEATURE_6_EAX)) */
254 	sHWPPackage = false;
255 
256 	dprintf("using Intel P-States: min %" B_PRIu16 ", max %" B_PRIu16
257 		", boost %" B_PRIu16 "%s\n", sMinPState, sMaxPState, sBoostPState,
258 		sHWPActive ? ", HWP active" : "");
259 
260 	if (sMaxPState <= sMinPState || sMaxPState == 0) {
261 		dprintf("unexpected or invalid Intel P-States limits, aborting\n");
262 		return B_ERROR;
263 	}
264 
265 	sCPUEntries = new(std::nothrow) CPUEntry[cpuCount];
266 	if (sCPUEntries == NULL)
267 		return B_NO_MEMORY;
268 
269 	pstates_set_scheduler_mode(SCHEDULER_MODE_LOW_LATENCY);
270 
271 	call_all_cpus_sync(set_normal_pstate, NULL);
272 	return B_OK;
273 }
274 
275 
276 static status_t
277 uninit_pstates()
278 {
279 	call_all_cpus_sync(set_normal_pstate, NULL);
280 	delete[] sCPUEntries;
281 
282 	return B_OK;
283 }
284 
285 
286 static status_t
287 std_ops(int32 op, ...)
288 {
289 	switch (op) {
290 		case B_MODULE_INIT:
291 			return init_pstates();
292 
293 		case B_MODULE_UNINIT:
294 			uninit_pstates();
295 			return B_OK;
296 	}
297 
298 	return B_ERROR;
299 }
300 
301 
302 static cpufreq_module_info sIntelPStates = {
303 	{
304 		INTEL_PSTATES_MODULE_NAME,
305 		0,
306 		std_ops,
307 	},
308 
309 	1.0f,
310 
311 	pstates_set_scheduler_mode,
312 
313 	pstates_increase_performance,
314 	pstates_decrease_performance,
315 };
316 
317 
318 module_info* modules[] = {
319 	(module_info*)&sIntelPStates,
320 	NULL
321 };
322 
323