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