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