xref: /haiku/src/system/kernel/arch/x86/timers/x86_hpet.cpp (revision 820dca4df6c7bf955c46e8f6521b9408f50b2900)
1 /*
2  * Copyright 2009-2010, Stefano Ceccherini (stefano.ceccherini@gmail.com)
3  * Copyright 2008, Dustin Howett, dustin.howett@gmail.com. All rights reserved.
4  * Distributed under the terms of the MIT License.
5  */
6 
7 #include <debug.h>
8 #include <int.h>
9 #include <smp.h>
10 #include <timer.h>
11 
12 #include <arch/int.h>
13 #include <arch/cpu.h>
14 #include <arch/x86/timer.h>
15 #include <arch/x86/arch_hpet.h>
16 
17 #include <boot/kernel_args.h>
18 #include <vm/vm.h>
19 
20 
21 //#define TRACE_HPET
22 #ifdef TRACE_HPET
23 	#define TRACE(x) dprintf x
24 #else
25 	#define TRACE(x) ;
26 #endif
27 
28 #define TEST_HPET
29 
30 static struct hpet_regs *sHPETRegs;
31 static volatile struct hpet_timer *sTimer;
32 static uint64 sHPETPeriod;
33 
34 static int hpet_get_priority();
35 static status_t hpet_set_hardware_timer(bigtime_t relativeTimeout);
36 static status_t hpet_clear_hardware_timer();
37 static status_t hpet_init(struct kernel_args *args);
38 
39 
40 struct timer_info gHPETTimer = {
41 	"HPET",
42 	&hpet_get_priority,
43 	&hpet_set_hardware_timer,
44 	&hpet_clear_hardware_timer,
45 	&hpet_init
46 };
47 
48 
49 static int
50 hpet_get_priority()
51 {
52 	// TODO: Fix HPET in SMP mode.
53 	if (smp_get_num_cpus() > 1)
54 		return 0;
55 
56 	// HPET timers, being off-chip, are more expensive to setup
57 	// than the LAPIC.
58 	return 0;
59 }
60 
61 
62 static int32
63 hpet_timer_interrupt(void *arg)
64 {
65 	//dprintf_no_syslog("HPET timer_interrupt!!!!\n");
66 	return timer_interrupt();
67 }
68 
69 
70 static inline bigtime_t
71 hpet_convert_timeout(const bigtime_t &relativeTimeout)
72 {
73 	return ((relativeTimeout * 1000000000ULL)
74 		/ sHPETPeriod) + sHPETRegs->u0.counter64;
75 }
76 
77 
78 #define MIN_TIMEOUT 3000
79 
80 static status_t
81 hpet_set_hardware_timer(bigtime_t relativeTimeout)
82 {
83 	cpu_status state = disable_interrupts();
84 
85 	// enable timer interrupt
86 	sTimer->config |= HPET_CONF_TIMER_INT_ENABLE;
87 
88 	// TODO:
89 	if (relativeTimeout < MIN_TIMEOUT)
90 		relativeTimeout = MIN_TIMEOUT;
91 
92 	bigtime_t timerValue = hpet_convert_timeout(relativeTimeout);
93 
94 	sTimer->u0.comparator64 = timerValue;
95 
96 	restore_interrupts(state);
97 
98 	return B_OK;
99 }
100 
101 
102 static status_t
103 hpet_clear_hardware_timer()
104 {
105 	// Disable timer interrupt
106 	sTimer->config &= ~HPET_CONF_TIMER_INT_ENABLE;
107 	return B_OK;
108 }
109 
110 
111 static status_t
112 hpet_set_enabled(bool enabled)
113 {
114 	if (enabled)
115 		sHPETRegs->config |= HPET_CONF_MASK_ENABLED;
116 	else
117 		sHPETRegs->config &= ~HPET_CONF_MASK_ENABLED;
118 	return B_OK;
119 }
120 
121 
122 static status_t
123 hpet_set_legacy(bool enabled)
124 {
125 	if (!HPET_IS_LEGACY_CAPABLE(sHPETRegs)) {
126 		dprintf("hpet_init: HPET doesn't support legacy mode. Skipping.\n");
127 		return B_NOT_SUPPORTED;
128 	}
129 
130 	if (enabled)
131 		sHPETRegs->config |= HPET_CONF_MASK_LEGACY;
132 	else
133 		sHPETRegs->config &= ~HPET_CONF_MASK_LEGACY;
134 
135 	return B_OK;
136 }
137 
138 
139 #ifdef TRACE_HPET
140 static void
141 hpet_dump_timer(volatile struct hpet_timer *timer)
142 {
143 	dprintf("HPET Timer %ld:\n", (timer - sHPETRegs->timer));
144 
145 	dprintf("\troutable IRQs: ");
146 	uint32 interrupts = (uint32)HPET_GET_CAP_TIMER_ROUTE(timer);
147 	for (int i = 0; i < 32; i++) {
148 		if (interrupts & (1 << i))
149 			dprintf("%d ", i);
150 	}
151 	dprintf("\n");
152 	dprintf("\tconfiguration: 0x%llx\n", timer->config);
153 	dprintf("\tFSB Enabled: %s\n",
154 		timer->config & HPET_CONF_TIMER_FSB_ENABLE ? "Yes" : "No");
155 	dprintf("\tInterrupt Enabled: %s\n",
156 		timer->config & HPET_CONF_TIMER_INT_ENABLE ? "Yes" : "No");
157 	dprintf("\tTimer type: %s\n",
158 		timer->config & HPET_CONF_TIMER_TYPE ? "Periodic" : "OneShot");
159 	dprintf("\tInterrupt Type: %s\n",
160 		timer->config & HPET_CONF_TIMER_INT_TYPE ? "Level" : "Edge");
161 
162 	dprintf("\tconfigured IRQ: %lld\n",
163 		HPET_GET_CONF_TIMER_INT_ROUTE(timer));
164 
165 	if (timer->config & HPET_CONF_TIMER_FSB_ENABLE) {
166 		dprintf("\tfsb_route[0]: 0x%llx\n", timer->fsb_route[0]);
167 		dprintf("\tfsb_route[1]: 0x%llx\n", timer->fsb_route[1]);
168 	}
169 }
170 #endif
171 
172 
173 static void
174 hpet_init_timer(volatile struct hpet_timer *timer)
175 {
176 	sTimer = timer;
177 
178 	uint32 interrupt = 0;
179 
180 	sTimer->config |= (interrupt << HPET_CONF_TIMER_INT_ROUTE_SHIFT)
181 		& HPET_CONF_TIMER_INT_ROUTE_MASK;
182 
183 	// Non-periodic mode, edge triggered
184 	sTimer->config &= ~(HPET_CONF_TIMER_TYPE | HPET_CONF_TIMER_INT_TYPE);
185 
186 	sTimer->config &= ~HPET_CONF_TIMER_FSB_ENABLE;
187 	sTimer->config &= ~HPET_CONF_TIMER_32MODE;
188 
189 	// Enable timer
190 	sTimer->config |= HPET_CONF_TIMER_INT_ENABLE;
191 
192 #ifdef TRACE_HPET
193 	hpet_dump_timer(sTimer);
194 #endif
195 }
196 
197 
198 static status_t
199 hpet_test()
200 {
201 	uint64 initialValue = sHPETRegs->u0.counter64;
202 	spin(10);
203 	uint64 finalValue = sHPETRegs->u0.counter64;
204 
205 	if (initialValue == finalValue) {
206 		dprintf("hpet_test: counter does not increment\n");
207 		return B_ERROR;
208 	}
209 
210 	return B_OK;
211 }
212 
213 
214 static status_t
215 hpet_init(struct kernel_args *args)
216 {
217 	/* hpet_acpi_probe() through a similar "scan spots" table
218 	   to that of smp.cpp.
219 	   Seems to be the most elegant solution right now. */
220 	if (args->arch_args.hpet == NULL)
221 		return B_ERROR;
222 
223 	if (sHPETRegs == NULL) {
224 		sHPETRegs = (struct hpet_regs *)args->arch_args.hpet.Pointer();
225 		if (vm_map_physical_memory(B_SYSTEM_TEAM, "hpet",
226 			(void **)&sHPETRegs, B_EXACT_ADDRESS, B_PAGE_SIZE,
227 			B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
228 			(phys_addr_t)args->arch_args.hpet_phys, true) < B_OK) {
229 			// Would it be better to panic here?
230 			dprintf("hpet_init: Failed to map memory for the HPET registers.");
231 			return B_ERROR;
232 		}
233 	}
234 
235 	sHPETPeriod = HPET_GET_PERIOD(sHPETRegs);
236 
237 	TRACE(("hpet_init: HPET is at %p.\n\tVendor ID: %llx, rev: %llx, period: %lld\n",
238 		sHPETRegs, HPET_GET_VENDOR_ID(sHPETRegs), HPET_GET_REVID(sHPETRegs),
239 		sHPETPeriod));
240 
241 	status_t status = hpet_set_enabled(false);
242 	if (status != B_OK)
243 		return status;
244 
245 	status = hpet_set_legacy(true);
246 	if (status != B_OK)
247 		return status;
248 
249 	uint32 numTimers = HPET_GET_NUM_TIMERS(sHPETRegs) + 1;
250 
251 	TRACE(("hpet_init: HPET supports %lu timers, and is %s bits wide.\n",
252 		numTimers, HPET_IS_64BIT(sHPETRegs) ? "64" : "32"));
253 
254 	TRACE(("hpet_init: configuration: 0x%llx, timer_interrupts: 0x%llx\n",
255 		sHPETRegs->config, sHPETRegs->interrupt_status));
256 
257 	if (numTimers < 3) {
258 		dprintf("hpet_init: HPET does not have at least 3 timers. Skipping.\n");
259 		return B_ERROR;
260 	}
261 
262 #ifdef TRACE_HPET
263 	for (uint32 c = 0; c < numTimers; c++)
264 		hpet_dump_timer(&sHPETRegs->timer[c]);
265 #endif
266 
267 	hpet_init_timer(&sHPETRegs->timer[0]);
268 
269 	status = hpet_set_enabled(true);
270 	if (status != B_OK)
271 		return status;
272 
273 #ifdef TEST_HPET
274 	status = hpet_test();
275 	if (status != B_OK)
276 		return status;
277 #endif
278 
279 	int32 configuredIRQ = HPET_GET_CONF_TIMER_INT_ROUTE(sTimer);
280 
281 	install_io_interrupt_handler(configuredIRQ, &hpet_timer_interrupt,
282 		NULL, B_NO_LOCK_VECTOR);
283 
284 	return status;
285 }
286 
287