delete mmap'ed clock; new clock handling
[unix-history] / usr / src / sys / hp300 / hp300 / clock.c
/*
* Copyright (c) 1988 University of Utah.
* Copyright (c) 1982, 1990 The Regents of the University of California.
* All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* the Systems Programming Group of the University of Utah Computer
* Science Department.
*
* %sccs.include.redist.c%
*
* from: Utah $Hdr: clock.c 1.18 91/01/21$
*
* @(#)clock.c 7.14 (Berkeley) %G%
*/
#include "param.h"
#include "kernel.h"
#include "hp/dev/hilreg.h"
#include "clockreg.h"
#include "../include/psl.h"
#include "../include/cpu.h"
#ifdef GPROF
#include "sys/gprof.h"
#endif
int clkstd[1];
static int clkint; /* clock interval, as loaded */
/*
* Statistics clock interval and variance, in usec. Variance must be a
* power of two. Since this gives us an even number, not an odd number,
* we discard one case and compensate. That is, a variance of 1024 would
* give us offsets in [0..1023]. Instead, we take offsets in [1..1023].
* This is symmetric about the point 512, or statvar/2, and thus averages
* to that value (assuming uniform random numbers).
*/
static int statvar = 1024 / 4; /* {stat,prof}clock variance */
static int statmin; /* statclock interval - variance/2 */
static int profmin; /* profclock interval - variance/2 */
static int timer3min; /* current, from above choices */
static int month_days[12] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
struct bbc_tm *gmt_to_bbc();
u_char bbc_registers[13];
u_char write_bbc_reg(), read_bbc_reg();
struct hil_dev *bbcaddr = NULL;
/*
* Machine-dependent clock routines.
*
* A note on the real-time clock:
* We actually load the clock with interval-1 instead of interval.
* This is because the counter decrements to zero after N+1 enabled clock
* periods where N is the value loaded into the counter.
*
* The frequencies of the HP300 clocks must be a multiple of four
* microseconds (since the clock counts in 4 us units).
*/
#define COUNTS_PER_SEC (1000000 / CLK_RESOLUTION)
/*
* Set up the real-time and statistics clocks. Leave stathz 0 only if
* no alternative timer is available.
*
*/
cpu_initclocks()
{
register volatile struct clkreg *clk;
register int intvl, statint, profint, minint;
clkstd[0] = IIOV(0x5F8000); /* XXX grot */
clk = (volatile struct clkreg *)clkstd[0];
if (COUNTS_PER_SEC % hz) {
printf("cannot get %d Hz clock; using 100 Hz\n", hz);
hz = 100;
}
/*
* Clock has several counters, so we can always use separate
* statclock.
*/
if (stathz == 0) /* XXX should be set in param.c */
stathz = hz;
else if (COUNTS_PER_SEC % stathz) {
printf("cannot get %d Hz statclock; using 100 Hz\n", stathz);
stathz = 100;
}
if (profhz == 0) /* XXX should be set in param.c */
profhz = stathz * 5;
else if (profhz < stathz || COUNTS_PER_SEC % profhz) {
printf("cannot get %d Hz profclock; using %d Hz\n",
profhz, stathz);
profhz = stathz;
}
intvl = COUNTS_PER_SEC / hz;
statint = COUNTS_PER_SEC / stathz;
profint = COUNTS_PER_SEC / profhz;
minint = statint / 2 + 100;
while (statvar > minint)
statvar >>= 1;
tick = intvl * CLK_RESOLUTION;
/* adjust interval counts, per note above */
intvl--;
statint--;
profint--;
/* calculate base reload values */
clkint = intvl;
statmin = statint - (statvar >> 1);
profmin = profint - (statvar >> 1);
timer3min = statmin;
/* finally, load hardware */
clk->clk_cr2 = CLK_CR1;
clk->clk_cr1 = CLK_RESET;
clk->clk_msb1 = intvl >> 8;
clk->clk_lsb1 = intvl;
clk->clk_msb2 = 0;
clk->clk_lsb2 = 0;
clk->clk_msb3 = statint >> 8;
clk->clk_lsb3 = statint;
clk->clk_cr2 = CLK_CR1;
clk->clk_cr1 = CLK_IENAB;
clk->clk_cr2 = CLK_CR3;
clk->clk_cr3 = CLK_IENAB;
}
/*
* We assume newhz is either stathz or profhz, and that neither will
* change after being set up above. Could recalculate intervals here
* but that would be a drag.
*/
void
setstatclockrate(newhz)
int newhz;
{
if (newhz == stathz)
timer3min = statmin;
else
timer3min = profmin;
}
/*
* Statistics/profiling clock interrupt. Clear the interrupt and
* compute a new interval.
*
* DO THIS INLINE IN locore.s?
*/
void
statintr(fp)
struct clockframe *fp;
{
register volatile struct clkreg *clk;
register int newint, r, var;
register u_char discard;
clk = (volatile struct clkreg *)clkstd[0];
discard = clk->clk_msb3; /* clear interrupt */
var = statvar;
do {
r = random() & (var - 1);
} while (r == 0);
newint = timer3min + r;
clk->clk_msb3 = newint >> 8;
clk->clk_lsb3 = newint;
statclock(fp);
}
/*
* Return the best possible estimate of the current time.
*/
microtime(tvp)
register struct timeval *tvp;
{
register volatile struct clkreg *clk;
register int s, u, h, l, sr, l2, h2, u2, s2;
/*
* Read registers from slowest-changing to fastest-changing,
* then re-read out to slowest. If the values read before the
* innermost match those read after, the innermost value is
* consistent with the outer values. If not, it may not be and
* we must retry. Typically this loop runs only once; occasionally
* it runs twice, and only rarely does it run longer.
*
* (Using this loop avoids the need to block interrupts.)
*/
clk = (volatile struct clkreg *)clkstd[0];
do {
s = time.tv_sec;
u = time.tv_usec;
h = clk->clk_msb1;
l = clk->clk_lsb1;
sr = clk->clk_sr;
l2 = clk->clk_lsb1;
h2 = clk->clk_msb1;
u2 = time.tv_usec;
s2 = time.tv_sec;
} while (l != l2 || h != h2 || u != u2 || s != s2);
/*
* Pending interrupt means that the counter wrapped and we did not
* take the interrupt yet (can only happen if clock interrupts are
* blocked). If so, add one tick. Then in any case, add remaining
* count. This should leave u < 2 seconds, since we can add at most
* two clock intervals (assuming hz > 2!).
*/
if (sr & CLK_INT1)
u += tick;
u += clkint - ((h << 8) | l);
if (u >= 1000000) { /* normalize */
s++;
u -= 1000000;
}
tvp->tv_sec = s;
tvp->tv_usec = u;
}
/*
* Initialize the time of day register, based on the time base which is, e.g.
* from a filesystem.
*/
inittodr(base)
time_t base;
{
u_long timbuf = base; /* assume no battery clock exists */
static int bbcinited = 0;
/* XXX */
if (!bbcinited) {
if (badbaddr(&BBCADDR->hil_stat))
printf("WARNING: no battery clock\n");
else
bbcaddr = BBCADDR;
bbcinited = 1;
}
/*
* bbc_to_gmt converts and stores the gmt in timbuf.
* If an error is detected in bbc_to_gmt, or if the filesystem
* time is more recent than the gmt time in the clock,
* then use the filesystem time and warn the user.
*/
if (!bbc_to_gmt(&timbuf) || timbuf < base) {
printf("WARNING: bad date in battery clock\n");
timbuf = base;
}
if (base < 5*SECYR) {
printf("WARNING: preposterous time in file system");
timbuf = 6*SECYR + 186*SECDAY + SECDAY/2;
printf(" -- CHECK AND RESET THE DATE!\n");
}
/* Battery clock does not store usec's, so forget about it. */
time.tv_sec = timbuf;
}
/*
* Restore the time of day hardware after a time change.
*/
resettodr()
{
register int i;
register struct bbc_tm *tmptr;
tmptr = gmt_to_bbc(time.tv_sec);
decimal_to_bbc(0, 1, tmptr->tm_sec);
decimal_to_bbc(2, 3, tmptr->tm_min);
decimal_to_bbc(4, 5, tmptr->tm_hour);
decimal_to_bbc(7, 8, tmptr->tm_mday);
decimal_to_bbc(9, 10, tmptr->tm_mon);
decimal_to_bbc(11, 12, tmptr->tm_year);
/* Some bogusness to deal with seemingly broken hardware. Nonsense */
bbc_registers[5] = ((tmptr->tm_hour / 10) & 0x03) + 8;
write_bbc_reg(15, 13); /* reset prescalar */
for (i = 0; i <= NUM_BBC_REGS; i++)
if (bbc_registers[i] != write_bbc_reg(i, bbc_registers[i])) {
printf("Cannot set battery backed clock\n");
break;
}
}
struct bbc_tm *
gmt_to_bbc(tim)
long tim;
{
register int i;
register long hms, day;
static struct bbc_tm rt;
day = tim / SECDAY;
hms = tim % SECDAY;
/* Hours, minutes, seconds are easy */
rt.tm_hour = hms / 3600;
rt.tm_min = (hms % 3600) / 60;
rt.tm_sec = (hms % 3600) % 60;
/* Number of years in days */
for (i = STARTOFTIME - 1900; day >= days_in_year(i); i++)
day -= days_in_year(i);
rt.tm_year = i;
/* Number of months in days left */
if (leapyear(rt.tm_year))
days_in_month(FEBRUARY) = 29;
for (i = 1; day >= days_in_month(i); i++)
day -= days_in_month(i);
days_in_month(FEBRUARY) = 28;
rt.tm_mon = i;
/* Days are what is left over (+1) from all that. */
rt.tm_mday = day + 1;
return(&rt);
}
bbc_to_gmt(timbuf)
u_long *timbuf;
{
register int i;
register u_long tmp;
int year, month, day, hour, min, sec;
read_bbc();
sec = bbc_to_decimal(1, 0);
min = bbc_to_decimal(3, 2);
/*
* Hours are different for some reason. Makes no sense really.
*/
hour = ((bbc_registers[5] & 0x03) * 10) + bbc_registers[4];
day = bbc_to_decimal(8, 7);
month = bbc_to_decimal(10, 9);
year = bbc_to_decimal(12, 11) + 1900;
range_test(hour, 0, 23);
range_test(day, 1, 31);
range_test(month, 1, 12);
range_test(year, STARTOFTIME, 2000);
tmp = 0;
for (i = STARTOFTIME; i < year; i++)
tmp += days_in_year(i);
if (leapyear(year) && month > FEBRUARY)
tmp++;
for (i = 1; i < month; i++)
tmp += days_in_month(i);
tmp += (day - 1);
tmp = ((tmp * 24 + hour) * 60 + min) * 60 + sec;
*timbuf = tmp;
return(1);
}
read_bbc()
{
register int i, read_okay;
read_okay = 0;
while (!read_okay) {
read_okay = 1;
for (i = 0; i <= NUM_BBC_REGS; i++)
bbc_registers[i] = read_bbc_reg(i);
for (i = 0; i <= NUM_BBC_REGS; i++)
if (bbc_registers[i] != read_bbc_reg(i))
read_okay = 0;
}
}
u_char
read_bbc_reg(reg)
int reg;
{
u_char data = reg;
if (bbcaddr) {
send_hil_cmd(bbcaddr, BBC_SET_REG, &data, 1, NULL);
send_hil_cmd(bbcaddr, BBC_READ_REG, NULL, 0, &data);
}
return(data);
}
u_char
write_bbc_reg(reg, data)
u_int data;
{
u_char tmp;
tmp = (u_char) ((data << HIL_SSHIFT) | reg);
if (bbcaddr) {
send_hil_cmd(bbcaddr, BBC_SET_REG, &tmp, 1, NULL);
send_hil_cmd(bbcaddr, BBC_WRITE_REG, NULL, 0, NULL);
send_hil_cmd(bbcaddr, BBC_READ_REG, NULL, 0, &tmp);
}
return(tmp);
}