diff options
Diffstat (limited to 'sys-utils/hwclock-rtc.c')
-rw-r--r-- | sys-utils/hwclock-rtc.c | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/sys-utils/hwclock-rtc.c b/sys-utils/hwclock-rtc.c new file mode 100644 index 0000000..3380941 --- /dev/null +++ b/sys-utils/hwclock-rtc.c @@ -0,0 +1,509 @@ +/* + * rtc.c - Use /dev/rtc for clock access + */ +#include <asm/ioctl.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <sysexits.h> +#include <sys/ioctl.h> +#include <sys/select.h> +#include <sys/time.h> +#include <time.h> +#include <unistd.h> + +#include "nls.h" + +#include "hwclock.h" + +/* + * Get defines for rtc stuff. + * + * Getting the rtc defines is nontrivial. The obvious way is by including + * <linux/mc146818rtc.h> but that again includes <asm/io.h> which again + * includes ... and on sparc and alpha this gives compilation errors for + * many kernel versions. So, we give the defines ourselves here. Moreover, + * some Sparc person decided to be incompatible, and used a struct rtc_time + * different from that used in mc146818rtc.h. + */ + +/* + * On Sparcs, there is a <asm/rtc.h> that defines different ioctls (that are + * required on my machine). However, this include file does not exist on + * other architectures. + */ +/* One might do: +#ifdef __sparc__ +# include <asm/rtc.h> +#endif + */ +/* The following is roughly equivalent */ +struct sparc_rtc_time +{ + int sec; /* Seconds 0-59 */ + int min; /* Minutes 0-59 */ + int hour; /* Hour 0-23 */ + int dow; /* Day of the week 1-7 */ + int dom; /* Day of the month 1-31 */ + int month; /* Month of year 1-12 */ + int year; /* Year 0-99 */ +}; + +#define RTCGET _IOR('p', 20, struct sparc_rtc_time) +#define RTCSET _IOW('p', 21, struct sparc_rtc_time) + +/* non-sparc stuff */ +#if 0 +# include <linux/version.h> +/* + * Check if the /dev/rtc interface is available in this version of the + * system headers. 131072 is linux 2.0.0. + */ +# if LINUX_VERSION_CODE >= 131072 +# include <linux/mc146818rtc.h> +# endif +#endif + +/* + * struct rtc_time is present since 1.3.99. + * Earlier (since 1.3.89), a struct tm was used. + */ +struct linux_rtc_time { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; +}; + +/* RTC_RD_TIME etc have this definition since 1.99.9 (pre2.0-9) */ +#ifndef RTC_RD_TIME +# define RTC_RD_TIME _IOR('p', 0x09, struct linux_rtc_time) +# define RTC_SET_TIME _IOW('p', 0x0a, struct linux_rtc_time) +# define RTC_UIE_ON _IO('p', 0x03) /* Update int. enable on */ +# define RTC_UIE_OFF _IO('p', 0x04) /* Update int. enable off */ +#endif + +/* RTC_EPOCH_READ and RTC_EPOCH_SET are present since 2.0.34 and 2.1.89 */ +#ifndef RTC_EPOCH_READ +# define RTC_EPOCH_READ _IOR('p', 0x0d, unsigned long) /* Read epoch */ +# define RTC_EPOCH_SET _IOW('p', 0x0e, unsigned long) /* Set epoch */ +#endif + +/* + * /dev/rtc is conventionally chardev 10/135 + * ia64 uses /dev/efirtc, chardev 10/136 + * devfs (obsolete) used /dev/misc/... for miscdev + * new RTC framework + udev uses dynamic major and /dev/rtc0.../dev/rtcN + * ... so we need an overridable default + */ + +/* default or user defined dev (by hwclock --rtc=<path>) */ +char *rtc_dev_name; + +static int rtc_dev_fd = -1; + +static void close_rtc(void) +{ + if (rtc_dev_fd != -1) + close(rtc_dev_fd); + rtc_dev_fd = -1; +} + +static int open_rtc(void) +{ + char *fls[] = { +#ifdef __ia64__ + "/dev/efirtc", + "/dev/misc/efirtc", +#endif + "/dev/rtc", + "/dev/rtc0", + "/dev/misc/rtc", + NULL + }; + char **p; + + if (rtc_dev_fd != -1) + return rtc_dev_fd; + + /* --rtc option has been given */ + if (rtc_dev_name) + rtc_dev_fd = open(rtc_dev_name, O_RDONLY); + else { + for (p = fls; *p; ++p) { + rtc_dev_fd = open(*p, O_RDONLY); + + if (rtc_dev_fd < 0 + && (errno == ENOENT || errno == ENODEV)) + continue; + rtc_dev_name = *p; + break; + } + if (rtc_dev_fd < 0) + rtc_dev_name = *fls; /* default for error messages */ + } + + if (rtc_dev_fd != 1) + atexit(close_rtc); + return rtc_dev_fd; +} + +static int open_rtc_or_exit(void) +{ + int rtc_fd = open_rtc(); + + if (rtc_fd < 0) { + warn(_("cannot open %s"), rtc_dev_name); + hwclock_exit(EX_OSFILE); + } + return rtc_fd; +} + +static int do_rtc_read_ioctl(int rtc_fd, struct tm *tm) +{ + int rc = -1; + char *ioctlname; + +#ifdef __sparc__ + /* some but not all sparcs use a different ioctl and struct */ + struct sparc_rtc_time stm; + + ioctlname = "RTCGET"; + rc = ioctl(rtc_fd, RTCGET, &stm); + if (rc == 0) { + tm->tm_sec = stm.sec; + tm->tm_min = stm.min; + tm->tm_hour = stm.hour; + tm->tm_mday = stm.dom; + tm->tm_mon = stm.month - 1; + tm->tm_year = stm.year - 1900; + tm->tm_wday = stm.dow - 1; + tm->tm_yday = -1; /* day in the year */ + } +#endif + if (rc == -1) { /* no sparc, or RTCGET failed */ + ioctlname = "RTC_RD_TIME"; + rc = ioctl(rtc_fd, RTC_RD_TIME, tm); + } + if (rc == -1) { + warn(_("ioctl(%s) to %s to read the time failed"), + ioctlname, rtc_dev_name); + return -1; + } + + tm->tm_isdst = -1; /* don't know whether it's dst */ + return 0; +} + +/* + * Wait for the top of a clock tick by reading /dev/rtc in a busy loop until + * we see it. + */ +static int busywait_for_rtc_clock_tick(const int rtc_fd) +{ + struct tm start_time; + /* The time when we were called (and started waiting) */ + struct tm nowtime; + int rc; + struct timeval begin, now; + + if (debug) + printf(_("Waiting in loop for time from %s to change\n"), + rtc_dev_name); + + rc = do_rtc_read_ioctl(rtc_fd, &start_time); + if (rc) + return 1; + + /* + * Wait for change. Should be within a second, but in case + * something weird happens, we have a time limit (1.5s) on this loop + * to reduce the impact of this failure. + */ + gettimeofday(&begin, NULL); + do { + rc = do_rtc_read_ioctl(rtc_fd, &nowtime); + if (rc || start_time.tm_sec != nowtime.tm_sec) + break; + gettimeofday(&now, NULL); + if (time_diff(now, begin) > 1.5) { + warnx(_("Timed out waiting for time change.")); + return 2; + } + } while (1); + + if (rc) + return 3; + return 0; +} + +/* + * Same as synchronize_to_clock_tick(), but just for /dev/rtc. + */ +static int synchronize_to_clock_tick_rtc(void) +{ + int rtc_fd; /* File descriptor of /dev/rtc */ + int ret; + + rtc_fd = open_rtc(); + if (rtc_fd == -1) { + warn(_("cannot open %s"), rtc_dev_name); + ret = 1; + } else { + int rc; /* Return code from ioctl */ + /* Turn on update interrupts (one per second) */ +#if defined(__alpha__) || defined(__sparc__) + /* + * Not all alpha kernels reject RTC_UIE_ON, but probably + * they should. + */ + rc = -1; + errno = EINVAL; +#else + rc = ioctl(rtc_fd, RTC_UIE_ON, 0); +#endif + if (rc == -1 && (errno == ENOTTY || errno == EINVAL)) { + /* + * This rtc device doesn't have interrupt functions. + * This is typical on an Alpha, where the Hardware + * Clock interrupts are used by the kernel for the + * system clock, so aren't at the user's disposal. + */ + if (debug) + printf(_ + ("%s does not have interrupt functions. "), + rtc_dev_name); + ret = busywait_for_rtc_clock_tick(rtc_fd); + } else if (rc == 0) { +#ifdef Wait_until_update_interrupt + unsigned long dummy; + + /* this blocks until the next update interrupt */ + rc = read(rtc_fd, &dummy, sizeof(dummy)); + ret = 1; + if (rc == -1) + warn(_ + ("read() to %s to wait for clock tick failed"), + rtc_dev_name); + else + ret = 0; +#else + /* + * Just reading rtc_fd fails on broken hardware: no + * update interrupt comes and a bootscript with a + * hwclock call hangs + */ + fd_set rfds; + struct timeval tv; + + /* + * Wait up to five seconds for the next update + * interrupt + */ + FD_ZERO(&rfds); + FD_SET(rtc_fd, &rfds); + tv.tv_sec = 5; + tv.tv_usec = 0; + rc = select(rtc_fd + 1, &rfds, NULL, NULL, &tv); + ret = 1; + if (rc == -1) + warn(_ + ("select() to %s to wait for clock tick failed"), + rtc_dev_name); + else if (rc == 0) + warn(_ + ("select() to %s to wait for clock tick timed out"), + rtc_dev_name); + else + ret = 0; +#endif + + /* Turn off update interrupts */ + rc = ioctl(rtc_fd, RTC_UIE_OFF, 0); + if (rc == -1) + warn(_ + ("ioctl() to %s to turn off update interrupts failed"), + rtc_dev_name); + } else { + warn(_ + ("ioctl() to %s to turn on update interrupts " + "failed unexpectedly"), rtc_dev_name); + ret = 1; + } + } + return ret; +} + +static int read_hardware_clock_rtc(struct tm *tm) +{ + int rtc_fd, rc; + + rtc_fd = open_rtc_or_exit(); + + /* Read the RTC time/date, return answer via tm */ + rc = do_rtc_read_ioctl(rtc_fd, tm); + + return rc; +} + +/* + * Set the Hardware Clock to the broken down time <new_broken_time>. Use + * ioctls to "rtc" device /dev/rtc. + */ +static int set_hardware_clock_rtc(const struct tm *new_broken_time) +{ + int rc = -1; + int rtc_fd; + char *ioctlname; + + rtc_fd = open_rtc_or_exit(); + +#ifdef __sparc__ + { + struct sparc_rtc_time stm; + + stm.sec = new_broken_time->tm_sec; + stm.min = new_broken_time->tm_min; + stm.hour = new_broken_time->tm_hour; + stm.dom = new_broken_time->tm_mday; + stm.month = new_broken_time->tm_mon + 1; + stm.year = new_broken_time->tm_year + 1900; + stm.dow = new_broken_time->tm_wday + 1; + + ioctlname = "RTCSET"; + rc = ioctl(rtc_fd, RTCSET, &stm); + } +#endif + if (rc == -1) { /* no sparc, or RTCSET failed */ + ioctlname = "RTC_SET_TIME"; + rc = ioctl(rtc_fd, RTC_SET_TIME, new_broken_time); + } + + if (rc == -1) { + warn(_("ioctl(%s) to %s to set the time failed."), + ioctlname, rtc_dev_name); + hwclock_exit(EX_IOERR); + } + + if (debug) + printf(_("ioctl(%s) was successful.\n"), ioctlname); + + return 0; +} + +static int get_permissions_rtc(void) +{ + return 0; +} + +static struct clock_ops rtc = { + "/dev interface to clock", + get_permissions_rtc, + read_hardware_clock_rtc, + set_hardware_clock_rtc, + synchronize_to_clock_tick_rtc, +}; + +/* return &rtc if /dev/rtc can be opened, NULL otherwise */ +struct clock_ops *probe_for_rtc_clock(void) +{ + int rtc_fd = open_rtc(); + if (rtc_fd >= 0) + return &rtc; + if (debug) + warn(_("cannot open %s"), rtc_dev_name); + return NULL; +} + +/* + * Get the Hardware Clock epoch setting from the kernel. + */ +int get_epoch_rtc(unsigned long *epoch_p, int silent) +{ + int rtc_fd; + + rtc_fd = open_rtc(); + if (rtc_fd < 0) { + if (!silent) { + if (errno == ENOENT) + warnx(_ + ("To manipulate the epoch value in the kernel, we must " + "access the Linux 'rtc' device driver via the device special " + "file %s. This file does not exist on this system."), + rtc_dev_name); + else + warn(_("cannot open %s"), rtc_dev_name); + } + return 1; + } + + if (ioctl(rtc_fd, RTC_EPOCH_READ, epoch_p) == -1) { + if (!silent) + warn(_("ioctl(RTC_EPOCH_READ) to %s failed"), + rtc_dev_name); + return 1; + } + + if (debug) + printf(_("we have read epoch %ld from %s " + "with RTC_EPOCH_READ ioctl.\n"), *epoch_p, + rtc_dev_name); + + return 0; +} + +/* + * Set the Hardware Clock epoch in the kernel. + */ +int set_epoch_rtc(unsigned long epoch) +{ + int rtc_fd; + + if (epoch < 1900) { + /* kernel would not accept this epoch value + * + * Bad habit, deciding not to do what the user asks just + * because one believes that the kernel might not like it. + */ + warnx(_("The epoch value may not be less than 1900. " + "You requested %ld"), epoch); + return 1; + } + + rtc_fd = open_rtc(); + if (rtc_fd < 0) { + if (errno == ENOENT) + warnx(_ + ("To manipulate the epoch value in the kernel, we must " + "access the Linux 'rtc' device driver via the device special " + "file %s. This file does not exist on this system."), + rtc_dev_name); + else + warn(_("cannot open %s"), rtc_dev_name); + return 1; + } + + if (debug) + printf(_("setting epoch to %ld " + "with RTC_EPOCH_SET ioctl to %s.\n"), epoch, + rtc_dev_name); + + if (ioctl(rtc_fd, RTC_EPOCH_SET, epoch) == -1) { + if (errno == EINVAL) + warnx(_("The kernel device driver for %s " + "does not have the RTC_EPOCH_SET ioctl."), + rtc_dev_name); + else + warn(_("ioctl(RTC_EPOCH_SET) to %s failed"), + rtc_dev_name); + return 1; + } + + return 0; +} |