1 /* $OpenBSD: clock.c,v 1.13 2007/08/02 16:40:27 deraadt 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 CLOCKDEBUG */ 90 /* #define CLOCK_PARANOIA */ 91 92 #include <sys/param.h> 93 #include <sys/systm.h> 94 #include <sys/time.h> 95 #include <sys/kernel.h> 96 #include <sys/device.h> 97 #include <sys/timeout.h> 98 #include <sys/timetc.h> 99 100 #include <machine/cpu.h> 101 #include <machine/intr.h> 102 #include <machine/pio.h> 103 #include <machine/cpufunc.h> 104 105 #include <dev/isa/isareg.h> 106 #include <dev/isa/isavar.h> 107 #include <dev/ic/mc146818reg.h> 108 #include <dev/ic/i8253reg.h> 109 #include <amd64/isa/nvram.h> 110 #include <dev/clock_subr.h> 111 #include <machine/specialreg.h> 112 113 /* Timecounter on the i8254 */ 114 u_int32_t i8254_lastcount; 115 u_int32_t i8254_offset; 116 int i8254_ticked; 117 u_int i8254_get_timecount(struct timecounter *tc); 118 119 u_int i8254_simple_get_timecount(struct timecounter *tc); 120 121 static struct timecounter i8254_timecounter = { 122 i8254_get_timecount, NULL, ~0u, TIMER_FREQ, "i8254", 0, NULL 123 }; 124 125 int clockintr(void *); 126 int rtcintr(void *); 127 int gettick(void); 128 void rtcdrain(void *v); 129 int rtcget(mc_todregs *); 130 void rtcput(mc_todregs *); 131 int bcdtobin(int); 132 int bintobcd(int); 133 134 __inline u_int mc146818_read(void *, u_int); 135 __inline void mc146818_write(void *, u_int, u_int); 136 137 __inline u_int 138 mc146818_read(void *sc, u_int reg) 139 { 140 outb(IO_RTC, reg); 141 DELAY(1); 142 return (inb(IO_RTC+1)); 143 } 144 145 __inline void 146 mc146818_write(void *sc, u_int reg, u_int datum) 147 { 148 outb(IO_RTC, reg); 149 DELAY(1); 150 outb(IO_RTC+1, datum); 151 DELAY(1); 152 } 153 154 struct mutex timer_mutex = MUTEX_INITIALIZER(IPL_HIGH); 155 156 u_long rtclock_tval; 157 int rtclock_init; 158 159 /* minimal initialization, enough for delay() */ 160 void 161 initrtclock(void) 162 { 163 u_long tval; 164 165 /* 166 * Compute timer_count, the count-down count the timer will be 167 * set to. Also, correctly round 168 * this by carrying an extra bit through the division. 169 */ 170 tval = (TIMER_FREQ * 2) / (u_long) hz; 171 tval = (tval / 2) + (tval & 0x1); 172 173 mtx_enter(&timer_mutex); 174 /* initialize 8253 clock */ 175 outb(IO_TIMER1+TIMER_MODE, TIMER_SEL0|TIMER_RATEGEN|TIMER_16BIT); 176 177 /* Correct rounding will buy us a better precision in timekeeping */ 178 outb(IO_TIMER1+TIMER_CNTR0, tval % 256); 179 outb(IO_TIMER1+TIMER_CNTR0, tval / 256); 180 181 rtclock_tval = tval; 182 rtclock_init = 1; 183 mtx_leave(&timer_mutex); 184 } 185 186 void 187 startrtclock(void) 188 { 189 int s; 190 191 if (!rtclock_init) 192 initrtclock(); 193 194 /* Check diagnostic status */ 195 if ((s = mc146818_read(NULL, NVRAM_DIAG)) != 0) /* XXX softc */ 196 printf("RTC BIOS diagnostic error %b\n", s, NVRAM_DIAG_BITS); 197 } 198 199 int 200 clockintr(void *arg) 201 { 202 struct clockframe *frame = arg; 203 204 if (timecounter->tc_get_timecount == i8254_get_timecount) { 205 if (i8254_ticked) { 206 i8254_ticked = 0; 207 } else { 208 i8254_offset += rtclock_tval; 209 i8254_lastcount = 0; 210 } 211 } 212 213 hardclock(frame); 214 215 return 1; 216 } 217 218 int 219 rtcintr(void *arg) 220 { 221 struct clockframe *frame = arg; 222 u_int stat = 0; 223 224 /* 225 * If rtcintr is 'late', next intr may happen immediately. 226 * Get them all. (Also, see comment in cpu_initclocks().) 227 */ 228 while (mc146818_read(NULL, MC_REGC) & MC_REGC_PF) { 229 statclock(frame); 230 stat = 1; 231 } 232 233 return (stat); 234 } 235 236 int 237 gettick(void) 238 { 239 u_long ef; 240 u_char lo, hi; 241 242 /* Don't want someone screwing with the counter while we're here. */ 243 mtx_enter(&timer_mutex); 244 ef = read_rflags(); 245 disable_intr(); 246 /* Select counter 0 and latch it. */ 247 outb(IO_TIMER1+TIMER_MODE, TIMER_SEL0 | TIMER_LATCH); 248 lo = inb(IO_TIMER1+TIMER_CNTR0); 249 hi = inb(IO_TIMER1+TIMER_CNTR0); 250 write_rflags(ef); 251 mtx_leave(&timer_mutex); 252 return ((hi << 8) | lo); 253 } 254 255 /* 256 * Wait "n" microseconds. 257 * Relies on timer 1 counting down from (TIMER_FREQ / hz) at TIMER_FREQ Hz. 258 * Note: timer had better have been programmed before this is first used! 259 * (Note that we use `rate generator' mode, which counts at 1:1; `square 260 * wave' mode counts at 2:1). 261 */ 262 void 263 i8254_delay(int n) 264 { 265 int limit, tick, otick; 266 static const int delaytab[26] = { 267 0, 2, 3, 4, 5, 6, 7, 9, 10, 11, 268 12, 13, 15, 16, 17, 18, 19, 21, 22, 23, 269 24, 25, 27, 28, 29, 30, 270 }; 271 272 /* allow DELAY() to be used before startrtclock() */ 273 if (!rtclock_init) 274 initrtclock(); 275 276 /* 277 * Read the counter first, so that the rest of the setup overhead is 278 * counted. 279 */ 280 otick = gettick(); 281 282 if (n <= 25) 283 n = delaytab[n]; 284 else { 285 #ifdef __GNUC__ 286 /* 287 * Calculate ((n * TIMER_FREQ) / 1e6) using explicit assembler 288 * code so we can take advantage of the intermediate 64-bit 289 * quantity to prevent loss of significance. 290 */ 291 int m; 292 __asm __volatile("mul %3" 293 : "=a" (n), "=d" (m) 294 : "0" (n), "r" (TIMER_FREQ)); 295 __asm __volatile("div %4" 296 : "=a" (n), "=d" (m) 297 : "0" (n), "1" (m), "r" (1000000)); 298 #else 299 /* 300 * Calculate ((n * TIMER_FREQ) / 1e6) without using floating 301 * point and without any avoidable overflows. 302 */ 303 int sec = n / 1000000, 304 usec = n % 1000000; 305 n = sec * TIMER_FREQ + 306 usec * (TIMER_FREQ / 1000000) + 307 usec * ((TIMER_FREQ % 1000000) / 1000) / 1000 + 308 usec * (TIMER_FREQ % 1000) / 1000000; 309 #endif 310 } 311 312 limit = TIMER_FREQ / hz; 313 314 while (n > 0) { 315 tick = gettick(); 316 if (tick > otick) 317 n -= limit - (tick - otick); 318 else 319 n -= otick - tick; 320 otick = tick; 321 } 322 } 323 324 void 325 rtcdrain(void *v) 326 { 327 struct timeout *to = (struct timeout *)v; 328 329 if (to != NULL) 330 timeout_del(to); 331 332 /* 333 * Drain any un-acknowledged RTC interrupts. 334 * See comment in cpu_initclocks(). 335 */ 336 while (mc146818_read(NULL, MC_REGC) & MC_REGC_PF) 337 ; /* Nothing. */ 338 } 339 340 void 341 i8254_initclocks(void) 342 { 343 static struct timeout rtcdrain_timeout; 344 345 stathz = 128; 346 profhz = 1024; 347 348 isa_intr_establish(NULL, 0, IST_PULSE, IPL_CLOCK, clockintr, 349 0, "clock"); 350 isa_intr_establish(NULL, 8, IST_PULSE, IPL_CLOCK, rtcintr, 0, "rtc"); 351 352 mc146818_write(NULL, MC_REGA, MC_BASE_32_KHz | MC_RATE_128_Hz); 353 mc146818_write(NULL, MC_REGB, MC_REGB_24HR | MC_REGB_PIE); 354 355 /* 356 * On a number of i386 systems, the rtc will fail to start when booting 357 * the system. This is due to us missing to acknowledge an interrupt 358 * during early stages of the boot process. If we do not acknowledge 359 * the interrupt, the rtc clock will not generate further interrupts. 360 * To solve this, once interrupts are enabled, use a timeout (once) 361 * to drain any un-acknowledged rtc interrupt(s). 362 */ 363 timeout_set(&rtcdrain_timeout, rtcdrain, (void *)&rtcdrain_timeout); 364 timeout_add(&rtcdrain_timeout, 1); 365 } 366 367 int 368 rtcget(mc_todregs *regs) 369 { 370 if ((mc146818_read(NULL, MC_REGD) & MC_REGD_VRT) == 0) /* XXX softc */ 371 return (-1); 372 MC146818_GETTOD(NULL, regs); /* XXX softc */ 373 return (0); 374 } 375 376 void 377 rtcput(mc_todregs *regs) 378 { 379 MC146818_PUTTOD(NULL, regs); /* XXX softc */ 380 } 381 382 int 383 bcdtobin(int n) 384 { 385 return (((n >> 4) & 0x0f) * 10 + (n & 0x0f)); 386 } 387 388 int 389 bintobcd(int n) 390 { 391 return ((u_char)(((n / 10) << 4) & 0xf0) | ((n % 10) & 0x0f)); 392 } 393 394 static int timeset; 395 396 /* 397 * check whether the CMOS layout is "standard"-like (ie, not PS/2-like), 398 * to be called at splclock() 399 */ 400 static int cmoscheck(void); 401 static int 402 cmoscheck(void) 403 { 404 int i; 405 unsigned short cksum = 0; 406 407 for (i = 0x10; i <= 0x2d; i++) 408 cksum += mc146818_read(NULL, i); /* XXX softc */ 409 410 return (cksum == (mc146818_read(NULL, 0x2e) << 8) 411 + mc146818_read(NULL, 0x2f)); 412 } 413 414 /* 415 * patchable to control century byte handling: 416 * 1: always update 417 * -1: never touch 418 * 0: try to figure out itself 419 */ 420 int rtc_update_century = 0; 421 422 /* 423 * Expand a two-digit year as read from the clock chip 424 * into full width. 425 * Being here, deal with the CMOS century byte. 426 */ 427 static int centb = NVRAM_CENTURY; 428 static int clock_expandyear(int); 429 static int 430 clock_expandyear(int clockyear) 431 { 432 int s, clockcentury, cmoscentury; 433 434 clockcentury = (clockyear < 70) ? 20 : 19; 435 clockyear += 100 * clockcentury; 436 437 if (rtc_update_century < 0) 438 return (clockyear); 439 440 s = splclock(); 441 if (cmoscheck()) 442 cmoscentury = mc146818_read(NULL, NVRAM_CENTURY); 443 else 444 cmoscentury = 0; 445 splx(s); 446 if (!cmoscentury) { 447 #ifdef DIAGNOSTIC 448 printf("clock: unknown CMOS layout\n"); 449 #endif 450 return (clockyear); 451 } 452 cmoscentury = bcdtobin(cmoscentury); 453 454 if (cmoscentury != clockcentury) { 455 /* XXX note: saying "century is 20" might confuse the naive. */ 456 printf("WARNING: NVRAM century is %d but RTC year is %d\n", 457 cmoscentury, clockyear); 458 459 /* Kludge to roll over century. */ 460 if ((rtc_update_century > 0) || 461 ((cmoscentury == 19) && (clockcentury == 20) && 462 (clockyear == 2000))) { 463 printf("WARNING: Setting NVRAM century to %d\n", 464 clockcentury); 465 s = splclock(); 466 mc146818_write(NULL, centb, bintobcd(clockcentury)); 467 splx(s); 468 } 469 } else if (cmoscentury == 19 && rtc_update_century == 0) 470 rtc_update_century = 1; /* will update later in resettodr() */ 471 472 return (clockyear); 473 } 474 475 /* 476 * Initialize the time of day register, based on the time base which is, e.g. 477 * from a filesystem. 478 */ 479 void 480 inittodr(time_t base) 481 { 482 struct timespec ts; 483 mc_todregs rtclk; 484 struct clock_ymdhms dt; 485 int s; 486 487 ts.tv_nsec = 0; 488 489 /* 490 * We mostly ignore the suggested time (which comes from the 491 * file system) and go for the RTC clock time stored in the 492 * CMOS RAM. If the time can't be obtained from the CMOS, or 493 * if the time obtained from the CMOS is 5 or more years less 494 * than the suggested time, we used the suggested time. (In 495 * the latter case, it's likely that the CMOS battery has 496 * died.) 497 */ 498 499 /* 500 * if the file system time is more than a year older than the 501 * kernel, warn and then set the base time to the CONFIG_TIME. 502 */ 503 if (base < 30*SECYR) { /* if before 2000, something's odd... */ 504 printf("WARNING: preposterous time in file system\n"); 505 base = 30*SECYR; 506 } 507 508 s = splclock(); 509 if (rtcget(&rtclk)) { 510 splx(s); 511 printf("WARNING: invalid time in clock chip\n"); 512 goto fstime; 513 } 514 splx(s); 515 #ifdef DEBUG_CLOCK 516 printf("readclock: %x/%x/%x %x:%x:%x\n", rtclk[MC_YEAR], 517 rtclk[MC_MONTH], rtclk[MC_DOM], rtclk[MC_HOUR], rtclk[MC_MIN], 518 rtclk[MC_SEC]); 519 #endif 520 521 dt.dt_sec = bcdtobin(rtclk[MC_SEC]); 522 dt.dt_min = bcdtobin(rtclk[MC_MIN]); 523 dt.dt_hour = bcdtobin(rtclk[MC_HOUR]); 524 dt.dt_day = bcdtobin(rtclk[MC_DOM]); 525 dt.dt_mon = bcdtobin(rtclk[MC_MONTH]); 526 dt.dt_year = clock_expandyear(bcdtobin(rtclk[MC_YEAR])); 527 528 /* 529 * If time_t is 32 bits, then the "End of Time" is 530 * Mon Jan 18 22:14:07 2038 (US/Eastern) 531 * This code copes with RTC's past the end of time if time_t 532 * is an int32 or less. Needed because sometimes RTCs screw 533 * up or are badly set, and that would cause the time to go 534 * negative in the calculation below, which causes Very Bad 535 * Mojo. This at least lets the user boot and fix the problem. 536 * Note the code is self eliminating once time_t goes to 64 bits. 537 */ 538 if (sizeof(time_t) <= sizeof(int32_t)) { 539 if (dt.dt_year >= 2038) { 540 printf("WARNING: RTC time at or beyond 2038.\n"); 541 dt.dt_year = 2037; 542 printf("WARNING: year set back to 2037.\n"); 543 printf("WARNING: CHECK AND RESET THE DATE!\n"); 544 } 545 } 546 547 ts.tv_sec = clock_ymdhms_to_secs(&dt) + tz.tz_minuteswest * 60; 548 if (tz.tz_dsttime) 549 ts.tv_sec -= 3600; 550 551 if (base != 0 && base < ts.tv_sec - 5*SECYR) 552 printf("WARNING: file system time much less than clock time\n"); 553 else if (base > ts.tv_sec + 5*SECYR) { 554 printf("WARNING: clock time much less than file system time\n"); 555 printf("WARNING: using file system time\n"); 556 goto fstime; 557 } 558 559 tc_setclock(&ts); 560 timeset = 1; 561 return; 562 563 fstime: 564 ts.tv_sec = base; 565 tc_setclock(&ts); 566 timeset = 1; 567 printf("WARNING: CHECK AND RESET THE DATE!\n"); 568 } 569 570 /* 571 * Reset the clock. 572 */ 573 void 574 resettodr(void) 575 { 576 mc_todregs rtclk; 577 struct clock_ymdhms dt; 578 int century, diff, s; 579 580 /* 581 * We might have been called by boot() due to a crash early 582 * on. Don't reset the clock chip in this case. 583 */ 584 if (!timeset) 585 return; 586 587 s = splclock(); 588 if (rtcget(&rtclk)) 589 memset(&rtclk, 0, sizeof(rtclk)); 590 splx(s); 591 592 diff = tz.tz_minuteswest * 60; 593 if (tz.tz_dsttime) 594 diff -= 3600; 595 clock_secs_to_ymdhms(time_second - diff, &dt); 596 597 rtclk[MC_SEC] = bintobcd(dt.dt_sec); 598 rtclk[MC_MIN] = bintobcd(dt.dt_min); 599 rtclk[MC_HOUR] = bintobcd(dt.dt_hour); 600 rtclk[MC_DOW] = dt.dt_wday + 1; 601 rtclk[MC_YEAR] = bintobcd(dt.dt_year % 100); 602 rtclk[MC_MONTH] = bintobcd(dt.dt_mon); 603 rtclk[MC_DOM] = bintobcd(dt.dt_day); 604 605 #ifdef DEBUG_CLOCK 606 printf("setclock: %x/%x/%x %x:%x:%x\n", rtclk[MC_YEAR], rtclk[MC_MONTH], 607 rtclk[MC_DOM], rtclk[MC_HOUR], rtclk[MC_MIN], rtclk[MC_SEC]); 608 #endif 609 s = splclock(); 610 rtcput(&rtclk); 611 if (rtc_update_century > 0) { 612 century = bintobcd(dt.dt_year / 100); 613 mc146818_write(NULL, centb, century); /* XXX softc */ 614 } 615 splx(s); 616 } 617 618 void 619 setstatclockrate(int arg) 620 { 621 if (arg == stathz) 622 mc146818_write(NULL, MC_REGA, MC_BASE_32_KHz | MC_RATE_128_Hz); 623 else 624 mc146818_write(NULL, MC_REGA, MC_BASE_32_KHz | MC_RATE_1024_Hz); 625 } 626 627 void 628 i8254_inittimecounter(void) 629 { 630 tc_init(&i8254_timecounter); 631 } 632 633 /* 634 * If we're using lapic to drive hardclock, we can use a simpler 635 * algorithm for the i8254 timecounters. 636 */ 637 void 638 i8254_inittimecounter_simple(void) 639 { 640 u_long tval = 0x8000; 641 642 i8254_timecounter.tc_get_timecount = i8254_simple_get_timecount; 643 i8254_timecounter.tc_counter_mask = 0x7fff; 644 645 i8254_timecounter.tc_frequency = TIMER_FREQ; 646 647 mtx_enter(&timer_mutex); 648 outb(IO_TIMER1 + TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT); 649 outb(IO_TIMER1 + TIMER_CNTR0, tval & 0xff); 650 outb(IO_TIMER1 + TIMER_CNTR0, tval >> 8); 651 652 rtclock_tval = tval; 653 rtclock_init = 1; 654 mtx_leave(&timer_mutex); 655 656 tc_init(&i8254_timecounter); 657 } 658 659 u_int 660 i8254_simple_get_timecount(struct timecounter *tc) 661 { 662 return (rtclock_tval - gettick()); 663 } 664 665 u_int 666 i8254_get_timecount(struct timecounter *tc) 667 { 668 u_char hi, lo; 669 u_int count; 670 u_long ef; 671 672 ef = read_rflags(); 673 disable_intr(); 674 675 outb(IO_TIMER1+TIMER_MODE, TIMER_SEL0 | TIMER_LATCH); 676 lo = inb(IO_TIMER1+TIMER_CNTR0); 677 hi = inb(IO_TIMER1+TIMER_CNTR0); 678 679 count = rtclock_tval - ((hi << 8) | lo); 680 681 if (count < i8254_lastcount) { 682 i8254_ticked = 1; 683 i8254_offset += rtclock_tval; 684 } 685 i8254_lastcount = count; 686 count += i8254_offset; 687 write_rflags(ef); 688 689 return (count); 690 } 691