kern_todr.c revision 1.49 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