Implement a way for userspace to query the status of the backup battery, if supported by the hardware and driver. The RTC_VL_* bits are a somewhat recent addition (3431ca4837bf, but really only from b0efe0281234) to the uapi header, so provide our own definition if the build host's header doesn't. Signed-off-by: Rasmus Villemoes <rasmus.villemoes@xxxxxxxxx> --- sys-utils/hwclock-rtc.c | 86 +++++++++++++++++++++++++++++++++++++++++ sys-utils/hwclock.c | 35 +++++++++++++++++ sys-utils/hwclock.h | 5 +++ 3 files changed, 126 insertions(+) diff --git a/sys-utils/hwclock-rtc.c b/sys-utils/hwclock-rtc.c index 7094cd063..f918272a1 100644 --- a/sys-utils/hwclock-rtc.c +++ b/sys-utils/hwclock-rtc.c @@ -518,3 +518,89 @@ done: free(opt); return rc; } + +#ifndef RTC_VL_DATA_INVALID +#define RTC_VL_DATA_INVALID 0x1 +#endif +#ifndef RTC_VL_BACKUP_LOW +#define RTC_VL_BACKUP_LOW 0x2 +#endif +#ifndef RTC_VL_BACKUP_EMPTY +#define RTC_VL_BACKUP_EMPTY 0x4 +#endif +#ifndef RTC_VL_ACCURACY_LOW +#define RTC_VL_ACCURACY_LOW 0x8 +#endif +#ifndef RTC_VL_BACKUP_SWITCH +#define RTC_VL_BACKUP_SWITCH 0x10 +#endif + +int rtc_vl_read(const struct hwclock_control *ctl) +{ + unsigned int vl; + int rtc_fd; + size_t i; + static const struct vl_bit { + unsigned int bit; + const char *desc; + } vl_bits[] = { + { RTC_VL_DATA_INVALID, N_("Voltage too low, RTC data is invalid") }, + { RTC_VL_BACKUP_LOW, N_("Backup voltage is low") }, + { RTC_VL_BACKUP_EMPTY, N_("Backup empty or not present") }, + { RTC_VL_ACCURACY_LOW, N_("Voltage is low, RTC accuracy is reduced") }, + { RTC_VL_BACKUP_SWITCH, N_("Backup switchover happened") }, + }; + + rtc_fd = open_rtc(ctl); + if (rtc_fd < 0) { + warnx(_("cannot open %s"), rtc_dev_name); + return 1; + } + + if (ioctl(rtc_fd, RTC_VL_READ, &vl) == -1) { + warn(_("ioctl(%d, RTC_VL_READ) on %s failed"), + rtc_fd, rtc_dev_name); + return 1; + } + + if (ctl->verbose) { + printf(_("ioctl(%d, RTC_VL_READ) on %s returned 0x%x\n"), + rtc_fd, rtc_dev_name, vl); + } + + for (i = 0; i < ARRAY_SIZE(vl_bits); ++i) { + const struct vl_bit *vlb = &vl_bits[i]; + + if (vl & vlb->bit) { + printf("0x%02x - %s\n", vlb->bit, vlb->desc); + vl &= ~vlb->bit; + } + } + if (vl) + printf("0x%02x - unknown bit(s)\n", vl); + + return 0; +} + +int rtc_vl_clear(const struct hwclock_control *ctl) +{ + int rtc_fd; + + rtc_fd = open_rtc(ctl); + if (rtc_fd < 0) { + warnx(_("cannot open %s"), rtc_dev_name); + return 1; + } + + if (ioctl(rtc_fd, RTC_VL_CLR) == -1) { + warn(_("ioctl(%d, RTC_VL_CLEAR) on %s failed"), + rtc_fd, rtc_dev_name); + return 1; + } + + if (ctl->verbose) + printf(_("ioctl(%d, RTC_VL_CLEAR) on %s succeeded.\n"), + rtc_fd, rtc_dev_name); + + return 0; +} diff --git a/sys-utils/hwclock.c b/sys-utils/hwclock.c index 2a1844309..b043646b3 100644 --- a/sys-utils/hwclock.c +++ b/sys-utils/hwclock.c @@ -1180,6 +1180,20 @@ manipulate_rtc_param(const struct hwclock_control *ctl) return 1; } + +static int +manipulate_rtc_voltage_low(const struct hwclock_control *ctl) +{ + if (ctl->vl_read) { + if (rtc_vl_read(ctl)) + return 1; + } + if (ctl->vl_clear) { + if (rtc_vl_clear(ctl)) + return 1; + } + return 0; +} #endif static void out_version(void) @@ -1215,6 +1229,8 @@ usage(void) #ifdef __linux__ puts(_(" --param-get <param> display the RTC parameter")); puts(_(" --param-set <param>=<value> set the RTC parameter")); + puts(_(" --vl-read read voltage low information")); + puts(_(" --vl-clear clear voltage low information")); #endif puts(_(" --predict predict the drifted RTC time according to --date")); fputs(USAGE_OPTIONS, stdout); @@ -1286,6 +1302,8 @@ int main(int argc, char **argv) OPT_NOADJFILE, OPT_PARAM_GET, OPT_PARAM_SET, + OPT_VL_READ, + OPT_VL_CLEAR, OPT_PREDICT, OPT_SET, OPT_SETEPOCH, @@ -1315,6 +1333,8 @@ int main(int argc, char **argv) #ifdef __linux__ { "param-get", required_argument, NULL, OPT_PARAM_GET }, { "param-set", required_argument, NULL, OPT_PARAM_SET }, + { "vl-read", no_argument, NULL, OPT_VL_READ }, + { "vl-clear", no_argument, NULL, OPT_VL_CLEAR }, #endif { "noadjfile", no_argument, NULL, OPT_NOADJFILE }, { "directisa", no_argument, NULL, OPT_DIRECTISA }, @@ -1439,6 +1459,14 @@ int main(int argc, char **argv) ctl.show = 0; ctl.hwaudit_on = 1; break; + case OPT_VL_READ: + ctl.vl_read = 1; + ctl.show = 0; + break; + case OPT_VL_CLEAR: + ctl.vl_clear = 1; + ctl.show = 0; + break; #endif case OPT_NOADJFILE: ctl.noadjfile = 1; @@ -1540,6 +1568,13 @@ int main(int argc, char **argv) hwclock_exit(&ctl, EXIT_SUCCESS); } + + if (ctl.vl_read || ctl.vl_clear) { + if (manipulate_rtc_voltage_low(&ctl)) + hwclock_exit(&ctl, EXIT_FAILURE); + + hwclock_exit(&ctl, EXIT_SUCCESS); + } #endif #if defined(__linux__) && defined(__alpha__) diff --git a/sys-utils/hwclock.h b/sys-utils/hwclock.h index b5b72d45d..a690e717b 100644 --- a/sys-utils/hwclock.h +++ b/sys-utils/hwclock.h @@ -53,6 +53,8 @@ struct hwclock_control { set:1, update:1, universal:1, /* will store hw_clock_is_utc() return value */ + vl_read:1, + vl_clear:1, verbose:1; }; @@ -88,6 +90,9 @@ extern int get_param_rtc(const struct hwclock_control *ctl, const char *name, uint64_t *id, uint64_t *value); extern int set_param_rtc(const struct hwclock_control *ctl, const char *name); +extern int rtc_vl_read(const struct hwclock_control *ctl); +extern int rtc_vl_clear(const struct hwclock_control *ctl); + extern void __attribute__((__noreturn__)) hwclock_exit(const struct hwclock_control *ctl, int status); -- 2.37.2