freebsd-dev/sys/amd64/isa/clock.c

540 lines
13 KiB
C
Raw Normal View History

1993-06-12 14:58:17 +00:00
/*-
* 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.15 1994/08/18 05:09:21 davidg Exp $
1993-06-12 14:58:17 +00:00
*/
/*
* Primitive clock interrupt routines.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/kernel.h>
#include <machine/segments.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>
#include <machine/cpu.h>
1993-06-12 14:58:17 +00:00
/* X-tals being what they are, it's nice to be able to fudge this one... */
/* Note, the name changed here from XTALSPEED to TIMER_FREQ rgrimes 4/26/93 */
#ifndef TIMER_FREQ
#define TIMER_FREQ 1193182 /* XXX - should be in isa.h */
#endif
#define TIMER_DIV(x) ((TIMER_FREQ+(x)/2)/(x))
1993-06-12 14:58:17 +00:00
static int beeping;
int timer0_divisor = TIMER_DIV(100); /* XXX should be hz */
u_int timer0_prescale;
static char timer0_state = 0, timer2_state = 0;
static char timer0_reprogram = 0;
static void (*timer_func)() = hardclock;
static void (*new_function)();
static u_int new_rate;
static u_int hardclock_divisor;
#ifdef I586_CPU
int pentium_mhz = 0;
#endif
void
clkintr(frame)
struct clockframe frame;
{
#ifdef I586_CPU
/*
* This resets the CPU cycle counter to zero, to make our
* job easier in microtime(). Some fancy ifdefs could speed
* this up for Pentium-only kernels.
* We want this to be done as close as possible to the actual
* timer incrementing in hardclock(), because there is a window
* between the two where the value is no longer valid. Experimentation
* may reveal a good precompensation to apply in microtime().
*/
if(pentium_mhz) {
__asm __volatile("movl $0x10,%%ecx\n"
"xorl %%eax,%%eax\n"
"movl %%eax,%%edx\n"
".byte 0x0f, 0x30\n"
"#%0%1"
: "=m"(frame) /* no outputs */
: "b"(&frame) /* fake input */
: "ax", "cx", "dx");
}
#endif
hardclock(&frame);
}
static u_char rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF;
/*
* 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 DEBUG
void
printrtc(void)
{
outb(IO_RTC, RTC_STATUSA);
printf("RTC status A = %x", inb(IO_RTC+1));
outb(IO_RTC, RTC_STATUSB);
printf(", B = %x", inb(IO_RTC+1));
outb(IO_RTC, RTC_INTR);
printf(", C = %x\n", inb(IO_RTC+1));
}
#endif
#if 0
void
timerintr(struct clockframe frame)
{
timer_func(&frame);
switch (timer0_state) {
case 0:
break;
case 1:
if ((timer0_prescale+=timer0_divisor) >= hardclock_divisor) {
hardclock(&frame);
timer0_prescale = 0;
}
break;
case 2:
disable_intr();
outb(TIMER_MODE, TIMER_SEL0|TIMER_RATEGEN|TIMER_16BIT);
outb(TIMER_CNTR0, TIMER_DIV(new_rate)%256);
outb(TIMER_CNTR0, TIMER_DIV(new_rate)/256);
enable_intr();
timer0_divisor = TIMER_DIV(new_rate);
timer0_prescale = 0;
timer_func = new_function;
timer0_state = 1;
break;
case 3:
if ((timer0_prescale+=timer0_divisor) >= hardclock_divisor) {
hardclock(&frame);
disable_intr();
outb(TIMER_MODE, TIMER_SEL0|TIMER_RATEGEN|TIMER_16BIT);
outb(TIMER_CNTR0, TIMER_DIV(hz)%256);
outb(TIMER_CNTR0, TIMER_DIV(hz)/256);
enable_intr();
timer0_divisor = TIMER_DIV(hz);
timer0_prescale = 0;
timer_func = hardclock;;
timer0_state = 0;
}
break;
}
}
#endif
int
acquire_timer0(int rate, void (*function)() )
{
if (timer0_state || !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;
}
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.
*/
cycles_per_sec = (long long)edx + eax;
cycles_per_sec -= (long long)lastedx + lasteax;
pentium_mhz = ((long)cycles_per_sec + 500000) / 1000000; /* round up */
}
#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 counter_limit, 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_divisor);
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()
{
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, 0, period);
}
return 0;
}
void
startrtclock()
{
1993-06-12 14:58:17 +00:00
int s;
/* initialize 8253 clock */
outb(TIMER_MODE, TIMER_SEL0|TIMER_RATEGEN|TIMER_16BIT);
/* Correct rounding will buy us a better precision in timekeeping */
outb (IO_TIMER1, TIMER_DIV(hz)%256);
outb (IO_TIMER1, TIMER_DIV(hz)/256);
timer0_divisor = hardclock_divisor = TIMER_DIV(hz);
1993-06-12 14:58:17 +00:00
/* initialize brain-dead battery powered clock */
outb (IO_RTC, RTC_STATUSA);
outb (IO_RTC+1, rtc_statusa);
1993-06-12 14:58:17 +00:00
outb (IO_RTC, RTC_STATUSB);
outb (IO_RTC+1, RTCSB_24HR);
1993-06-12 14:58:17 +00:00
outb (IO_RTC, RTC_DIAG);
if (s = inb (IO_RTC+1))
printf("RTC BIOS diagnostic error %b\n", s, RTCDG_BITS);
}
/* convert 2 digit BCD number */
int
bcd(int i)
1993-06-12 14:58:17 +00:00
{
return ((i/16)*10 + (i%16));
}
1993-06-12 14:58:17 +00:00
/* convert years to seconds (from 1970) */
unsigned long
ytos(int y)
1993-06-12 14:58:17 +00:00
{
int i;
unsigned long ret;
ret = 0;
for(i = 1970; i < y; i++) {
if (i % 4) ret += 365*24*60*60;
else ret += 366*24*60*60;
}
return ret;
}
1993-06-12 14:58:17 +00:00
/* convert months to seconds */
unsigned long
mtos(int m, int leap)
1993-06-12 14:58:17 +00:00
{
int i;
unsigned long ret;
ret = 0;
for(i=1; i<m; i++) {
1993-06-12 14:58:17 +00:00
switch(i){
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
ret += 31*24*60*60; break;
case 4: case 6: case 9: case 11:
ret += 30*24*60*60; break;
case 2:
if (leap) ret += 29*24*60*60;
else ret += 28*24*60*60;
}
}
return ret;
}
/*
* Initialize the time of day register, based on the time base which is, e.g.
* from a filesystem.
*/
void
inittodr(time_t base)
1993-06-12 14:58:17 +00:00
{
unsigned long sec;
int leap, day_week, t, yd;
1993-06-12 14:58:17 +00:00
int sa,s;
/* do we have a realtime clock present? (otherwise we loop below) */
sa = rtcin(RTC_STATUSA);
if (sa == 0xff || sa == 0) return;
/* ready for a read? */
while ((sa&RTCSA_TUP) == RTCSA_TUP)
sa = rtcin(RTC_STATUSA);
sec = bcd(rtcin(RTC_YEAR)) + 1900;
if (sec < 1970)
sec += 100;
leap = !(sec % 4); sec = ytos(sec); /* year */
yd = mtos(bcd(rtcin(RTC_MONTH)),leap); sec+=yd; /* month */
t = (bcd(rtcin(RTC_DAY))-1) * 24*60*60; sec+=t; yd+=t; /* date */
1993-06-12 14:58:17 +00:00
day_week = rtcin(RTC_WDAY); /* day */
sec += bcd(rtcin(RTC_HRS)) * 60*60; /* hour */
sec += bcd(rtcin(RTC_MIN)) * 60; /* minutes */
sec += bcd(rtcin(RTC_SEC)); /* seconds */
sec += tz.tz_minuteswest * 60;
time.tv_sec = sec;
}
1993-06-12 14:58:17 +00:00
#ifdef garbage
/*
* Initialze the time of day register, based on the time base which is, e.g.
* from a filesystem.
*/
test_inittodr(time_t base)
1993-06-12 14:58:17 +00:00
{
outb(IO_RTC,9); /* year */
printf("%d ",bcd(inb(IO_RTC+1)));
outb(IO_RTC,8); /* month */
printf("%d ",bcd(inb(IO_RTC+1)));
outb(IO_RTC,7); /* day */
printf("%d ",bcd(inb(IO_RTC+1)));
outb(IO_RTC,4); /* hour */
printf("%d ",bcd(inb(IO_RTC+1)));
outb(IO_RTC,2); /* minutes */
printf("%d ",bcd(inb(IO_RTC+1)));
outb(IO_RTC,0); /* seconds */
printf("%d\n",bcd(inb(IO_RTC+1)));
time.tv_sec = base;
}
#endif
/*
* Wire clock interrupt in.
*/
void
enablertclock()
{
Bruce Evans' dynamic interrupt support. /usr/src/sys/i386/isa/clock.c: o Garrett's statclock changes. o Wire xxxintr, not Vclk. o Wire using register_intr(), not setidt(). /usr/src/sys/i386/isa/icu.s: o Garrett's statclock changes. o Removed unused variable high_imask. o Fake int 8 for rtc as well as int 0 for clk. Required for kernel profiling with statclock, harmless otherwise. /usr/src/sys/i386/isa/isa.c: o Allow isdp->id_irq and other things in *isdp to be changed by probes. Changing interrupts later requires direct calls to register_intr() and unregister_intr() and more care. ALLOW_CONFLICT_* is brought over from 1.1.5, except ALLOW_CONFLICT_IRQ is not supported. IRQ conflict checking is delayed until after probing so that drivers can change the IRQ to a free one; real conflicts require more cooperation between drivers to handle. o Too many details to list. o This file requires splitting and a lot more work. /usr/src/sys/i386/isa/isa_device.h: o Declare more things more completely. /usr/src/sys/i386/isa/sio.c: o Prepare to register interrupt handlers as fast. /usr/src/sys/i386/isa/vector.s: o Generate entry code for 16 fast interrupt handlers and 16 normal interrupt handlers. Changed some constants to variables: # $unit is now intr_unit[intr]. Type is int. Someday it should be a cookie suitable for the handler (e.g., a struct com_s for sio). # $handler is now intr_handler[intr]. # intrcnt_actv[id_num] is now *intr_countp[intr]. The indirection is required to get a contiguous range of counters for vmstat and so that the drivers depend more in the driver than on the interrupt number (drivers could take turns using an interrupt and the counts would remain correct). There is a separate counter for each device and for each stray interrupt. In 1.1.5, stray interrupt 7 clobbers the count for device 7 or something worse if there is no device 7 :-(. # mask is now intr_mask[intr] (was already indirect). o Entry points are now _XintrI and _XfastintrI (I = intr = 0-15), not _VdevU (U = unit). o Removed BUILD_VECTORS stuff. There's a trace of it left for the string table for vmstat but config now generates the string in one piece because nothing more is required. o Removed old handling of stray interrupts and older comments about it. Submitted by: Bruce Evans
1994-08-18 05:09:36 +00:00
register_intr(/* irq */ 0, /* XXX id */ 0, /* flags */ 0, clkintr,
HWI_MASK | SWI_MASK, /* unit */ 0);
1993-06-12 14:58:17 +00:00
INTREN(IRQ0);
Bruce Evans' dynamic interrupt support. /usr/src/sys/i386/isa/clock.c: o Garrett's statclock changes. o Wire xxxintr, not Vclk. o Wire using register_intr(), not setidt(). /usr/src/sys/i386/isa/icu.s: o Garrett's statclock changes. o Removed unused variable high_imask. o Fake int 8 for rtc as well as int 0 for clk. Required for kernel profiling with statclock, harmless otherwise. /usr/src/sys/i386/isa/isa.c: o Allow isdp->id_irq and other things in *isdp to be changed by probes. Changing interrupts later requires direct calls to register_intr() and unregister_intr() and more care. ALLOW_CONFLICT_* is brought over from 1.1.5, except ALLOW_CONFLICT_IRQ is not supported. IRQ conflict checking is delayed until after probing so that drivers can change the IRQ to a free one; real conflicts require more cooperation between drivers to handle. o Too many details to list. o This file requires splitting and a lot more work. /usr/src/sys/i386/isa/isa_device.h: o Declare more things more completely. /usr/src/sys/i386/isa/sio.c: o Prepare to register interrupt handlers as fast. /usr/src/sys/i386/isa/vector.s: o Generate entry code for 16 fast interrupt handlers and 16 normal interrupt handlers. Changed some constants to variables: # $unit is now intr_unit[intr]. Type is int. Someday it should be a cookie suitable for the handler (e.g., a struct com_s for sio). # $handler is now intr_handler[intr]. # intrcnt_actv[id_num] is now *intr_countp[intr]. The indirection is required to get a contiguous range of counters for vmstat and so that the drivers depend more in the driver than on the interrupt number (drivers could take turns using an interrupt and the counts would remain correct). There is a separate counter for each device and for each stray interrupt. In 1.1.5, stray interrupt 7 clobbers the count for device 7 or something worse if there is no device 7 :-(. # mask is now intr_mask[intr] (was already indirect). o Entry points are now _XintrI and _XfastintrI (I = intr = 0-15), not _VdevU (U = unit). o Removed BUILD_VECTORS stuff. There's a trace of it left for the string table for vmstat but config now generates the string in one piece because nothing more is required. o Removed old handling of stray interrupts and older comments about it. Submitted by: Bruce Evans
1994-08-18 05:09:36 +00:00
register_intr(/* irq */ 8, /* XXX id */ 1, /* flags */ 0, rtcintr,
SWI_CLOCK_MASK, /* unit */ 0);
INTREN(IRQ8);
outb(IO_RTC, RTC_STATUSB);
outb(IO_RTC+1, RTCSB_PINTR | RTCSB_24HR);
1993-06-12 14:58:17 +00:00
}
1993-06-12 14:58:17 +00:00
/*
* Delay for some number of milliseconds.
*/
void
spinwait(int millisecs)
1993-06-12 14:58:17 +00:00
{
DELAY(1000 * millisecs);
}
void
cpu_initclocks()
{
stathz = RTC_NOPROFRATE;
profhz = RTC_PROFRATE;
enablertclock();
}
void
setstatclockrate(int newhz)
{
if(newhz == RTC_PROFRATE) {
rtc_statusa = RTCSA_DIVIDER | RTCSA_PROF;
} else {
rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF;
}
outb(IO_RTC, RTC_STATUSA);
outb(IO_RTC+1, rtc_statusa);
}