xref: /haiku/src/add-ons/kernel/power/cpuidle/x86_cstates/x86_cstates.cpp (revision 344ded80d400028c8f561b4b876257b94c12db4a)
1 /*
2  * Copyright 2012-2013, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Yongcong Du <ycdu.vmcore@gmail.com>
7  *		Paweł Dziepak, <pdziepak@quarnos.org>
8  */
9 
10 
11 #include <arch_system_info.h>
12 #include <cpu.h>
13 #include <debug.h>
14 #include <smp.h>
15 #include <thread.h>
16 #include <util/AutoLock.h>
17 
18 #include <cpuidle.h>
19 #include <KernelExport.h>
20 
21 #include <stdio.h>
22 
23 
24 #define CPUIDLE_CSTATE_MAX			8
25 
26 #define MWAIT_INTERRUPTS_BREAK		(1 << 0)
27 
28 #define X86_CSTATES_MODULE_NAME	CPUIDLE_MODULES_PREFIX "/x86_cstates/v1"
29 
30 #define BASE_TIME_STEP				500
31 
32 struct CState {
33 	uint32	fCode;
34 	int		fSubStatesCount;
35 };
36 
37 static CState sCStates[CPUIDLE_CSTATE_MAX];
38 static int sCStateCount;
39 
40 static int sTimeStep = BASE_TIME_STEP;
41 static bool sEnableWait = false;
42 
43 static bigtime_t* sIdleTime;
44 
45 
46 static inline void
47 x86_monitor(void* address, uint32 ecx, uint32 edx)
48 {
49 	asm volatile("monitor" : : "a" (address), "c" (ecx), "d"(edx));
50 }
51 
52 
53 static inline void
54 x86_mwait(uint32 eax, uint32 ecx)
55 {
56 	asm volatile("mwait" : : "a" (eax), "c" (ecx));
57 }
58 
59 
60 static void
61 cstates_set_scheduler_mode(scheduler_mode mode)
62 {
63 	if (mode == SCHEDULER_MODE_POWER_SAVING) {
64 		sTimeStep = BASE_TIME_STEP / 4;
65 		sEnableWait = true;
66 	} else {
67 		sTimeStep = BASE_TIME_STEP;
68 		sEnableWait = false;
69 	}
70 }
71 
72 
73 static void
74 cstates_idle(void)
75 {
76 	ASSERT(thread_get_current_thread()->pinned_to_cpu > 0);
77 	int32 cpu = smp_get_current_cpu();
78 
79 	bigtime_t timeStep = sTimeStep;
80 	bigtime_t idleTime = sIdleTime[cpu];
81 	int state = min_c(idleTime / timeStep, sCStateCount - 1);
82 
83 	if(state < 0 || state >= sCStateCount) {
84 		panic("State %d of CPU %" B_PRId32 " is out of range (0 to %d), "
85 			"idleTime %" B_PRIdBIGTIME "/%" B_PRIdBIGTIME, state, cpu,
86 			sCStateCount, idleTime, timeStep);
87 	}
88 
89 	int subState = idleTime % timeStep;
90 	subState *= sCStates[state].fSubStatesCount;
91 	subState /= timeStep;
92 
93 	ASSERT(subState >= 0 && subState < sCStates[state].fSubStatesCount);
94 
95 	InterruptsLocker locker;
96 	int dummy;
97 	bigtime_t start = system_time();
98 	x86_monitor(&dummy, 0, 0);
99 	x86_mwait(sCStates[state].fCode | subState, MWAIT_INTERRUPTS_BREAK);
100 	bigtime_t delta = system_time() - start;
101 	locker.Unlock();
102 
103 	// Negative delta shouldn't happen, but apparently it does...
104 	if (delta >= 0)
105 		sIdleTime[cpu] = (idleTime + delta) / 2;
106 }
107 
108 
109 static void
110 cstates_wait(int32* variable, int32 test)
111 {
112 	if (!sEnableWait) {
113 		arch_cpu_pause();
114 		return;
115 	}
116 
117 	InterruptsLocker _;
118 	x86_monitor(variable, 0, 0);
119 	if (*variable != test)
120 		x86_mwait(sCStates[0].fCode, MWAIT_INTERRUPTS_BREAK);
121 }
122 
123 
124 static status_t
125 init_cstates()
126 {
127 	if (!x86_check_feature(IA32_FEATURE_EXT_MONITOR, FEATURE_EXT))
128 		return B_ERROR;
129 	if (!x86_check_feature(IA32_FEATURE_POWER_MWAIT, FEATURE_5_ECX))
130 		return B_ERROR;
131 	if (!x86_check_feature(IA32_FEATURE_INTERRUPT_MWAIT, FEATURE_5_ECX))
132 		return B_ERROR;
133 
134 	// we need invariant TSC
135 	if (!x86_check_feature(IA32_FEATURE_INVARIANT_TSC, FEATURE_EXT_7_EDX))
136 		return B_ERROR;
137 
138 	// get C-state data
139 	cpuid_info cpuid;
140 	get_current_cpuid(&cpuid, 0, 0);
141 	uint32 maxBasicLeaf = cpuid.eax_0.max_eax;
142 	if (maxBasicLeaf < 5)
143 		return B_ERROR;
144 
145 	get_current_cpuid(&cpuid, 5, 0);
146 	if ((cpuid.regs.eax & 0xffff) < sizeof(int32))
147 		return B_ERROR;
148 
149 	char cStates[64];
150 	unsigned int offset = 0;
151 	for (int32 i = 1; i < CPUIDLE_CSTATE_MAX; i++) {
152 		int32 subStates = (cpuid.regs.edx >> (i * 4)) & 0xf;
153 		// no sub-states means the state is not available
154 		if (subStates == 0)
155 			continue;
156 
157 		if (offset < sizeof(cStates)) {
158 			offset += snprintf(cStates + offset, sizeof(cStates) - offset,
159 					", C%" B_PRId32, i);
160 		}
161 
162 		sCStates[sCStateCount].fCode = sCStateCount * 0x10;
163 		sCStates[sCStateCount].fSubStatesCount = subStates;
164 		sCStateCount++;
165 	}
166 
167 	if (sCStateCount == 0)
168 		return B_ERROR;
169 
170 	sIdleTime = new(std::nothrow) bigtime_t[smp_get_num_cpus()];
171 	if (sIdleTime == NULL)
172 		return B_NO_MEMORY;
173 	memset(sIdleTime, 0, sizeof(bigtime_t) * smp_get_num_cpus());
174 
175 	cstates_set_scheduler_mode(SCHEDULER_MODE_LOW_LATENCY);
176 
177 	dprintf("using Intel C-States: C0%s\n", cStates);
178 	return B_OK;
179 }
180 
181 
182 static status_t
183 uninit_cstates()
184 {
185 	delete[] sIdleTime;
186 	return B_OK;
187 }
188 
189 
190 static status_t
191 std_ops(int32 op, ...)
192 {
193 	switch (op) {
194 		case B_MODULE_INIT:
195 			return init_cstates();
196 
197 		case B_MODULE_UNINIT:
198 			uninit_cstates();
199 			return B_OK;
200 	}
201 
202 	return B_ERROR;
203 }
204 
205 
206 static cpuidle_module_info sX86CStates = {
207 	{
208 		X86_CSTATES_MODULE_NAME,
209 		0,
210 		std_ops,
211 	},
212 
213 	0.8f,
214 
215 	cstates_set_scheduler_mode,
216 
217 	cstates_idle,
218 	cstates_wait
219 };
220 
221 
222 module_info* modules[] = {
223 	(module_info*)&sX86CStates,
224 	NULL
225 };
226 
227