xref: /openbsd-src/sys/arch/amd64/isa/clock.c (revision 1a8dbaac879b9f3335ad7fb25429ce63ac1d6bac)
1 /*	$OpenBSD: clock.c,v 1.34 2020/07/06 13:33:06 pirofti Exp $	*/
2 /*	$NetBSD: clock.c,v 1.1 2003/04/26 18:39:50 fvdl Exp $	*/
3 
4 /*-
5  * Copyright (c) 1993, 1994 Charles M. Hannum.
6  * Copyright (c) 1990 The Regents of the University of California.
7  * All rights reserved.
8  *
9  * This code is derived from software contributed to Berkeley by
10  * William Jolitz and Don Ahn.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  *
36  *	@(#)clock.c	7.2 (Berkeley) 5/12/91
37  */
38 /*
39  * Mach Operating System
40  * Copyright (c) 1991,1990,1989 Carnegie Mellon University
41  * All Rights Reserved.
42  *
43  * Permission to use, copy, modify and distribute this software and its
44  * documentation is hereby granted, provided that both the copyright
45  * notice and this permission notice appear in all copies of the
46  * software, derivative works or modified versions, and any portions
47  * thereof, and that both notices appear in supporting documentation.
48  *
49  * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
50  * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
51  * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
52  *
53  * Carnegie Mellon requests users of this software to return to
54  *
55  *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
56  *  School of Computer Science
57  *  Carnegie Mellon University
58  *  Pittsburgh PA 15213-3890
59  *
60  * any improvements or extensions that they make and grant Carnegie Mellon
61  * the rights to redistribute these changes.
62  */
63 /*
64   Copyright 1988, 1989 by Intel Corporation, Santa Clara, California.
65 
66 		All Rights Reserved
67 
68 Permission to use, copy, modify, and distribute this software and
69 its documentation for any purpose and without fee is hereby
70 granted, provided that the above copyright notice appears in all
71 copies and that both the copyright notice and this permission notice
72 appear in supporting documentation, and that the name of Intel
73 not be used in advertising or publicity pertaining to distribution
74 of the software without specific, written prior permission.
75 
76 INTEL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
77 INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
78 IN NO EVENT SHALL INTEL BE LIABLE FOR ANY SPECIAL, INDIRECT, OR
79 CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
80 LOSS OF USE, DATA OR PROFITS, WHETHER IN ACTION OF CONTRACT,
81 NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
82 WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
83 */
84 
85 /*
86  * Primitive clock interrupt routines.
87  */
88 
89 /* #define CLOCK_DEBUG */
90 
91 #include <sys/param.h>
92 #include <sys/systm.h>
93 #include <sys/time.h>
94 #include <sys/kernel.h>
95 #include <sys/timeout.h>
96 #include <sys/timetc.h>
97 
98 #include <machine/cpu.h>
99 #include <machine/intr.h>
100 #include <machine/pio.h>
101 #include <machine/cpufunc.h>
102 
103 #include <dev/clock_subr.h>
104 #include <dev/isa/isareg.h>
105 #include <dev/isa/isavar.h>
106 #include <dev/ic/mc146818reg.h>
107 #include <dev/ic/i8253reg.h>
108 #include <amd64/isa/nvram.h>
109 
110 /* Timecounter on the i8254 */
111 u_int32_t i8254_lastcount;
112 u_int32_t i8254_offset;
113 int i8254_ticked;
114 u_int i8254_get_timecount(struct timecounter *tc);
115 
116 u_int i8254_simple_get_timecount(struct timecounter *tc);
117 
118 static struct timecounter i8254_timecounter = {
119 	i8254_get_timecount, NULL, ~0u, TIMER_FREQ, "i8254", 0, NULL, 0
120 };
121 
122 int	clockintr(void *);
123 int	rtcintr(void *);
124 int	gettick(void);
125 void	rtcdrain(void *v);
126 int	rtcget(mc_todregs *);
127 void	rtcput(mc_todregs *);
128 int	bcdtobin(int);
129 int	bintobcd(int);
130 
131 u_int mc146818_read(void *, u_int);
132 void mc146818_write(void *, u_int, u_int);
133 
134 u_int
135 mc146818_read(void *sc, u_int reg)
136 {
137 	outb(IO_RTC, reg);
138 	DELAY(1);
139 	return (inb(IO_RTC+1));
140 }
141 
142 void
143 mc146818_write(void *sc, u_int reg, u_int datum)
144 {
145 	outb(IO_RTC, reg);
146 	DELAY(1);
147 	outb(IO_RTC+1, datum);
148 	DELAY(1);
149 }
150 
151 struct mutex timer_mutex = MUTEX_INITIALIZER(IPL_HIGH);
152 
153 u_long rtclock_tval;
154 
155 void
156 startclocks(void)
157 {
158 	mtx_enter(&timer_mutex);
159 	rtclock_tval = TIMER_DIV(hz);
160 	i8254_startclock();
161 	mtx_leave(&timer_mutex);
162 }
163 
164 int
165 clockintr(void *arg)
166 {
167 	struct clockframe *frame = arg;
168 
169 	if (timecounter->tc_get_timecount == i8254_get_timecount) {
170 		if (i8254_ticked) {
171 			i8254_ticked = 0;
172 		} else {
173 			i8254_offset += rtclock_tval;
174 			i8254_lastcount = 0;
175 		}
176 	}
177 
178 	hardclock(frame);
179 
180 	return 1;
181 }
182 
183 int
184 rtcintr(void *arg)
185 {
186 	struct clockframe *frame = arg;
187 	u_int stat = 0;
188 
189 	/*
190 	 * If rtcintr is 'late', next intr may happen immediately.
191 	 * Get them all. (Also, see comment in cpu_initclocks().)
192 	 */
193 	while (mc146818_read(NULL, MC_REGC) & MC_REGC_PF) {
194 		statclock(frame);
195 		stat = 1;
196 	}
197 
198 	return (stat);
199 }
200 
201 int
202 gettick(void)
203 {
204 	u_long s;
205 	u_char lo, hi;
206 
207 	/* Don't want someone screwing with the counter while we're here. */
208 	mtx_enter(&timer_mutex);
209 	s = intr_disable();
210 	/* Select counter 0 and latch it. */
211 	outb(IO_TIMER1+TIMER_MODE, TIMER_SEL0 | TIMER_LATCH);
212 	lo = inb(IO_TIMER1+TIMER_CNTR0);
213 	hi = inb(IO_TIMER1+TIMER_CNTR0);
214 	intr_restore(s);
215 	mtx_leave(&timer_mutex);
216 	return ((hi << 8) | lo);
217 }
218 
219 /*
220  * Wait "n" microseconds.
221  * Relies on timer 1 counting down from (TIMER_FREQ / hz) at TIMER_FREQ Hz.
222  * Note: timer had better have been programmed before this is first used!
223  * (Note that we use `rate generator' mode, which counts at 1:1; `square
224  * wave' mode counts at 2:1).
225  */
226 void
227 i8254_delay(int n)
228 {
229 	int limit, tick, otick;
230 	static const int delaytab[26] = {
231 		 0,  2,  3,  4,  5,  6,  7,  9, 10, 11,
232 		12, 13, 15, 16, 17, 18, 19, 21, 22, 23,
233 		24, 25, 27, 28, 29, 30,
234 	};
235 
236 	/*
237 	 * Read the counter first, so that the rest of the setup overhead is
238 	 * counted.
239 	 */
240 	otick = gettick();
241 
242 	if (n <= 25)
243 		n = delaytab[n];
244 	else {
245 		/* Force 64-bit math to avoid 32-bit overflow if possible. */
246 		n = (int64_t)n * TIMER_FREQ / 1000000;
247 	}
248 
249 	limit = TIMER_FREQ / hz;
250 
251 	while (n > 0) {
252 		tick = gettick();
253 		if (tick > otick)
254 			n -= limit - (tick - otick);
255 		else
256 			n -= otick - tick;
257 		otick = tick;
258 	}
259 }
260 
261 void
262 rtcdrain(void *v)
263 {
264 	struct timeout *to = (struct timeout *)v;
265 
266 	if (to != NULL)
267 		timeout_del(to);
268 
269 	/* Drain any un-acknowledged RTC interrupts. */
270 	while (mc146818_read(NULL, MC_REGC) & MC_REGC_PF)
271 		; /* Nothing. */
272 }
273 
274 void
275 i8254_initclocks(void)
276 {
277 	stathz = 128;
278 	profhz = 1024;
279 
280 	/*
281 	 * While the clock interrupt handler isn't really MPSAFE, the
282 	 * i8254 can't really be used as a clock on a true MP system.
283 	 */
284 	isa_intr_establish(NULL, 0, IST_PULSE, IPL_CLOCK | IPL_MPSAFE,
285 	    clockintr, 0, "clock");
286 	isa_intr_establish(NULL, 8, IST_PULSE, IPL_STATCLOCK | IPL_MPSAFE,
287 	    rtcintr, 0, "rtc");
288 
289 	rtcstart();			/* start the mc146818 clock */
290 
291 	i8254_inittimecounter();	/* hook the interrupt-based i8254 tc */
292 }
293 
294 void
295 rtcstart(void)
296 {
297 	static struct timeout rtcdrain_timeout;
298 
299 	mc146818_write(NULL, MC_REGA, MC_BASE_32_KHz | MC_RATE_128_Hz);
300 	mc146818_write(NULL, MC_REGB, MC_REGB_24HR | MC_REGB_PIE);
301 
302 	/*
303 	 * On a number of i386 systems, the rtc will fail to start when booting
304 	 * the system. This is due to us missing to acknowledge an interrupt
305 	 * during early stages of the boot process. If we do not acknowledge
306 	 * the interrupt, the rtc clock will not generate further interrupts.
307 	 * To solve this, once interrupts are enabled, use a timeout (once)
308 	 * to drain any un-acknowledged rtc interrupt(s).
309 	 */
310 	timeout_set(&rtcdrain_timeout, rtcdrain, (void *)&rtcdrain_timeout);
311 	timeout_add(&rtcdrain_timeout, 1);
312 }
313 
314 void
315 rtcstop(void)
316 {
317 	mc146818_write(NULL, MC_REGB, MC_REGB_24HR);
318 }
319 
320 int
321 rtcget(mc_todregs *regs)
322 {
323 	if ((mc146818_read(NULL, MC_REGD) & MC_REGD_VRT) == 0) /* XXX softc */
324 		return (-1);
325 	MC146818_GETTOD(NULL, regs);			/* XXX softc */
326 	return (0);
327 }
328 
329 void
330 rtcput(mc_todregs *regs)
331 {
332 	MC146818_PUTTOD(NULL, regs);			/* XXX softc */
333 }
334 
335 int
336 bcdtobin(int n)
337 {
338 	return (((n >> 4) & 0x0f) * 10 + (n & 0x0f));
339 }
340 
341 int
342 bintobcd(int n)
343 {
344 	return ((u_char)(((n / 10) << 4) & 0xf0) | ((n % 10) & 0x0f));
345 }
346 
347 /*
348  * check whether the CMOS layout is "standard"-like (ie, not PS/2-like),
349  * to be called at splclock()
350  */
351 static int cmoscheck(void);
352 static int
353 cmoscheck(void)
354 {
355 	int i;
356 	unsigned short cksum = 0;
357 
358 	for (i = 0x10; i <= 0x2d; i++)
359 		cksum += mc146818_read(NULL, i); /* XXX softc */
360 
361 	return (cksum == (mc146818_read(NULL, 0x2e) << 8)
362 			  + mc146818_read(NULL, 0x2f));
363 }
364 
365 /*
366  * patchable to control century byte handling:
367  * 1: always update
368  * -1: never touch
369  * 0: try to figure out itself
370  */
371 int rtc_update_century = 0;
372 
373 /*
374  * Expand a two-digit year as read from the clock chip
375  * into full width.
376  * Being here, deal with the CMOS century byte.
377  */
378 static int centb = NVRAM_CENTURY;
379 static int clock_expandyear(int);
380 static int
381 clock_expandyear(int clockyear)
382 {
383 	int s, clockcentury, cmoscentury;
384 
385 	clockcentury = (clockyear < 70) ? 20 : 19;
386 	clockyear += 100 * clockcentury;
387 
388 	if (rtc_update_century < 0)
389 		return (clockyear);
390 
391 	s = splclock();
392 	if (cmoscheck())
393 		cmoscentury = mc146818_read(NULL, NVRAM_CENTURY);
394 	else
395 		cmoscentury = 0;
396 	splx(s);
397 	if (!cmoscentury)
398 		return (clockyear);
399 
400 	cmoscentury = bcdtobin(cmoscentury);
401 
402 	if (cmoscentury != clockcentury) {
403 		/* XXX note: saying "century is 20" might confuse the naive. */
404 		printf("WARNING: NVRAM century is %d but RTC year is %d\n",
405 		       cmoscentury, clockyear);
406 
407 		/* Kludge to roll over century. */
408 		if ((rtc_update_century > 0) ||
409 		    ((cmoscentury == 19) && (clockcentury == 20) &&
410 		     (clockyear == 2000))) {
411 			printf("WARNING: Setting NVRAM century to %d\n",
412 			       clockcentury);
413 			s = splclock();
414 			mc146818_write(NULL, centb, bintobcd(clockcentury));
415 			splx(s);
416 		}
417 	} else if (cmoscentury == 19 && rtc_update_century == 0)
418 		rtc_update_century = 1; /* will update later in resettodr() */
419 
420 	return (clockyear);
421 }
422 
423 int
424 rtcgettime(struct todr_chip_handle *handle, struct timeval *tv)
425 {
426 	mc_todregs rtclk;
427 	struct clock_ymdhms dt;
428 	int s;
429 
430 	s = splclock();
431 	if (rtcget(&rtclk)) {
432 		splx(s);
433 		return EINVAL;
434 	}
435 	splx(s);
436 
437 #ifdef CLOCK_DEBUG
438 	printf("readclock: %x/%x/%x %x:%x:%x\n", rtclk[MC_YEAR],
439 	    rtclk[MC_MONTH], rtclk[MC_DOM], rtclk[MC_HOUR], rtclk[MC_MIN],
440 	    rtclk[MC_SEC]);
441 #endif
442 
443 	dt.dt_sec = bcdtobin(rtclk[MC_SEC]);
444 	dt.dt_min = bcdtobin(rtclk[MC_MIN]);
445 	dt.dt_hour = bcdtobin(rtclk[MC_HOUR]);
446 	dt.dt_day = bcdtobin(rtclk[MC_DOM]);
447 	dt.dt_mon = bcdtobin(rtclk[MC_MONTH]);
448 	dt.dt_year = clock_expandyear(bcdtobin(rtclk[MC_YEAR]));
449 
450 	tv->tv_sec = clock_ymdhms_to_secs(&dt) - utc_offset;
451 	tv->tv_usec = 0;
452 	return 0;
453 }
454 
455 int
456 rtcsettime(struct todr_chip_handle *handle, struct timeval *tv)
457 {
458 	mc_todregs rtclk;
459 	struct clock_ymdhms dt;
460 	int century, s;
461 
462 	s = splclock();
463 	if (rtcget(&rtclk))
464 		memset(&rtclk, 0, sizeof(rtclk));
465 	splx(s);
466 
467 	clock_secs_to_ymdhms(tv->tv_sec + utc_offset, &dt);
468 
469 	rtclk[MC_SEC] = bintobcd(dt.dt_sec);
470 	rtclk[MC_MIN] = bintobcd(dt.dt_min);
471 	rtclk[MC_HOUR] = bintobcd(dt.dt_hour);
472 	rtclk[MC_DOW] = dt.dt_wday + 1;
473 	rtclk[MC_YEAR] = bintobcd(dt.dt_year % 100);
474 	rtclk[MC_MONTH] = bintobcd(dt.dt_mon);
475 	rtclk[MC_DOM] = bintobcd(dt.dt_day);
476 
477 #ifdef CLOCK_DEBUG
478 	printf("setclock: %x/%x/%x %x:%x:%x\n", rtclk[MC_YEAR], rtclk[MC_MONTH],
479 	   rtclk[MC_DOM], rtclk[MC_HOUR], rtclk[MC_MIN], rtclk[MC_SEC]);
480 #endif
481 
482 	s = splclock();
483 	rtcput(&rtclk);
484 	if (rtc_update_century > 0) {
485 		century = bintobcd(dt.dt_year / 100);
486 		mc146818_write(NULL, centb, century); /* XXX softc */
487 	}
488 	splx(s);
489 	return 0;
490 }
491 
492 extern todr_chip_handle_t todr_handle;
493 struct todr_chip_handle rtc_todr;
494 
495 void
496 rtcinit(void)
497 {
498 	rtc_todr.todr_gettime = rtcgettime;
499 	rtc_todr.todr_settime = rtcsettime;
500 	todr_handle = &rtc_todr;
501 }
502 
503 void
504 setstatclockrate(int arg)
505 {
506 	if (initclock_func == i8254_initclocks) {
507 		if (arg == stathz)
508 			mc146818_write(NULL, MC_REGA,
509 			    MC_BASE_32_KHz | MC_RATE_128_Hz);
510 		else
511 			mc146818_write(NULL, MC_REGA,
512 			    MC_BASE_32_KHz | MC_RATE_1024_Hz);
513 	}
514 }
515 
516 void
517 i8254_inittimecounter(void)
518 {
519 	tc_init(&i8254_timecounter);
520 }
521 
522 /*
523  * If we're using lapic to drive hardclock, we can use a simpler
524  * algorithm for the i8254 timecounters.
525  */
526 void
527 i8254_inittimecounter_simple(void)
528 {
529 	i8254_timecounter.tc_get_timecount = i8254_simple_get_timecount;
530 	i8254_timecounter.tc_counter_mask = 0x7fff;
531         i8254_timecounter.tc_frequency = TIMER_FREQ;
532 
533 	mtx_enter(&timer_mutex);
534 	rtclock_tval = 0x8000;
535 	i8254_startclock();
536 	mtx_leave(&timer_mutex);
537 
538 	tc_init(&i8254_timecounter);
539 }
540 
541 void
542 i8254_startclock(void)
543 {
544 	u_long tval = rtclock_tval;
545 
546 	outb(IO_TIMER1 + TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT);
547 	outb(IO_TIMER1 + TIMER_CNTR0, tval & 0xff);
548 	outb(IO_TIMER1 + TIMER_CNTR0, tval >> 8);
549 }
550 
551 u_int
552 i8254_simple_get_timecount(struct timecounter *tc)
553 {
554 	return (rtclock_tval - gettick());
555 }
556 
557 u_int
558 i8254_get_timecount(struct timecounter *tc)
559 {
560 	u_char hi, lo;
561 	u_int count;
562 	u_long s;
563 
564 	s = intr_disable();
565 
566 	outb(IO_TIMER1+TIMER_MODE, TIMER_SEL0 | TIMER_LATCH);
567 	lo = inb(IO_TIMER1+TIMER_CNTR0);
568 	hi = inb(IO_TIMER1+TIMER_CNTR0);
569 
570 	count = rtclock_tval - ((hi << 8) | lo);
571 
572 	if (count < i8254_lastcount) {
573 		i8254_ticked = 1;
574 		i8254_offset += rtclock_tval;
575 	}
576 	i8254_lastcount = count;
577 	count += i8254_offset;
578 
579 	intr_restore(s);
580 
581 	return (count);
582 }
583