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 | |