1 /* 2 * Copyright (c) 1988 University of Utah. 3 * Copyright (c) 1982, 1990, 1993 4 * The Regents of the University of California. All rights reserved. 5 * 6 * This code is derived from software contributed to Berkeley by 7 * the Systems Programming Group of the University of Utah Computer 8 * Science Department. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 4. Neither the name of the University nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 * 34 * from: Utah $Hdr: clock.c 1.18 91/01/21$ 35 * from: @(#)clock.c 8.2 (Berkeley) 1/12/94 36 * from: NetBSD: clock_subr.c,v 1.6 2001/07/07 17:04:02 thorpej Exp 37 * and 38 * from: src/sys/i386/isa/clock.c,v 1.176 2001/09/04 39 */ 40 41 /* 42 * Helpers for time-of-day clocks. This is useful for architectures that need 43 * support multiple models of such clocks, and generally serves to make the 44 * code more machine-independent. 45 * If the clock in question can also be used as a time counter, the driver 46 * needs to initiate this. 47 * This code is not yet used by all architectures. 48 */ 49 50 /* 51 * Generic routines to convert between a POSIX date 52 * (seconds since 1/1/1970) and yr/mo/day/hr/min/sec 53 * Derived from NetBSD arch/hp300/hp300/clock.c 54 */ 55 56 #include <sys/cdefs.h> 57 __FBSDID("$FreeBSD$"); 58 59 #include <sys/param.h> 60 #include <sys/systm.h> 61 #include <sys/kernel.h> 62 #include <sys/bus.h> 63 #include <sys/clock.h> 64 #include <sys/sysctl.h> 65 #include <sys/timetc.h> 66 67 /* XXX: for the CPU_* sysctl OID constants. */ 68 #include <machine/cpu.h> 69 70 #include "clock_if.h" 71 72 static __inline int leapyear(int year); 73 static int sysctl_machdep_adjkerntz(SYSCTL_HANDLER_ARGS); 74 75 #define FEBRUARY 2 76 #define days_in_year(y) (leapyear(y) ? 366 : 365) 77 #define days_in_month(y, m) \ 78 (month_days[(m) - 1] + (m == FEBRUARY ? leapyear(y) : 0)) 79 /* Day of week. Days are counted from 1/1/1970, which was a Thursday */ 80 #define day_of_week(days) (((days) + 4) % 7) 81 82 static const int month_days[12] = { 83 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 84 }; 85 86 static device_t clock_dev = NULL; 87 static long clock_res; 88 89 int adjkerntz; /* local offset from GMT in seconds */ 90 int disable_rtc_set; /* disable resettodr() if != 0 */ 91 int wall_cmos_clock; /* wall CMOS clock assumed if != 0 */ 92 93 /* 94 * These have traditionally been in machdep, but should probably be moved to 95 * kern. 96 */ 97 SYSCTL_PROC(_machdep, CPU_ADJKERNTZ, adjkerntz, CTLTYPE_INT|CTLFLAG_RW, 98 &adjkerntz, 0, sysctl_machdep_adjkerntz, "I", ""); 99 100 SYSCTL_INT(_machdep, CPU_DISRTCSET, disable_rtc_set, 101 CTLFLAG_RW, &disable_rtc_set, 0, ""); 102 103 SYSCTL_INT(_machdep, CPU_WALLCLOCK, wall_cmos_clock, 104 CTLFLAG_RW, &wall_cmos_clock, 0, ""); 105 106 static int 107 sysctl_machdep_adjkerntz(SYSCTL_HANDLER_ARGS) 108 { 109 int error; 110 error = sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2, 111 req); 112 if (!error && req->newptr) 113 resettodr(); 114 return (error); 115 } 116 117 /* 118 * This inline avoids some unnecessary modulo operations 119 * as compared with the usual macro: 120 * ( ((year % 4) == 0 && 121 * (year % 100) != 0) || 122 * ((year % 400) == 0) ) 123 * It is otherwise equivalent. 124 */ 125 static __inline int 126 leapyear(int year) 127 { 128 int rv = 0; 129 130 if ((year & 3) == 0) { 131 rv = 1; 132 if ((year % 100) == 0) { 133 rv = 0; 134 if ((year % 400) == 0) 135 rv = 1; 136 } 137 } 138 return (rv); 139 } 140 141 int 142 clock_ct_to_ts(struct clocktime *ct, struct timespec *ts) 143 { 144 time_t secs; 145 int i, year, days; 146 147 year = ct->year; 148 149 /* Sanity checks. */ 150 if (ct->mon < 1 || ct->mon > 12 || ct->day < 1 || 151 ct->day > days_in_month(year, ct->mon) || 152 ct->hour > 23 || ct->min > 59 || ct->sec > 59 || 153 ct->year > 2037) /* time_t overflow */ 154 return (EINVAL); 155 156 /* 157 * Compute days since start of time 158 * First from years, then from months. 159 */ 160 days = 0; 161 for (i = POSIX_BASE_YEAR; i < year; i++) 162 days += days_in_year(i); 163 164 /* Months */ 165 for (i = 1; i < ct->mon; i++) 166 days += days_in_month(year, i); 167 days += (ct->day - 1); 168 169 /* Another sanity check. */ 170 if (ct->dow != -1 && ct->dow != day_of_week(days)) 171 return (EINVAL); 172 173 /* Add hours, minutes, seconds. */ 174 secs = ((days * 24 + ct->hour) * 60 + ct->min) * 60 + ct->sec; 175 176 ts->tv_sec = secs; 177 ts->tv_nsec = ct->nsec; 178 return (0); 179 } 180 181 void 182 clock_ts_to_ct(struct timespec *ts, struct clocktime *ct) 183 { 184 int i, year, days; 185 time_t rsec; /* remainder seconds */ 186 time_t secs; 187 188 secs = ts->tv_sec; 189 days = secs / SECDAY; 190 rsec = secs % SECDAY; 191 192 ct->dow = day_of_week(days); 193 194 /* Subtract out whole years, counting them in i. */ 195 for (year = POSIX_BASE_YEAR; days >= days_in_year(year); year++) 196 days -= days_in_year(year); 197 ct->year = year; 198 199 /* Subtract out whole months, counting them in i. */ 200 for (i = 1; days >= days_in_month(year, i); i++) 201 days -= days_in_month(year, i); 202 ct->mon = i; 203 204 /* Days are what is left over (+1) from all that. */ 205 ct->day = days + 1; 206 207 /* Hours, minutes, seconds are easy */ 208 ct->hour = rsec / 3600; 209 rsec = rsec % 3600; 210 ct->min = rsec / 60; 211 rsec = rsec % 60; 212 ct->sec = rsec; 213 ct->nsec = ts->tv_nsec; 214 } 215 216 void 217 clock_register(device_t dev, long res) 218 { 219 220 if (clock_dev != NULL) { 221 if (clock_res > res) { 222 if (bootverbose) { 223 device_printf(dev, "not installed as " 224 "time-of-day clock: clock %s has higher " 225 "resolution\n", device_get_name(clock_dev)); 226 } 227 return; 228 } else { 229 if (bootverbose) { 230 device_printf(clock_dev, "removed as " 231 "time-of-day clock: clock %s has higher " 232 "resolution\n", device_get_name(dev)); 233 } 234 } 235 } 236 clock_dev = dev; 237 clock_res = res; 238 if (bootverbose) { 239 device_printf(dev, "registered as a time-of-day clock " 240 "(resolution %ldus)\n", res); 241 } 242 } 243 244 /* 245 * inittodr and settodr derived from the i386 versions written 246 * by Christoph Robitschko <chmr@edvz.tu-graz.ac.at>, reintroduced and 247 * updated by Chris Stenton <chris@gnome.co.uk> 8/10/94 248 */ 249 250 /* 251 * Initialize the time of day register, based on the time base which is, e.g. 252 * from a filesystem. 253 */ 254 void 255 inittodr(time_t base) 256 { 257 struct timespec diff, ref, ts; 258 int error; 259 260 if (base) { 261 ref.tv_sec = base; 262 ref.tv_nsec = 0; 263 tc_setclock(&ref); 264 } 265 266 if (clock_dev == NULL) { 267 printf("warning: no time-of-day clock registered, system time " 268 "will not be set accurately\n"); 269 return; 270 } 271 error = CLOCK_GETTIME(clock_dev, &ts); 272 if (error != 0 && error != EINVAL) { 273 printf("warning: clock_gettime failed (%d), the system time " 274 "will not be set accurately\n", error); 275 return; 276 } 277 if (error == EINVAL || ts.tv_sec < 0) { 278 printf("Invalid time in real time clock.\n"); 279 printf("Check and reset the date immediately!\n"); 280 } 281 282 ts.tv_sec += tz_minuteswest * 60 + 283 (wall_cmos_clock ? adjkerntz : 0); 284 285 if (timespeccmp(&ref, &ts, >)) { 286 diff = ref; 287 timespecsub(&ref, &ts); 288 } else { 289 diff = ts; 290 timespecsub(&diff, &ref); 291 } 292 if (ts.tv_sec >= 2) { 293 /* badly off, adjust it */ 294 tc_setclock(&ts); 295 } 296 } 297 298 /* 299 * Write system time back to RTC 300 */ 301 void 302 resettodr() 303 { 304 struct timespec ts; 305 int error; 306 307 if (disable_rtc_set || clock_dev == NULL) 308 return; 309 310 getnanotime(&ts); 311 ts.tv_sec -= tz_minuteswest * 60 + (wall_cmos_clock ? adjkerntz : 0); 312 if ((error = CLOCK_SETTIME(clock_dev, &ts)) != 0) { 313 printf("warning: clock_settime failed (%d), time-of-day clock " 314 "not adjusted to system time\n", error); 315 return; 316 } 317 } 318