subr_clock.c revision 116182
193835Stmm/*
293835Stmm * Copyright (c) 1988 University of Utah.
393835Stmm * Copyright (c) 1982, 1990, 1993
493835Stmm *	The Regents of the University of California.  All rights reserved.
593835Stmm *
693835Stmm * This code is derived from software contributed to Berkeley by
793835Stmm * the Systems Programming Group of the University of Utah Computer
893835Stmm * Science Department.
993835Stmm *
1093835Stmm * Redistribution and use in source and binary forms, with or without
1193835Stmm * modification, are permitted provided that the following conditions
1293835Stmm * are met:
1393835Stmm * 1. Redistributions of source code must retain the above copyright
1493835Stmm *    notice, this list of conditions and the following disclaimer.
1593835Stmm * 2. Redistributions in binary form must reproduce the above copyright
1693835Stmm *    notice, this list of conditions and the following disclaimer in the
1793835Stmm *    documentation and/or other materials provided with the distribution.
1893835Stmm * 3. All advertising materials mentioning features or use of this software
1993835Stmm *    must display the following acknowledgement:
2093835Stmm *	This product includes software developed by the University of
2193835Stmm *	California, Berkeley and its contributors.
2293835Stmm * 4. Neither the name of the University nor the names of its contributors
2393835Stmm *    may be used to endorse or promote products derived from this software
2493835Stmm *    without specific prior written permission.
2593835Stmm *
2693835Stmm * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2793835Stmm * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2893835Stmm * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2993835Stmm * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
3093835Stmm * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
3193835Stmm * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
3293835Stmm * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
3393835Stmm * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3493835Stmm * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3593835Stmm * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3693835Stmm * SUCH DAMAGE.
3793835Stmm *
3893835Stmm *	from: Utah $Hdr: clock.c 1.18 91/01/21$
3993835Stmm *	from: @(#)clock.c	8.2 (Berkeley) 1/12/94
4093835Stmm *	from: NetBSD: clock_subr.c,v 1.6 2001/07/07 17:04:02 thorpej Exp
4193835Stmm *	and
4293835Stmm *	from: src/sys/i386/isa/clock.c,v 1.176 2001/09/04
4393835Stmm */
4493835Stmm
4593835Stmm/*
4693835Stmm * Helpers for time-of-day clocks. This is useful for architectures that need
4793835Stmm * support multiple models of such clocks, and generally serves to make the
4893835Stmm * code more machine-independent.
4993835Stmm * If the clock in question can also be used as a time counter, the driver
5093835Stmm * needs to initiate this.
5193835Stmm * This code is not yet used by all architectures.
5293835Stmm */
5393835Stmm
5493835Stmm/*
5593835Stmm * Generic routines to convert between a POSIX date
5693835Stmm * (seconds since 1/1/1970) and yr/mo/day/hr/min/sec
5793835Stmm * Derived from NetBSD arch/hp300/hp300/clock.c
5893835Stmm */
59116182Sobrien
60116182Sobrien#include <sys/cdefs.h>
61116182Sobrien__FBSDID("$FreeBSD: head/sys/kern/subr_clock.c 116182 2003-06-11 00:56:59Z obrien $");
62116182Sobrien
6393835Stmm#include <sys/param.h>
6493835Stmm#include <sys/systm.h>
6593835Stmm#include <sys/kernel.h>
6693835Stmm#include <sys/bus.h>
6793835Stmm#include <sys/clock.h>
6893835Stmm#include <sys/sysctl.h>
6993835Stmm#include <sys/timetc.h>
7093835Stmm
71101484Stmm/* XXX: for the  CPU_* sysctl OID constants. */
72101484Stmm#include <machine/cpu.h>
73101484Stmm
7493835Stmm#include "clock_if.h"
7593835Stmm
7693835Stmmstatic __inline int leapyear(int year);
7793835Stmmstatic int sysctl_machdep_adjkerntz(SYSCTL_HANDLER_ARGS);
7893835Stmm
7993835Stmm#define	FEBRUARY	2
8093835Stmm#define	days_in_year(y) 	(leapyear(y) ? 366 : 365)
8193835Stmm#define	days_in_month(y, m) \
8293835Stmm	(month_days[(m) - 1] + (m == FEBRUARY ? leapyear(y) : 0))
8393835Stmm/* Day of week. Days are counted from 1/1/1970, which was a Thursday */
8493835Stmm#define	day_of_week(days)	(((days) + 4) % 7)
8593835Stmm
8693835Stmmstatic const int month_days[12] = {
8793835Stmm	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
8893835Stmm};
8993835Stmm
9093835Stmmstatic device_t clock_dev = NULL;
9193835Stmmstatic long clock_res;
9293835Stmm
9393835Stmmint adjkerntz;		/* local offset from GMT in seconds */
9493835Stmmint disable_rtc_set;	/* disable resettodr() if != 0 */
9593835Stmmint wall_cmos_clock;	/* wall CMOS clock assumed if != 0 */
9693835Stmm
9793835Stmm/*
9893835Stmm * These have traditionally been in machdep, but should probably be moved to
9993835Stmm * kern.
10093835Stmm */
101101484StmmSYSCTL_PROC(_machdep, CPU_ADJKERNTZ, adjkerntz, CTLTYPE_INT|CTLFLAG_RW,
10293835Stmm	&adjkerntz, 0, sysctl_machdep_adjkerntz, "I", "");
10393835Stmm
104101484StmmSYSCTL_INT(_machdep, CPU_DISRTCSET, disable_rtc_set,
10593835Stmm	CTLFLAG_RW, &disable_rtc_set, 0, "");
10693835Stmm
107101484StmmSYSCTL_INT(_machdep, CPU_WALLCLOCK, wall_cmos_clock,
10893835Stmm	CTLFLAG_RW, &wall_cmos_clock, 0, "");
10993835Stmm
11093835Stmmstatic int
11193835Stmmsysctl_machdep_adjkerntz(SYSCTL_HANDLER_ARGS)
11293835Stmm{
11393835Stmm	int error;
11493835Stmm	error = sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2,
11593835Stmm		req);
11693835Stmm	if (!error && req->newptr)
11793835Stmm		resettodr();
11893835Stmm	return (error);
11993835Stmm}
12093835Stmm
12193835Stmm/*
12293835Stmm * This inline avoids some unnecessary modulo operations
12393835Stmm * as compared with the usual macro:
12493835Stmm *   ( ((year % 4) == 0 &&
12593835Stmm *      (year % 100) != 0) ||
12693835Stmm *     ((year % 400) == 0) )
12793835Stmm * It is otherwise equivalent.
12893835Stmm */
12993835Stmmstatic __inline int
13093835Stmmleapyear(int year)
13193835Stmm{
13293835Stmm	int rv = 0;
13393835Stmm
13493835Stmm	if ((year & 3) == 0) {
13593835Stmm		rv = 1;
13693835Stmm		if ((year % 100) == 0) {
13793835Stmm			rv = 0;
13893835Stmm			if ((year % 400) == 0)
13993835Stmm				rv = 1;
14093835Stmm		}
14193835Stmm	}
14293835Stmm	return (rv);
14393835Stmm}
14493835Stmm
14593835Stmmint
14693835Stmmclock_ct_to_ts(struct clocktime *ct, struct timespec *ts)
14793835Stmm{
14893835Stmm	time_t secs;
14993835Stmm	int i, year, days;
15093835Stmm
15193835Stmm	year = ct->year;
15293835Stmm
15393835Stmm	/* Sanity checks. */
15493835Stmm	if (ct->mon < 1 || ct->mon > 12 || ct->day < 1 ||
15593835Stmm	    ct->day > days_in_month(year, ct->mon) ||
15693835Stmm	    ct->hour > 23 ||  ct->min > 59 || ct->sec > 59 ||
15793835Stmm	    ct->year > 2037)		/* time_t overflow */
15893835Stmm		return (EINVAL);
15993835Stmm
16093835Stmm	/*
16193835Stmm	 * Compute days since start of time
16293835Stmm	 * First from years, then from months.
16393835Stmm	 */
16493835Stmm	days = 0;
16593835Stmm	for (i = POSIX_BASE_YEAR; i < year; i++)
16693835Stmm		days += days_in_year(i);
16793835Stmm
16893835Stmm	/* Months */
16993835Stmm	for (i = 1; i < ct->mon; i++)
17093835Stmm	  	days += days_in_month(year, i);
17193835Stmm	days += (ct->day - 1);
17293835Stmm
17393835Stmm	/* Another sanity check. */
17493835Stmm	if (ct->dow != -1 && ct->dow != day_of_week(days))
17593835Stmm		return (EINVAL);
17693835Stmm
17793835Stmm	/* Add hours, minutes, seconds. */
17893835Stmm	secs = ((days * 24 + ct->hour) * 60 + ct->min) * 60 + ct->sec;
17993835Stmm
18093835Stmm	ts->tv_sec = secs;
18193835Stmm	ts->tv_nsec = ct->nsec;
18293835Stmm	return (0);
18393835Stmm}
18493835Stmm
18593835Stmmvoid
18693835Stmmclock_ts_to_ct(struct timespec *ts, struct clocktime *ct)
18793835Stmm{
18893835Stmm	int i, year, days;
18993835Stmm	time_t rsec;	/* remainder seconds */
19093835Stmm	time_t secs;
19193835Stmm
19293835Stmm	secs = ts->tv_sec;
19393835Stmm	days = secs / SECDAY;
19493835Stmm	rsec = secs % SECDAY;
19593835Stmm
19693835Stmm	ct->dow = day_of_week(days);
19793835Stmm
19893835Stmm	/* Subtract out whole years, counting them in i. */
19993835Stmm	for (year = POSIX_BASE_YEAR; days >= days_in_year(year); year++)
20093835Stmm		days -= days_in_year(year);
20193835Stmm	ct->year = year;
20293835Stmm
20393835Stmm	/* Subtract out whole months, counting them in i. */
20493835Stmm	for (i = 1; days >= days_in_month(year, i); i++)
20593835Stmm		days -= days_in_month(year, i);
20693835Stmm	ct->mon = i;
20793835Stmm
20893835Stmm	/* Days are what is left over (+1) from all that. */
20993835Stmm	ct->day = days + 1;
21093835Stmm
21193835Stmm	/* Hours, minutes, seconds are easy */
21293835Stmm	ct->hour = rsec / 3600;
21393835Stmm	rsec = rsec % 3600;
21493835Stmm	ct->min  = rsec / 60;
21593835Stmm	rsec = rsec % 60;
21693835Stmm	ct->sec  = rsec;
21793835Stmm	ct->nsec = ts->tv_nsec;
21893835Stmm}
21993835Stmm
22093835Stmmvoid
22193835Stmmclock_register(device_t dev, long res)
22293835Stmm{
22393835Stmm
22493835Stmm	if (clock_dev != NULL) {
22593835Stmm		if (clock_res > res) {
22693835Stmm			if (bootverbose) {
22793835Stmm				device_printf(dev, "not installed as "
22893835Stmm				    "time-of-day clock: clock %s has higher "
22993835Stmm				    "resolution\n", device_get_name(clock_dev));
23093835Stmm			}
23193835Stmm			return;
23293835Stmm		} else {
23393835Stmm			if (bootverbose) {
23493835Stmm				device_printf(clock_dev, "removed as "
23593835Stmm				    "time-of-day clock: clock %s has higher "
23693835Stmm				    "resolution\n", device_get_name(dev));
23793835Stmm			}
23893835Stmm		}
23993835Stmm	}
24093835Stmm	clock_dev = dev;
24193835Stmm	clock_res = res;
24293835Stmm	if (bootverbose) {
24393835Stmm		device_printf(dev, "registered as a time-of-day clock "
24493835Stmm		    "(resolution %ldus)\n", res);
24593835Stmm	}
24693835Stmm}
24793835Stmm
24893835Stmm/*
24993835Stmm * inittodr and settodr derived from the i386 versions written
25093835Stmm * by Christoph Robitschko <chmr@edvz.tu-graz.ac.at>,  reintroduced and
25193835Stmm * updated by Chris Stenton <chris@gnome.co.uk> 8/10/94
25293835Stmm */
25393835Stmm
25493835Stmm/*
25593835Stmm * Initialize the time of day register, based on the time base which is, e.g.
25693835Stmm * from a filesystem.
25793835Stmm */
25893835Stmmvoid
25993835Stmminittodr(time_t base)
26093835Stmm{
26193835Stmm	struct timespec diff, ref, ts;
26293835Stmm	int error;
26393835Stmm
26493835Stmm	if (base) {
26593835Stmm		ref.tv_sec = base;
26693835Stmm		ref.tv_nsec = 0;
26793835Stmm		tc_setclock(&ref);
26893835Stmm	}
26993835Stmm
27093835Stmm	if (clock_dev == NULL) {
27193835Stmm		printf("warning: no time-of-day clock registered, system time "
27293835Stmm		    "will not be set accurately\n");
27393835Stmm		return;
27493835Stmm	}
27593835Stmm	error = CLOCK_GETTIME(clock_dev, &ts);
27693835Stmm	if (error != 0 && error != EINVAL) {
27793835Stmm		printf("warning: clock_gettime failed (%d), the system time "
27893835Stmm		    "will not be set accurately\n", error);
27993835Stmm		return;
28093835Stmm	}
28193835Stmm	if (error == EINVAL || ts.tv_sec < 0) {
28293835Stmm		printf("Invalid time in real time clock.\n");
28393835Stmm		printf("Check and reset the date immediately!\n");
28493835Stmm	}
28593835Stmm
286110299Sphk	ts.tv_sec += tz_minuteswest * 60 +
28793835Stmm	    (wall_cmos_clock ? adjkerntz : 0);
28893835Stmm
28993835Stmm	if (timespeccmp(&ref, &ts, >)) {
29093835Stmm		diff = ref;
29193835Stmm		timespecsub(&ref, &ts);
29293835Stmm	} else {
29393835Stmm		diff = ts;
29493835Stmm		timespecsub(&diff, &ref);
29593835Stmm	}
29693835Stmm	if (ts.tv_sec >= 2) {
29793835Stmm		/* badly off, adjust it */
29893835Stmm		tc_setclock(&ts);
29993835Stmm	}
30093835Stmm}
30193835Stmm
30293835Stmm/*
30393835Stmm * Write system time back to RTC
30493835Stmm */
30593835Stmmvoid
30693835Stmmresettodr()
30793835Stmm{
30893835Stmm	struct timespec ts;
30993835Stmm	int error;
31093835Stmm
31193835Stmm	if (disable_rtc_set || clock_dev == NULL)
31293835Stmm		return;
31393835Stmm
31493835Stmm	getnanotime(&ts);
315110299Sphk	ts.tv_sec -= tz_minuteswest * 60 + (wall_cmos_clock ? adjkerntz : 0);
31693835Stmm	if ((error = CLOCK_SETTIME(clock_dev, &ts)) != 0) {
31793835Stmm		printf("warning: clock_settime failed (%d), time-of-day clock "
31893835Stmm		    "not adjusted to system time\n", error);
31993835Stmm		return;
32093835Stmm	}
32193835Stmm}
322