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
apic_timer_init(int hertz)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
periodic_timer_enable(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
periodic_timer_disable(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
periodic_timer_reprogram(hrtime_t time)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
oneshot_timer_enable(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
oneshot_timer_disable(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
oneshot_timer_reprogram(hrtime_t time)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
deadline_timer_enable(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
deadline_timer_disable(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
deadline_timer_reprogram(hrtime_t time)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
apic_timer_reprogram(hrtime_t time)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
apic_timer_enable(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
apic_timer_disable(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
apic_timer_stop_count(void)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
apic_timer_restart(hrtime_t time)396 apic_timer_restart(hrtime_t time)
397 {
398 apic_timer_reprogram(time);
399 }
400