xref: /xnu-11417.121.6/osfmk/i386/hpet.c (revision a1e26a70f38d1d7daa7b49b258e2f8538ad81650)
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 <architecture/i386/pio.h>
52 #include <i386/cpuid.h>
53 #include <i386/apic.h>
54 #include <i386/tsc.h>
55 #include <i386/hpet.h>
56 #include <i386/pmCPU.h>
57 #include <i386/cpu_topology.h>
58 #include <i386/cpu_threads.h>
59 #include <pexpert/device_tree.h>
60 
61 /* Decimal powers: */
62 #define kilo (1000ULL)
63 #define Mega (kilo * kilo)
64 #define Giga (kilo * Mega)
65 #define Tera (kilo * Giga)
66 #define Peta (kilo * Tera)
67 
68 vm_offset_t hpetArea = 0;
69 uint32_t hpetAreap = 0;
70 uint64_t hpetFemto = 0;
71 uint64_t hpetFreq = 0;
72 uint64_t hpetCvt = 0;                   /* (TAKE OUT LATER)  */
73 uint64_t hpetCvtt2n = 0;
74 uint64_t hpetCvtn2t = 0;
75 uint64_t tsc2hpet = 0;
76 uint64_t hpet2tsc = 0;
77 uint64_t bus2hpet = 0;
78 uint64_t hpet2bus = 0;
79 
80 vm_offset_t rcbaArea = 0;
81 uint32_t rcbaAreap = 0;
82 
83 static int (*hpet_req)(uint32_t apicid, void *arg, hpetRequest_t *hpet) = NULL;
84 static void *hpet_arg = NULL;
85 
86 #if DEBUG
87 #define DBG(x...)       kprintf("DBG: " x)
88 #else
89 #define DBG(x...)
90 #endif
91 
92 int
hpet_register_callback(int (* hpet_reqst)(uint32_t apicid,void * arg,hpetRequest_t * hpet),void * arg)93 hpet_register_callback(int (*hpet_reqst)(uint32_t apicid,
94     void *arg,
95     hpetRequest_t *hpet),
96     void *arg)
97 {
98 	hpet_req = hpet_reqst;
99 	hpet_arg = arg;
100 	return 0;
101 }
102 
103 /*
104  * This routine is called to obtain an HPET and have it assigned
105  * to a CPU.  It returns 0 if successful and non-zero if one could
106  * not be assigned.
107  */
108 int
hpet_request(uint32_t cpu)109 hpet_request(uint32_t cpu)
110 {
111 	hpetRequest_t       hpetReq;
112 	int                 rc;
113 	x86_lcpu_t          *lcpu;
114 	x86_core_t          *core;
115 	x86_pkg_t           *pkg;
116 	boolean_t           enabled;
117 
118 	if (hpet_req == NULL) {
119 		return -1;
120 	}
121 
122 	/*
123 	 * Deal with the case where the CPU # passed in is past the
124 	 * value specified in cpus=n in boot-args.
125 	 */
126 	if (cpu >= real_ncpus) {
127 		enabled = ml_set_interrupts_enabled(FALSE);
128 		lcpu = cpu_to_lcpu(cpu);
129 		if (lcpu != NULL) {
130 			core = lcpu->core;
131 			pkg  = core->package;
132 
133 			if (lcpu->primary) {
134 				pkg->flags |= X86PKG_FL_HAS_HPET;
135 			}
136 		}
137 
138 		ml_set_interrupts_enabled(enabled);
139 		return 0;
140 	}
141 
142 	rc = (*hpet_req)(ml_get_apicid(cpu), hpet_arg, &hpetReq);
143 	if (rc != 0) {
144 		return rc;
145 	}
146 
147 	enabled = ml_set_interrupts_enabled(FALSE);
148 	lcpu = cpu_to_lcpu(cpu);
149 	core = lcpu->core;
150 	pkg  = core->package;
151 
152 	/*
153 	 * Compute the address of the HPET.
154 	 */
155 	core->Hpet = (hpetTimer_t *)((uint8_t *)hpetArea + hpetReq.hpetOffset);
156 	core->HpetVec = hpetReq.hpetVector;
157 
158 	/*
159 	 * Enable interrupts
160 	 */
161 	core->Hpet->Config |= Tn_INT_ENB_CNF;
162 
163 	/*
164 	 * Save the configuration
165 	 */
166 	core->HpetCfg = core->Hpet->Config;
167 	core->HpetCmp = 0;
168 
169 	/*
170 	 * If the CPU is the "primary" for the package, then
171 	 * add the HPET to the package too.
172 	 */
173 	if (lcpu->primary) {
174 		pkg->Hpet = core->Hpet;
175 		pkg->HpetCfg = core->HpetCfg;
176 		pkg->HpetCmp = core->HpetCmp;
177 		pkg->flags |= X86PKG_FL_HAS_HPET;
178 	}
179 
180 	ml_set_interrupts_enabled(enabled);
181 
182 	return 0;
183 }
184 
185 /*
186  * Map the RCBA area.
187  */
188 static void
map_rcbaArea(void)189 map_rcbaArea(void)
190 {
191 	/*
192 	 * Get RCBA area physical address and map it
193 	 */
194 	outl(cfgAdr, lpcCfg | (0xF0 & 0xFC));
195 	rcbaAreap = inl(cfgDat | (0xF0 & 0x03));
196 	rcbaArea = ml_io_map(rcbaAreap & -4096, PAGE_SIZE * 4);
197 	kprintf("RCBA: vaddr = %lX, paddr = %08X\n", (unsigned long)rcbaArea, rcbaAreap);
198 }
199 
200 /*
201  * Initialize the HPET
202  */
203 void
hpet_init(void)204 hpet_init(void)
205 {
206 	unsigned int    *xmod;
207 
208 	map_rcbaArea();
209 
210 	/*
211 	 * Is the HPET memory already enabled?
212 	 * If not, set address and enable.
213 	 */
214 	xmod = (uint32_t *)(rcbaArea + 0x3404); /* Point to the HPTC */
215 	uint32_t hptc = *xmod;                  /* Get HPET config */
216 	DBG("    current RCBA.HPTC:  %08X\n", *xmod);
217 	if (!(hptc & hptcAE)) {
218 		DBG("HPET memory is not enabled, "
219 		    "enabling and assigning to 0xFED00000 (hope that's ok)\n");
220 		*xmod = (hptc & ~3) | hptcAE;
221 	}
222 
223 	/*
224 	 * Get physical address of HPET and map it.
225 	 */
226 	hpetAreap = hpetAddr | ((hptc & 3) << 12);
227 	hpetArea = ml_io_map(hpetAreap & -4096, PAGE_SIZE * 4);
228 	kprintf("HPET: vaddr = %lX, paddr = %08X\n", (unsigned long)hpetArea, hpetAreap);
229 
230 	/*
231 	 * Extract the HPET tick rate.
232 	 * The period of the HPET is reported in femtoseconds (10**-15s)
233 	 * and convert to frequency in hertz.
234 	 */
235 	hpetFemto = (uint32_t)(((hpetReg_t *)hpetArea)->GCAP_ID >> 32);
236 	hpetFreq = (1 * Peta) / hpetFemto;
237 
238 	/*
239 	 * The conversion factor is the number of nanoseconds per HPET tick
240 	 * with about 32 bits of fraction.  The value is converted to a
241 	 * base-2 fixed point number.  To convert from HPET to nanoseconds,
242 	 * multiply the value by the conversion factor using 96-bit arithmetic,
243 	 * then shift right 32 bits.  If the value is known to be small,
244 	 * 64-bit arithmetic will work.
245 	 */
246 
247 	/*
248 	 * Begin conversion of base 10 femtoseconds to base 2, calculate:
249 	 *  - HPET ticks to nanoseconds conversion in base 2 fraction (* 2**32)
250 	 *  - nanoseconds to HPET ticks conversion
251 	 */
252 	hpetCvtt2n = (uint64_t)hpetFemto << 32;
253 	hpetCvtt2n = hpetCvtt2n / 1000000ULL;
254 	hpetCvtn2t = 0xFFFFFFFFFFFFFFFFULL / hpetCvtt2n;
255 	kprintf("HPET: Frequency = %6d.%04dMHz, "
256 	    "cvtt2n = %08X.%08X, cvtn2t = %08X.%08X\n",
257 	    (uint32_t)(hpetFreq / Mega), (uint32_t)(hpetFreq % Mega),
258 	    (uint32_t)(hpetCvtt2n >> 32), (uint32_t)hpetCvtt2n,
259 	    (uint32_t)(hpetCvtn2t >> 32), (uint32_t)hpetCvtn2t);
260 
261 
262 	/* (TAKE OUT LATER)
263 	 * Begin conversion of base 10 femtoseconds to base 2
264 	 * HPET ticks to nanoseconds in base 2 fraction (times 1048576)
265 	 */
266 	hpetCvt = (uint64_t)hpetFemto << 20;
267 	hpetCvt = hpetCvt / 1000000ULL;
268 
269 	/* Calculate conversion from TSC to HPET */
270 	tsc2hpet = tmrCvt(tscFCvtt2n, hpetCvtn2t);
271 	DBG(" CVT: TSC to HPET = %08X.%08X\n",
272 	    (uint32_t)(tsc2hpet >> 32), (uint32_t)tsc2hpet);
273 
274 	/* Calculate conversion from HPET to TSC */
275 	hpet2tsc = tmrCvt(hpetCvtt2n, tscFCvtn2t);
276 	DBG(" CVT: HPET to TSC = %08X.%08X\n",
277 	    (uint32_t)(hpet2tsc >> 32), (uint32_t)hpet2tsc);
278 
279 	/* Calculate conversion from BUS to HPET */
280 	bus2hpet = tmrCvt(busFCvtt2n, hpetCvtn2t);
281 	DBG(" CVT: BUS to HPET = %08X.%08X\n",
282 	    (uint32_t)(bus2hpet >> 32), (uint32_t)bus2hpet);
283 
284 	/* Calculate conversion from HPET to BUS */
285 	hpet2bus = tmrCvt(hpetCvtt2n, busFCvtn2t);
286 	DBG(" CVT: HPET to BUS = %08X.%08X\n",
287 	    (uint32_t)(hpet2bus >> 32), (uint32_t)hpet2bus);
288 }
289 
290 /*
291  * This routine is used to get various information about the HPET
292  * without having to export gobs of globals.  It fills in a data
293  * structure with the info.
294  */
295 void
hpet_get_info(hpetInfo_t * info)296 hpet_get_info(hpetInfo_t *info)
297 {
298 	info->hpetCvtt2n = hpetCvtt2n;
299 	info->hpetCvtn2t = hpetCvtn2t;
300 	info->tsc2hpet   = tsc2hpet;
301 	info->hpet2tsc   = hpet2tsc;
302 	info->bus2hpet   = bus2hpet;
303 	info->hpet2bus   = hpet2bus;
304 	/*
305 	 * XXX
306 	 * We're repurposing the rcbaArea so we can use the HPET.
307 	 * Eventually we'll rename this correctly.
308 	 */
309 	info->rcbaArea   = hpetArea;
310 	info->rcbaAreap  = hpetAreap;
311 }
312 
313 
314 /*
315  * This routine is called by the HPET driver
316  * when it assigns an HPET timer to a processor.
317  *
318  * XXX with the new callback into the HPET driver,
319  * this routine will be deprecated.
320  */
321 void
ml_hpet_cfg(uint32_t cpu,uint32_t hpetVect)322 ml_hpet_cfg(uint32_t cpu, uint32_t hpetVect)
323 {
324 	uint64_t        *hpetVaddr;
325 	hpetTimer_t     *hpet;
326 	x86_lcpu_t      *lcpu;
327 	x86_core_t      *core;
328 	x86_pkg_t       *pkg;
329 	boolean_t       enabled;
330 
331 	if (cpu > 1) {
332 		panic("ml_hpet_cfg: invalid cpu = %d", cpu);
333 	}
334 
335 	lcpu = cpu_to_lcpu(cpu);
336 	core = lcpu->core;
337 	pkg  = core->package;
338 
339 	/*
340 	 * Only deal with the primary CPU for the package.
341 	 */
342 	if (!lcpu->primary) {
343 		return;
344 	}
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
HPETInterrupt(void)388 HPETInterrupt(void)
389 {
390 	/* All we do here is to bump the count */
391 	x86_package()->HpetInt++;
392 
393 	/*
394 	 * Let power management do it's thing.
395 	 */
396 	pmHPETInterrupt();
397 
398 	/* Return and show that the 'rupt has been handled... */
399 	return 1;
400 }
401 
402 
403 static hpetReg_t saved_hpet;
404 
405 void
hpet_save(void)406 hpet_save(void)
407 {
408 	hpetReg_t       *from = (hpetReg_t *) hpetArea;
409 	hpetReg_t       *to = &saved_hpet;
410 
411 	to->GEN_CONF  = from->GEN_CONF;
412 	to->TIM0_CONF = from->TIM0_CONF;
413 	to->TIM0_COMP = from->TIM0_COMP;
414 	to->TIM1_CONF = from->TIM1_CONF;
415 	to->TIM1_COMP = from->TIM1_COMP;
416 	to->TIM2_CONF = from->TIM2_CONF;
417 	to->TIM2_COMP = from->TIM2_COMP;
418 	to->MAIN_CNT  = from->MAIN_CNT;
419 }
420 
421 void
hpet_restore(void)422 hpet_restore(void)
423 {
424 	hpetReg_t       *from = &saved_hpet;
425 	hpetReg_t       *to = (hpetReg_t *) hpetArea;
426 
427 	/*
428 	 * Is the HPET memory already enabled?
429 	 * If not, set address and enable.
430 	 */
431 	uint32_t *hptcp = (uint32_t *)(rcbaArea + 0x3404);
432 	uint32_t hptc = *hptcp;
433 	if (!(hptc & hptcAE)) {
434 		DBG("HPET memory is not enabled, "
435 		    "enabling and assigning to 0xFED00000 (hope that's ok)\n");
436 		*hptcp = (hptc & ~3) | hptcAE;
437 	}
438 
439 	to->GEN_CONF  = from->GEN_CONF & ~1;
440 
441 	to->TIM0_CONF = from->TIM0_CONF;
442 	to->TIM0_COMP = from->TIM0_COMP;
443 	to->TIM1_CONF = from->TIM1_CONF;
444 	to->TIM1_COMP = from->TIM1_COMP;
445 	to->TIM2_CONF = from->TIM2_CONF;
446 	to->TIM2_COMP = from->TIM2_COMP;
447 	to->GINTR_STA = -1ULL;
448 	to->MAIN_CNT  = from->MAIN_CNT;
449 
450 	to->GEN_CONF = from->GEN_CONF;
451 }
452 
453 /*
454  *      Read the HPET timer
455  *
456  */
457 uint64_t
rdHPET(void)458 rdHPET(void)
459 {
460 	hpetReg_t               *hpetp = (hpetReg_t *) hpetArea;
461 	volatile uint32_t       *regp = (uint32_t *) &hpetp->MAIN_CNT;
462 	uint32_t                high;
463 	uint32_t                low;
464 
465 	do {
466 		high = *(regp + 1);
467 		low = *regp;
468 	} while (high != *(regp + 1));
469 
470 	return (((uint64_t) high) << 32) | low;
471 }
472