source: MondoRescue/branches/3.3/mindi-busybox/util-linux/hwclock.c@ 3625

Last change on this file since 3625 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

File size: 9.3 KB
Line 
1/* vi: set sw=4 ts=4: */
2/*
3 * Mini hwclock implementation for busybox
4 *
5 * Copyright (C) 2002 Robert Griebl <griebl@gmx.de>
6 *
7 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8*/
9
10#include "libbb.h"
11/* After libbb.h, since it needs sys/types.h on some systems */
12#include <sys/utsname.h>
13#include "rtc_.h"
14
15/* diff code is disabled: it's not sys/hw clock diff, it's some useless
16 * "time between hwclock was started and we saw CMOS tick" quantity.
17 * It's useless since hwclock is started at a random moment,
18 * thus the quantity is also random, useless. Showing 0.000000 does not
19 * deprive us from any useful info.
20 *
21 * SHOW_HWCLOCK_DIFF code in this file shows the difference between system
22 * and hw clock. It is useful, but not compatible with standard hwclock.
23 * Thus disabled.
24 */
25#define SHOW_HWCLOCK_DIFF 0
26
27
28#if !SHOW_HWCLOCK_DIFF
29# define read_rtc(pp_rtcname, sys_tv, utc) read_rtc(pp_rtcname, utc)
30#endif
31static time_t read_rtc(const char **pp_rtcname, struct timeval *sys_tv, int utc)
32{
33 struct tm tm_time;
34 int fd;
35
36 fd = rtc_xopen(pp_rtcname, O_RDONLY);
37
38 rtc_read_tm(&tm_time, fd);
39
40#if SHOW_HWCLOCK_DIFF
41 {
42 int before = tm_time.tm_sec;
43 while (1) {
44 rtc_read_tm(&tm_time, fd);
45 gettimeofday(sys_tv, NULL);
46 if (before != (int)tm_time.tm_sec)
47 break;
48 }
49 }
50#endif
51
52 if (ENABLE_FEATURE_CLEAN_UP)
53 close(fd);
54
55 return rtc_tm2time(&tm_time, utc);
56}
57
58static void show_clock(const char **pp_rtcname, int utc)
59{
60#if SHOW_HWCLOCK_DIFF
61 struct timeval sys_tv;
62#endif
63 time_t t = read_rtc(pp_rtcname, &sys_tv, utc);
64
65#if ENABLE_LOCALE_SUPPORT
66 /* Standard hwclock uses locale-specific output format */
67 char cp[64];
68 struct tm *ptm = localtime(&t);
69 strftime(cp, sizeof(cp), "%c", ptm);
70#else
71 char *cp = ctime(&t);
72 chomp(cp);
73#endif
74
75#if !SHOW_HWCLOCK_DIFF
76 printf("%s 0.000000 seconds\n", cp);
77#else
78 {
79 long diff = sys_tv.tv_sec - t;
80 if (diff < 0 /*&& tv.tv_usec != 0*/) {
81 /* Why we need diff++? */
82 /* diff >= 0 is ok: | diff < 0, can't just use tv.tv_usec: */
83 /* 45.520820 | 43.520820 */
84 /* - 44.000000 | - 45.000000 */
85 /* = 1.520820 | = -1.479180, not -2.520820! */
86 diff++;
87 /* Should be 1000000 - tv.tv_usec, but then we must check tv.tv_usec != 0 */
88 sys_tv.tv_usec = 999999 - sys_tv.tv_usec;
89 }
90 printf("%s %ld.%06lu seconds\n", cp, diff, (unsigned long)sys_tv.tv_usec);
91 }
92#endif
93}
94
95static void to_sys_clock(const char **pp_rtcname, int utc)
96{
97 struct timeval tv;
98 struct timezone tz;
99
100 tz.tz_minuteswest = timezone/60;
101 /* ^^^ used to also subtract 60*daylight, but it's wrong:
102 * daylight!=0 means "this timezone has some DST
103 * during the year", not "DST is in effect now".
104 */
105 tz.tz_dsttime = 0;
106
107 tv.tv_sec = read_rtc(pp_rtcname, NULL, utc);
108 tv.tv_usec = 0;
109 if (settimeofday(&tv, &tz))
110 bb_perror_msg_and_die("settimeofday");
111}
112
113static void from_sys_clock(const char **pp_rtcname, int utc)
114{
115#if 1
116 struct timeval tv;
117 struct tm tm_time;
118 int rtc;
119
120 rtc = rtc_xopen(pp_rtcname, O_WRONLY);
121 gettimeofday(&tv, NULL);
122 /* Prepare tm_time */
123 if (sizeof(time_t) == sizeof(tv.tv_sec)) {
124 if (utc)
125 gmtime_r((time_t*)&tv.tv_sec, &tm_time);
126 else
127 localtime_r((time_t*)&tv.tv_sec, &tm_time);
128 } else {
129 time_t t = tv.tv_sec;
130 if (utc)
131 gmtime_r(&t, &tm_time);
132 else
133 localtime_r(&t, &tm_time);
134 }
135#else
136/* Bloated code which tries to set hw clock with better precision.
137 * On x86, even though code does set hw clock within <1ms of exact
138 * whole seconds, apparently hw clock (at least on some machines)
139 * doesn't reset internal fractional seconds to 0,
140 * making all this a pointless excercise.
141 */
142 /* If we see that we are N usec away from whole second,
143 * we'll sleep for N-ADJ usecs. ADJ corrects for the fact
144 * that CPU is not infinitely fast.
145 * On infinitely fast CPU, next wakeup would be
146 * on (exactly_next_whole_second - ADJ). On real CPUs,
147 * this difference between current time and whole second
148 * is less than ADJ (assuming system isn't heavily loaded).
149 */
150 /* Small value of 256us gives very precise sync for 2+ GHz CPUs.
151 * Slower CPUs will fail to sync and will go to bigger
152 * ADJ values. qemu-emulated armv4tl with ~100 MHz
153 * performance ends up using ADJ ~= 4*1024 and it takes
154 * 2+ secs (2 tries with successively larger ADJ)
155 * to sync. Even straced one on the same qemu (very slow)
156 * takes only 4 tries.
157 */
158#define TWEAK_USEC 256
159 unsigned adj = TWEAK_USEC;
160 struct tm tm_time;
161 struct timeval tv;
162 int rtc = rtc_xopen(pp_rtcname, O_WRONLY);
163
164 /* Try to catch the moment when whole second is close */
165 while (1) {
166 unsigned rem_usec;
167 time_t t;
168
169 gettimeofday(&tv, NULL);
170
171 t = tv.tv_sec;
172 rem_usec = 1000000 - tv.tv_usec;
173 if (rem_usec < adj) {
174 /* Close enough */
175 small_rem:
176 t++;
177 }
178
179 /* Prepare tm_time from t */
180 if (utc)
181 gmtime_r(&t, &tm_time); /* may read /etc/xxx (it takes time) */
182 else
183 localtime_r(&t, &tm_time); /* same */
184
185 if (adj >= 32*1024) {
186 break; /* 32 ms diff and still no luck?? give up trying to sync */
187 }
188
189 /* gmtime/localtime took some time, re-get cur time */
190 gettimeofday(&tv, NULL);
191
192 if (tv.tv_sec < t /* we are still in old second */
193 || (tv.tv_sec == t && tv.tv_usec < adj) /* not too far into next second */
194 ) {
195 break; /* good, we are in sync! */
196 }
197
198 rem_usec = 1000000 - tv.tv_usec;
199 if (rem_usec < adj) {
200 t = tv.tv_sec;
201 goto small_rem; /* already close to next sec, don't sleep */
202 }
203
204 /* Try to sync up by sleeping */
205 usleep(rem_usec - adj);
206
207 /* Jump to 1ms diff, then increase fast (x2): EVERY loop
208 * takes ~1 sec, people won't like slowly converging code here!
209 */
210 //bb_error_msg("adj:%d tv.tv_usec:%d", adj, (int)tv.tv_usec);
211 if (adj < 512)
212 adj = 512;
213 /* ... and if last "overshoot" does not look insanely big,
214 * just use it as adj increment. This makes convergence faster.
215 */
216 if (tv.tv_usec < adj * 8) {
217 adj += tv.tv_usec;
218 continue;
219 }
220 adj *= 2;
221 }
222 /* Debug aid to find "optimal" TWEAK_USEC with nearly exact sync.
223 * Look for a value which makes tv_usec close to 999999 or 0.
224 * For 2.20GHz Intel Core 2: optimal TWEAK_USEC ~= 200
225 */
226 //bb_error_msg("tv.tv_usec:%d", (int)tv.tv_usec);
227#endif
228
229 tm_time.tm_isdst = 0;
230 xioctl(rtc, RTC_SET_TIME, &tm_time);
231
232 if (ENABLE_FEATURE_CLEAN_UP)
233 close(rtc);
234}
235
236/*
237 * At system boot, kernel may set system time from RTC,
238 * but it knows nothing about timezones. If RTC is in local time,
239 * then system time is wrong - it is offset by timezone.
240 * This option corrects system time if RTC is in local time,
241 * and (always) sets in-kernel timezone.
242 *
243 * This is an alternate option to --hctosys that does not read the
244 * hardware clock.
245 */
246static void set_system_clock_timezone(int utc)
247{
248 struct timeval tv;
249 struct tm *broken;
250 struct timezone tz;
251
252 gettimeofday(&tv, NULL);
253 broken = localtime(&tv.tv_sec);
254 tz.tz_minuteswest = timezone / 60;
255 if (broken->tm_isdst > 0)
256 tz.tz_minuteswest -= 60;
257 tz.tz_dsttime = 0;
258 gettimeofday(&tv, NULL);
259 if (!utc)
260 tv.tv_sec += tz.tz_minuteswest * 60;
261 if (settimeofday(&tv, &tz))
262 bb_perror_msg_and_die("settimeofday");
263}
264
265//usage:#define hwclock_trivial_usage
266//usage: IF_FEATURE_HWCLOCK_LONG_OPTIONS(
267//usage: "[-r|--show] [-s|--hctosys] [-w|--systohc] [-t|--systz]"
268//usage: " [-l|--localtime] [-u|--utc]"
269//usage: " [-f|--rtc FILE]"
270//usage: )
271//usage: IF_NOT_FEATURE_HWCLOCK_LONG_OPTIONS(
272//usage: "[-r] [-s] [-w] [-t] [-l] [-u] [-f FILE]"
273//usage: )
274//usage:#define hwclock_full_usage "\n\n"
275//usage: "Query and set hardware clock (RTC)\n"
276//usage: "\n -r Show hardware clock time"
277//usage: "\n -s Set system time from hardware clock"
278//usage: "\n -w Set hardware clock from system time"
279//usage: "\n -t Set in-kernel timezone, correct system time"
280//usage: "\n if hardware clock is in local time"
281//usage: "\n -u Assume hardware clock is kept in UTC"
282//usage: "\n -l Assume hardware clock is kept in local time"
283//usage: "\n -f FILE Use specified device (e.g. /dev/rtc2)"
284
285#define HWCLOCK_OPT_LOCALTIME 0x01
286#define HWCLOCK_OPT_UTC 0x02
287#define HWCLOCK_OPT_SHOW 0x04
288#define HWCLOCK_OPT_HCTOSYS 0x08
289#define HWCLOCK_OPT_SYSTOHC 0x10
290#define HWCLOCK_OPT_SYSTZ 0x20
291#define HWCLOCK_OPT_RTCFILE 0x40
292
293int hwclock_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
294int hwclock_main(int argc UNUSED_PARAM, char **argv)
295{
296 const char *rtcname = NULL;
297 unsigned opt;
298 int utc;
299
300#if ENABLE_FEATURE_HWCLOCK_LONG_OPTIONS
301 static const char hwclock_longopts[] ALIGN1 =
302 "localtime\0" No_argument "l" /* short opt is non-standard */
303 "utc\0" No_argument "u"
304 "show\0" No_argument "r"
305 "hctosys\0" No_argument "s"
306 "systohc\0" No_argument "w"
307 "systz\0" No_argument "t" /* short opt is non-standard */
308 "rtc\0" Required_argument "f"
309 ;
310 applet_long_options = hwclock_longopts;
311#endif
312
313 /* Initialize "timezone" (libc global variable) */
314 tzset();
315
316 opt_complementary = "r--wst:w--rst:s--wrt:t--rsw:l--u:u--l";
317 opt = getopt32(argv, "lurswtf:", &rtcname);
318
319 /* If -u or -l wasn't given check if we are using utc */
320 if (opt & (HWCLOCK_OPT_UTC | HWCLOCK_OPT_LOCALTIME))
321 utc = (opt & HWCLOCK_OPT_UTC);
322 else
323 utc = rtc_adjtime_is_utc();
324
325 if (opt & HWCLOCK_OPT_HCTOSYS)
326 to_sys_clock(&rtcname, utc);
327 else if (opt & HWCLOCK_OPT_SYSTOHC)
328 from_sys_clock(&rtcname, utc);
329 else if (opt & HWCLOCK_OPT_SYSTZ)
330 set_system_clock_timezone(utc);
331 else
332 /* default HWCLOCK_OPT_SHOW */
333 show_clock(&rtcname, utc);
334
335 return 0;
336}
Note: See TracBrowser for help on using the repository browser.