]>
Commit | Line | Data |
---|---|---|
b0d623f7 A |
1 | /* |
2 | * Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. The rights granted to you under the License | |
10 | * may not be used to create, or enable the creation or redistribution of, | |
11 | * unlawful or unlicensed copies of an Apple operating system, or to | |
12 | * circumvent, violate, or enable the circumvention or violation of, any | |
13 | * terms of an Apple operating system software license agreement. | |
14 | * | |
15 | * Please obtain a copy of the License at | |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. | |
17 | * | |
18 | * The Original Code and all software distributed under the License are | |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
23 | * Please see the License for the specific language governing rights and | |
24 | * limitations under the License. | |
25 | * | |
26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ | |
27 | */ | |
28 | ||
29 | #include <string.h> | |
30 | #include <mach/vm_param.h> | |
31 | #include <mach/vm_prot.h> | |
32 | #include <mach/machine.h> | |
33 | #include <mach/time_value.h> | |
34 | #include <kern/spl.h> | |
35 | #include <kern/assert.h> | |
36 | #include <kern/debug.h> | |
37 | #include <kern/misc_protos.h> | |
38 | #include <kern/startup.h> | |
39 | #include <kern/clock.h> | |
40 | #include <kern/cpu_data.h> | |
41 | #include <kern/processor.h> | |
42 | #include <vm/vm_page.h> | |
43 | #include <vm/pmap.h> | |
44 | #include <vm/vm_kern.h> | |
45 | #include <i386/cpuid.h> | |
46 | #include <i386/machine_cpu.h> | |
47 | #include <i386/mp.h> | |
48 | #include <i386/machine_routines.h> | |
49 | #include <i386/pmap.h> | |
50 | #include <i386/misc_protos.h> | |
51 | #include <i386/io_map_entries.h> | |
52 | #include <architecture/i386/pio.h> | |
53 | #include <i386/cpuid.h> | |
54 | #include <i386/apic.h> | |
55 | #include <i386/tsc.h> | |
56 | #include <i386/hpet.h> | |
57 | #include <i386/pmCPU.h> | |
58 | #include <i386/cpu_topology.h> | |
59 | #include <i386/cpu_threads.h> | |
60 | #include <pexpert/device_tree.h> | |
b0d623f7 A |
61 | |
62 | /* Decimal powers: */ | |
63 | #define kilo (1000ULL) | |
64 | #define Mega (kilo * kilo) | |
65 | #define Giga (kilo * Mega) | |
66 | #define Tera (kilo * Giga) | |
67 | #define Peta (kilo * Tera) | |
68 | ||
69 | vm_offset_t hpetArea = 0; | |
70 | uint32_t hpetAreap = 0; | |
71 | uint64_t hpetFemto = 0; | |
72 | uint64_t hpetFreq = 0; | |
73 | uint64_t hpetCvt = 0; /* (TAKE OUT LATER) */ | |
74 | uint64_t hpetCvtt2n = 0; | |
75 | uint64_t hpetCvtn2t = 0; | |
76 | uint64_t tsc2hpet = 0; | |
77 | uint64_t hpet2tsc = 0; | |
78 | uint64_t bus2hpet = 0; | |
79 | uint64_t hpet2bus = 0; | |
80 | ||
81 | vm_offset_t rcbaArea = 0; | |
82 | uint32_t rcbaAreap = 0; | |
83 | ||
84 | static int (*hpet_req)(uint32_t apicid, void *arg, hpetRequest_t *hpet) = NULL; | |
85 | static void *hpet_arg = NULL; | |
86 | ||
87 | #if DEBUG | |
88 | #define DBG(x...) kprintf("DBG: " x) | |
89 | #else | |
90 | #define DBG(x...) | |
91 | #endif | |
92 | ||
93 | int | |
94 | hpet_register_callback(int (*hpet_reqst)(uint32_t apicid, | |
95 | void *arg, | |
96 | hpetRequest_t *hpet), | |
97 | void *arg) | |
98 | { | |
99 | hpet_req = hpet_reqst; | |
100 | hpet_arg = arg; | |
101 | return(0); | |
102 | } | |
103 | ||
104 | /* | |
105 | * This routine is called to obtain an HPET and have it assigned | |
106 | * to a CPU. It returns 0 if successful and non-zero if one could | |
107 | * not be assigned. | |
108 | */ | |
109 | int | |
110 | hpet_request(uint32_t cpu) | |
111 | { | |
112 | hpetRequest_t hpetReq; | |
113 | int rc; | |
114 | x86_lcpu_t *lcpu; | |
115 | x86_core_t *core; | |
116 | x86_pkg_t *pkg; | |
117 | boolean_t enabled; | |
118 | ||
119 | if (hpet_req == NULL) { | |
120 | return(-1); | |
121 | } | |
122 | ||
123 | /* | |
124 | * Deal with the case where the CPU # passed in is past the | |
125 | * value specified in cpus=n in boot-args. | |
126 | */ | |
127 | if (cpu >= real_ncpus) { | |
128 | enabled = ml_set_interrupts_enabled(FALSE); | |
129 | lcpu = cpu_to_lcpu(cpu); | |
130 | if (lcpu != NULL) { | |
131 | core = lcpu->core; | |
132 | pkg = core->package; | |
133 | ||
134 | if (lcpu->primary) { | |
135 | pkg->flags |= X86PKG_FL_HAS_HPET; | |
136 | } | |
137 | } | |
138 | ||
139 | ml_set_interrupts_enabled(enabled); | |
140 | return(0); | |
141 | } | |
142 | ||
143 | rc = (*hpet_req)(ml_get_apicid(cpu), hpet_arg, &hpetReq); | |
144 | if (rc != 0) { | |
145 | return(rc); | |
146 | } | |
147 | ||
148 | enabled = ml_set_interrupts_enabled(FALSE); | |
149 | lcpu = cpu_to_lcpu(cpu); | |
150 | core = lcpu->core; | |
151 | pkg = core->package; | |
152 | ||
153 | /* | |
154 | * Compute the address of the HPET. | |
155 | */ | |
156 | core->Hpet = (hpetTimer_t *)((uint8_t *)hpetArea + hpetReq.hpetOffset); | |
157 | core->HpetVec = hpetReq.hpetVector; | |
158 | ||
159 | /* | |
160 | * Enable interrupts | |
161 | */ | |
162 | core->Hpet->Config |= Tn_INT_ENB_CNF; | |
163 | ||
164 | /* | |
165 | * Save the configuration | |
166 | */ | |
167 | core->HpetCfg = core->Hpet->Config; | |
168 | core->HpetCmp = 0; | |
169 | ||
170 | /* | |
171 | * If the CPU is the "primary" for the package, then | |
172 | * add the HPET to the package too. | |
173 | */ | |
174 | if (lcpu->primary) { | |
175 | pkg->Hpet = core->Hpet; | |
176 | pkg->HpetCfg = core->HpetCfg; | |
177 | pkg->HpetCmp = core->HpetCmp; | |
178 | pkg->flags |= X86PKG_FL_HAS_HPET; | |
179 | } | |
180 | ||
181 | ml_set_interrupts_enabled(enabled); | |
182 | ||
183 | return(0); | |
184 | } | |
185 | ||
186 | /* | |
187 | * Map the RCBA area. | |
188 | */ | |
189 | static void | |
190 | map_rcbaArea(void) | |
191 | { | |
192 | /* | |
193 | * Get RCBA area physical address and map it | |
194 | */ | |
195 | outl(cfgAdr, lpcCfg | (0xF0 & 0xFC)); | |
196 | rcbaAreap = inl(cfgDat | (0xF0 & 0x03)); | |
197 | rcbaArea = io_map_spec(rcbaAreap & -4096, PAGE_SIZE * 4, VM_WIMG_IO); | |
198 | kprintf("RCBA: vaddr = %lX, paddr = %08X\n", (unsigned long)rcbaArea, rcbaAreap); | |
199 | } | |
200 | ||
201 | /* | |
202 | * Initialize the HPET | |
203 | */ | |
204 | void | |
205 | hpet_init(void) | |
206 | { | |
207 | unsigned int *xmod; | |
208 | ||
209 | map_rcbaArea(); | |
210 | ||
211 | /* | |
212 | * Is the HPET memory already enabled? | |
213 | * If not, set address and enable. | |
214 | */ | |
215 | xmod = (uint32_t *)(rcbaArea + 0x3404); /* Point to the HPTC */ | |
216 | uint32_t hptc = *xmod; /* Get HPET config */ | |
217 | DBG(" current RCBA.HPTC: %08X\n", *xmod); | |
218 | if(!(hptc & hptcAE)) { | |
219 | DBG("HPET memory is not enabled, " | |
220 | "enabling and assigning to 0xFED00000 (hope that's ok)\n"); | |
221 | *xmod = (hptc & ~3) | hptcAE; | |
222 | } | |
223 | ||
224 | /* | |
225 | * Get physical address of HPET and map it. | |
226 | */ | |
227 | hpetAreap = hpetAddr | ((hptc & 3) << 12); | |
228 | hpetArea = io_map_spec(hpetAreap & -4096, PAGE_SIZE * 4, VM_WIMG_IO); | |
229 | kprintf("HPET: vaddr = %lX, paddr = %08X\n", (unsigned long)hpetArea, hpetAreap); | |
230 | ||
231 | /* | |
232 | * Extract the HPET tick rate. | |
233 | * The period of the HPET is reported in femtoseconds (10**-15s) | |
234 | * and convert to frequency in hertz. | |
235 | */ | |
236 | hpetFemto = (uint32_t)(((hpetReg_t *)hpetArea)->GCAP_ID >> 32); | |
237 | hpetFreq = (1 * Peta) / hpetFemto; | |
238 | ||
239 | /* | |
240 | * The conversion factor is the number of nanoseconds per HPET tick | |
241 | * with about 32 bits of fraction. The value is converted to a | |
242 | * base-2 fixed point number. To convert from HPET to nanoseconds, | |
243 | * multiply the value by the conversion factor using 96-bit arithmetic, | |
244 | * then shift right 32 bits. If the value is known to be small, | |
245 | * 64-bit arithmetic will work. | |
246 | */ | |
247 | ||
248 | /* | |
249 | * Begin conversion of base 10 femtoseconds to base 2, calculate: | |
250 | * - HPET ticks to nanoseconds conversion in base 2 fraction (* 2**32) | |
251 | * - nanoseconds to HPET ticks conversion | |
252 | */ | |
253 | hpetCvtt2n = (uint64_t)hpetFemto << 32; | |
254 | hpetCvtt2n = hpetCvtt2n / 1000000ULL; | |
255 | hpetCvtn2t = 0xFFFFFFFFFFFFFFFFULL / hpetCvtt2n; | |
256 | kprintf("HPET: Frequency = %6d.%04dMHz, " | |
257 | "cvtt2n = %08X.%08X, cvtn2t = %08X.%08X\n", | |
258 | (uint32_t)(hpetFreq / Mega), (uint32_t)(hpetFreq % Mega), | |
259 | (uint32_t)(hpetCvtt2n >> 32), (uint32_t)hpetCvtt2n, | |
260 | (uint32_t)(hpetCvtn2t >> 32), (uint32_t)hpetCvtn2t); | |
261 | ||
262 | ||
263 | /* (TAKE OUT LATER) | |
264 | * Begin conversion of base 10 femtoseconds to base 2 | |
265 | * HPET ticks to nanoseconds in base 2 fraction (times 1048576) | |
266 | */ | |
267 | hpetCvt = (uint64_t)hpetFemto << 20; | |
268 | hpetCvt = hpetCvt / 1000000ULL; | |
269 | ||
270 | /* Calculate conversion from TSC to HPET */ | |
271 | tsc2hpet = tmrCvt(tscFCvtt2n, hpetCvtn2t); | |
272 | DBG(" CVT: TSC to HPET = %08X.%08X\n", | |
273 | (uint32_t)(tsc2hpet >> 32), (uint32_t)tsc2hpet); | |
274 | ||
275 | /* Calculate conversion from HPET to TSC */ | |
276 | hpet2tsc = tmrCvt(hpetCvtt2n, tscFCvtn2t); | |
277 | DBG(" CVT: HPET to TSC = %08X.%08X\n", | |
278 | (uint32_t)(hpet2tsc >> 32), (uint32_t)hpet2tsc); | |
279 | ||
280 | /* Calculate conversion from BUS to HPET */ | |
281 | bus2hpet = tmrCvt(busFCvtt2n, hpetCvtn2t); | |
282 | DBG(" CVT: BUS to HPET = %08X.%08X\n", | |
283 | (uint32_t)(bus2hpet >> 32), (uint32_t)bus2hpet); | |
284 | ||
285 | /* Calculate conversion from HPET to BUS */ | |
286 | hpet2bus = tmrCvt(hpetCvtt2n, busFCvtn2t); | |
287 | DBG(" CVT: HPET to BUS = %08X.%08X\n", | |
288 | (uint32_t)(hpet2bus >> 32), (uint32_t)hpet2bus); | |
b0d623f7 A |
289 | } |
290 | ||
291 | /* | |
292 | * This routine is used to get various information about the HPET | |
293 | * without having to export gobs of globals. It fills in a data | |
294 | * structure with the info. | |
295 | */ | |
296 | void | |
297 | hpet_get_info(hpetInfo_t *info) | |
298 | { | |
299 | info->hpetCvtt2n = hpetCvtt2n; | |
300 | info->hpetCvtn2t = hpetCvtn2t; | |
301 | info->tsc2hpet = tsc2hpet; | |
302 | info->hpet2tsc = hpet2tsc; | |
303 | info->bus2hpet = bus2hpet; | |
304 | info->hpet2bus = hpet2bus; | |
305 | /* | |
306 | * XXX | |
307 | * We're repurposing the rcbaArea so we can use the HPET. | |
308 | * Eventually we'll rename this correctly. | |
309 | */ | |
310 | info->rcbaArea = hpetArea; | |
311 | info->rcbaAreap = hpetAreap; | |
312 | } | |
313 | ||
314 | ||
315 | /* | |
316 | * This routine is called by the HPET driver | |
317 | * when it assigns an HPET timer to a processor. | |
318 | * | |
319 | * XXX with the new callback into the HPET driver, | |
320 | * this routine will be deprecated. | |
321 | */ | |
322 | void | |
323 | ml_hpet_cfg(uint32_t cpu, uint32_t hpetVect) | |
324 | { | |
325 | uint64_t *hpetVaddr; | |
326 | hpetTimer_t *hpet; | |
327 | x86_lcpu_t *lcpu; | |
328 | x86_core_t *core; | |
329 | x86_pkg_t *pkg; | |
330 | boolean_t enabled; | |
331 | ||
332 | if(cpu > 1) { | |
333 | panic("ml_hpet_cfg: invalid cpu = %d\n", cpu); | |
334 | } | |
335 | ||
336 | lcpu = cpu_to_lcpu(cpu); | |
337 | core = lcpu->core; | |
338 | pkg = core->package; | |
339 | ||
340 | /* | |
341 | * Only deal with the primary CPU for the package. | |
342 | */ | |
343 | if (!lcpu->primary) | |
344 | return; | |
345 | ||
346 | enabled = ml_set_interrupts_enabled(FALSE); | |
347 | ||
348 | /* Calculate address of the HPET for this processor */ | |
349 | hpetVaddr = (uint64_t *)(((uintptr_t)&(((hpetReg_t *)hpetArea)->TIM1_CONF)) + (cpu << 5)); | |
350 | hpet = (hpetTimer_t *)hpetVaddr; | |
351 | ||
352 | DBG("ml_hpet_cfg: HPET for cpu %d at %p, vector = %d\n", | |
353 | cpu, hpetVaddr, hpetVect); | |
354 | ||
355 | /* Save the address and vector of the HPET for this processor */ | |
356 | core->Hpet = hpet; | |
357 | core->HpetVec = hpetVect; | |
358 | ||
359 | /* | |
360 | * Enable interrupts | |
361 | */ | |
362 | core->Hpet->Config |= Tn_INT_ENB_CNF; | |
363 | ||
364 | /* Save the configuration */ | |
365 | core->HpetCfg = core->Hpet->Config; | |
366 | core->HpetCmp = 0; | |
367 | ||
368 | /* | |
369 | * We're only doing this for the primary CPU, so go | |
370 | * ahead and add the HPET to the package too. | |
371 | */ | |
372 | pkg->Hpet = core->Hpet; | |
373 | pkg->HpetVec = core->HpetVec; | |
374 | pkg->HpetCfg = core->HpetCfg; | |
375 | pkg->HpetCmp = core->HpetCmp; | |
376 | pkg->flags |= X86PKG_FL_HAS_HPET; | |
377 | ||
378 | ml_set_interrupts_enabled(enabled); | |
379 | } | |
380 | ||
381 | /* | |
382 | * This is the HPET interrupt handler. | |
383 | * | |
384 | * It just hands off to the power management code so that the | |
385 | * appropriate things get done there. | |
386 | */ | |
387 | int | |
388 | HPETInterrupt(void) | |
389 | { | |
390 | ||
391 | /* All we do here is to bump the count */ | |
392 | x86_package()->HpetInt++; | |
393 | ||
394 | /* | |
395 | * Let power management do it's thing. | |
396 | */ | |
397 | pmHPETInterrupt(); | |
398 | ||
399 | /* Return and show that the 'rupt has been handled... */ | |
400 | return 1; | |
401 | } | |
402 | ||
403 | ||
404 | static hpetReg_t saved_hpet; | |
405 | ||
406 | void | |
407 | hpet_save(void) | |
408 | { | |
409 | hpetReg_t *from = (hpetReg_t *) hpetArea; | |
410 | hpetReg_t *to = &saved_hpet; | |
411 | ||
412 | to->GEN_CONF = from->GEN_CONF; | |
413 | to->TIM0_CONF = from->TIM0_CONF; | |
414 | to->TIM0_COMP = from->TIM0_COMP; | |
415 | to->TIM1_CONF = from->TIM1_CONF; | |
416 | to->TIM1_COMP = from->TIM1_COMP; | |
417 | to->TIM2_CONF = from->TIM2_CONF; | |
418 | to->TIM2_COMP = from->TIM2_COMP; | |
419 | to->MAIN_CNT = from->MAIN_CNT; | |
420 | } | |
421 | ||
422 | void | |
423 | hpet_restore(void) | |
424 | { | |
425 | hpetReg_t *from = &saved_hpet; | |
426 | hpetReg_t *to = (hpetReg_t *) hpetArea; | |
427 | ||
428 | /* | |
429 | * Is the HPET memory already enabled? | |
430 | * If not, set address and enable. | |
431 | */ | |
432 | uint32_t *hptcp = (uint32_t *)(rcbaArea + 0x3404); | |
433 | uint32_t hptc = *hptcp; | |
434 | if(!(hptc & hptcAE)) { | |
435 | DBG("HPET memory is not enabled, " | |
436 | "enabling and assigning to 0xFED00000 (hope that's ok)\n"); | |
437 | *hptcp = (hptc & ~3) | hptcAE; | |
438 | } | |
439 | ||
440 | to->GEN_CONF = from->GEN_CONF & ~1; | |
441 | ||
442 | to->TIM0_CONF = from->TIM0_CONF; | |
443 | to->TIM0_COMP = from->TIM0_COMP; | |
444 | to->TIM1_CONF = from->TIM1_CONF; | |
445 | to->TIM1_COMP = from->TIM1_COMP; | |
446 | to->TIM2_CONF = from->TIM2_CONF; | |
447 | to->TIM2_COMP = from->TIM2_COMP; | |
448 | to->GINTR_STA = -1ULL; | |
449 | to->MAIN_CNT = from->MAIN_CNT; | |
450 | ||
451 | to->GEN_CONF = from->GEN_CONF; | |
452 | } | |
453 | ||
454 | /* | |
455 | * Read the HPET timer | |
456 | * | |
457 | */ | |
458 | uint64_t | |
459 | rdHPET(void) | |
460 | { | |
461 | hpetReg_t *hpetp = (hpetReg_t *) hpetArea; | |
462 | volatile uint32_t *regp = (uint32_t *) &hpetp->MAIN_CNT; | |
463 | uint32_t high; | |
464 | uint32_t low; | |
465 | ||
466 | do { | |
467 | high = *(regp + 1); | |
468 | low = *regp; | |
469 | } while (high != *(regp + 1)); | |
470 | ||
471 | return (((uint64_t) high) << 32) | low; | |
472 | } |