subr_clock.c revision 139804
1238106Sdes/*- 2238106Sdes * Copyright (c) 1988 University of Utah. 3238106Sdes * Copyright (c) 1982, 1990, 1993 4238106Sdes * The Regents of the University of California. All rights reserved. 5238106Sdes * 6238106Sdes * This code is derived from software contributed to Berkeley by 7238106Sdes * the Systems Programming Group of the University of Utah Computer 8238106Sdes * Science Department. 9238106Sdes * 10238106Sdes * Redistribution and use in source and binary forms, with or without 11238106Sdes * modification, are permitted provided that the following conditions 12238106Sdes * are met: 13238106Sdes * 1. Redistributions of source code must retain the above copyright 14238106Sdes * notice, this list of conditions and the following disclaimer. 15238106Sdes * 2. Redistributions in binary form must reproduce the above copyright 16238106Sdes * notice, this list of conditions and the following disclaimer in the 17238106Sdes * documentation and/or other materials provided with the distribution. 18238106Sdes * 4. Neither the name of the University nor the names of its contributors 19238106Sdes * may be used to endorse or promote products derived from this software 20238106Sdes * without specific prior written permission. 21238106Sdes * 22238106Sdes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23238106Sdes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24269257Sdes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25269257Sdes * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26269257Sdes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27269257Sdes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28269257Sdes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29269257Sdes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30269257Sdes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31269257Sdes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32269257Sdes * SUCH DAMAGE. 33269257Sdes * 34238106Sdes * from: Utah $Hdr: clock.c 1.18 91/01/21$ 35238106Sdes * from: @(#)clock.c 8.2 (Berkeley) 1/12/94 36238106Sdes * from: NetBSD: clock_subr.c,v 1.6 2001/07/07 17:04:02 thorpej Exp 37238106Sdes * and 38238106Sdes * from: src/sys/i386/isa/clock.c,v 1.176 2001/09/04 39238106Sdes */ 40238106Sdes 41238106Sdes/* 42238106Sdes * Helpers for time-of-day clocks. This is useful for architectures that need 43238106Sdes * support multiple models of such clocks, and generally serves to make the 44238106Sdes * code more machine-independent. 45238106Sdes * If the clock in question can also be used as a time counter, the driver 46238106Sdes * needs to initiate this. 47238106Sdes * This code is not yet used by all architectures. 48249141Sdes */ 49249141Sdes 50249141Sdes/* 51249141Sdes * Generic routines to convert between a POSIX date 52238106Sdes * (seconds since 1/1/1970) and yr/mo/day/hr/min/sec 53238106Sdes * Derived from NetBSD arch/hp300/hp300/clock.c 54238106Sdes */ 55238106Sdes 56238106Sdes#include <sys/cdefs.h> 57238106Sdes__FBSDID("$FreeBSD: head/sys/kern/subr_clock.c 139804 2005-01-06 23:35:40Z imp $"); 58238106Sdes 59238106Sdes#include <sys/param.h> 60238106Sdes#include <sys/systm.h> 61238106Sdes#include <sys/kernel.h> 62238106Sdes#include <sys/bus.h> 63238106Sdes#include <sys/clock.h> 64238106Sdes#include <sys/sysctl.h> 65269257Sdes#include <sys/timetc.h> 66238106Sdes 67238106Sdes/* XXX: for the CPU_* sysctl OID constants. */ 68238106Sdes#include <machine/cpu.h> 69238106Sdes 70238106Sdes#include "clock_if.h" 71269257Sdes 72238106Sdesstatic __inline int leapyear(int year); 73238106Sdesstatic int sysctl_machdep_adjkerntz(SYSCTL_HANDLER_ARGS); 74238106Sdes 75238106Sdes#define FEBRUARY 2 76238106Sdes#define days_in_year(y) (leapyear(y) ? 366 : 365) 77269257Sdes#define days_in_month(y, m) \ 78238106Sdes (month_days[(m) - 1] + (m == FEBRUARY ? leapyear(y) : 0)) 79238106Sdes/* Day of week. Days are counted from 1/1/1970, which was a Thursday */ 80238106Sdes#define day_of_week(days) (((days) + 4) % 7) 81238106Sdes 82238106Sdesstatic const int month_days[12] = { 83238106Sdes 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 84238106Sdes}; 85238106Sdes 86238106Sdesstatic device_t clock_dev = NULL; 87238106Sdesstatic long clock_res; 88238106Sdes 89238106Sdesint adjkerntz; /* local offset from GMT in seconds */ 90238106Sdesint disable_rtc_set; /* disable resettodr() if != 0 */ 91238106Sdesint wall_cmos_clock; /* wall CMOS clock assumed if != 0 */ 92238106Sdes 93238106Sdes/* 94238106Sdes * These have traditionally been in machdep, but should probably be moved to 95238106Sdes * kern. 96238106Sdes */ 97238106SdesSYSCTL_PROC(_machdep, CPU_ADJKERNTZ, adjkerntz, CTLTYPE_INT|CTLFLAG_RW, 98238106Sdes &adjkerntz, 0, sysctl_machdep_adjkerntz, "I", ""); 99238106Sdes 100238106SdesSYSCTL_INT(_machdep, CPU_DISRTCSET, disable_rtc_set, 101238106Sdes CTLFLAG_RW, &disable_rtc_set, 0, ""); 102238106Sdes 103238106SdesSYSCTL_INT(_machdep, CPU_WALLCLOCK, wall_cmos_clock, 104238106Sdes CTLFLAG_RW, &wall_cmos_clock, 0, ""); 105238106Sdes 106238106Sdesstatic int 107238106Sdessysctl_machdep_adjkerntz(SYSCTL_HANDLER_ARGS) 108238106Sdes{ 109238106Sdes int error; 110238106Sdes error = sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2, 111238106Sdes req); 112238106Sdes if (!error && req->newptr) 113238106Sdes resettodr(); 114238106Sdes return (error); 115238106Sdes} 116238106Sdes 117238106Sdes/* 118238106Sdes * This inline avoids some unnecessary modulo operations 119238106Sdes * as compared with the usual macro: 120238106Sdes * ( ((year % 4) == 0 && 121238106Sdes * (year % 100) != 0) || 122238106Sdes * ((year % 400) == 0) ) 123238106Sdes * It is otherwise equivalent. 124238106Sdes */ 125238106Sdesstatic __inline int 126238106Sdesleapyear(int year) 127238106Sdes{ 128238106Sdes int rv = 0; 129238106Sdes 130238106Sdes if ((year & 3) == 0) { 131238106Sdes rv = 1; 132238106Sdes if ((year % 100) == 0) { 133238106Sdes rv = 0; 134238106Sdes if ((year % 400) == 0) 135238106Sdes rv = 1; 136238106Sdes } 137238106Sdes } 138238106Sdes return (rv); 139238106Sdes} 140238106Sdes 141238106Sdesint 142238106Sdesclock_ct_to_ts(struct clocktime *ct, struct timespec *ts) 143238106Sdes{ 144238106Sdes time_t secs; 145238106Sdes int i, year, days; 146238106Sdes 147238106Sdes year = ct->year; 148238106Sdes 149238106Sdes /* Sanity checks. */ 150238106Sdes if (ct->mon < 1 || ct->mon > 12 || ct->day < 1 || 151238106Sdes ct->day > days_in_month(year, ct->mon) || 152238106Sdes ct->hour > 23 || ct->min > 59 || ct->sec > 59 || 153238106Sdes ct->year > 2037) /* time_t overflow */ 154238106Sdes return (EINVAL); 155238106Sdes 156238106Sdes /* 157238106Sdes * Compute days since start of time 158238106Sdes * First from years, then from months. 159238106Sdes */ 160238106Sdes days = 0; 161238106Sdes for (i = POSIX_BASE_YEAR; i < year; i++) 162238106Sdes days += days_in_year(i); 163238106Sdes 164238106Sdes /* Months */ 165238106Sdes for (i = 1; i < ct->mon; i++) 166238106Sdes days += days_in_month(year, i); 167238106Sdes days += (ct->day - 1); 168238106Sdes 169238106Sdes /* Another sanity check. */ 170238106Sdes if (ct->dow != -1 && ct->dow != day_of_week(days)) 171238106Sdes return (EINVAL); 172238106Sdes 173238106Sdes /* Add hours, minutes, seconds. */ 174238106Sdes secs = ((days * 24 + ct->hour) * 60 + ct->min) * 60 + ct->sec; 175238106Sdes 176238106Sdes ts->tv_sec = secs; 177238106Sdes ts->tv_nsec = ct->nsec; 178238106Sdes return (0); 179238106Sdes} 180238106Sdes 181238106Sdesvoid 182238106Sdesclock_ts_to_ct(struct timespec *ts, struct clocktime *ct) 183238106Sdes{ 184238106Sdes int i, year, days; 185238106Sdes time_t rsec; /* remainder seconds */ 186238106Sdes time_t secs; 187238106Sdes 188238106Sdes secs = ts->tv_sec; 189238106Sdes days = secs / SECDAY; 190238106Sdes rsec = secs % SECDAY; 191238106Sdes 192238106Sdes ct->dow = day_of_week(days); 193238106Sdes 194238106Sdes /* Subtract out whole years, counting them in i. */ 195238106Sdes for (year = POSIX_BASE_YEAR; days >= days_in_year(year); year++) 196238106Sdes days -= days_in_year(year); 197238106Sdes ct->year = year; 198238106Sdes 199238106Sdes /* Subtract out whole months, counting them in i. */ 200238106Sdes for (i = 1; days >= days_in_month(year, i); i++) 201238106Sdes days -= days_in_month(year, i); 202238106Sdes ct->mon = i; 203238106Sdes 204238106Sdes /* Days are what is left over (+1) from all that. */ 205238106Sdes ct->day = days + 1; 206238106Sdes 207238106Sdes /* Hours, minutes, seconds are easy */ 208238106Sdes ct->hour = rsec / 3600; 209238106Sdes rsec = rsec % 3600; 210238106Sdes ct->min = rsec / 60; 211238106Sdes rsec = rsec % 60; 212238106Sdes ct->sec = rsec; 213238106Sdes ct->nsec = ts->tv_nsec; 214238106Sdes} 215238106Sdes 216238106Sdesvoid 217238106Sdesclock_register(device_t dev, long res) 218238106Sdes{ 219238106Sdes 220238106Sdes if (clock_dev != NULL) { 221238106Sdes if (clock_res > res) { 222238106Sdes if (bootverbose) { 223238106Sdes device_printf(dev, "not installed as " 224238106Sdes "time-of-day clock: clock %s has higher " 225238106Sdes "resolution\n", device_get_name(clock_dev)); 226238106Sdes } 227238106Sdes return; 228238106Sdes } else { 229238106Sdes if (bootverbose) { 230238106Sdes device_printf(clock_dev, "removed as " 231238106Sdes "time-of-day clock: clock %s has higher " 232238106Sdes "resolution\n", device_get_name(dev)); 233238106Sdes } 234238106Sdes } 235238106Sdes } 236238106Sdes clock_dev = dev; 237238106Sdes clock_res = res; 238238106Sdes if (bootverbose) { 239238106Sdes device_printf(dev, "registered as a time-of-day clock " 240238106Sdes "(resolution %ldus)\n", res); 241238106Sdes } 242238106Sdes} 243238106Sdes 244238106Sdes/* 245238106Sdes * inittodr and settodr derived from the i386 versions written 246238106Sdes * by Christoph Robitschko <chmr@edvz.tu-graz.ac.at>, reintroduced and 247238106Sdes * updated by Chris Stenton <chris@gnome.co.uk> 8/10/94 248238106Sdes */ 249238106Sdes 250238106Sdes/* 251238106Sdes * Initialize the time of day register, based on the time base which is, e.g. 252238106Sdes * from a filesystem. 253238106Sdes */ 254238106Sdesvoid 255238106Sdesinittodr(time_t base) 256238106Sdes{ 257238106Sdes struct timespec diff, ref, ts; 258238106Sdes int error; 259238106Sdes 260238106Sdes if (base) { 261238106Sdes ref.tv_sec = base; 262238106Sdes ref.tv_nsec = 0; 263269257Sdes tc_setclock(&ref); 264238106Sdes } 265238106Sdes 266238106Sdes if (clock_dev == NULL) { 267238106Sdes printf("warning: no time-of-day clock registered, system time " 268238106Sdes "will not be set accurately\n"); 269238106Sdes return; 270238106Sdes } 271238106Sdes error = CLOCK_GETTIME(clock_dev, &ts); 272238106Sdes if (error != 0 && error != EINVAL) { 273238106Sdes printf("warning: clock_gettime failed (%d), the system time " 274238106Sdes "will not be set accurately\n", error); 275238106Sdes return; 276238106Sdes } 277238106Sdes if (error == EINVAL || ts.tv_sec < 0) { 278238106Sdes printf("Invalid time in real time clock.\n"); 279238106Sdes printf("Check and reset the date immediately!\n"); 280238106Sdes } 281238106Sdes 282238106Sdes ts.tv_sec += tz_minuteswest * 60 + 283238106Sdes (wall_cmos_clock ? adjkerntz : 0); 284238106Sdes 285238106Sdes if (timespeccmp(&ref, &ts, >)) { 286238106Sdes diff = ref; 287238106Sdes timespecsub(&ref, &ts); 288238106Sdes } else { 289238106Sdes diff = ts; 290238106Sdes timespecsub(&diff, &ref); 291238106Sdes } 292238106Sdes if (ts.tv_sec >= 2) { 293238106Sdes /* badly off, adjust it */ 294238106Sdes tc_setclock(&ts); 295238106Sdes } 296238106Sdes} 297238106Sdes 298238106Sdes/* 299238106Sdes * Write system time back to RTC 300238106Sdes */ 301238106Sdesvoid 302238106Sdesresettodr() 303238106Sdes{ 304238106Sdes struct timespec ts; 305238106Sdes int error; 306238106Sdes 307238106Sdes if (disable_rtc_set || clock_dev == NULL) 308238106Sdes return; 309238106Sdes 310238106Sdes getnanotime(&ts); 311238106Sdes ts.tv_sec -= tz_minuteswest * 60 + (wall_cmos_clock ? adjkerntz : 0); 312238106Sdes if ((error = CLOCK_SETTIME(clock_dev, &ts)) != 0) { 313238106Sdes printf("warning: clock_settime failed (%d), time-of-day clock " 314238106Sdes "not adjusted to system time\n", error); 315238106Sdes return; 316238106Sdes } 317238106Sdes} 318238106Sdes