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//config:config HWCLOCK 10//config: bool "hwclock (5.8 kb)" 11//config: default y 12//config: help 13//config: The hwclock utility is used to read and set the hardware clock 14//config: on a system. This is primarily used to set the current time on 15//config: shutdown in the hardware clock, so the hardware will keep the 16//config: correct time when Linux is _not_ running. 17//config: 18//config:config FEATURE_HWCLOCK_ADJTIME_FHS 19//config: bool "Use FHS /var/lib/hwclock/adjtime" 20//config: default n # util-linux-ng in Fedora 13 still uses /etc/adjtime 21//config: depends on HWCLOCK 22//config: help 23//config: Starting with FHS 2.3, the adjtime state file is supposed to exist 24//config: at /var/lib/hwclock/adjtime instead of /etc/adjtime. If you wish 25//config: to use the FHS behavior, answer Y here, otherwise answer N for the 26//config: classic /etc/adjtime path. 27//config: 28//config: pathname.com/fhs/pub/fhs-2.3.html#VARLIBHWCLOCKSTATEDIRECTORYFORHWCLO 29 30//applet:IF_HWCLOCK(APPLET(hwclock, BB_DIR_SBIN, BB_SUID_DROP)) 31 32//kbuild:lib-$(CONFIG_HWCLOCK) += hwclock.o 33 34#include "libbb.h" 35/* After libbb.h, since it needs sys/types.h on some systems */ 36#include <sys/utsname.h> 37#include "rtc_.h" 38 39 40//musl has no __MUSL__ or similar define to check for, 41//but its <sys/types.h> has these lines: 42// #define __NEED_fsblkcnt_t 43// #define __NEED_fsfilcnt_t 44#if defined(__linux__) && defined(__NEED_fsblkcnt_t) && defined(__NEED_fsfilcnt_t) 45# define LIBC_IS_MUSL 1 46# include <sys/syscall.h> 47#else 48# define LIBC_IS_MUSL 0 49#endif 50 51 52/* diff code is disabled: it's not sys/hw clock diff, it's some useless 53 * "time between hwclock was started and we saw CMOS tick" quantity. 54 * It's useless since hwclock is started at a random moment, 55 * thus the quantity is also random, useless. Showing 0.000000 does not 56 * deprive us from any useful info. 57 * 58 * SHOW_HWCLOCK_DIFF code in this file shows the difference between system 59 * and hw clock. It is useful, but not compatible with standard hwclock. 60 * Thus disabled. 61 */ 62#define SHOW_HWCLOCK_DIFF 0 63 64 65#if !SHOW_HWCLOCK_DIFF 66# define read_rtc(pp_rtcname, sys_tv, utc) read_rtc(pp_rtcname, utc) 67#endif 68static time_t read_rtc(const char **pp_rtcname, struct timeval *sys_tv, int utc) 69{ 70 struct tm tm_time; 71 int fd; 72 73 fd = rtc_xopen(pp_rtcname, O_RDONLY); 74 75 rtc_read_tm(&tm_time, fd); 76 77#if SHOW_HWCLOCK_DIFF 78 { 79 int before = tm_time.tm_sec; 80 while (1) { 81 rtc_read_tm(&tm_time, fd); 82 xgettimeofday(sys_tv); 83 if (before != (int)tm_time.tm_sec) 84 break; 85 } 86 } 87#endif 88 89 if (ENABLE_FEATURE_CLEAN_UP) 90 close(fd); 91 92 return rtc_tm2time(&tm_time, utc); 93} 94 95static void show_clock(const char **pp_rtcname, int utc) 96{ 97#if SHOW_HWCLOCK_DIFF 98 struct timeval sys_tv; 99#endif 100 time_t t = read_rtc(pp_rtcname, &sys_tv, utc); 101 102#if ENABLE_LOCALE_SUPPORT 103 /* Standard hwclock uses locale-specific output format */ 104 char cp[64]; 105 struct tm *ptm = localtime(&t); 106 strftime(cp, sizeof(cp), "%c", ptm); 107#else 108 char *cp = ctime(&t); 109 chomp(cp); 110#endif 111 112#if !SHOW_HWCLOCK_DIFF 113 printf("%s 0.000000 seconds\n", cp); 114#else 115 { 116 long diff = sys_tv.tv_sec - t; 117 if (diff < 0 /*&& tv.tv_usec != 0*/) { 118 /* Why we need diff++? */ 119 /* diff >= 0 is ok: | diff < 0, can't just use tv.tv_usec: */ 120 /* 45.520820 | 43.520820 */ 121 /* - 44.000000 | - 45.000000 */ 122 /* = 1.520820 | = -1.479180, not -2.520820! */ 123 diff++; 124 /* Should be 1000000 - tv.tv_usec, but then we must check tv.tv_usec != 0 */ 125 sys_tv.tv_usec = 999999 - sys_tv.tv_usec; 126 } 127 printf("%s %ld.%06lu seconds\n", cp, diff, (unsigned long)sys_tv.tv_usec); 128 } 129#endif 130} 131 132static void set_kernel_tz(const struct timezone *tz) 133{ 134#if LIBC_IS_MUSL 135 /* musl libc does not pass tz argument to syscall 136 * because "it's deprecated by POSIX, therefore it's fine 137 * if we gratuitously break stuff" :( 138 */ 139#if !defined(SYS_settimeofday) && defined(SYS_settimeofday_time32) 140# define SYS_settimeofday SYS_settimeofday_time32 141#endif 142 int ret = syscall(SYS_settimeofday, NULL, tz); 143#else 144 int ret = settimeofday(NULL, tz); 145#endif 146 if (ret) 147 bb_simple_perror_msg_and_die("settimeofday"); 148} 149 150/* 151 * At system boot, kernel may set system time from RTC, 152 * but it knows nothing about timezones. If RTC is in local time, 153 * then system time is wrong - it is offset by timezone. 154 * --systz option corrects system time if RTC is in local time, 155 * and (always) sets in-kernel timezone. 156 * (Unlike --hctosys, it does not read the RTC). 157 * 158 * util-linux's code has this comment: 159 * RTC | settimeofday calls 160 * ------|------------------------------------------------- 161 * Local | 1st) warps system time*, sets PCIL* and kernel tz 162 * UTC | 1st) locks warp_clock 2nd) sets kernel tz 163 * * only on first call after boot 164 * (PCIL is "persistent_clock_is_local" kernel internal flag, 165 * it makes kernel save RTC in local time, not UTC.) 166 */ 167static void set_kernel_timezone_and_clock(int utc, const struct timeval *hctosys) 168{ 169 time_t cur; 170 struct tm *broken; 171 struct timezone tz = { 0 }; 172 173 /* if --utc, prevent kernel's warp_clock() with a dummy call */ 174 if (utc) 175 set_kernel_tz(&tz); 176 177 /* Set kernel's timezone offset based on userspace one */ 178//It's tempting to call tzset() and use libc global "timezone" variable 179//...but it does NOT include DST shift (IOW: it's WRONG, usually by one hour, 180//if DST is in effect!) Thus this ridiculous dance: 181 cur = time(NULL); 182 broken = localtime(&cur); 183 tz.tz_minuteswest = -broken->tm_gmtoff / 60; 184 /*tz.tz_dsttime = 0; already is */ 185 set_kernel_tz(&tz); /* MIGHT warp_clock() if 1st call since boot */ 186 187 if (hctosys) /* it's --hctosys: set time too */ 188 xsettimeofday(hctosys); 189} 190 191static void to_sys_clock(const char **pp_rtcname, int utc) 192{ 193 struct timeval tv; 194 195 tv.tv_sec = read_rtc(pp_rtcname, NULL, utc); 196 tv.tv_usec = 0; 197 return set_kernel_timezone_and_clock(utc, &tv); 198} 199 200static void from_sys_clock(const char **pp_rtcname, int utc) 201{ 202#if 1 203 struct timeval tv; 204 struct tm tm_time; 205 int rtc; 206 207 rtc = rtc_xopen(pp_rtcname, O_WRONLY); 208 xgettimeofday(&tv); 209 /* Prepare tm_time */ 210 if (sizeof(time_t) == sizeof(tv.tv_sec)) { 211 if (utc) 212 gmtime_r((time_t*)&tv.tv_sec, &tm_time); 213 else 214 localtime_r((time_t*)&tv.tv_sec, &tm_time); 215 } else { 216 time_t t = tv.tv_sec; 217 if (utc) 218 gmtime_r(&t, &tm_time); 219 else 220 localtime_r(&t, &tm_time); 221 } 222#else 223/* Bloated code which tries to set hw clock with better precision. 224 * On x86, even though code does set hw clock within <1ms of exact 225 * whole seconds, apparently hw clock (at least on some machines) 226 * doesn't reset internal fractional seconds to 0, 227 * making all this a pointless exercise. 228 */ 229 /* If we see that we are N usec away from whole second, 230 * we'll sleep for N-ADJ usecs. ADJ corrects for the fact 231 * that CPU is not infinitely fast. 232 * On infinitely fast CPU, next wakeup would be 233 * on (exactly_next_whole_second - ADJ). On real CPUs, 234 * this difference between current time and whole second 235 * is less than ADJ (assuming system isn't heavily loaded). 236 */ 237 /* Small value of 256us gives very precise sync for 2+ GHz CPUs. 238 * Slower CPUs will fail to sync and will go to bigger 239 * ADJ values. qemu-emulated armv4tl with ~100 MHz 240 * performance ends up using ADJ ~= 4*1024 and it takes 241 * 2+ secs (2 tries with successively larger ADJ) 242 * to sync. Even straced one on the same qemu (very slow) 243 * takes only 4 tries. 244 */ 245#define TWEAK_USEC 256 246 unsigned adj = TWEAK_USEC; 247 struct tm tm_time; 248 struct timeval tv; 249 int rtc = rtc_xopen(pp_rtcname, O_WRONLY); 250 251 /* Try to catch the moment when whole second is close */ 252 while (1) { 253 unsigned rem_usec; 254 time_t t; 255 256 xgettimeofday(&tv); 257 258 t = tv.tv_sec; 259 rem_usec = 1000000 - tv.tv_usec; 260 if (rem_usec < adj) { 261 /* Close enough */ 262 small_rem: 263 t++; 264 } 265 266 /* Prepare tm_time from t */ 267 if (utc) 268 gmtime_r(&t, &tm_time); /* may read /etc/xxx (it takes time) */ 269 else 270 localtime_r(&t, &tm_time); /* same */ 271 272 if (adj >= 32*1024) { 273 break; /* 32 ms diff and still no luck?? give up trying to sync */ 274 } 275 276 /* gmtime/localtime took some time, re-get cur time */ 277 xgettimeofday(&tv); 278 279 if (tv.tv_sec < t /* we are still in old second */ 280 || (tv.tv_sec == t && tv.tv_usec < adj) /* not too far into next second */ 281 ) { 282 break; /* good, we are in sync! */ 283 } 284 285 rem_usec = 1000000 - tv.tv_usec; 286 if (rem_usec < adj) { 287 t = tv.tv_sec; 288 goto small_rem; /* already close to next sec, don't sleep */ 289 } 290 291 /* Try to sync up by sleeping */ 292 usleep(rem_usec - adj); 293 294 /* Jump to 1ms diff, then increase fast (x2): EVERY loop 295 * takes ~1 sec, people won't like slowly converging code here! 296 */ 297 //bb_error_msg("adj:%d tv.tv_usec:%d", adj, (int)tv.tv_usec); 298 if (adj < 512) 299 adj = 512; 300 /* ... and if last "overshoot" does not look insanely big, 301 * just use it as adj increment. This makes convergence faster. 302 */ 303 if (tv.tv_usec < adj * 8) { 304 adj += tv.tv_usec; 305 continue; 306 } 307 adj *= 2; 308 } 309 /* Debug aid to find "optimal" TWEAK_USEC with nearly exact sync. 310 * Look for a value which makes tv_usec close to 999999 or 0. 311 * For 2.20GHz Intel Core 2: optimal TWEAK_USEC ~= 200 312 */ 313 //bb_error_msg("tv.tv_usec:%d", (int)tv.tv_usec); 314#endif 315 316 tm_time.tm_isdst = 0; 317 xioctl(rtc, RTC_SET_TIME, &tm_time); 318 319 if (ENABLE_FEATURE_CLEAN_UP) 320 close(rtc); 321} 322 323// hwclock from util-linux 2.36.1 324// hwclock [function] [option...] 325//Functions: 326// -r, --show display the RTC time 327// --get display drift corrected RTC time 328// --set set the RTC according to --date 329// -s, --hctosys set the system time from the RTC 330// -w, --systohc set the RTC from the system time 331// --systz send timescale configurations to the kernel 332// -a, --adjust adjust the RTC to account for systematic drift 333// --predict predict the drifted RTC time according to --date 334//Options: 335// -u, --utc the RTC timescale is UTC 336// -l, --localtime the RTC timescale is Local 337// -f, --rtc <file> use an alternate file to /dev/rtc0 338// --directisa use the ISA bus instead of /dev/rtc0 access 339// --date <time> date/time input for --set and --predict 340// --delay <sec> delay used when set new RTC time 341// --update-drift update the RTC drift factor 342// --noadjfile do not use /etc/adjtime 343// --adjfile <file> use an alternate file to /etc/adjtime 344// --test dry run; implies --verbose 345// -v, --verbose display more details 346 347//usage:#define hwclock_trivial_usage 348//usage: IF_LONG_OPTS( 349//usage: "[-swul] [--systz] [-f DEV]" 350//usage: ) 351//usage: IF_NOT_LONG_OPTS( 352//usage: "[-swult] [-f DEV]" 353//usage: ) 354//usage:#define hwclock_full_usage "\n\n" 355//usage: "Show or set hardware clock (RTC)\n" 356///////: "\n -r Show RTC time" 357///////-r is default, don't bother showing it in help 358//usage: "\n -s Set system time from RTC" 359//usage: "\n -w Set RTC from system time" 360//usage: IF_LONG_OPTS( 361//usage: "\n --systz Set in-kernel timezone, correct system time" 362//usage: "\n if RTC is kept in local time" 363//usage: ) 364//usage: "\n -f DEV Use specified device (e.g. /dev/rtc2)" 365//usage: "\n -u Assume RTC is kept in UTC" 366//usage: "\n -l Assume RTC is kept in local time" 367//usage: "\n (if neither is given, read from "ADJTIME_PATH")" 368 369//TODO: get rid of incompatible -t alias to --systz? 370 371#define HWCLOCK_OPT_LOCALTIME 0x01 372#define HWCLOCK_OPT_UTC 0x02 373#define HWCLOCK_OPT_SHOW 0x04 374#define HWCLOCK_OPT_HCTOSYS 0x08 375#define HWCLOCK_OPT_SYSTOHC 0x10 376#define HWCLOCK_OPT_SYSTZ 0x20 377#define HWCLOCK_OPT_RTCFILE 0x40 378 379int hwclock_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 380int hwclock_main(int argc UNUSED_PARAM, char **argv) 381{ 382 const char *rtcname = NULL; 383 unsigned opt; 384 int utc; 385#if ENABLE_LONG_OPTS 386 static const char hwclock_longopts[] ALIGN1 = 387 "localtime\0" No_argument "l" 388 "utc\0" No_argument "u" 389 "show\0" No_argument "r" 390 "hctosys\0" No_argument "s" 391 "systohc\0" No_argument "w" 392 "systz\0" No_argument "t" /* short opt is non-standard */ 393 "rtc\0" Required_argument "f" 394 ; 395#endif 396 opt = getopt32long(argv, 397 "^""lurswtf:v" /* -v is accepted and ignored */ 398 "\0" 399 "r--wst:w--rst:s--wrt:t--rsw:l--u:u--l", 400 hwclock_longopts, 401 &rtcname 402 ); 403 404 /* If -u or -l wasn't given, check if we are using utc */ 405 if (opt & (HWCLOCK_OPT_UTC | HWCLOCK_OPT_LOCALTIME)) 406 utc = (opt & HWCLOCK_OPT_UTC); 407 else 408 utc = rtc_adjtime_is_utc(); 409 410 if (opt & HWCLOCK_OPT_HCTOSYS) 411 to_sys_clock(&rtcname, utc); 412 else if (opt & HWCLOCK_OPT_SYSTOHC) 413 from_sys_clock(&rtcname, utc); 414 else if (opt & HWCLOCK_OPT_SYSTZ) 415 set_kernel_timezone_and_clock(utc, NULL); 416 else 417 /* default HWCLOCK_OPT_SHOW */ 418 show_clock(&rtcname, utc); 419 420 return 0; 421} 422