1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-12-30 12:04:07 +00:00
freebsd/sys/amd64/isa/clock.c
Andreas Schulz a9700525d8 Work around a compiler bug in gcc2.6.3 in handling (long long) variables and
shifting. Also correct the original code as Garrett noticed it in mail.
Leave the mishandled code in to use it later if future versions of gcc
are correct. The code was part of the calibrate_cyclecounter routine to
get the speed of the pentium chip.
1995-01-07 17:26:35 +00:00

607 lines
16 KiB
C

/*-
* Copyright (c) 1990 The Regents of the University of California.
* All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* William Jolitz and Don Ahn.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* from: @(#)clock.c 7.2 (Berkeley) 5/12/91
* $Id: clock.c,v 1.29 1994/12/30 12:43:34 bde Exp $
*/
/*
* inittodr, settodr and support routines written
* by Christoph Robitschko <chmr@edvz.tu-graz.ac.at>
*
* reintroduced and updated by Chris Stenton <chris@gnome.co.uk> 8/10/94
*/
/*
* Primitive clock interrupt routines.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/kernel.h>
#include <machine/clock.h>
#include <machine/frame.h>
#include <i386/isa/icu.h>
#include <i386/isa/isa.h>
#include <i386/isa/rtc.h>
#include <i386/isa/timerreg.h>
/*
* 32-bit time_t's can't reach leap years before 1904 or after 2036, so we
* can use a simple formula for leap years.
*/
#define LEAPYEAR(y) ((u_int)(y) % 4 == 0)
#define DAYSPERYEAR (31+28+31+30+31+30+31+31+30+31+30+31)
/* X-tals being what they are, it's nice to be able to fudge this one... */
#ifndef TIMER_FREQ
#define TIMER_FREQ 1193182 /* XXX - should be in isa.h */
#endif
#define TIMER_DIV(x) ((TIMER_FREQ+(x)/2)/(x))
/*
* Time in timer cycles that it takes for microtime() to disable interrupts
* and latch the count. microtime() currently uses "cli; outb ..." so it
* normally takes less than 2 timer cycles. Add a few for cache misses.
* Add a few more to allow for latency in bogus calls to microtime() with
* interrupts already disabled.
*/
#define TIMER0_LATCH_COUNT 20
/*
* Minimum maximum count that we are willing to program into timer0.
* Must be large enough to guarantee that the timer interrupt handler
* returns before the next timer interrupt. Must be larger than
* TIMER0_LATCH_COUNT so that we don't have to worry about underflow in
* the calculation of timer0_overflow_threshold.
*/
#define TIMER0_MIN_MAX_COUNT TIMER_DIV(20000)
int adjkerntz = 0; /* offset from CMOS clock */
int disable_rtc_set = 0; /* disable resettodr() if != 0 */
#ifdef I586_CPU
int pentium_mhz;
#endif
u_int stat_imask = SWI_CLOCK_MASK;
int timer0_max_count;
u_int timer0_overflow_threshold;
u_int timer0_prescaler_count;
static int beeping = 0;
static u_int clk_imask = HWI_MASK | SWI_MASK;
static const u_char daysinmonth[] = {31,28,31,30,31,30,31,31,30,31,30,31};
static u_int hardclock_max_count;
/*
* XXX new_function and timer_func should not handle clockframes, but
* timer_func currently needs to hold hardclock to handle the
* timer0_state == 0 case. We should use register_intr()/unregister_intr()
* to switch between clkintr() and a slightly different timerintr().
* This will require locking when acquiring and releasing timer0 - the
* current (nonexistent) locking doesn't seem to be adequate even now.
*/
static void (*new_function) __P((struct clockframe *frame));
static u_int new_rate;
static u_char rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF;
static char timer0_state = 0;
static char timer2_state = 0;
static void (*timer_func) __P((struct clockframe *frame)) = hardclock;
#if 0
void
clkintr(struct clockframe frame)
{
hardclock(&frame);
}
#else
void
clkintr(struct clockframe frame)
{
timer_func(&frame);
switch (timer0_state) {
case 0:
break;
case 1:
if ((timer0_prescaler_count += timer0_max_count)
>= hardclock_max_count) {
hardclock(&frame);
timer0_prescaler_count -= hardclock_max_count;
}
break;
case 2:
timer0_max_count = TIMER_DIV(new_rate);
timer0_overflow_threshold =
timer0_max_count - TIMER0_LATCH_COUNT;
disable_intr();
outb(TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT);
outb(TIMER_CNTR0, timer0_max_count & 0xff);
outb(TIMER_CNTR0, timer0_max_count >> 8);
enable_intr();
timer0_prescaler_count = 0;
timer_func = new_function;
timer0_state = 1;
break;
case 3:
if ((timer0_prescaler_count += timer0_max_count)
>= hardclock_max_count) {
hardclock(&frame);
timer0_max_count = hardclock_max_count;
timer0_overflow_threshold =
timer0_max_count - TIMER0_LATCH_COUNT;
disable_intr();
outb(TIMER_MODE,
TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT);
outb(TIMER_CNTR0, timer0_max_count & 0xff);
outb(TIMER_CNTR0, timer0_max_count >> 8);
enable_intr();
/*
* See microtime.s for this magic.
*/
time.tv_usec += (27645 *
(timer0_prescaler_count - hardclock_max_count))
>> 15;
if (time.tv_usec >= 1000000)
time.tv_usec -= 1000000;
timer0_prescaler_count = 0;
timer_func = hardclock;;
timer0_state = 0;
}
break;
}
}
#endif
int
acquire_timer0(int rate, void (*function) __P((struct clockframe *frame)))
{
if (timer0_state || TIMER_DIV(rate) < TIMER0_MIN_MAX_COUNT ||
!function)
return -1;
new_function = function;
new_rate = rate;
timer0_state = 2;
return 0;
}
int
acquire_timer2(int mode)
{
if (timer2_state)
return -1;
timer2_state = 1;
outb(TIMER_MODE, TIMER_SEL2 | (mode &0x3f));
return 0;
}
int
release_timer0()
{
if (!timer0_state)
return -1;
timer0_state = 3;
return 0;
}
int
release_timer2()
{
if (!timer2_state)
return -1;
timer2_state = 0;
outb(TIMER_MODE, TIMER_SEL2|TIMER_SQWAVE|TIMER_16BIT);
return 0;
}
/*
* This routine receives statistical clock interrupts from the RTC.
* As explained above, these occur at 128 interrupts per second.
* When profiling, we receive interrupts at a rate of 1024 Hz.
*
* This does not actually add as much overhead as it sounds, because
* when the statistical clock is active, the hardclock driver no longer
* needs to keep (inaccurate) statistics on its own. This decouples
* statistics gathering from scheduling interrupts.
*
* The RTC chip requires that we read status register C (RTC_INTR)
* to acknowledge an interrupt, before it will generate the next one.
*/
void
rtcintr(struct clockframe frame)
{
u_char stat;
stat = rtcin(RTC_INTR);
if(stat & RTCIR_PERIOD) {
statclock(&frame);
}
}
#ifdef DDB
static void
printrtc(void)
{
printf("%02x/%02x/%02x %02x:%02x:%02x, A = %02x, B = %02x, C = %02x\n",
rtcin(RTC_YEAR), rtcin(RTC_MONTH), rtcin(RTC_DAY),
rtcin(RTC_HRS), rtcin(RTC_MIN), rtcin(RTC_SEC),
rtcin(RTC_STATUSA), rtcin(RTC_STATUSB), rtcin(RTC_INTR));
}
#endif
static int
getit()
{
int high, low;
disable_intr();
/* select timer0 and latch counter value */
outb(TIMER_MODE, TIMER_SEL0);
low = inb(TIMER_CNTR0);
high = inb(TIMER_CNTR0);
enable_intr();
return ((high << 8) | low);
}
#ifdef I586_CPU
static long long cycles_per_sec = 0;
/*
* Figure out how fast the cyclecounter runs. This must be run with
* clock interrupts disabled, but with the timer/counter programmed
* and running.
*/
void
calibrate_cyclecounter(void)
{
volatile long edx, eax, lasteax, lastedx;
__asm __volatile(".byte 0x0f, 0x31" : "=a"(lasteax), "=d"(lastedx) : );
DELAY(1000000);
__asm __volatile(".byte 0x0f, 0x31" : "=a"(eax), "=d"(edx) : );
/*
* This assumes that you will never have a clock rate higher
* than 4GHz, probably a good assumption.
*/
/* The following C code is correct, but our current gcc 2.6.3
* seems to produce bad assembly code for it , ATS , XXXX */
#if 0
cycles_per_sec = ((long long)edx << 32) + eax;
cycles_per_sec -= ((long long)lastedx << 32) + lasteax;
pentium_mhz = ((long)cycles_per_sec + 500000) / 1000000; /* round up */
#else
/* produce a workaround for the code above */
{
union {
long long extralong;
long shorty[2];
} tmp;
tmp.shorty[0] = eax;
tmp.shorty[1] = edx;
cycles_per_sec = tmp.extralong;
tmp.shorty[0] = lasteax;
tmp.shorty[1] = lastedx;
cycles_per_sec -= tmp.extralong;
/* round up */
pentium_mhz = (long) ((cycles_per_sec + 500000) / 1000000);
}
#endif
}
#endif
/*
* Wait "n" microseconds.
* Relies on timer 1 counting down from (TIMER_FREQ / hz)
* Note: timer had better have been programmed before this is first used!
*/
void
DELAY(int n)
{
int prev_tick, tick, ticks_left, sec, usec;
#ifdef DELAYDEBUG
int getit_calls = 1;
int n1;
static int state = 0;
if (state == 0) {
state = 1;
for (n1 = 1; n1 <= 10000000; n1 *= 10)
DELAY(n1);
state = 2;
}
if (state == 1)
printf("DELAY(%d)...", n);
#endif
/*
* Read the counter first, so that the rest of the setup overhead is
* counted. Guess the initial overhead is 20 usec (on most systems it
* takes about 1.5 usec for each of the i/o's in getit(). The loop
* takes about 6 usec on a 486/33 and 13 usec on a 386/20. The
* multiplications and divisions to scale the count take a while).
*/
prev_tick = getit(0, 0);
n -= 20;
/*
* Calculate (n * (TIMER_FREQ / 1e6)) without using floating point
* and without any avoidable overflows.
*/
sec = n / 1000000;
usec = n - sec * 1000000;
ticks_left = sec * TIMER_FREQ
+ usec * (TIMER_FREQ / 1000000)
+ usec * ((TIMER_FREQ % 1000000) / 1000) / 1000
+ usec * (TIMER_FREQ % 1000) / 1000000;
while (ticks_left > 0) {
tick = getit(0, 0);
#ifdef DELAYDEBUG
++getit_calls;
#endif
if (tick > prev_tick)
ticks_left -= prev_tick - (tick - timer0_max_count);
else
ticks_left -= prev_tick - tick;
prev_tick = tick;
}
#ifdef DELAYDEBUG
if (state == 1)
printf(" %d calls to getit() at %d usec each\n",
getit_calls, (n + 5) / getit_calls);
#endif
}
static void
sysbeepstop(void *chan)
{
outb(IO_PPI, inb(IO_PPI)&0xFC); /* disable counter2 output to speaker */
release_timer2();
beeping = 0;
}
int
sysbeep(int pitch, int period)
{
if (acquire_timer2(TIMER_SQWAVE|TIMER_16BIT))
return -1;
disable_intr();
outb(TIMER_CNTR2, pitch);
outb(TIMER_CNTR2, (pitch>>8));
enable_intr();
if (!beeping) {
outb(IO_PPI, inb(IO_PPI) | 3); /* enable counter2 output to speaker */
beeping = period;
timeout(sysbeepstop, (void *)NULL, period);
}
return 0;
}
/*
* RTC support routines
*/
static int
bcd2int(int bcd)
{
return(bcd/16 * 10 + bcd%16);
}
static int
int2bcd(int dez)
{
return(dez/10 * 16 + dez%10);
}
static inline void
writertc(u_char reg, u_char val)
{
outb(IO_RTC, reg);
outb(IO_RTC + 1, val);
}
static int
readrtc(int port)
{
return(bcd2int(rtcin(port)));
}
/*
* Initialize 8253 timer 0 early so that it can be used in DELAY().
* XXX initialization of other timers is unintentionally left blank.
*/
void
startrtclock()
{
timer0_max_count = hardclock_max_count = TIMER_DIV(hz);
timer0_overflow_threshold = timer0_max_count - TIMER0_LATCH_COUNT;
outb(TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT);
outb(TIMER_CNTR0, timer0_max_count & 0xff);
outb(TIMER_CNTR0, timer0_max_count >> 8);
}
/*
* Initialize the time of day register, based on the time base which is, e.g.
* from a filesystem.
*/
void
inittodr(time_t base)
{
unsigned long sec, days;
int yd;
int year, month;
int y, m, s;
s = splclock();
time.tv_sec = base;
time.tv_usec = 0;
splx(s);
/* Look if we have a RTC present and the time is valid */
if (rtcin(RTC_STATUSD) != RTCSD_PWR)
goto wrong_time;
/* wait for time update to complete */
/* If RTCSA_TUP is zero, we have at least 244us before next update */
while (rtcin(RTC_STATUSA) & RTCSA_TUP);
days = 0;
#ifdef USE_RTC_CENTURY
year = readrtc(RTC_YEAR) + readrtc(RTC_CENTURY) * 100;
#else
year = readrtc(RTC_YEAR) + 1900;
if (year < 1970)
year += 100;
#endif
if (year < 1970)
goto wrong_time;
month = readrtc(RTC_MONTH);
for (m = 1; m < month; m++)
days += daysinmonth[m-1];
if ((month > 2) && LEAPYEAR(year))
days ++;
days += readrtc(RTC_DAY) - 1;
yd = days;
for (y = 1970; y < year; y++)
days += DAYSPERYEAR + LEAPYEAR(y);
sec = ((( days * 24 +
readrtc(RTC_HRS)) * 60 +
readrtc(RTC_MIN)) * 60 +
readrtc(RTC_SEC));
/* sec now contains the number of seconds, since Jan 1 1970,
in the local time zone */
sec += tz.tz_minuteswest * 60 + adjkerntz;
s = splclock();
time.tv_sec = sec;
splx(s);
return;
wrong_time:
printf("Invalid time in real time clock.\n");
printf("Check and reset the date immediately!\n");
}
/*
* Write system time back to RTC
*/
void
resettodr()
{
unsigned long tm;
int y, m, fd, r, s;
if (disable_rtc_set)
return;
s = splclock();
tm = time.tv_sec;
splx(s);
/* Disable RTC updates and interrupts. */
writertc(RTC_STATUSB, RTCSB_HALT | RTCSB_24HR);
/* Calculate local time to put in RTC */
tm -= tz.tz_minuteswest * 60 + adjkerntz;
writertc(RTC_SEC, int2bcd(tm%60)); tm /= 60; /* Write back Seconds */
writertc(RTC_MIN, int2bcd(tm%60)); tm /= 60; /* Write back Minutes */
writertc(RTC_HRS, int2bcd(tm%24)); tm /= 24; /* Write back Hours */
/* We have now the days since 01-01-1970 in tm */
writertc(RTC_WDAY, (tm+4)%7); /* Write back Weekday */
for (y=1970;; y++)
if ((tm - DAYSPERYEAR - LEAPYEAR(y)) > tm)
break;
else
tm -= DAYSPERYEAR + LEAPYEAR(y);
/* Now we have the years in y and the day-of-the-year in tm */
writertc(RTC_YEAR, int2bcd(y%100)); /* Write back Year */
#ifdef USE_RTC_CENTURY
writertc(RTC_CENTURY, int2bcd(y/100)); /* ... and Century */
#endif
if (LEAPYEAR(y) && (tm >= 31+29))
tm--; /* Subtract Feb-29 */
for (m=1;; m++)
if (tm - daysinmonth[m-1] > tm)
break;
else
tm -= daysinmonth[m-1];
writertc(RTC_MONTH, int2bcd(m)); /* Write back Month */
writertc(RTC_DAY, int2bcd(tm+1)); /* Write back Day */
/* Reenable RTC updates and interrupts. */
writertc(RTC_STATUSB, RTCSB_24HR | RTCSB_PINTR);
}
/*
* Start both clocks running.
*/
void
cpu_initclocks()
{
int diag;
stathz = RTC_NOPROFRATE;
profhz = RTC_PROFRATE;
/* Finish initializing 8253 timer 0. */
register_intr(/* irq */ 0, /* XXX id */ 0, /* flags */ 0, clkintr,
&clk_imask, /* unit */ 0);
INTREN(IRQ0);
/* Initialize RTC. */
writertc(RTC_STATUSA, rtc_statusa);
writertc(RTC_STATUSB, RTCSB_24HR);
diag = rtcin(RTC_DIAG);
if (diag != 0)
printf("RTC BIOS diagnostic error %b\n", diag, RTCDG_BITS);
register_intr(/* irq */ 8, /* XXX id */ 1, /* flags */ 0, rtcintr,
&stat_imask, /* unit */ 0);
INTREN(IRQ8);
writertc(RTC_STATUSB, RTCSB_24HR | RTCSB_PINTR);
}
void
setstatclockrate(int newhz)
{
if (newhz == RTC_PROFRATE)
rtc_statusa = RTCSA_DIVIDER | RTCSA_PROF;
else
rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF;
writertc(RTC_STATUSA, rtc_statusa);
}