xref: /illumos-gate/usr/src/uts/i86pc/io/pcplusmp/apic_timer.c (revision 69a119caa6570c7077699161b7c28b6ee9f8b0f4)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
23  */
24 /*
25  * Copyright (c) 2010, Intel Corporation.
26  * All rights reserved.
27  */
28 /*
29  * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
30  */
31 
32 #include <sys/time.h>
33 #include <sys/psm.h>
34 #include <sys/psm_common.h>
35 #include <sys/apic.h>
36 #include <sys/pit.h>
37 #include <sys/x86_archext.h>
38 #include <sys/archsystm.h>
39 #include <sys/machsystm.h>
40 #include <sys/cpuvar.h>
41 #include <sys/clock.h>
42 #include <sys/apic_timer.h>
43 
44 /*
45  * preferred apic timer mode, allow tuning from the /etc/system file.
46  */
47 int		apic_timer_preferred_mode = APIC_TIMER_MODE_DEADLINE;
48 
49 int		apic_oneshot = 0;
50 uint_t		apic_hertz_count;
51 uint_t		apic_nsec_per_intr = 0;
52 uint64_t	apic_ticks_per_SFnsecs;		/* # of ticks in SF nsecs */
53 
54 static int		apic_min_timer_ticks = 1; /* minimum timer tick */
55 static hrtime_t		apic_nsec_max;
56 
57 static void	periodic_timer_enable(void);
58 static void	periodic_timer_disable(void);
59 static void	periodic_timer_reprogram(hrtime_t);
60 static void	oneshot_timer_enable(void);
61 static void	oneshot_timer_disable(void);
62 static void	oneshot_timer_reprogram(hrtime_t);
63 static void	deadline_timer_enable(void);
64 static void	deadline_timer_disable(void);
65 static void	deadline_timer_reprogram(hrtime_t);
66 
67 extern int	apic_clkvect;
68 extern uint32_t	apic_divide_reg_init;
69 
70 /*
71  * apic timer data structure
72  */
73 typedef struct apic_timer {
74 	int	mode;
75 	void	(*apic_timer_enable_ops)(void);
76 	void	(*apic_timer_disable_ops)(void);
77 	void	(*apic_timer_reprogram_ops)(hrtime_t);
78 } apic_timer_t;
79 
80 static apic_timer_t	apic_timer;
81 
82 /*
83  * apic timer initialization
84  *
85  * For the one-shot mode request case, the function returns the
86  * resolution (in nanoseconds) for the hardware timer interrupt.
87  * If one-shot mode capability is not available, the return value
88  * will be 0.
89  */
90 int
91 apic_timer_init(int hertz)
92 {
93 	uint_t		apic_ticks = 0;
94 	uint_t		pit_ticks;
95 	int		ret, timer_mode;
96 	uint16_t	pit_ticks_adj;
97 	static int	firsttime = 1;
98 
99 	if (firsttime) {
100 		/* first time calibrate on CPU0 only */
101 
102 		apic_reg_ops->apic_write(APIC_DIVIDE_REG, apic_divide_reg_init);
103 		apic_reg_ops->apic_write(APIC_INIT_COUNT, APIC_MAXVAL);
104 		apic_ticks = apic_calibrate(apicadr, &pit_ticks_adj);
105 
106 		/* total number of PIT ticks corresponding to apic_ticks */
107 		pit_ticks = APIC_TIME_COUNT + pit_ticks_adj;
108 
109 		/*
110 		 * Determine the number of nanoseconds per APIC clock tick
111 		 * and then determine how many APIC ticks to interrupt at the
112 		 * desired frequency
113 		 * apic_ticks / (pitticks / PIT_HZ) = apic_ticks_per_s
114 		 * (apic_ticks * PIT_HZ) / pitticks = apic_ticks_per_s
115 		 * apic_ticks_per_ns = (apic_ticks * PIT_HZ) / (pitticks * 10^9)
116 		 * pic_ticks_per_SFns =
117 		 * (SF * apic_ticks * PIT_HZ) / (pitticks * 10^9)
118 		 */
119 		apic_ticks_per_SFnsecs = ((SF * apic_ticks * PIT_HZ) /
120 		    ((uint64_t)pit_ticks * NANOSEC));
121 
122 		/* the interval timer initial count is 32 bit max */
123 		apic_nsec_max = APIC_TICKS_TO_NSECS(APIC_MAXVAL);
124 		firsttime = 0;
125 	}
126 
127 	if (hertz == 0) {
128 		/* requested one_shot */
129 
130 		/*
131 		 * return 0 if TSC is not supported.
132 		 */
133 		if (!tsc_gethrtime_enable)
134 			return (0);
135 		/*
136 		 * return 0 if one_shot is not preferred.
137 		 * here, APIC_TIMER_DEADLINE is also an one_shot mode.
138 		 */
139 		if ((apic_timer_preferred_mode != APIC_TIMER_MODE_ONESHOT) &&
140 		    (apic_timer_preferred_mode != APIC_TIMER_MODE_DEADLINE))
141 			return (0);
142 
143 		apic_oneshot = 1;
144 		ret = (int)APIC_TICKS_TO_NSECS(1);
145 		if ((apic_timer_preferred_mode == APIC_TIMER_MODE_DEADLINE) &&
146 		    cpuid_deadline_tsc_supported()) {
147 			timer_mode = APIC_TIMER_MODE_DEADLINE;
148 		} else {
149 			timer_mode = APIC_TIMER_MODE_ONESHOT;
150 		}
151 	} else {
152 		/* periodic */
153 		apic_nsec_per_intr = NANOSEC / hertz;
154 		apic_hertz_count = APIC_NSECS_TO_TICKS(apic_nsec_per_intr);
155 
156 		/* program the local APIC to interrupt at the given frequency */
157 		apic_reg_ops->apic_write(APIC_INIT_COUNT, apic_hertz_count);
158 		apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
159 		    (apic_clkvect + APIC_BASE_VECT) | AV_PERIODIC);
160 		apic_oneshot = 0;
161 		timer_mode = APIC_TIMER_MODE_PERIODIC;
162 		ret = NANOSEC / hertz;
163 	}
164 
165 	/*
166 	 * initialize apic_timer data structure, install the timer ops
167 	 */
168 	apic_timer.mode = timer_mode;
169 	switch (timer_mode) {
170 	default:
171 		/* FALLTHROUGH */
172 	case APIC_TIMER_MODE_ONESHOT:
173 		apic_timer.apic_timer_enable_ops = oneshot_timer_enable;
174 		apic_timer.apic_timer_disable_ops = oneshot_timer_disable;
175 		apic_timer.apic_timer_reprogram_ops = oneshot_timer_reprogram;
176 		break;
177 
178 	case APIC_TIMER_MODE_PERIODIC:
179 		apic_timer.apic_timer_enable_ops = periodic_timer_enable;
180 		apic_timer.apic_timer_disable_ops = periodic_timer_disable;
181 		apic_timer.apic_timer_reprogram_ops = periodic_timer_reprogram;
182 		break;
183 
184 	case APIC_TIMER_MODE_DEADLINE:
185 		apic_timer.apic_timer_enable_ops = deadline_timer_enable;
186 		apic_timer.apic_timer_disable_ops = deadline_timer_disable;
187 		apic_timer.apic_timer_reprogram_ops = deadline_timer_reprogram;
188 		break;
189 	}
190 
191 	return (ret);
192 }
193 
194 /*
195  * periodic timer mode ops
196  */
197 /* periodic timer enable */
198 static void
199 periodic_timer_enable(void)
200 {
201 	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
202 	    (apic_clkvect + APIC_BASE_VECT) | AV_PERIODIC);
203 }
204 
205 /* periodic timer disable */
206 static void
207 periodic_timer_disable(void)
208 {
209 	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
210 	    (apic_clkvect + APIC_BASE_VECT) | AV_MASK);
211 }
212 
213 /* periodic timer reprogram */
214 static void
215 periodic_timer_reprogram(hrtime_t time)
216 {
217 	uint_t	ticks;
218 	/* time is the interval for periodic mode */
219 	ticks = APIC_NSECS_TO_TICKS(time);
220 
221 	if (ticks < apic_min_timer_ticks)
222 		ticks = apic_min_timer_ticks;
223 
224 	apic_reg_ops->apic_write(APIC_INIT_COUNT, ticks);
225 }
226 
227 /*
228  * oneshot timer mode ops
229  */
230 /* oneshot timer enable */
231 static void
232 oneshot_timer_enable(void)
233 {
234 	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
235 	    (apic_clkvect + APIC_BASE_VECT));
236 }
237 
238 /* oneshot timer disable */
239 static void
240 oneshot_timer_disable(void)
241 {
242 	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
243 	    (apic_clkvect + APIC_BASE_VECT) | AV_MASK);
244 }
245 
246 /* oneshot timer reprogram */
247 static void
248 oneshot_timer_reprogram(hrtime_t time)
249 {
250 	hrtime_t	now;
251 	int64_t		delta;
252 	uint_t		ticks;
253 
254 	now = gethrtime();
255 	delta = time - now;
256 
257 	if (delta <= 0) {
258 		/*
259 		 * requested to generate an interrupt in the past
260 		 * generate an interrupt as soon as possible
261 		 */
262 		ticks = apic_min_timer_ticks;
263 	} else if (delta > apic_nsec_max) {
264 		/*
265 		 * requested to generate an interrupt at a time
266 		 * further than what we are capable of. Set to max
267 		 * the hardware can handle
268 		 */
269 		ticks = APIC_MAXVAL;
270 #ifdef DEBUG
271 		cmn_err(CE_CONT, "apic_timer_reprogram, request at"
272 		    "  %lld  too far in future, current time"
273 		    "  %lld \n", time, now);
274 #endif
275 	} else {
276 		ticks = APIC_NSECS_TO_TICKS(delta);
277 	}
278 
279 	if (ticks < apic_min_timer_ticks)
280 		ticks = apic_min_timer_ticks;
281 
282 	apic_reg_ops->apic_write(APIC_INIT_COUNT, ticks);
283 }
284 
285 /*
286  * deadline timer mode ops
287  */
288 /* deadline timer enable */
289 static void
290 deadline_timer_enable(void)
291 {
292 	uint64_t ticks;
293 
294 	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
295 	    (apic_clkvect + APIC_BASE_VECT) | AV_DEADLINE);
296 	/*
297 	 * Now we have to serialize this per the SDM.  That is to
298 	 * say, the above enabling can race in the pipeline with
299 	 * changes to the MSR.  We need to make sure the above
300 	 * operation is complete before we proceed to reprogram
301 	 * the deadline value in reprogram().  The algorithm
302 	 * recommended by the Intel SDM 3A in 10.5.1.4 is:
303 	 *
304 	 * a) write a big value to the deadline register
305 	 * b) read the register back
306 	 * c) if it reads zero, go back to a and try again
307 	 */
308 
309 	do {
310 		/* write a really big value */
311 		wrmsr(IA32_DEADLINE_TSC_MSR, 1ULL << 63);
312 		ticks = rdmsr(IA32_DEADLINE_TSC_MSR);
313 	} while (ticks == 0);
314 }
315 
316 /* deadline timer disable */
317 static void
318 deadline_timer_disable(void)
319 {
320 	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
321 	    (apic_clkvect + APIC_BASE_VECT) | AV_MASK);
322 }
323 
324 /* deadline timer reprogram */
325 static void
326 deadline_timer_reprogram(hrtime_t time)
327 {
328 	int64_t		delta;
329 	uint64_t	ticks;
330 
331 	/*
332 	 * Note that this entire routine is called with
333 	 * CBE_HIGH_PIL, so we needn't worry about preemption.
334 	 */
335 	delta = time - gethrtime();
336 
337 	/* The unscalehrtime wants unsigned values. */
338 	delta = max(delta, 0);
339 
340 	/* Now we shouldn't be interrupted, we can set the deadline */
341 	ticks = (uint64_t)tsc_read() + unscalehrtime(delta);
342 	wrmsr(IA32_DEADLINE_TSC_MSR, ticks);
343 }
344 
345 /*
346  * This function will reprogram the timer.
347  *
348  * When in oneshot mode the argument is the absolute time in future to
349  * generate the interrupt at.
350  *
351  * When in periodic mode, the argument is the interval at which the
352  * interrupts should be generated. There is no need to support the periodic
353  * mode timer change at this time.
354  */
355 void
356 apic_timer_reprogram(hrtime_t time)
357 {
358 	/*
359 	 * we should be Called from high PIL context (CBE_HIGH_PIL),
360 	 * so kpreempt is disabled.
361 	 */
362 	apic_timer.apic_timer_reprogram_ops(time);
363 }
364 
365 /*
366  * This function will enable timer interrupts.
367  */
368 void
369 apic_timer_enable(void)
370 {
371 	/*
372 	 * we should be Called from high PIL context (CBE_HIGH_PIL),
373 	 * so kpreempt is disabled.
374 	 */
375 	apic_timer.apic_timer_enable_ops();
376 }
377 
378 /*
379  * This function will disable timer interrupts.
380  */
381 void
382 apic_timer_disable(void)
383 {
384 	/*
385 	 * we should be Called from high PIL context (CBE_HIGH_PIL),
386 	 * so kpreempt is disabled.
387 	 */
388 	apic_timer.apic_timer_disable_ops();
389 }
390 
391 /*
392  * Set timer far into the future and return timer
393  * current count in nanoseconds.
394  */
395 hrtime_t
396 apic_timer_stop_count(void)
397 {
398 	hrtime_t	ns_val;
399 	int		enable_val, count_val;
400 
401 	/*
402 	 * Should be called with interrupts disabled.
403 	 */
404 	ASSERT(!interrupts_enabled());
405 
406 	enable_val = apic_reg_ops->apic_read(APIC_LOCAL_TIMER);
407 	if ((enable_val & AV_MASK) == AV_MASK)
408 		return ((hrtime_t)-1);	/* timer is disabled */
409 
410 	count_val = apic_reg_ops->apic_read(APIC_CURR_COUNT);
411 	ns_val = APIC_TICKS_TO_NSECS(count_val);
412 
413 	apic_reg_ops->apic_write(APIC_INIT_COUNT, APIC_MAXVAL);
414 
415 	return (ns_val);
416 }
417 
418 /*
419  * Reprogram timer after Deep C-State.
420  */
421 void
422 apic_timer_restart(hrtime_t time)
423 {
424 	apic_timer_reprogram(time);
425 }
426