summaryrefslogtreecommitdiffstats
path: root/sys-utils/hwclock-rtc.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys-utils/hwclock-rtc.c')
-rw-r--r--sys-utils/hwclock-rtc.c599
1 files changed, 599 insertions, 0 deletions
diff --git a/sys-utils/hwclock-rtc.c b/sys-utils/hwclock-rtc.c
new file mode 100644
index 0000000..70c3a2f
--- /dev/null
+++ b/sys-utils/hwclock-rtc.c
@@ -0,0 +1,599 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * 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 <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "monotonic.h"
+#include "strutils.h"
+#include "xalloc.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
+ */
+#ifdef __sparc__
+/* 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)
+#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
+
+#ifndef RTC_PARAM_GET
+struct rtc_param {
+ uint64_t param;
+ union {
+ uint64_t uvalue;
+ int64_t svalue;
+ uint64_t ptr;
+ };
+ uint32_t index;
+ uint32_t __pad;
+};
+
+# define RTC_PARAM_GET _IOW('p', 0x13, struct rtc_param)
+# define RTC_PARAM_SET _IOW('p', 0x14, struct rtc_param)
+
+# define RTC_PARAM_FEATURES 0
+# define RTC_PARAM_CORRECTION 1
+# define RTC_PARAM_BACKUP_SWITCH_MODE 2
+#endif /* RTC_PARAM_GET */
+
+static const struct hwclock_param hwclock_params[] =
+{
+ { RTC_PARAM_FEATURES, "features", N_("supported features") },
+ { RTC_PARAM_CORRECTION, "correction", N_("time correction") },
+ { RTC_PARAM_BACKUP_SWITCH_MODE, "bsm", N_("backup switch mode") },
+ { }
+};
+
+const struct hwclock_param *get_hwclock_params(void)
+{
+ return hwclock_params;
+}
+
+/*
+ * /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>) */
+static const 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(const struct hwclock_control *ctl)
+{
+ static const char *fls[] = {
+#ifdef __ia64__
+ "/dev/efirtc",
+ "/dev/misc/efirtc",
+#endif
+ "/dev/rtc0",
+ "/dev/rtc",
+ "/dev/misc/rtc"
+ };
+ size_t i;
+
+ if (rtc_dev_fd != -1)
+ return rtc_dev_fd;
+
+ /* --rtc option has been given */
+ if (ctl->rtc_dev_name) {
+ rtc_dev_name = ctl->rtc_dev_name;
+ rtc_dev_fd = open(rtc_dev_name, O_RDONLY);
+ } else {
+ for (i = 0; i < ARRAY_SIZE(fls); i++) {
+ if (ctl->verbose)
+ printf(_("Trying to open: %s\n"), fls[i]);
+ rtc_dev_fd = open(fls[i], O_RDONLY);
+
+ if (rtc_dev_fd < 0) {
+ if (errno == ENOENT || errno == ENODEV)
+ continue;
+ if (ctl->verbose)
+ warn(_("cannot open %s"), fls[i]);
+ }
+ rtc_dev_name = fls[i];
+ 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(const struct hwclock_control *ctl)
+{
+ int rtc_fd = open_rtc(ctl);
+
+ if (rtc_fd < 0) {
+ warn(_("cannot open rtc device"));
+ hwclock_exit(ctl, EXIT_FAILURE);
+ }
+ 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;
+#endif
+
+ ioctlname = "RTC_RD_TIME";
+ rc = ioctl(rtc_fd, RTC_RD_TIME, tm);
+
+#ifdef __sparc__
+ if (rc == -1) { /* sparc sbus */
+ 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) {
+ 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. This function is used for rtc drivers without ioctl
+ * interrupts. 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.
+ */
+static int busywait_for_rtc_clock_tick(const struct hwclock_control *ctl,
+ 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 = { 0 }, now = { 0 };
+
+ if (ctl->verbose) {
+ printf("ioctl(%d, RTC_UIE_ON, 0): %s\n",
+ rtc_fd, strerror(errno));
+ printf(_("Waiting in loop for time from %s to change\n"),
+ rtc_dev_name);
+ }
+
+ if (do_rtc_read_ioctl(rtc_fd, &start_time))
+ 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.
+ */
+ gettime_monotonic(&begin);
+ do {
+ rc = do_rtc_read_ioctl(rtc_fd, &nowtime);
+ if (rc || start_time.tm_sec != nowtime.tm_sec)
+ break;
+ gettime_monotonic(&now);
+ if (time_diff(now, begin) > 1.5) {
+ warnx(_("Timed out waiting for time change."));
+ return 1;
+ }
+ } while (1);
+
+ if (rc)
+ return 1;
+ return 0;
+}
+
+/*
+ * Same as synchronize_to_clock_tick(), but just for /dev/rtc.
+ */
+static int synchronize_to_clock_tick_rtc(const struct hwclock_control *ctl)
+{
+ int rtc_fd; /* File descriptor of /dev/rtc */
+ int ret = 1;
+
+ rtc_fd = open_rtc(ctl);
+ if (rtc_fd == -1) {
+ warn(_("cannot open rtc device"));
+ return ret;
+ }
+
+ /* Turn on update interrupts (one per second) */
+ int rc = ioctl(rtc_fd, RTC_UIE_ON, 0);
+
+ if (rc != -1) {
+ /*
+ * 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 ten seconds for the next update
+ * interrupt
+ */
+ FD_ZERO(&rfds);
+ FD_SET(rtc_fd, &rfds);
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+ rc = select(rtc_fd + 1, &rfds, NULL, NULL, &tv);
+ if (0 < rc)
+ ret = 0;
+ else if (rc == 0) {
+ warnx(_("select() to %s to wait for clock tick timed out"),
+ rtc_dev_name);
+ } else
+ warn(_("select() to %s to wait for clock tick failed"),
+ rtc_dev_name);
+ /* 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 if (errno == ENOTTY || errno == EINVAL) {
+ /* rtc ioctl interrupts are unimplemented */
+ ret = busywait_for_rtc_clock_tick(ctl, rtc_fd);
+ } else
+ warn(_("ioctl(%d, RTC_UIE_ON, 0) to %s failed"),
+ rtc_fd, rtc_dev_name);
+ return ret;
+}
+
+static int read_hardware_clock_rtc(const struct hwclock_control *ctl,
+ struct tm *tm)
+{
+ int rtc_fd, rc;
+
+ rtc_fd = open_rtc_or_exit(ctl);
+
+ /* 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 hwclock_control *ctl,
+ const struct tm *new_broken_time)
+{
+ int rc = -1;
+ int rtc_fd;
+ char *ioctlname;
+
+ rtc_fd = open_rtc_or_exit(ctl);
+
+ ioctlname = "RTC_SET_TIME";
+ rc = ioctl(rtc_fd, RTC_SET_TIME, new_broken_time);
+
+#ifdef __sparc__
+ if (rc == -1) { /* sparc sbus */
+ 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) {
+ warn(_("ioctl(%s) to %s to set the time failed"),
+ ioctlname, rtc_dev_name);
+ hwclock_exit(ctl, EXIT_FAILURE);
+ }
+
+ if (ctl->verbose)
+ printf(_("ioctl(%s) was successful.\n"), ioctlname);
+
+ return 0;
+}
+
+static int get_permissions_rtc(void)
+{
+ return 0;
+}
+
+static const char *get_device_path(void)
+{
+ return rtc_dev_name;
+}
+
+static struct clock_ops rtc_interface = {
+ N_("Using the rtc interface to the clock."),
+ get_permissions_rtc,
+ read_hardware_clock_rtc,
+ set_hardware_clock_rtc,
+ synchronize_to_clock_tick_rtc,
+ get_device_path,
+};
+
+/* return &rtc if /dev/rtc can be opened, NULL otherwise */
+struct clock_ops *probe_for_rtc_clock(const struct hwclock_control *ctl)
+{
+ const int rtc_fd = open_rtc(ctl);
+
+ if (rtc_fd < 0)
+ return NULL;
+ return &rtc_interface;
+}
+
+#ifdef __alpha__
+/*
+ * Get the Hardware Clock epoch setting from the kernel.
+ */
+int get_epoch_rtc(const struct hwclock_control *ctl, unsigned long *epoch_p)
+{
+ int rtc_fd;
+
+ rtc_fd = open_rtc(ctl);
+ if (rtc_fd < 0) {
+ warn(_("cannot open %s"), rtc_dev_name);
+ return 1;
+ }
+
+ if (ioctl(rtc_fd, RTC_EPOCH_READ, epoch_p) == -1) {
+ warn(_("ioctl(%d, RTC_EPOCH_READ, epoch_p) to %s failed"),
+ rtc_fd, rtc_dev_name);
+ return 1;
+ }
+
+ if (ctl->verbose)
+ printf(_("ioctl(%d, RTC_EPOCH_READ, epoch_p) to %s succeeded.\n"),
+ rtc_fd, rtc_dev_name);
+
+ return 0;
+}
+
+/*
+ * Set the Hardware Clock epoch in the kernel.
+ */
+int set_epoch_rtc(const struct hwclock_control *ctl)
+{
+ int rtc_fd;
+ unsigned long epoch;
+
+ errno = 0;
+ epoch = strtoul(ctl->epoch_option, NULL, 10);
+
+ /* There were no RTC clocks before 1900. */
+ if (errno || epoch < 1900 || epoch == ULONG_MAX) {
+ warnx(_("invalid epoch '%s'."), ctl->epoch_option);
+ return 1;
+ }
+
+ rtc_fd = open_rtc(ctl);
+ if (rtc_fd < 0) {
+ warn(_("cannot open %s"), rtc_dev_name);
+ return 1;
+ }
+
+ if (ioctl(rtc_fd, RTC_EPOCH_SET, epoch) == -1) {
+ warn(_("ioctl(%d, RTC_EPOCH_SET, %lu) to %s failed"),
+ rtc_fd, epoch, rtc_dev_name);
+ return 1;
+ }
+
+ if (ctl->verbose)
+ printf(_("ioctl(%d, RTC_EPOCH_SET, %lu) to %s succeeded.\n"),
+ rtc_fd, epoch, rtc_dev_name);
+
+ return 0;
+}
+#endif /* __alpha__ */
+
+
+
+static int resolve_rtc_param_alias(const char *alias, uint64_t *value)
+{
+ const struct hwclock_param *param = &hwclock_params[0];
+
+ while (param->name) {
+ if (!strcmp(alias, param->name)) {
+ *value = param->id;
+ return 0;
+ }
+ param++;
+ }
+
+ return 1;
+}
+
+/*
+ * Get the Hardware Clock parameter setting from the kernel.
+ */
+int get_param_rtc(const struct hwclock_control *ctl,
+ const char *name, uint64_t *id, uint64_t *value)
+{
+ int rtc_fd;
+ struct rtc_param param = { .param = 0 };
+
+ /* handle name */
+ if (resolve_rtc_param_alias(name, &param.param) != 0
+ && ul_strtou64(name, &param.param, 0) != 0) {
+ warnx(_("could not convert parameter name to number"));
+ return 1;
+ }
+
+ /* get parameter */
+ rtc_fd = open_rtc(ctl);
+ if (rtc_fd < 0) {
+ warn(_("cannot open %s"), rtc_dev_name);
+ return 1;
+ }
+
+ if (ioctl(rtc_fd, RTC_PARAM_GET, &param) == -1) {
+ warn(_("ioctl(%d, RTC_PARAM_GET, param) to %s failed"),
+ rtc_fd, rtc_dev_name);
+ return 1;
+ }
+
+ if (id)
+ *id = param.param;
+ if (value)
+ *value = param.uvalue;
+
+ if (ctl->verbose)
+ printf(_("ioctl(%d, RTC_PARAM_GET, param) to %s succeeded.\n"),
+ rtc_fd, rtc_dev_name);
+
+ return 0;
+}
+
+/*
+ * Set the Hardware Clock parameter in the kernel.
+ */
+int set_param_rtc(const struct hwclock_control *ctl, const char *opt0)
+{
+ int rtc_fd, rc = 1;
+ struct rtc_param param = { .param = 0 };
+ char *tok, *opt = xstrdup(opt0);
+
+ /* handle name */
+ tok = strtok(opt, "=");
+ if (resolve_rtc_param_alias(tok, &param.param) != 0
+ && ul_strtou64(tok, &param.param, 0) != 0) {
+ warnx(_("could not convert parameter name to number"));
+ goto done;
+ }
+
+ /* handle value */
+ tok = strtok(NULL, "=");
+ if (!tok) {
+ warnx(_("expected <param>=<value>"));
+ goto done;
+ }
+ if (ul_strtou64(tok, &param.uvalue, 0) != 0) {
+ warnx(_("could not convert parameter value to number"));
+ goto done;
+ }
+
+ /* set parameter */
+ rtc_fd = open_rtc(ctl);
+ if (rtc_fd < 0) {
+ warnx(_("cannot open %s"), rtc_dev_name);
+ return 1;
+ }
+
+ if (ioctl(rtc_fd, RTC_PARAM_SET, &param) == -1) {
+ warn(_("ioctl(%d, RTC_PARAM_SET, param) to %s failed"),
+ rtc_fd, rtc_dev_name);
+ goto done;
+ }
+
+ if (ctl->verbose)
+ printf(_("ioctl(%d, RTC_PARAM_SET, param) to %s succeeded.\n"),
+ rtc_fd, rtc_dev_name);
+
+ rc = 0;
+done:
+ free(opt);
+ return rc;
+}