1 /* $NetBSD: kern_todr.c,v 1.49 2025/09/08 00:12:21 thorpej Exp $ */ 2 3 /*- 4 * Copyright (c) 2020 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Jason R. Thorpe. 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 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* 33 * Copyright (c) 1988 University of Utah. 34 * Copyright (c) 1992, 1993 35 * The Regents of the University of California. All rights reserved. 36 * 37 * This code is derived from software contributed to Berkeley by 38 * the Systems Programming Group of the University of Utah Computer 39 * Science Department and Ralph Campbell. 40 * 41 * Redistribution and use in source and binary forms, with or without 42 * modification, are permitted provided that the following conditions 43 * are met: 44 * 1. Redistributions of source code must retain the above copyright 45 * notice, this list of conditions and the following disclaimer. 46 * 2. Redistributions in binary form must reproduce the above copyright 47 * notice, this list of conditions and the following disclaimer in the 48 * documentation and/or other materials provided with the distribution. 49 * 3. Neither the name of the University nor the names of its contributors 50 * may be used to endorse or promote products derived from this software 51 * without specific prior written permission. 52 * 53 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 54 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 55 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 56 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 57 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 58 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 59 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 60 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 61 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 62 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 63 * SUCH DAMAGE. 64 * 65 * from: Utah Hdr: clock.c 1.18 91/01/21 66 * 67 * @(#)clock.c 8.1 (Berkeley) 6/10/93 68 */ 69 70 #include "opt_todr.h" 71 72 #include <sys/cdefs.h> 73 __KERNEL_RCSID(0, "$NetBSD: kern_todr.c,v 1.49 2025/09/08 00:12:21 thorpej Exp $"); 74 75 #include <sys/param.h> 76 #include <sys/kernel.h> 77 #include <sys/systm.h> 78 #include <sys/device.h> 79 #include <sys/device_calls.h> 80 #include <sys/timetc.h> 81 #include <sys/intr.h> 82 #include <sys/rndsource.h> 83 #include <sys/mutex.h> 84 85 #include <dev/clock_subr.h> /* hmm.. this should probably move to sys */ 86 87 static int todr_gettime(todr_chip_handle_t, struct timeval *); 88 static int todr_settime(todr_chip_handle_t, struct timeval *); 89 90 static kmutex_t todr_mutex; 91 static todr_chip_handle_t todr_handle; 92 static bool todr_initialized; 93 94 /* The minimum reasonable RTC date before preposterousness */ 95 #define PREPOSTEROUS_YEARS (2021 - POSIX_BASE_YEAR) 96 97 /* 98 * todr_init: 99 * Initialize TOD clock data. 100 */ 101 void 102 todr_init(void) 103 { 104 105 mutex_init(&todr_mutex, MUTEX_DEFAULT, IPL_NONE); 106 todr_initialized = true; 107 } 108 109 /* 110 * todr_lock: 111 * Acquire the TODR lock. 112 */ 113 void 114 todr_lock(void) 115 { 116 117 mutex_enter(&todr_mutex); 118 } 119 120 /* 121 * todr_unlock: 122 * Release the TODR lock. 123 */ 124 void 125 todr_unlock(void) 126 { 127 128 mutex_exit(&todr_mutex); 129 } 130 131 /* 132 * todr_lock_owned: 133 * Return true if the current thread owns the TODR lock. 134 * This is to be used by diagnostic assertions only. 135 */ 136 bool 137 todr_lock_owned(void) 138 { 139 140 return mutex_owned(&todr_mutex) ? true : false; 141 } 142 143 /* 144 * todr_should_skip: 145 * Evaluate if we should skip attaching the clock device 146 * specifed by the todr handle. 147 */ 148 static bool 149 todr_should_skip(todr_chip_handle_t todr) 150 { 151 device_t dev = todr->todr_dev; 152 struct device_is_system_todr_args args = { }; 153 154 /* No basis for evaluation if no device backs the TODR. */ 155 if (dev == NULL) { 156 return false; 157 } 158 159 if (device_call(dev, DEVICE_IS_SYSTEM_TODR(&args)) != 0) { 160 /* Call is not supported; proceed as if we should attach. */ 161 return false; 162 } 163 164 if (args.result) { 165 /* This is the system TODR; proceed. */ 166 return false; 167 } 168 169 aprint_normal_dev(dev, "disabled (not system TODR)\n"); 170 return true; 171 } 172 173 /* 174 * todr_attach: 175 * Attach the clock device to todr_handle. 176 */ 177 void 178 todr_attach(todr_chip_handle_t todr) 179 { 180 181 /* 182 * todr_init() is called very early in main(), but this is 183 * here to catch a case where todr_attach() is called before 184 * main(). 185 */ 186 KASSERT(todr_initialized); 187 188 if (todr_should_skip(todr)) { 189 return; 190 } 191 192 todr_lock(); 193 if (todr_handle) { 194 todr_unlock(); 195 printf("todr_attach: TOD already configured\n"); 196 return; 197 } 198 todr_handle = todr; 199 todr_unlock(); 200 } 201 202 static bool timeset = false; 203 204 /* 205 * todr_set_systime: 206 * Set up the system's time. The "base" argument is a best-guess 207 * close-enough value to use if the TOD clock is unavailable or 208 * contains garbage. Must be called with the TODR lock held. 209 */ 210 void 211 todr_set_systime(time_t base) 212 { 213 bool badbase = false; 214 bool waszero = (base == 0); 215 bool goodtime = false; 216 bool badrtc = false; 217 struct timespec ts; 218 struct timeval tv; 219 220 KASSERT(todr_lock_owned()); 221 222 rnd_add_data(NULL, &base, sizeof(base), 0); 223 224 if (base < 5 * SECS_PER_COMMON_YEAR) { 225 struct clock_ymdhms basedate; 226 227 /* 228 * If base is 0, assume filesystem time is just unknown 229 * instead of preposterous. Don't bark. 230 */ 231 if (base != 0) 232 printf("WARNING: preposterous time in file system\n"); 233 /* not going to use it anyway, if the chip is readable */ 234 basedate.dt_year = 2010; 235 basedate.dt_mon = 1; 236 basedate.dt_day = 1; 237 basedate.dt_hour = 12; 238 basedate.dt_min = 0; 239 basedate.dt_sec = 0; 240 base = clock_ymdhms_to_secs(&basedate); 241 badbase = true; 242 } 243 244 /* 245 * Some ports need to be supplied base in order to fabricate a time_t. 246 */ 247 if (todr_handle) 248 todr_handle->todr_base_time = base; 249 250 memset(&tv, 0, sizeof(tv)); 251 252 if ((todr_handle == NULL) || 253 (todr_gettime(todr_handle, &tv) != 0) || 254 (tv.tv_sec < (PREPOSTEROUS_YEARS * SECS_PER_COMMON_YEAR))) { 255 256 if (todr_handle != NULL) 257 printf("WARNING: preposterous TOD clock time\n"); 258 else 259 printf("WARNING: no TOD clock present\n"); 260 badrtc = true; 261 } else { 262 time_t deltat = tv.tv_sec - base; 263 264 if (deltat < 0) 265 deltat = -deltat; 266 267 if (!badbase && deltat >= 2 * SECS_PER_DAY) { 268 269 if (tv.tv_sec < base) { 270 /* 271 * The clock should never go backwards 272 * relative to filesystem time. If it 273 * does by more than the threshold, 274 * believe the filesystem. 275 */ 276 printf("WARNING: clock lost %" PRId64 " days\n", 277 deltat / SECS_PER_DAY); 278 badrtc = true; 279 } else { 280 aprint_verbose("WARNING: clock gained %" PRId64 281 " days\n", deltat / SECS_PER_DAY); 282 goodtime = true; 283 } 284 } else { 285 goodtime = true; 286 } 287 288 rnd_add_data(NULL, &tv, sizeof(tv), 0); 289 } 290 291 /* if the rtc time is bad, use the filesystem time */ 292 if (badrtc) { 293 if (badbase) { 294 printf("WARNING: using default initial time\n"); 295 } else { 296 printf("WARNING: using filesystem time\n"); 297 } 298 tv.tv_sec = base; 299 tv.tv_usec = 0; 300 } 301 302 timeset = true; 303 304 ts.tv_sec = tv.tv_sec; 305 ts.tv_nsec = tv.tv_usec * 1000; 306 tc_setclock(&ts); 307 308 if (waszero || goodtime) 309 return; 310 311 printf("WARNING: CHECK AND RESET THE DATE!\n"); 312 } 313 314 /* 315 * todr_save_systime: 316 * Save the current system time back to the TOD clock. 317 * Must be called with the TODR lock held. 318 */ 319 void 320 todr_save_systime(void) 321 { 322 struct timeval tv; 323 324 KASSERT(todr_lock_owned()); 325 326 /* 327 * We might have been called by boot() due to a crash early 328 * on. Don't reset the clock chip if we don't know what time 329 * it is. 330 */ 331 if (!timeset) 332 return; 333 334 getmicrotime(&tv); 335 336 if (tv.tv_sec == 0) 337 return; 338 339 if (todr_handle) 340 if (todr_settime(todr_handle, &tv) != 0) 341 printf("Cannot set TOD clock time\n"); 342 } 343 344 /* 345 * inittodr: 346 * Legacy wrapper around todr_set_systime(). 347 */ 348 void 349 inittodr(time_t base) 350 { 351 352 todr_lock(); 353 todr_set_systime(base); 354 todr_unlock(); 355 } 356 357 /* 358 * resettodr: 359 * Legacy wrapper around todr_save_systime(). 360 */ 361 void 362 resettodr(void) 363 { 364 365 /* 366 * If we're shutting down, we don't want to get stuck in case 367 * someone was already fiddling with the TOD clock. 368 */ 369 if (shutting_down) { 370 if (mutex_tryenter(&todr_mutex) == 0) { 371 printf("WARNING: Cannot set TOD clock time (busy)\n"); 372 return; 373 } 374 } else { 375 todr_lock(); 376 } 377 todr_save_systime(); 378 todr_unlock(); 379 } 380 381 #ifdef TODR_DEBUG 382 static void 383 todr_debug(const char *prefix, int rv, struct clock_ymdhms *dt, 384 struct timeval *tvp) 385 { 386 struct timeval tv_val; 387 struct clock_ymdhms dt_val; 388 389 if (dt == NULL) { 390 clock_secs_to_ymdhms(tvp->tv_sec, &dt_val); 391 dt = &dt_val; 392 } 393 if (tvp == NULL) { 394 tvp = &tv_val; 395 tvp->tv_sec = clock_ymdhms_to_secs(dt); 396 tvp->tv_usec = 0; 397 } 398 printf("%s: rv = %d\n", prefix, rv); 399 printf("%s: rtc_offset = %d\n", prefix, rtc_offset); 400 printf("%s: %4u/%02u/%02u %02u:%02u:%02u, (wday %d) (epoch %u.%06u)\n", 401 prefix, 402 (unsigned)dt->dt_year, dt->dt_mon, dt->dt_day, 403 dt->dt_hour, dt->dt_min, dt->dt_sec, 404 dt->dt_wday, (unsigned)tvp->tv_sec, (unsigned)tvp->tv_usec); 405 } 406 #else /* !TODR_DEBUG */ 407 #define todr_debug(prefix, rv, dt, tvp) 408 #endif /* TODR_DEBUG */ 409 410 static int 411 todr_wenable(todr_chip_handle_t todr, int onoff) 412 { 413 414 if (todr->todr_setwen != NULL) 415 return todr->todr_setwen(todr, onoff); 416 417 return 0; 418 } 419 420 #define ENABLE_TODR_WRITES() \ 421 do { \ 422 if ((rv = todr_wenable(tch, 1)) != 0) { \ 423 printf("%s: cannot enable TODR writes\n", __func__); \ 424 return rv; \ 425 } \ 426 } while (/*CONSTCOND*/0) 427 428 #define DISABLE_TODR_WRITES() \ 429 do { \ 430 if (todr_wenable(tch, 0) != 0) \ 431 printf("%s: WARNING: cannot disable TODR writes\n", \ 432 __func__); \ 433 } while (/*CONSTCOND*/0) 434 435 static int 436 todr_gettime(todr_chip_handle_t tch, struct timeval *tvp) 437 { 438 int rv; 439 440 /* 441 * Write-enable is used even when reading the TODR because 442 * writing to registers may be required in order to do so. 443 */ 444 445 if (tch->todr_gettime) { 446 ENABLE_TODR_WRITES(); 447 rv = tch->todr_gettime(tch, tvp); 448 DISABLE_TODR_WRITES(); 449 /* 450 * Some unconverted ports have their own references to 451 * rtc_offset. A converted port must not do that. 452 */ 453 if (rv == 0) 454 tvp->tv_sec += rtc_offset * 60; 455 todr_debug("TODR-GET-SECS", rv, NULL, tvp); 456 return rv; 457 } else if (tch->todr_gettime_ymdhms) { 458 struct clock_ymdhms dt = { 0 }; 459 ENABLE_TODR_WRITES(); 460 rv = tch->todr_gettime_ymdhms(tch, &dt); 461 DISABLE_TODR_WRITES(); 462 todr_debug("TODR-GET-YMDHMS", rv, &dt, NULL); 463 if (rv) 464 return rv; 465 466 /* 467 * Simple sanity checks. Note that this includes a 468 * value for clocks that can return a leap second. 469 * Note that we don't support double leap seconds, 470 * since this was apparently an error/misunderstanding 471 * on the part of the ISO C committee, and can never 472 * actually occur. If your clock issues us a double 473 * leap second, it must be broken. Ultimately, you'd 474 * have to be trying to read time at precisely that 475 * instant to even notice, so even broken clocks will 476 * work the vast majority of the time. In such a case 477 * it is recommended correction be applied in the 478 * clock driver. 479 */ 480 if (dt.dt_mon < 1 || dt.dt_mon > 12 || 481 dt.dt_day < 1 || dt.dt_day > 31 || 482 dt.dt_hour > 23 || dt.dt_min > 59 || dt.dt_sec > 60) { 483 return EINVAL; 484 } 485 tvp->tv_sec = clock_ymdhms_to_secs(&dt) + rtc_offset * 60; 486 tvp->tv_usec = 0; 487 return tvp->tv_sec < 0 ? EINVAL : 0; 488 } 489 490 return ENXIO; 491 } 492 493 static int 494 todr_settime(todr_chip_handle_t tch, struct timeval *tvp) 495 { 496 int rv; 497 498 if (tch->todr_settime) { 499 struct timeval copy = *tvp; 500 copy.tv_sec -= rtc_offset * 60; 501 ENABLE_TODR_WRITES(); 502 rv = tch->todr_settime(tch, ©); 503 DISABLE_TODR_WRITES(); 504 todr_debug("TODR-SET-SECS", rv, NULL, tvp); 505 return rv; 506 } else if (tch->todr_settime_ymdhms) { 507 struct clock_ymdhms dt; 508 time_t sec = tvp->tv_sec - rtc_offset * 60; 509 if (tvp->tv_usec >= 500000) 510 sec++; 511 clock_secs_to_ymdhms(sec, &dt); 512 ENABLE_TODR_WRITES(); 513 rv = tch->todr_settime_ymdhms(tch, &dt); 514 DISABLE_TODR_WRITES(); 515 todr_debug("TODR-SET-YMDHMS", rv, &dt, NULL); 516 return rv; 517 } 518 519 return ENXIO; 520 } 521