FreeBSD kernel kern code
subr_clock.c
Go to the documentation of this file.
1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1988 University of Utah.
5 * Copyright (c) 1982, 1990, 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.
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 * from: @(#)clock.c 8.2 (Berkeley) 1/12/94
38 * from: NetBSD: clock_subr.c,v 1.6 2001/07/07 17:04:02 thorpej Exp
39 * and
40 * from: src/sys/i386/isa/clock.c,v 1.176 2001/09/04
41 */
42
43#include <sys/cdefs.h>
44__FBSDID("$FreeBSD$");
45
46#include <sys/param.h>
47#include <sys/systm.h>
48#include <sys/kernel.h>
49#include <sys/bus.h>
50#include <sys/clock.h>
51#include <sys/limits.h>
52#include <sys/sysctl.h>
53#include <sys/timetc.h>
54
55/*
56 * The adjkerntz and wall_cmos_clock sysctls are in the "machdep" sysctl
57 * namespace because they were misplaced there originally.
58 */
59static int adjkerntz;
60static int
61sysctl_machdep_adjkerntz(SYSCTL_HANDLER_ARGS)
62{
63 int error;
64 error = sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2, req);
65 if (!error && req->newptr)
66 resettodr();
67 return (error);
68}
69SYSCTL_PROC(_machdep, OID_AUTO, adjkerntz, CTLTYPE_INT | CTLFLAG_RW |
70 CTLFLAG_MPSAFE, &adjkerntz, 0, sysctl_machdep_adjkerntz, "I",
71 "Local offset from UTC in seconds");
72
73static int ct_debug;
74SYSCTL_INT(_debug, OID_AUTO, clocktime, CTLFLAG_RWTUN,
75 &ct_debug, 0, "Enable printing of clocktime debugging");
76
77static int wall_cmos_clock;
78SYSCTL_INT(_machdep, OID_AUTO, wall_cmos_clock, CTLFLAG_RW,
79 &wall_cmos_clock, 0, "Enables application of machdep.adjkerntz");
80
81/*--------------------------------------------------------------------*
82 * Generic routines to convert between a POSIX date
83 * (seconds since 1/1/1970) and yr/mo/day/hr/min/sec
84 * Derived from NetBSD arch/hp300/hp300/clock.c
85 */
86
87#define FEBRUARY 2
88#define days_in_year(y) (leapyear(y) ? 366 : 365)
89#define days_in_month(y, m) \
90 (month_days[(m) - 1] + (m == FEBRUARY ? leapyear(y) : 0))
91/* Day of week. Days are counted from 1/1/1970, which was a Thursday */
92#define day_of_week(days) (((days) + 4) % 7)
93
94static const int month_days[12] = {
95 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
96};
97
98/*
99 * Optimization: using a precomputed count of days between POSIX_BASE_YEAR and
100 * some recent year avoids lots of unnecessary loop iterations in conversion.
101 * recent_base_days is the number of days before the start of recent_base_year.
102 */
103static const int recent_base_year = 2017;
104static const int recent_base_days = 17167;
105
106/*
107 * Table to 'calculate' pow(10, 9 - nsdigits) via lookup of nsdigits.
108 * Before doing the lookup, the code asserts 0 <= nsdigits <= 9.
109 */
110static u_int nsdivisors[] = {
111 1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1
112};
113
114/*
115 * This inline avoids some unnecessary modulo operations
116 * as compared with the usual macro:
117 * ( ((year % 4) == 0 &&
118 * (year % 100) != 0) ||
119 * ((year % 400) == 0) )
120 * It is otherwise equivalent.
121 */
122static int
123leapyear(int year)
124{
125 int rv = 0;
126
127 if ((year & 3) == 0) {
128 rv = 1;
129 if ((year % 100) == 0) {
130 rv = 0;
131 if ((year % 400) == 0)
132 rv = 1;
133 }
134 }
135 return (rv);
136}
137
138int
139clock_ct_to_ts(const struct clocktime *ct, struct timespec *ts)
140{
141 int i, year, days;
142
143 if (ct_debug) {
144 printf("ct_to_ts([");
145 clock_print_ct(ct, 9);
146 printf("])");
147 }
148
149 /*
150 * Many realtime clocks store the year as 2-digit BCD; pivot on 70 to
151 * determine century. Some clocks have a "century bit" and drivers do
152 * year += 100, so interpret values between 70-199 as relative to 1900.
153 */
154 year = ct->year;
155 if (year < 70)
156 year += 2000;
157 else if (year < 200)
158 year += 1900;
159
160 /* Sanity checks. */
161 if (ct->mon < 1 || ct->mon > 12 || ct->day < 1 ||
162 ct->day > days_in_month(year, ct->mon) ||
163 ct->hour > 23 || ct->min > 59 || ct->sec > 59 || year < 1970 ||
164 (sizeof(time_t) == 4 && year > 2037)) { /* time_t overflow */
165 if (ct_debug)
166 printf(" = EINVAL\n");
167 return (EINVAL);
168 }
169
170 /*
171 * Compute days since start of time
172 * First from years, then from months.
173 */
174 if (year >= recent_base_year) {
177 } else {
178 i = POSIX_BASE_YEAR;
179 days = 0;
180 }
181 for (; i < year; i++)
182 days += days_in_year(i);
183
184 /* Months */
185 for (i = 1; i < ct->mon; i++)
186 days += days_in_month(year, i);
187 days += (ct->day - 1);
188
189 ts->tv_sec = (((time_t)days * 24 + ct->hour) * 60 + ct->min) * 60 +
190 ct->sec;
191 ts->tv_nsec = ct->nsec;
192
193 if (ct_debug)
194 printf(" = %jd.%09ld\n", (intmax_t)ts->tv_sec, ts->tv_nsec);
195 return (0);
196}
197
198int
199clock_bcd_to_ts(const struct bcd_clocktime *bct, struct timespec *ts, bool ampm)
200{
201 struct clocktime ct;
202 int bcent, byear;
203
204 /*
205 * Year may come in as 2-digit or 4-digit BCD. Split the value into
206 * separate BCD century and year values for validation and conversion.
207 */
208 bcent = bct->year >> 8;
209 byear = bct->year & 0xff;
210
211 /*
212 * Ensure that all values are valid BCD numbers, to avoid assertions in
213 * the BCD-to-binary conversion routines. clock_ct_to_ts() will further
214 * validate the field ranges (such as 0 <= min <= 59) during conversion.
215 */
216 if (!validbcd(bcent) || !validbcd(byear) || !validbcd(bct->mon) ||
217 !validbcd(bct->day) || !validbcd(bct->hour) ||
218 !validbcd(bct->min) || !validbcd(bct->sec)) {
219 if (ct_debug)
220 printf("clock_bcd_to_ts: bad BCD: "
221 "[%04x-%02x-%02x %02x:%02x:%02x]\n",
222 bct->year, bct->mon, bct->day,
223 bct->hour, bct->min, bct->sec);
224 return (EINVAL);
225 }
226
227 ct.year = FROMBCD(byear) + FROMBCD(bcent) * 100;
228 ct.mon = FROMBCD(bct->mon);
229 ct.day = FROMBCD(bct->day);
230 ct.hour = FROMBCD(bct->hour);
231 ct.min = FROMBCD(bct->min);
232 ct.sec = FROMBCD(bct->sec);
233 ct.dow = bct->dow;
234 ct.nsec = bct->nsec;
235
236 /* If asked to handle am/pm, convert from 12hr+pmflag to 24hr. */
237 if (ampm) {
238 if (ct.hour == 12)
239 ct.hour = 0;
240 if (bct->ispm)
241 ct.hour += 12;
242 }
243
244 return (clock_ct_to_ts(&ct, ts));
245}
246
247void
248clock_ts_to_ct(const struct timespec *ts, struct clocktime *ct)
249{
250 time_t i, year, days;
251 time_t rsec; /* remainder seconds */
252 time_t secs;
253
254 secs = ts->tv_sec;
255 days = secs / SECDAY;
256 rsec = secs % SECDAY;
257
258 ct->dow = day_of_week(days);
259
260 /* Subtract out whole years. */
261 if (days >= recent_base_days) {
262 year = recent_base_year;
264 } else {
265 year = POSIX_BASE_YEAR;
266 }
267 for (; days >= days_in_year(year); year++)
268 days -= days_in_year(year);
269 ct->year = year;
270
271 /* Subtract out whole months, counting them in i. */
272 for (i = 1; days >= days_in_month(year, i); i++)
273 days -= days_in_month(year, i);
274 ct->mon = i;
275
276 /* Days are what is left over (+1) from all that. */
277 ct->day = days + 1;
278
279 /* Hours, minutes, seconds are easy */
280 ct->hour = rsec / 3600;
281 rsec = rsec % 3600;
282 ct->min = rsec / 60;
283 rsec = rsec % 60;
284 ct->sec = rsec;
285 ct->nsec = ts->tv_nsec;
286 if (ct_debug) {
287 printf("ts_to_ct(%jd.%09ld) = [",
288 (intmax_t)ts->tv_sec, ts->tv_nsec);
289 clock_print_ct(ct, 9);
290 printf("]\n");
291 }
292
293 KASSERT(ct->year >= 0 && ct->year < 10000,
294 ("year %d isn't a 4 digit year", ct->year));
295 KASSERT(ct->mon >= 1 && ct->mon <= 12,
296 ("month %d not in 1-12", ct->mon));
297 KASSERT(ct->day >= 1 && ct->day <= 31,
298 ("day %d not in 1-31", ct->day));
299 KASSERT(ct->hour >= 0 && ct->hour <= 23,
300 ("hour %d not in 0-23", ct->hour));
301 KASSERT(ct->min >= 0 && ct->min <= 59,
302 ("minute %d not in 0-59", ct->min));
303 /* Not sure if this interface needs to handle leapseconds or not. */
304 KASSERT(ct->sec >= 0 && ct->sec <= 60,
305 ("seconds %d not in 0-60", ct->sec));
306}
307
308void
309clock_ts_to_bcd(const struct timespec *ts, struct bcd_clocktime *bct, bool ampm)
310{
311 struct clocktime ct;
312
313 clock_ts_to_ct(ts, &ct);
314
315 /* If asked to handle am/pm, convert from 24hr to 12hr+pmflag. */
316 bct->ispm = false;
317 if (ampm) {
318 if (ct.hour >= 12) {
319 ct.hour -= 12;
320 bct->ispm = true;
321 }
322 if (ct.hour == 0)
323 ct.hour = 12;
324 }
325
326 bct->year = TOBCD(ct.year % 100) | (TOBCD(ct.year / 100) << 8);
327 bct->mon = TOBCD(ct.mon);
328 bct->day = TOBCD(ct.day);
329 bct->hour = TOBCD(ct.hour);
330 bct->min = TOBCD(ct.min);
331 bct->sec = TOBCD(ct.sec);
332 bct->dow = ct.dow;
333 bct->nsec = ct.nsec;
334}
335
336void
337clock_print_bcd(const struct bcd_clocktime *bct, int nsdigits)
338{
339
340 KASSERT(nsdigits >= 0 && nsdigits <= 9, ("bad nsdigits %d", nsdigits));
341
342 if (nsdigits > 0) {
343 printf("%4.4x-%2.2x-%2.2x %2.2x:%2.2x:%2.2x.%*.*ld",
344 bct->year, bct->mon, bct->day,
345 bct->hour, bct->min, bct->sec,
346 nsdigits, nsdigits, bct->nsec / nsdivisors[nsdigits]);
347 } else {
348 printf("%4.4x-%2.2x-%2.2x %2.2x:%2.2x:%2.2x",
349 bct->year, bct->mon, bct->day,
350 bct->hour, bct->min, bct->sec);
351 }
352}
353
354void
355clock_print_ct(const struct clocktime *ct, int nsdigits)
356{
357
358 KASSERT(nsdigits >= 0 && nsdigits <= 9, ("bad nsdigits %d", nsdigits));
359
360 if (nsdigits > 0) {
361 printf("%04d-%02d-%02d %02d:%02d:%02d.%*.*ld",
362 ct->year, ct->mon, ct->day,
363 ct->hour, ct->min, ct->sec,
364 nsdigits, nsdigits, ct->nsec / nsdivisors[nsdigits]);
365 } else {
366 printf("%04d-%02d-%02d %02d:%02d:%02d",
367 ct->year, ct->mon, ct->day,
368 ct->hour, ct->min, ct->sec);
369 }
370}
371
372void
373clock_print_ts(const struct timespec *ts, int nsdigits)
374{
375 struct clocktime ct;
376
377 clock_ts_to_ct(ts, &ct);
378 clock_print_ct(&ct, nsdigits);
379}
380
381int
383{
384
385 return (wall_cmos_clock ? adjkerntz : 0);
386}
struct timespec * ts
Definition: clock_if.m:39
int sysctl_handle_int(SYSCTL_HANDLER_ARGS)
Definition: kern_sysctl.c:1644
SYSCTL_INT(_debug, OID_AUTO, clocktime, CTLFLAG_RWTUN, &ct_debug, 0, "Enable printing of clocktime debugging")
static int wall_cmos_clock
Definition: subr_clock.c:77
static const int recent_base_year
Definition: subr_clock.c:103
void clock_print_bcd(const struct bcd_clocktime *bct, int nsdigits)
Definition: subr_clock.c:337
static u_int nsdivisors[]
Definition: subr_clock.c:110
static const int recent_base_days
Definition: subr_clock.c:104
SYSCTL_PROC(_machdep, OID_AUTO, adjkerntz, CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_MPSAFE, &adjkerntz, 0, sysctl_machdep_adjkerntz, "I", "Local offset from UTC in seconds")
void clock_ts_to_ct(const struct timespec *ts, struct clocktime *ct)
Definition: subr_clock.c:248
static int adjkerntz
Definition: subr_clock.c:59
int utc_offset(void)
Definition: subr_clock.c:382
static int ct_debug
Definition: subr_clock.c:73
int clock_ct_to_ts(const struct clocktime *ct, struct timespec *ts)
Definition: subr_clock.c:139
__FBSDID("$FreeBSD$")
#define days_in_month(y, m)
Definition: subr_clock.c:89
static const int month_days[12]
Definition: subr_clock.c:94
#define days_in_year(y)
Definition: subr_clock.c:88
int clock_bcd_to_ts(const struct bcd_clocktime *bct, struct timespec *ts, bool ampm)
Definition: subr_clock.c:199
static int leapyear(int year)
Definition: subr_clock.c:123
void clock_ts_to_bcd(const struct timespec *ts, struct bcd_clocktime *bct, bool ampm)
Definition: subr_clock.c:309
#define day_of_week(days)
Definition: subr_clock.c:92
void clock_print_ts(const struct timespec *ts, int nsdigits)
Definition: subr_clock.c:373
void clock_print_ct(const struct clocktime *ct, int nsdigits)
Definition: subr_clock.c:355
static int sysctl_machdep_adjkerntz(SYSCTL_HANDLER_ARGS)
Definition: subr_clock.c:61
uint16_t days
Definition: subr_fattime.c:104
int printf(const char *fmt,...)
Definition: subr_prf.c:397
void resettodr(void)
Definition: subr_rtc.c:377