kern_todr.c revision 1.33.6.1 1 /* $NetBSD: kern_todr.c,v 1.33.6.1 2011/06/06 09:09:32 jruoho 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 <sys/cdefs.h>
42 __KERNEL_RCSID(0, "$NetBSD: kern_todr.c,v 1.33.6.1 2011/06/06 09:09:32 jruoho Exp $");
43
44 #include <sys/param.h>
45 #include <sys/kernel.h>
46 #include <sys/systm.h>
47 #include <sys/device.h>
48 #include <sys/timetc.h>
49 #include <sys/intr.h>
50
51 #include <dev/clock_subr.h> /* hmm.. this should probably move to sys */
52
53 static todr_chip_handle_t todr_handle = NULL;
54
55 /*
56 * Attach the clock device to todr_handle.
57 */
58 void
59 todr_attach(todr_chip_handle_t todr)
60 {
61
62 if (todr_handle) {
63 printf("todr_attach: TOD already configured\n");
64 return;
65 }
66 todr_handle = todr;
67 }
68
69 static bool timeset = false;
70
71 /*
72 * Set up the system's time, given a `reasonable' time value.
73 */
74 void
75 inittodr(time_t base)
76 {
77 bool badbase = false;
78 bool waszero = (base == 0);
79 bool goodtime = false;
80 bool badrtc = false;
81 int s;
82 struct timespec ts;
83 struct timeval tv;
84
85 if (base < 5 * SECYR) {
86 struct clock_ymdhms basedate;
87
88 /*
89 * If base is 0, assume filesystem time is just unknown
90 * instead of preposterous. Don't bark.
91 */
92 if (base != 0)
93 printf("WARNING: preposterous time in file system\n");
94 /* not going to use it anyway, if the chip is readable */
95 basedate.dt_year = 2010;
96 basedate.dt_mon = 1;
97 basedate.dt_day = 1;
98 basedate.dt_hour = 12;
99 basedate.dt_min = 0;
100 basedate.dt_sec = 0;
101 base = clock_ymdhms_to_secs(&basedate);
102 badbase = true;
103 }
104
105 /*
106 * Some ports need to be supplied base in order to fabricate a time_t.
107 */
108 if (todr_handle)
109 todr_handle->base_time = base;
110
111 if ((todr_handle == NULL) ||
112 (todr_gettime(todr_handle, &tv) != 0) ||
113 (tv.tv_sec < (25 * SECYR))) {
114
115 if (todr_handle != NULL)
116 printf("WARNING: preposterous TOD clock time\n");
117 else
118 printf("WARNING: no TOD clock present\n");
119 badrtc = true;
120 } else {
121 time_t deltat = tv.tv_sec - base;
122
123 if (deltat < 0)
124 deltat = -deltat;
125
126 if (!badbase && deltat >= 2 * SECDAY) {
127
128 if (tv.tv_sec < base) {
129 /*
130 * The clock should never go backwards
131 * relative to filesystem time. If it
132 * does by more than the threshold,
133 * believe the filesystem.
134 */
135 printf("WARNING: clock lost %" PRId64 " days\n",
136 deltat / SECDAY);
137 badrtc = true;
138 } else {
139 aprint_verbose("WARNING: clock gained %" PRId64
140 " days\n", deltat / SECDAY);
141 goodtime = true;
142 }
143 } else {
144 goodtime = true;
145 }
146 }
147
148 /* if the rtc time is bad, use the filesystem time */
149 if (badrtc) {
150 if (badbase) {
151 printf("WARNING: using default initial time\n");
152 } else {
153 printf("WARNING: using filesystem time\n");
154 }
155 tv.tv_sec = base;
156 tv.tv_usec = 0;
157 }
158
159 timeset = true;
160
161 ts.tv_sec = tv.tv_sec;
162 ts.tv_nsec = tv.tv_usec * 1000;
163 s = splclock();
164 tc_setclock(&ts);
165 splx(s);
166
167 if (waszero || goodtime)
168 return;
169
170 printf("WARNING: CHECK AND RESET THE DATE!\n");
171 }
172
173 /*
174 * Reset the TODR based on the time value; used when the TODR
175 * has a preposterous value and also when the time is reset
176 * by the stime system call. Also called when the TODR goes past
177 * TODRZERO + 100*(SECYEAR+2*SECDAY) (e.g. on Jan 2 just after midnight)
178 * to wrap the TODR around.
179 */
180 void
181 resettodr(void)
182 {
183 struct timeval tv;
184
185 /*
186 * We might have been called by boot() due to a crash early
187 * on. Don't reset the clock chip if we don't know what time
188 * it is.
189 */
190 if (!timeset)
191 return;
192
193 getmicrotime(&tv);
194
195 if (tv.tv_sec == 0)
196 return;
197
198 if (todr_handle)
199 if (todr_settime(todr_handle, &tv) != 0)
200 printf("Cannot set TOD clock time\n");
201 }
202
203 #ifdef TODR_DEBUG
204 static void
205 todr_debug(const char *prefix, int rv, struct clock_ymdhms *dt,
206 struct timeval *tvp)
207 {
208 struct timeval tv_val;
209 struct clock_ymdhms dt_val;
210
211 if (dt == NULL) {
212 clock_secs_to_ymdhms(tvp->tv_sec, &dt_val);
213 dt = &dt_val;
214 }
215 if (tvp == NULL) {
216 tvp = &tv_val;
217 tvp->tv_sec = clock_ymdhms_to_secs(dt);
218 tvp->tv_usec = 0;
219 }
220 printf("%s: rv = %d\n", prefix, rv);
221 printf("%s: rtc_offset = %d\n", prefix, rtc_offset);
222 printf("%s: %4u/%02u/%02u %02u:%02u:%02u, (wday %d) (epoch %u.%06u)\n",
223 prefix,
224 dt->dt_year, dt->dt_mon, dt->dt_day,
225 dt->dt_hour, dt->dt_min, dt->dt_sec,
226 dt->dt_wday, (unsigned)tvp->tv_sec, (unsigned)tvp->tv_usec);
227 }
228 #else /* !TODR_DEBUG */
229 #define todr_debug(prefix, rv, dt, tvp)
230 #endif /* TODR_DEBUG */
231
232
233 int
234 todr_gettime(todr_chip_handle_t tch, struct timeval *tvp)
235 {
236 struct clock_ymdhms dt;
237 int rv;
238
239 if (tch->todr_gettime) {
240 rv = tch->todr_gettime(tch, tvp);
241 /*
242 * Some unconverted ports have their own references to
243 * rtc_offset. A converted port must not do that.
244 */
245 if (rv == 0)
246 tvp->tv_sec += rtc_offset * 60;
247 todr_debug("TODR-GET-SECS", rv, NULL, tvp);
248 return rv;
249 } else if (tch->todr_gettime_ymdhms) {
250 rv = tch->todr_gettime_ymdhms(tch, &dt);
251 todr_debug("TODR-GET-YMDHMS", rv, &dt, NULL);
252 if (rv)
253 return rv;
254
255 /*
256 * Simple sanity checks. Note that this includes a
257 * value for clocks that can return a leap second.
258 * Note that we don't support double leap seconds,
259 * since this was apparently an error/misunderstanding
260 * on the part of the ISO C committee, and can never
261 * actually occur. If your clock issues us a double
262 * leap second, it must be broken. Ultimately, you'd
263 * have to be trying to read time at precisely that
264 * instant to even notice, so even broken clocks will
265 * work the vast majority of the time. In such a case
266 * it is recommended correction be applied in the
267 * clock driver.
268 */
269 if (dt.dt_mon < 1 || dt.dt_mon > 12 ||
270 dt.dt_day < 1 || dt.dt_day > 31 ||
271 dt.dt_hour > 23 || dt.dt_min > 59 || dt.dt_sec > 60) {
272 return EINVAL;
273 }
274 tvp->tv_sec = clock_ymdhms_to_secs(&dt) + rtc_offset * 60;
275 tvp->tv_usec = 0;
276 return tvp->tv_sec < 0 ? EINVAL : 0;
277 }
278
279 return ENXIO;
280 }
281
282 int
283 todr_settime(todr_chip_handle_t tch, struct timeval *tvp)
284 {
285 struct clock_ymdhms dt;
286 int rv;
287
288 if (tch->todr_settime) {
289 /* See comments above in gettime why this is ifdef'd */
290 struct timeval copy = *tvp;
291 copy.tv_sec -= rtc_offset * 60;
292 rv = tch->todr_settime(tch, ©);
293 todr_debug("TODR-SET-SECS", rv, NULL, tvp);
294 return rv;
295 } else if (tch->todr_settime_ymdhms) {
296 time_t sec = tvp->tv_sec - rtc_offset * 60;
297 if (tvp->tv_usec >= 500000)
298 sec++;
299 clock_secs_to_ymdhms(sec, &dt);
300 rv = tch->todr_settime_ymdhms(tch, &dt);
301 todr_debug("TODR-SET-YMDHMS", rv, &dt, NULL);
302 return rv;
303 } else {
304 return ENXIO;
305 }
306 }
307