xref: /illumos-gate/usr/src/uts/i86pc/io/pcplusmp/apic_timer.c (revision 6a634c9dca3093f3922e4b7ab826d7bdf17bf78e)
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 #include <sys/time.h>
30 #include <sys/psm.h>
31 #include <sys/psm_common.h>
32 #include <sys/apic.h>
33 #include <sys/pit.h>
34 #include <sys/x86_archext.h>
35 #include <sys/archsystm.h>
36 #include <sys/machsystm.h>
37 #include <sys/cpuvar.h>
38 #include <sys/clock.h>
39 #include <sys/apic_timer.h>
40 
41 /*
42  * preferred apic timer mode, allow tuning from the /etc/system file.
43  */
44 int		apic_timer_preferred_mode = APIC_TIMER_MODE_DEADLINE;
45 
46 int		apic_oneshot = 0;
47 uint_t		apic_hertz_count;
48 uint_t		apic_nsec_per_intr = 0;
49 uint64_t	apic_ticks_per_SFnsecs;		/* # of ticks in SF nsecs */
50 
51 static int		apic_min_timer_ticks = 1; /* minimum timer tick */
52 static hrtime_t		apic_nsec_max;
53 
54 static void	periodic_timer_enable(void);
55 static void	periodic_timer_disable(void);
56 static void	periodic_timer_reprogram(hrtime_t);
57 static void	oneshot_timer_enable(void);
58 static void	oneshot_timer_disable(void);
59 static void	oneshot_timer_reprogram(hrtime_t);
60 static void	deadline_timer_enable(void);
61 static void	deadline_timer_disable(void);
62 static void	deadline_timer_reprogram(hrtime_t);
63 
64 extern int	apic_clkvect;
65 extern uint32_t	apic_divide_reg_init;
66 
67 /*
68  * apic timer data structure
69  */
70 typedef struct apic_timer {
71 	int	mode;
72 	void	(*apic_timer_enable_ops)(void);
73 	void	(*apic_timer_disable_ops)(void);
74 	void	(*apic_timer_reprogram_ops)(hrtime_t);
75 } apic_timer_t;
76 
77 static apic_timer_t	apic_timer;
78 
79 /*
80  * apic timer initialization
81  *
82  * For the one-shot mode request case, the function returns the
83  * resolution (in nanoseconds) for the hardware timer interrupt.
84  * If one-shot mode capability is not available, the return value
85  * will be 0.
86  */
87 int
88 apic_timer_init(int hertz)
89 {
90 	uint_t		apic_ticks = 0;
91 	uint_t		pit_ticks;
92 	int		ret, timer_mode;
93 	uint16_t	pit_ticks_adj;
94 	static int	firsttime = 1;
95 
96 	if (firsttime) {
97 		/* first time calibrate on CPU0 only */
98 
99 		apic_reg_ops->apic_write(APIC_DIVIDE_REG, apic_divide_reg_init);
100 		apic_reg_ops->apic_write(APIC_INIT_COUNT, APIC_MAXVAL);
101 		apic_ticks = apic_calibrate(apicadr, &pit_ticks_adj);
102 
103 		/* total number of PIT ticks corresponding to apic_ticks */
104 		pit_ticks = APIC_TIME_COUNT + pit_ticks_adj;
105 
106 		/*
107 		 * Determine the number of nanoseconds per APIC clock tick
108 		 * and then determine how many APIC ticks to interrupt at the
109 		 * desired frequency
110 		 * apic_ticks / (pitticks / PIT_HZ) = apic_ticks_per_s
111 		 * (apic_ticks * PIT_HZ) / pitticks = apic_ticks_per_s
112 		 * apic_ticks_per_ns = (apic_ticks * PIT_HZ) / (pitticks * 10^9)
113 		 * pic_ticks_per_SFns =
114 		 * (SF * apic_ticks * PIT_HZ) / (pitticks * 10^9)
115 		 */
116 		apic_ticks_per_SFnsecs = ((SF * apic_ticks * PIT_HZ) /
117 		    ((uint64_t)pit_ticks * NANOSEC));
118 
119 		/* the interval timer initial count is 32 bit max */
120 		apic_nsec_max = APIC_TICKS_TO_NSECS(APIC_MAXVAL);
121 		firsttime = 0;
122 	}
123 
124 	if (hertz == 0) {
125 		/* requested one_shot */
126 
127 		/*
128 		 * return 0 if TSC is not supported.
129 		 */
130 		if (!tsc_gethrtime_enable)
131 			return (0);
132 		/*
133 		 * return 0 if one_shot is not preferred.
134 		 * here, APIC_TIMER_DEADLINE is also an one_shot mode.
135 		 */
136 		if ((apic_timer_preferred_mode != APIC_TIMER_MODE_ONESHOT) &&
137 		    (apic_timer_preferred_mode != APIC_TIMER_MODE_DEADLINE))
138 			return (0);
139 
140 		apic_oneshot = 1;
141 		ret = (int)APIC_TICKS_TO_NSECS(1);
142 		if ((apic_timer_preferred_mode == APIC_TIMER_MODE_DEADLINE) &&
143 		    cpuid_deadline_tsc_supported()) {
144 			timer_mode = APIC_TIMER_MODE_DEADLINE;
145 		} else {
146 			timer_mode = APIC_TIMER_MODE_ONESHOT;
147 		}
148 	} else {
149 		/* periodic */
150 		apic_nsec_per_intr = NANOSEC / hertz;
151 		apic_hertz_count = APIC_NSECS_TO_TICKS(apic_nsec_per_intr);
152 
153 		/* program the local APIC to interrupt at the given frequency */
154 		apic_reg_ops->apic_write(APIC_INIT_COUNT, apic_hertz_count);
155 		apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
156 		    (apic_clkvect + APIC_BASE_VECT) | AV_PERIODIC);
157 		apic_oneshot = 0;
158 		timer_mode = APIC_TIMER_MODE_PERIODIC;
159 		ret = NANOSEC / hertz;
160 	}
161 
162 	/*
163 	 * initialize apic_timer data structure, install the timer ops
164 	 */
165 	apic_timer.mode = timer_mode;
166 	switch (timer_mode) {
167 	default:
168 		/* FALLTHROUGH */
169 	case APIC_TIMER_MODE_ONESHOT:
170 		apic_timer.apic_timer_enable_ops = oneshot_timer_enable;
171 		apic_timer.apic_timer_disable_ops = oneshot_timer_disable;
172 		apic_timer.apic_timer_reprogram_ops = oneshot_timer_reprogram;
173 		break;
174 
175 	case APIC_TIMER_MODE_PERIODIC:
176 		apic_timer.apic_timer_enable_ops = periodic_timer_enable;
177 		apic_timer.apic_timer_disable_ops = periodic_timer_disable;
178 		apic_timer.apic_timer_reprogram_ops = periodic_timer_reprogram;
179 		break;
180 
181 	case APIC_TIMER_MODE_DEADLINE:
182 		apic_timer.apic_timer_enable_ops = deadline_timer_enable;
183 		apic_timer.apic_timer_disable_ops = deadline_timer_disable;
184 		apic_timer.apic_timer_reprogram_ops = deadline_timer_reprogram;
185 		break;
186 	}
187 
188 	return (ret);
189 }
190 
191 /*
192  * periodic timer mode ops
193  */
194 /* periodic timer enable */
195 static void
196 periodic_timer_enable(void)
197 {
198 	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
199 	    (apic_clkvect + APIC_BASE_VECT) | AV_PERIODIC);
200 }
201 
202 /* periodic timer disable */
203 static void
204 periodic_timer_disable(void)
205 {
206 	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
207 	    (apic_clkvect + APIC_BASE_VECT) | AV_MASK);
208 }
209 
210 /* periodic timer reprogram */
211 static void
212 periodic_timer_reprogram(hrtime_t time)
213 {
214 	uint_t	ticks;
215 	/* time is the interval for periodic mode */
216 	ticks = APIC_NSECS_TO_TICKS(time);
217 
218 	if (ticks < apic_min_timer_ticks)
219 		ticks = apic_min_timer_ticks;
220 
221 	apic_reg_ops->apic_write(APIC_INIT_COUNT, ticks);
222 }
223 
224 /*
225  * oneshot timer mode ops
226  */
227 /* oneshot timer enable */
228 static void
229 oneshot_timer_enable(void)
230 {
231 	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
232 	    (apic_clkvect + APIC_BASE_VECT));
233 }
234 
235 /* oneshot timer disable */
236 static void
237 oneshot_timer_disable(void)
238 {
239 	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
240 	    (apic_clkvect + APIC_BASE_VECT) | AV_MASK);
241 }
242 
243 /* oneshot timer reprogram */
244 static void
245 oneshot_timer_reprogram(hrtime_t time)
246 {
247 	hrtime_t	now;
248 	int64_t		delta;
249 	uint_t		ticks;
250 
251 	now = gethrtime();
252 	delta = time - now;
253 
254 	if (delta <= 0) {
255 		/*
256 		 * requested to generate an interrupt in the past
257 		 * generate an interrupt as soon as possible
258 		 */
259 		ticks = apic_min_timer_ticks;
260 	} else if (delta > apic_nsec_max) {
261 		/*
262 		 * requested to generate an interrupt at a time
263 		 * further than what we are capable of. Set to max
264 		 * the hardware can handle
265 		 */
266 		ticks = APIC_MAXVAL;
267 #ifdef DEBUG
268 		cmn_err(CE_CONT, "apic_timer_reprogram, request at"
269 		    "  %lld  too far in future, current time"
270 		    "  %lld \n", time, now);
271 #endif
272 	} else {
273 		ticks = APIC_NSECS_TO_TICKS(delta);
274 	}
275 
276 	if (ticks < apic_min_timer_ticks)
277 		ticks = apic_min_timer_ticks;
278 
279 	apic_reg_ops->apic_write(APIC_INIT_COUNT, ticks);
280 }
281 
282 /*
283  * deadline timer mode ops
284  */
285 /* deadline timer enable */
286 static void
287 deadline_timer_enable(void)
288 {
289 	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
290 	    (apic_clkvect + APIC_BASE_VECT) | AV_DEADLINE);
291 }
292 
293 /* deadline timer disable */
294 static void
295 deadline_timer_disable(void)
296 {
297 	apic_reg_ops->apic_write(APIC_LOCAL_TIMER,
298 	    (apic_clkvect + APIC_BASE_VECT) | AV_MASK);
299 }
300 
301 /* deadline timer reprogram */
302 static void
303 deadline_timer_reprogram(hrtime_t time)
304 {
305 	uint64_t	ticks;
306 
307 	if (time <= 0) {
308 		/*
309 		 * generate an immediate interrupt
310 		 */
311 		ticks = (uint64_t)tsc_read();
312 	} else {
313 		ticks = unscalehrtime(time);
314 	}
315 
316 	wrmsr(IA32_DEADLINE_TSC_MSR, ticks);
317 }
318 
319 /*
320  * This function will reprogram the timer.
321  *
322  * When in oneshot mode the argument is the absolute time in future to
323  * generate the interrupt at.
324  *
325  * When in periodic mode, the argument is the interval at which the
326  * interrupts should be generated. There is no need to support the periodic
327  * mode timer change at this time.
328  */
329 void
330 apic_timer_reprogram(hrtime_t time)
331 {
332 	/*
333 	 * we should be Called from high PIL context (CBE_HIGH_PIL),
334 	 * so kpreempt is disabled.
335 	 */
336 	apic_timer.apic_timer_reprogram_ops(time);
337 }
338 
339 /*
340  * This function will enable timer interrupts.
341  */
342 void
343 apic_timer_enable(void)
344 {
345 	/*
346 	 * we should be Called from high PIL context (CBE_HIGH_PIL),
347 	 * so kpreempt is disabled.
348 	 */
349 	apic_timer.apic_timer_enable_ops();
350 }
351 
352 /*
353  * This function will disable timer interrupts.
354  */
355 void
356 apic_timer_disable(void)
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_disable_ops();
363 }
364 
365 /*
366  * Set timer far into the future and return timer
367  * current count in nanoseconds.
368  */
369 hrtime_t
370 apic_timer_stop_count(void)
371 {
372 	hrtime_t	ns_val;
373 	int		enable_val, count_val;
374 
375 	/*
376 	 * Should be called with interrupts disabled.
377 	 */
378 	ASSERT(!interrupts_enabled());
379 
380 	enable_val = apic_reg_ops->apic_read(APIC_LOCAL_TIMER);
381 	if ((enable_val & AV_MASK) == AV_MASK)
382 		return ((hrtime_t)-1);	/* timer is disabled */
383 
384 	count_val = apic_reg_ops->apic_read(APIC_CURR_COUNT);
385 	ns_val = APIC_TICKS_TO_NSECS(count_val);
386 
387 	apic_reg_ops->apic_write(APIC_INIT_COUNT, APIC_MAXVAL);
388 
389 	return (ns_val);
390 }
391 
392 /*
393  * Reprogram timer after Deep C-State.
394  */
395 void
396 apic_timer_restart(hrtime_t time)
397 {
398 	apic_timer_reprogram(time);
399 }
400