xref: /openbsd-src/sys/arch/amd64/isa/clock.c (revision f1dd7b858388b4a23f4f67a4957ec5ff656ebbe8)
1 /*	$OpenBSD: clock.c,v 1.35 2021/02/23 04:44:30 cheloha 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 	.tc_get_timecount = i8254_get_timecount,
120 	.tc_poll_pps = NULL,
121 	.tc_counter_mask = ~0u,
122 	.tc_frequency = TIMER_FREQ,
123 	.tc_name = "i8254",
124 	.tc_quality = 0,
125 	.tc_priv = NULL,
126 	.tc_user = 0,
127 };
128 
129 int	clockintr(void *);
130 int	rtcintr(void *);
131 int	gettick(void);
132 void	rtcdrain(void *v);
133 int	rtcget(mc_todregs *);
134 void	rtcput(mc_todregs *);
135 int	bcdtobin(int);
136 int	bintobcd(int);
137 
138 u_int mc146818_read(void *, u_int);
139 void mc146818_write(void *, u_int, u_int);
140 
141 u_int
142 mc146818_read(void *sc, u_int reg)
143 {
144 	outb(IO_RTC, reg);
145 	DELAY(1);
146 	return (inb(IO_RTC+1));
147 }
148 
149 void
150 mc146818_write(void *sc, u_int reg, u_int datum)
151 {
152 	outb(IO_RTC, reg);
153 	DELAY(1);
154 	outb(IO_RTC+1, datum);
155 	DELAY(1);
156 }
157 
158 struct mutex timer_mutex = MUTEX_INITIALIZER(IPL_HIGH);
159 
160 u_long rtclock_tval;
161 
162 void
163 startclocks(void)
164 {
165 	mtx_enter(&timer_mutex);
166 	rtclock_tval = TIMER_DIV(hz);
167 	i8254_startclock();
168 	mtx_leave(&timer_mutex);
169 }
170 
171 int
172 clockintr(void *arg)
173 {
174 	struct clockframe *frame = arg;
175 
176 	if (timecounter->tc_get_timecount == i8254_get_timecount) {
177 		if (i8254_ticked) {
178 			i8254_ticked = 0;
179 		} else {
180 			i8254_offset += rtclock_tval;
181 			i8254_lastcount = 0;
182 		}
183 	}
184 
185 	hardclock(frame);
186 
187 	return 1;
188 }
189 
190 int
191 rtcintr(void *arg)
192 {
193 	struct clockframe *frame = arg;
194 	u_int stat = 0;
195 
196 	/*
197 	 * If rtcintr is 'late', next intr may happen immediately.
198 	 * Get them all. (Also, see comment in cpu_initclocks().)
199 	 */
200 	while (mc146818_read(NULL, MC_REGC) & MC_REGC_PF) {
201 		statclock(frame);
202 		stat = 1;
203 	}
204 
205 	return (stat);
206 }
207 
208 int
209 gettick(void)
210 {
211 	u_long s;
212 	u_char lo, hi;
213 
214 	/* Don't want someone screwing with the counter while we're here. */
215 	mtx_enter(&timer_mutex);
216 	s = intr_disable();
217 	/* Select counter 0 and latch it. */
218 	outb(IO_TIMER1+TIMER_MODE, TIMER_SEL0 | TIMER_LATCH);
219 	lo = inb(IO_TIMER1+TIMER_CNTR0);
220 	hi = inb(IO_TIMER1+TIMER_CNTR0);
221 	intr_restore(s);
222 	mtx_leave(&timer_mutex);
223 	return ((hi << 8) | lo);
224 }
225 
226 /*
227  * Wait "n" microseconds.
228  * Relies on timer 1 counting down from (TIMER_FREQ / hz) at TIMER_FREQ Hz.
229  * Note: timer had better have been programmed before this is first used!
230  * (Note that we use `rate generator' mode, which counts at 1:1; `square
231  * wave' mode counts at 2:1).
232  */
233 void
234 i8254_delay(int n)
235 {
236 	int limit, tick, otick;
237 	static const int delaytab[26] = {
238 		 0,  2,  3,  4,  5,  6,  7,  9, 10, 11,
239 		12, 13, 15, 16, 17, 18, 19, 21, 22, 23,
240 		24, 25, 27, 28, 29, 30,
241 	};
242 
243 	/*
244 	 * Read the counter first, so that the rest of the setup overhead is
245 	 * counted.
246 	 */
247 	otick = gettick();
248 
249 	if (n <= 25)
250 		n = delaytab[n];
251 	else {
252 		/* Force 64-bit math to avoid 32-bit overflow if possible. */
253 		n = (int64_t)n * TIMER_FREQ / 1000000;
254 	}
255 
256 	limit = TIMER_FREQ / hz;
257 
258 	while (n > 0) {
259 		tick = gettick();
260 		if (tick > otick)
261 			n -= limit - (tick - otick);
262 		else
263 			n -= otick - tick;
264 		otick = tick;
265 	}
266 }
267 
268 void
269 rtcdrain(void *v)
270 {
271 	struct timeout *to = (struct timeout *)v;
272 
273 	if (to != NULL)
274 		timeout_del(to);
275 
276 	/* Drain any un-acknowledged RTC interrupts. */
277 	while (mc146818_read(NULL, MC_REGC) & MC_REGC_PF)
278 		; /* Nothing. */
279 }
280 
281 void
282 i8254_initclocks(void)
283 {
284 	stathz = 128;
285 	profhz = 1024;
286 
287 	/*
288 	 * While the clock interrupt handler isn't really MPSAFE, the
289 	 * i8254 can't really be used as a clock on a true MP system.
290 	 */
291 	isa_intr_establish(NULL, 0, IST_PULSE, IPL_CLOCK | IPL_MPSAFE,
292 	    clockintr, 0, "clock");
293 	isa_intr_establish(NULL, 8, IST_PULSE, IPL_STATCLOCK | IPL_MPSAFE,
294 	    rtcintr, 0, "rtc");
295 
296 	rtcstart();			/* start the mc146818 clock */
297 
298 	i8254_inittimecounter();	/* hook the interrupt-based i8254 tc */
299 }
300 
301 void
302 rtcstart(void)
303 {
304 	static struct timeout rtcdrain_timeout;
305 
306 	mc146818_write(NULL, MC_REGA, MC_BASE_32_KHz | MC_RATE_128_Hz);
307 	mc146818_write(NULL, MC_REGB, MC_REGB_24HR | MC_REGB_PIE);
308 
309 	/*
310 	 * On a number of i386 systems, the rtc will fail to start when booting
311 	 * the system. This is due to us missing to acknowledge an interrupt
312 	 * during early stages of the boot process. If we do not acknowledge
313 	 * the interrupt, the rtc clock will not generate further interrupts.
314 	 * To solve this, once interrupts are enabled, use a timeout (once)
315 	 * to drain any un-acknowledged rtc interrupt(s).
316 	 */
317 	timeout_set(&rtcdrain_timeout, rtcdrain, (void *)&rtcdrain_timeout);
318 	timeout_add(&rtcdrain_timeout, 1);
319 }
320 
321 void
322 rtcstop(void)
323 {
324 	mc146818_write(NULL, MC_REGB, MC_REGB_24HR);
325 }
326 
327 int
328 rtcget(mc_todregs *regs)
329 {
330 	if ((mc146818_read(NULL, MC_REGD) & MC_REGD_VRT) == 0) /* XXX softc */
331 		return (-1);
332 	MC146818_GETTOD(NULL, regs);			/* XXX softc */
333 	return (0);
334 }
335 
336 void
337 rtcput(mc_todregs *regs)
338 {
339 	MC146818_PUTTOD(NULL, regs);			/* XXX softc */
340 }
341 
342 int
343 bcdtobin(int n)
344 {
345 	return (((n >> 4) & 0x0f) * 10 + (n & 0x0f));
346 }
347 
348 int
349 bintobcd(int n)
350 {
351 	return ((u_char)(((n / 10) << 4) & 0xf0) | ((n % 10) & 0x0f));
352 }
353 
354 /*
355  * check whether the CMOS layout is "standard"-like (ie, not PS/2-like),
356  * to be called at splclock()
357  */
358 static int cmoscheck(void);
359 static int
360 cmoscheck(void)
361 {
362 	int i;
363 	unsigned short cksum = 0;
364 
365 	for (i = 0x10; i <= 0x2d; i++)
366 		cksum += mc146818_read(NULL, i); /* XXX softc */
367 
368 	return (cksum == (mc146818_read(NULL, 0x2e) << 8)
369 			  + mc146818_read(NULL, 0x2f));
370 }
371 
372 /*
373  * patchable to control century byte handling:
374  * 1: always update
375  * -1: never touch
376  * 0: try to figure out itself
377  */
378 int rtc_update_century = 0;
379 
380 /*
381  * Expand a two-digit year as read from the clock chip
382  * into full width.
383  * Being here, deal with the CMOS century byte.
384  */
385 static int centb = NVRAM_CENTURY;
386 static int clock_expandyear(int);
387 static int
388 clock_expandyear(int clockyear)
389 {
390 	int s, clockcentury, cmoscentury;
391 
392 	clockcentury = (clockyear < 70) ? 20 : 19;
393 	clockyear += 100 * clockcentury;
394 
395 	if (rtc_update_century < 0)
396 		return (clockyear);
397 
398 	s = splclock();
399 	if (cmoscheck())
400 		cmoscentury = mc146818_read(NULL, NVRAM_CENTURY);
401 	else
402 		cmoscentury = 0;
403 	splx(s);
404 	if (!cmoscentury)
405 		return (clockyear);
406 
407 	cmoscentury = bcdtobin(cmoscentury);
408 
409 	if (cmoscentury != clockcentury) {
410 		/* XXX note: saying "century is 20" might confuse the naive. */
411 		printf("WARNING: NVRAM century is %d but RTC year is %d\n",
412 		       cmoscentury, clockyear);
413 
414 		/* Kludge to roll over century. */
415 		if ((rtc_update_century > 0) ||
416 		    ((cmoscentury == 19) && (clockcentury == 20) &&
417 		     (clockyear == 2000))) {
418 			printf("WARNING: Setting NVRAM century to %d\n",
419 			       clockcentury);
420 			s = splclock();
421 			mc146818_write(NULL, centb, bintobcd(clockcentury));
422 			splx(s);
423 		}
424 	} else if (cmoscentury == 19 && rtc_update_century == 0)
425 		rtc_update_century = 1; /* will update later in resettodr() */
426 
427 	return (clockyear);
428 }
429 
430 int
431 rtcgettime(struct todr_chip_handle *handle, struct timeval *tv)
432 {
433 	mc_todregs rtclk;
434 	struct clock_ymdhms dt;
435 	int s;
436 
437 	s = splclock();
438 	if (rtcget(&rtclk)) {
439 		splx(s);
440 		return EINVAL;
441 	}
442 	splx(s);
443 
444 #ifdef CLOCK_DEBUG
445 	printf("readclock: %x/%x/%x %x:%x:%x\n", rtclk[MC_YEAR],
446 	    rtclk[MC_MONTH], rtclk[MC_DOM], rtclk[MC_HOUR], rtclk[MC_MIN],
447 	    rtclk[MC_SEC]);
448 #endif
449 
450 	dt.dt_sec = bcdtobin(rtclk[MC_SEC]);
451 	dt.dt_min = bcdtobin(rtclk[MC_MIN]);
452 	dt.dt_hour = bcdtobin(rtclk[MC_HOUR]);
453 	dt.dt_day = bcdtobin(rtclk[MC_DOM]);
454 	dt.dt_mon = bcdtobin(rtclk[MC_MONTH]);
455 	dt.dt_year = clock_expandyear(bcdtobin(rtclk[MC_YEAR]));
456 
457 	tv->tv_sec = clock_ymdhms_to_secs(&dt) - utc_offset;
458 	tv->tv_usec = 0;
459 	return 0;
460 }
461 
462 int
463 rtcsettime(struct todr_chip_handle *handle, struct timeval *tv)
464 {
465 	mc_todregs rtclk;
466 	struct clock_ymdhms dt;
467 	int century, s;
468 
469 	s = splclock();
470 	if (rtcget(&rtclk))
471 		memset(&rtclk, 0, sizeof(rtclk));
472 	splx(s);
473 
474 	clock_secs_to_ymdhms(tv->tv_sec + utc_offset, &dt);
475 
476 	rtclk[MC_SEC] = bintobcd(dt.dt_sec);
477 	rtclk[MC_MIN] = bintobcd(dt.dt_min);
478 	rtclk[MC_HOUR] = bintobcd(dt.dt_hour);
479 	rtclk[MC_DOW] = dt.dt_wday + 1;
480 	rtclk[MC_YEAR] = bintobcd(dt.dt_year % 100);
481 	rtclk[MC_MONTH] = bintobcd(dt.dt_mon);
482 	rtclk[MC_DOM] = bintobcd(dt.dt_day);
483 
484 #ifdef CLOCK_DEBUG
485 	printf("setclock: %x/%x/%x %x:%x:%x\n", rtclk[MC_YEAR], rtclk[MC_MONTH],
486 	   rtclk[MC_DOM], rtclk[MC_HOUR], rtclk[MC_MIN], rtclk[MC_SEC]);
487 #endif
488 
489 	s = splclock();
490 	rtcput(&rtclk);
491 	if (rtc_update_century > 0) {
492 		century = bintobcd(dt.dt_year / 100);
493 		mc146818_write(NULL, centb, century); /* XXX softc */
494 	}
495 	splx(s);
496 	return 0;
497 }
498 
499 extern todr_chip_handle_t todr_handle;
500 struct todr_chip_handle rtc_todr;
501 
502 void
503 rtcinit(void)
504 {
505 	rtc_todr.todr_gettime = rtcgettime;
506 	rtc_todr.todr_settime = rtcsettime;
507 	todr_handle = &rtc_todr;
508 }
509 
510 void
511 setstatclockrate(int arg)
512 {
513 	if (initclock_func == i8254_initclocks) {
514 		if (arg == stathz)
515 			mc146818_write(NULL, MC_REGA,
516 			    MC_BASE_32_KHz | MC_RATE_128_Hz);
517 		else
518 			mc146818_write(NULL, MC_REGA,
519 			    MC_BASE_32_KHz | MC_RATE_1024_Hz);
520 	}
521 }
522 
523 void
524 i8254_inittimecounter(void)
525 {
526 	tc_init(&i8254_timecounter);
527 }
528 
529 /*
530  * If we're using lapic to drive hardclock, we can use a simpler
531  * algorithm for the i8254 timecounters.
532  */
533 void
534 i8254_inittimecounter_simple(void)
535 {
536 	i8254_timecounter.tc_get_timecount = i8254_simple_get_timecount;
537 	i8254_timecounter.tc_counter_mask = 0x7fff;
538         i8254_timecounter.tc_frequency = TIMER_FREQ;
539 
540 	mtx_enter(&timer_mutex);
541 	rtclock_tval = 0x8000;
542 	i8254_startclock();
543 	mtx_leave(&timer_mutex);
544 
545 	tc_init(&i8254_timecounter);
546 }
547 
548 void
549 i8254_startclock(void)
550 {
551 	u_long tval = rtclock_tval;
552 
553 	outb(IO_TIMER1 + TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT);
554 	outb(IO_TIMER1 + TIMER_CNTR0, tval & 0xff);
555 	outb(IO_TIMER1 + TIMER_CNTR0, tval >> 8);
556 }
557 
558 u_int
559 i8254_simple_get_timecount(struct timecounter *tc)
560 {
561 	return (rtclock_tval - gettick());
562 }
563 
564 u_int
565 i8254_get_timecount(struct timecounter *tc)
566 {
567 	u_char hi, lo;
568 	u_int count;
569 	u_long s;
570 
571 	s = intr_disable();
572 
573 	outb(IO_TIMER1+TIMER_MODE, TIMER_SEL0 | TIMER_LATCH);
574 	lo = inb(IO_TIMER1+TIMER_CNTR0);
575 	hi = inb(IO_TIMER1+TIMER_CNTR0);
576 
577 	count = rtclock_tval - ((hi << 8) | lo);
578 
579 	if (count < i8254_lastcount) {
580 		i8254_ticked = 1;
581 		i8254_offset += rtclock_tval;
582 	}
583 	i8254_lastcount = count;
584 	count += i8254_offset;
585 
586 	intr_restore(s);
587 
588 	return (count);
589 }
590