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