| 1 | /* $NetBSD: kern_todr.c,v 1.39 2015/04/13 16:36:54 riastradh Exp $ */ |
| 2 | |
| 3 | /* |
| 4 | * Copyright (c) 1988 University of Utah. |
| 5 | * Copyright (c) 1992, 1993 |
| 6 | * The Regents of the University of California. All rights reserved. |
| 7 | * |
| 8 | * This code is derived from software contributed to Berkeley by |
| 9 | * the Systems Programming Group of the University of Utah Computer |
| 10 | * Science Department and Ralph Campbell. |
| 11 | * |
| 12 | * Redistribution and use in source and binary forms, with or without |
| 13 | * modification, are permitted provided that the following conditions |
| 14 | * are met: |
| 15 | * 1. Redistributions of source code must retain the above copyright |
| 16 | * notice, this list of conditions and the following disclaimer. |
| 17 | * 2. Redistributions in binary form must reproduce the above copyright |
| 18 | * notice, this list of conditions and the following disclaimer in the |
| 19 | * documentation and/or other materials provided with the distribution. |
| 20 | * 3. Neither the name of the University nor the names of its contributors |
| 21 | * may be used to endorse or promote products derived from this software |
| 22 | * without specific prior written permission. |
| 23 | * |
| 24 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
| 25 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 26 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
| 28 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 29 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| 30 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| 32 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| 33 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| 34 | * SUCH DAMAGE. |
| 35 | * |
| 36 | * from: Utah Hdr: clock.c 1.18 91/01/21 |
| 37 | * |
| 38 | * @(#)clock.c 8.1 (Berkeley) 6/10/93 |
| 39 | */ |
| 40 | |
| 41 | #include "opt_todr.h" |
| 42 | |
| 43 | #include <sys/cdefs.h> |
| 44 | __KERNEL_RCSID(0, "$NetBSD: kern_todr.c,v 1.39 2015/04/13 16:36:54 riastradh Exp $" ); |
| 45 | |
| 46 | #include <sys/param.h> |
| 47 | #include <sys/kernel.h> |
| 48 | #include <sys/systm.h> |
| 49 | #include <sys/device.h> |
| 50 | #include <sys/timetc.h> |
| 51 | #include <sys/intr.h> |
| 52 | #include <sys/rndsource.h> |
| 53 | |
| 54 | #include <dev/clock_subr.h> /* hmm.. this should probably move to sys */ |
| 55 | |
| 56 | static todr_chip_handle_t todr_handle = NULL; |
| 57 | |
| 58 | /* |
| 59 | * Attach the clock device to todr_handle. |
| 60 | */ |
| 61 | void |
| 62 | todr_attach(todr_chip_handle_t todr) |
| 63 | { |
| 64 | |
| 65 | if (todr_handle) { |
| 66 | printf("todr_attach: TOD already configured\n" ); |
| 67 | return; |
| 68 | } |
| 69 | todr_handle = todr; |
| 70 | } |
| 71 | |
| 72 | static bool timeset = false; |
| 73 | |
| 74 | /* |
| 75 | * Set up the system's time, given a `reasonable' time value. |
| 76 | */ |
| 77 | void |
| 78 | inittodr(time_t base) |
| 79 | { |
| 80 | bool badbase = false; |
| 81 | bool waszero = (base == 0); |
| 82 | bool goodtime = false; |
| 83 | bool badrtc = false; |
| 84 | int s; |
| 85 | struct timespec ts; |
| 86 | struct timeval tv; |
| 87 | |
| 88 | rnd_add_data(NULL, &base, sizeof(base), 0); |
| 89 | |
| 90 | if (base < 5 * SECS_PER_COMMON_YEAR) { |
| 91 | struct clock_ymdhms basedate; |
| 92 | |
| 93 | /* |
| 94 | * If base is 0, assume filesystem time is just unknown |
| 95 | * instead of preposterous. Don't bark. |
| 96 | */ |
| 97 | if (base != 0) |
| 98 | printf("WARNING: preposterous time in file system\n" ); |
| 99 | /* not going to use it anyway, if the chip is readable */ |
| 100 | basedate.dt_year = 2010; |
| 101 | basedate.dt_mon = 1; |
| 102 | basedate.dt_day = 1; |
| 103 | basedate.dt_hour = 12; |
| 104 | basedate.dt_min = 0; |
| 105 | basedate.dt_sec = 0; |
| 106 | base = clock_ymdhms_to_secs(&basedate); |
| 107 | badbase = true; |
| 108 | } |
| 109 | |
| 110 | /* |
| 111 | * Some ports need to be supplied base in order to fabricate a time_t. |
| 112 | */ |
| 113 | if (todr_handle) |
| 114 | todr_handle->base_time = base; |
| 115 | |
| 116 | if ((todr_handle == NULL) || |
| 117 | (todr_gettime(todr_handle, &tv) != 0) || |
| 118 | (tv.tv_sec < (25 * SECS_PER_COMMON_YEAR))) { |
| 119 | |
| 120 | if (todr_handle != NULL) |
| 121 | printf("WARNING: preposterous TOD clock time\n" ); |
| 122 | else |
| 123 | printf("WARNING: no TOD clock present\n" ); |
| 124 | badrtc = true; |
| 125 | } else { |
| 126 | time_t deltat = tv.tv_sec - base; |
| 127 | |
| 128 | if (deltat < 0) |
| 129 | deltat = -deltat; |
| 130 | |
| 131 | if (!badbase && deltat >= 2 * SECS_PER_DAY) { |
| 132 | |
| 133 | if (tv.tv_sec < base) { |
| 134 | /* |
| 135 | * The clock should never go backwards |
| 136 | * relative to filesystem time. If it |
| 137 | * does by more than the threshold, |
| 138 | * believe the filesystem. |
| 139 | */ |
| 140 | printf("WARNING: clock lost %" PRId64 " days\n" , |
| 141 | deltat / SECS_PER_DAY); |
| 142 | badrtc = true; |
| 143 | } else { |
| 144 | aprint_verbose("WARNING: clock gained %" PRId64 |
| 145 | " days\n" , deltat / SECS_PER_DAY); |
| 146 | goodtime = true; |
| 147 | } |
| 148 | } else { |
| 149 | goodtime = true; |
| 150 | } |
| 151 | |
| 152 | rnd_add_data(NULL, &tv, sizeof(tv), 0); |
| 153 | } |
| 154 | |
| 155 | /* if the rtc time is bad, use the filesystem time */ |
| 156 | if (badrtc) { |
| 157 | if (badbase) { |
| 158 | printf("WARNING: using default initial time\n" ); |
| 159 | } else { |
| 160 | printf("WARNING: using filesystem time\n" ); |
| 161 | } |
| 162 | tv.tv_sec = base; |
| 163 | tv.tv_usec = 0; |
| 164 | } |
| 165 | |
| 166 | timeset = true; |
| 167 | |
| 168 | ts.tv_sec = tv.tv_sec; |
| 169 | ts.tv_nsec = tv.tv_usec * 1000; |
| 170 | s = splclock(); |
| 171 | tc_setclock(&ts); |
| 172 | splx(s); |
| 173 | |
| 174 | if (waszero || goodtime) |
| 175 | return; |
| 176 | |
| 177 | printf("WARNING: CHECK AND RESET THE DATE!\n" ); |
| 178 | } |
| 179 | |
| 180 | /* |
| 181 | * Reset the TODR based on the time value; used when the TODR |
| 182 | * has a preposterous value and also when the time is reset |
| 183 | * by the stime system call. Also called when the TODR goes past |
| 184 | * TODRZERO + 100*(SECS_PER_COMMON_YEAR+2*SECS_PER_DAY) |
| 185 | * (e.g. on Jan 2 just after midnight) to wrap the TODR around. |
| 186 | */ |
| 187 | void |
| 188 | resettodr(void) |
| 189 | { |
| 190 | struct timeval tv; |
| 191 | |
| 192 | /* |
| 193 | * We might have been called by boot() due to a crash early |
| 194 | * on. Don't reset the clock chip if we don't know what time |
| 195 | * it is. |
| 196 | */ |
| 197 | if (!timeset) |
| 198 | return; |
| 199 | |
| 200 | getmicrotime(&tv); |
| 201 | |
| 202 | if (tv.tv_sec == 0) |
| 203 | return; |
| 204 | |
| 205 | if (todr_handle) |
| 206 | if (todr_settime(todr_handle, &tv) != 0) |
| 207 | printf("Cannot set TOD clock time\n" ); |
| 208 | } |
| 209 | |
| 210 | #ifdef TODR_DEBUG |
| 211 | static void |
| 212 | todr_debug(const char *prefix, int rv, struct clock_ymdhms *dt, |
| 213 | struct timeval *tvp) |
| 214 | { |
| 215 | struct timeval tv_val; |
| 216 | struct clock_ymdhms dt_val; |
| 217 | |
| 218 | if (dt == NULL) { |
| 219 | clock_secs_to_ymdhms(tvp->tv_sec, &dt_val); |
| 220 | dt = &dt_val; |
| 221 | } |
| 222 | if (tvp == NULL) { |
| 223 | tvp = &tv_val; |
| 224 | tvp->tv_sec = clock_ymdhms_to_secs(dt); |
| 225 | tvp->tv_usec = 0; |
| 226 | } |
| 227 | printf("%s: rv = %d\n" , prefix, rv); |
| 228 | printf("%s: rtc_offset = %d\n" , prefix, rtc_offset); |
| 229 | printf("%s: %4u/%02u/%02u %02u:%02u:%02u, (wday %d) (epoch %u.%06u)\n" , |
| 230 | prefix, |
| 231 | (unsigned)dt->dt_year, dt->dt_mon, dt->dt_day, |
| 232 | dt->dt_hour, dt->dt_min, dt->dt_sec, |
| 233 | dt->dt_wday, (unsigned)tvp->tv_sec, (unsigned)tvp->tv_usec); |
| 234 | } |
| 235 | #else /* !TODR_DEBUG */ |
| 236 | #define todr_debug(prefix, rv, dt, tvp) |
| 237 | #endif /* TODR_DEBUG */ |
| 238 | |
| 239 | |
| 240 | int |
| 241 | todr_gettime(todr_chip_handle_t tch, struct timeval *tvp) |
| 242 | { |
| 243 | struct clock_ymdhms dt; |
| 244 | int rv; |
| 245 | |
| 246 | if (tch->todr_gettime) { |
| 247 | rv = tch->todr_gettime(tch, tvp); |
| 248 | /* |
| 249 | * Some unconverted ports have their own references to |
| 250 | * rtc_offset. A converted port must not do that. |
| 251 | */ |
| 252 | if (rv == 0) |
| 253 | tvp->tv_sec += rtc_offset * 60; |
| 254 | todr_debug("TODR-GET-SECS" , rv, NULL, tvp); |
| 255 | return rv; |
| 256 | } else if (tch->todr_gettime_ymdhms) { |
| 257 | rv = tch->todr_gettime_ymdhms(tch, &dt); |
| 258 | todr_debug("TODR-GET-YMDHMS" , rv, &dt, NULL); |
| 259 | if (rv) |
| 260 | return rv; |
| 261 | |
| 262 | /* |
| 263 | * Simple sanity checks. Note that this includes a |
| 264 | * value for clocks that can return a leap second. |
| 265 | * Note that we don't support double leap seconds, |
| 266 | * since this was apparently an error/misunderstanding |
| 267 | * on the part of the ISO C committee, and can never |
| 268 | * actually occur. If your clock issues us a double |
| 269 | * leap second, it must be broken. Ultimately, you'd |
| 270 | * have to be trying to read time at precisely that |
| 271 | * instant to even notice, so even broken clocks will |
| 272 | * work the vast majority of the time. In such a case |
| 273 | * it is recommended correction be applied in the |
| 274 | * clock driver. |
| 275 | */ |
| 276 | if (dt.dt_mon < 1 || dt.dt_mon > 12 || |
| 277 | dt.dt_day < 1 || dt.dt_day > 31 || |
| 278 | dt.dt_hour > 23 || dt.dt_min > 59 || dt.dt_sec > 60) { |
| 279 | return EINVAL; |
| 280 | } |
| 281 | tvp->tv_sec = clock_ymdhms_to_secs(&dt) + rtc_offset * 60; |
| 282 | tvp->tv_usec = 0; |
| 283 | return tvp->tv_sec < 0 ? EINVAL : 0; |
| 284 | } |
| 285 | |
| 286 | return ENXIO; |
| 287 | } |
| 288 | |
| 289 | int |
| 290 | todr_settime(todr_chip_handle_t tch, struct timeval *tvp) |
| 291 | { |
| 292 | struct clock_ymdhms dt; |
| 293 | int rv; |
| 294 | |
| 295 | if (tch->todr_settime) { |
| 296 | /* See comments above in gettime why this is ifdef'd */ |
| 297 | struct timeval copy = *tvp; |
| 298 | copy.tv_sec -= rtc_offset * 60; |
| 299 | rv = tch->todr_settime(tch, ©); |
| 300 | todr_debug("TODR-SET-SECS" , rv, NULL, tvp); |
| 301 | return rv; |
| 302 | } else if (tch->todr_settime_ymdhms) { |
| 303 | time_t sec = tvp->tv_sec - rtc_offset * 60; |
| 304 | if (tvp->tv_usec >= 500000) |
| 305 | sec++; |
| 306 | clock_secs_to_ymdhms(sec, &dt); |
| 307 | rv = tch->todr_settime_ymdhms(tch, &dt); |
| 308 | todr_debug("TODR-SET-YMDHMS" , rv, &dt, NULL); |
| 309 | return rv; |
| 310 | } else { |
| 311 | return ENXIO; |
| 312 | } |
| 313 | } |
| 314 | |