]>
Commit | Line | Data |
---|---|---|
ef71b8f1 | 1 | /* |
68a2ade7 KZ |
2 | * SPDX-License-Identifier: GPL-2.0-or-later |
3 | * | |
9abd5e4b KZ |
4 | * This program is free software; you can redistribute it and/or modify |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
ef71b8f1 SK |
9 | * rtc.c - Use /dev/rtc for clock access |
10 | */ | |
998f392a | 11 | #include <asm/ioctl.h> |
5213517f | 12 | #include <errno.h> |
c7fc54b9 TW |
13 | #include <linux/rtc.h> |
14 | #include <linux/types.h> | |
998f392a SK |
15 | #include <fcntl.h> |
16 | #include <stdio.h> | |
17 | #include <stdlib.h> | |
7eda085c | 18 | #include <sys/ioctl.h> |
998f392a SK |
19 | #include <sys/select.h> |
20 | #include <sys/time.h> | |
21 | #include <time.h> | |
22 | #include <unistd.h> | |
7eda085c | 23 | |
f78a9021 | 24 | #include "monotonic.h" |
511a5126 KZ |
25 | #include "strutils.h" |
26 | #include "xalloc.h" | |
7eda085c KZ |
27 | #include "nls.h" |
28 | ||
c7f75390 KZ |
29 | #include "hwclock.h" |
30 | ||
511a5126 KZ |
31 | #ifndef RTC_PARAM_GET |
32 | struct rtc_param { | |
c7fc54b9 | 33 | __u64 param; |
511a5126 | 34 | union { |
c7fc54b9 TW |
35 | __u64 uvalue; |
36 | __s64 svalue; | |
37 | __u64 ptr; | |
511a5126 | 38 | }; |
c7fc54b9 TW |
39 | __u32 index; |
40 | __u32 __pad; | |
511a5126 KZ |
41 | }; |
42 | ||
43 | # define RTC_PARAM_GET _IOW('p', 0x13, struct rtc_param) | |
44 | # define RTC_PARAM_SET _IOW('p', 0x14, struct rtc_param) | |
45 | ||
46 | # define RTC_PARAM_FEATURES 0 | |
47 | # define RTC_PARAM_CORRECTION 1 | |
48 | # define RTC_PARAM_BACKUP_SWITCH_MODE 2 | |
49 | #endif /* RTC_PARAM_GET */ | |
50 | ||
63d81834 KZ |
51 | static const struct hwclock_param hwclock_params[] = |
52 | { | |
53 | { RTC_PARAM_FEATURES, "features", N_("supported features") }, | |
54 | { RTC_PARAM_CORRECTION, "correction", N_("time correction") }, | |
55 | { RTC_PARAM_BACKUP_SWITCH_MODE, "bsm", N_("backup switch mode") }, | |
56 | { } | |
57 | }; | |
58 | ||
59 | const struct hwclock_param *get_hwclock_params(void) | |
60 | { | |
61 | return hwclock_params; | |
62 | } | |
63 | ||
ef71b8f1 SK |
64 | /* |
65 | * /dev/rtc is conventionally chardev 10/135 | |
88681c5f KZ |
66 | * ia64 uses /dev/efirtc, chardev 10/136 |
67 | * devfs (obsolete) used /dev/misc/... for miscdev | |
68 | * new RTC framework + udev uses dynamic major and /dev/rtc0.../dev/rtcN | |
69 | * ... so we need an overridable default | |
70 | */ | |
7eda085c | 71 | |
88681c5f | 72 | /* default or user defined dev (by hwclock --rtc=<path>) */ |
067b6028 | 73 | static const char *rtc_dev_name; |
27f9db17 KZ |
74 | static int rtc_dev_fd = -1; |
75 | ||
ef71b8f1 SK |
76 | static void close_rtc(void) |
77 | { | |
27f9db17 KZ |
78 | if (rtc_dev_fd != -1) |
79 | close(rtc_dev_fd); | |
80 | rtc_dev_fd = -1; | |
81 | } | |
82 | ||
336f7c5f | 83 | static int open_rtc(const struct hwclock_control *ctl) |
ef71b8f1 | 84 | { |
e08dddbc | 85 | static const char * const fls[] = { |
88681c5f KZ |
86 | #ifdef __ia64__ |
87 | "/dev/efirtc", | |
88 | "/dev/misc/efirtc", | |
89 | #endif | |
88681c5f | 90 | "/dev/rtc0", |
1811900a | 91 | "/dev/rtc", |
067b6028 | 92 | "/dev/misc/rtc" |
88681c5f | 93 | }; |
067b6028 | 94 | size_t i; |
5d1f6bae | 95 | |
27f9db17 KZ |
96 | if (rtc_dev_fd != -1) |
97 | return rtc_dev_fd; | |
98 | ||
5d1f6bae | 99 | /* --rtc option has been given */ |
336f7c5f SK |
100 | if (ctl->rtc_dev_name) { |
101 | rtc_dev_name = ctl->rtc_dev_name; | |
27f9db17 | 102 | rtc_dev_fd = open(rtc_dev_name, O_RDONLY); |
336f7c5f | 103 | } else { |
067b6028 | 104 | for (i = 0; i < ARRAY_SIZE(fls); i++) { |
de4568f7 | 105 | if (ctl->verbose) |
067b6028 SK |
106 | printf(_("Trying to open: %s\n"), fls[i]); |
107 | rtc_dev_fd = open(fls[i], O_RDONLY); | |
27f9db17 | 108 | |
b3fc2a3c KZ |
109 | if (rtc_dev_fd < 0) { |
110 | if (errno == ENOENT || errno == ENODEV) | |
111 | continue; | |
112 | if (ctl->verbose) | |
113 | warn(_("cannot open %s"), fls[i]); | |
114 | } | |
067b6028 | 115 | rtc_dev_name = fls[i]; |
27f9db17 KZ |
116 | break; |
117 | } | |
118 | if (rtc_dev_fd < 0) | |
119 | rtc_dev_name = *fls; /* default for error messages */ | |
5d1f6bae | 120 | } |
926ffe74 | 121 | if (rtc_dev_fd != -1) |
27f9db17 KZ |
122 | atexit(close_rtc); |
123 | return rtc_dev_fd; | |
63cccae4 KZ |
124 | } |
125 | ||
336f7c5f | 126 | static int open_rtc_or_exit(const struct hwclock_control *ctl) |
ef71b8f1 | 127 | { |
336f7c5f | 128 | int rtc_fd = open_rtc(ctl); |
63cccae4 KZ |
129 | |
130 | if (rtc_fd < 0) { | |
067b6028 | 131 | warn(_("cannot open rtc device")); |
c47a8f2a | 132 | hwclock_exit(ctl, EXIT_FAILURE); |
364cda48 KZ |
133 | } |
134 | return rtc_fd; | |
135 | } | |
136 | ||
ef71b8f1 SK |
137 | static int do_rtc_read_ioctl(int rtc_fd, struct tm *tm) |
138 | { | |
364cda48 | 139 | int rc = -1; |
a423fe19 | 140 | struct rtc_time rtc_tm = { 0 }; |
fc35f2db | 141 | |
a423fe19 | 142 | rc = ioctl(rtc_fd, RTC_RD_TIME, &rtc_tm); |
364cda48 | 143 | |
364cda48 | 144 | if (rc == -1) { |
bed96b1c TW |
145 | warn(_("ioctl(RTC_RD_NAME) to %s to read the time failed"), |
146 | rtc_dev_name); | |
cdedde03 | 147 | return -1; |
364cda48 KZ |
148 | } |
149 | ||
a423fe19 KZ |
150 | /* kernel uses private struct tm definition to be self contained */ |
151 | tm->tm_sec = rtc_tm.tm_sec; | |
152 | tm->tm_min = rtc_tm.tm_min; | |
153 | tm->tm_hour = rtc_tm.tm_hour; | |
154 | tm->tm_mday = rtc_tm.tm_mday; | |
155 | tm->tm_mon = rtc_tm.tm_mon; | |
156 | tm->tm_year = rtc_tm.tm_year; | |
157 | tm->tm_wday = rtc_tm.tm_wday; | |
158 | tm->tm_yday = rtc_tm.tm_yday; | |
ef71b8f1 | 159 | tm->tm_isdst = -1; /* don't know whether it's dst */ |
364cda48 KZ |
160 | return 0; |
161 | } | |
7eda085c | 162 | |
ef71b8f1 | 163 | /* |
0411a57e WP |
164 | * Wait for the top of a clock tick by reading /dev/rtc in a busy loop |
165 | * until we see it. This function is used for rtc drivers without ioctl | |
166 | * interrupts. This is typical on an Alpha, where the Hardware Clock | |
167 | * interrupts are used by the kernel for the system clock, so aren't at | |
168 | * the user's disposal. | |
ef71b8f1 | 169 | */ |
336f7c5f SK |
170 | static int busywait_for_rtc_clock_tick(const struct hwclock_control *ctl, |
171 | const int rtc_fd) | |
ef71b8f1 | 172 | { |
0bc13e01 | 173 | struct tm start_time = { 0 }; |
ef71b8f1 | 174 | /* The time when we were called (and started waiting) */ |
0bc13e01 | 175 | struct tm nowtime = { 0 }; |
ef71b8f1 | 176 | int rc; |
cf8c1917 | 177 | struct timeval begin = { 0 }, now = { 0 }; |
ef71b8f1 | 178 | |
de4568f7 | 179 | if (ctl->verbose) { |
0411a57e WP |
180 | printf("ioctl(%d, RTC_UIE_ON, 0): %s\n", |
181 | rtc_fd, strerror(errno)); | |
ef71b8f1 SK |
182 | printf(_("Waiting in loop for time from %s to change\n"), |
183 | rtc_dev_name); | |
0411a57e | 184 | } |
ef71b8f1 | 185 | |
4a6f658c WP |
186 | if (do_rtc_read_ioctl(rtc_fd, &start_time)) |
187 | return 1; | |
ef71b8f1 SK |
188 | |
189 | /* | |
190 | * Wait for change. Should be within a second, but in case | |
191 | * something weird happens, we have a time limit (1.5s) on this loop | |
192 | * to reduce the impact of this failure. | |
193 | */ | |
f78a9021 | 194 | gettime_monotonic(&begin); |
ef71b8f1 SK |
195 | do { |
196 | rc = do_rtc_read_ioctl(rtc_fd, &nowtime); | |
197 | if (rc || start_time.tm_sec != nowtime.tm_sec) | |
198 | break; | |
f78a9021 | 199 | gettime_monotonic(&now); |
ef71b8f1 | 200 | if (time_diff(now, begin) > 1.5) { |
111c05d3 | 201 | warnx(_("Timed out waiting for time change.")); |
4a6f658c | 202 | return 1; |
ef71b8f1 SK |
203 | } |
204 | } while (1); | |
205 | ||
206 | if (rc) | |
4a6f658c WP |
207 | return 1; |
208 | return 0; | |
7eda085c KZ |
209 | } |
210 | ||
ef71b8f1 SK |
211 | /* |
212 | * Same as synchronize_to_clock_tick(), but just for /dev/rtc. | |
213 | */ | |
336f7c5f | 214 | static int synchronize_to_clock_tick_rtc(const struct hwclock_control *ctl) |
ef71b8f1 SK |
215 | { |
216 | int rtc_fd; /* File descriptor of /dev/rtc */ | |
4bfd519e | 217 | int ret = 1; |
ef71b8f1 | 218 | |
336f7c5f | 219 | rtc_fd = open_rtc(ctl); |
ef71b8f1 | 220 | if (rtc_fd == -1) { |
067b6028 | 221 | warn(_("cannot open rtc device")); |
4bfd519e | 222 | return ret; |
042f62df RP |
223 | } |
224 | ||
225 | /* Turn on update interrupts (one per second) */ | |
226 | int rc = ioctl(rtc_fd, RTC_UIE_ON, 0); | |
227 | ||
228 | if (rc != -1) { | |
229 | /* | |
230 | * Just reading rtc_fd fails on broken hardware: no | |
231 | * update interrupt comes and a bootscript with a | |
232 | * hwclock call hangs | |
233 | */ | |
234 | fd_set rfds; | |
235 | struct timeval tv; | |
236 | ||
237 | /* | |
238 | * Wait up to ten seconds for the next update | |
239 | * interrupt | |
240 | */ | |
241 | FD_ZERO(&rfds); | |
242 | FD_SET(rtc_fd, &rfds); | |
243 | tv.tv_sec = 10; | |
244 | tv.tv_usec = 0; | |
245 | rc = select(rtc_fd + 1, &rfds, NULL, NULL, &tv); | |
246 | if (0 < rc) | |
247 | ret = 0; | |
248 | else if (rc == 0) { | |
249 | warnx(_("select() to %s to wait for clock tick timed out"), | |
250 | rtc_dev_name); | |
251 | } else | |
252 | warn(_("select() to %s to wait for clock tick failed"), | |
253 | rtc_dev_name); | |
254 | /* Turn off update interrupts */ | |
255 | rc = ioctl(rtc_fd, RTC_UIE_OFF, 0); | |
256 | if (rc == -1) | |
257 | warn(_("ioctl() to %s to turn off update interrupts failed"), | |
258 | rtc_dev_name); | |
c8650db3 ŁS |
259 | } else if (errno == ENOTTY || errno == EINVAL) { |
260 | /* rtc ioctl interrupts are unimplemented */ | |
261 | ret = busywait_for_rtc_clock_tick(ctl, rtc_fd); | |
262 | } else | |
263 | warn(_("ioctl(%d, RTC_UIE_ON, 0) to %s failed"), | |
264 | rtc_fd, rtc_dev_name); | |
ef71b8f1 | 265 | return ret; |
7eda085c KZ |
266 | } |
267 | ||
336f7c5f SK |
268 | static int read_hardware_clock_rtc(const struct hwclock_control *ctl, |
269 | struct tm *tm) | |
ef71b8f1 | 270 | { |
55a4a75c | 271 | int rtc_fd, rc; |
7eda085c | 272 | |
336f7c5f | 273 | rtc_fd = open_rtc_or_exit(ctl); |
7eda085c | 274 | |
364cda48 | 275 | /* Read the RTC time/date, return answer via tm */ |
55a4a75c | 276 | rc = do_rtc_read_ioctl(rtc_fd, tm); |
7eda085c | 277 | |
55a4a75c | 278 | return rc; |
7eda085c KZ |
279 | } |
280 | ||
ef71b8f1 SK |
281 | /* |
282 | * Set the Hardware Clock to the broken down time <new_broken_time>. Use | |
283 | * ioctls to "rtc" device /dev/rtc. | |
284 | */ | |
336f7c5f SK |
285 | static int set_hardware_clock_rtc(const struct hwclock_control *ctl, |
286 | const struct tm *new_broken_time) | |
ef71b8f1 | 287 | { |
364cda48 KZ |
288 | int rc = -1; |
289 | int rtc_fd; | |
a423fe19 | 290 | struct rtc_time rtc_tm = { 0 }; |
364cda48 | 291 | |
336f7c5f | 292 | rtc_fd = open_rtc_or_exit(ctl); |
63cccae4 | 293 | |
a423fe19 KZ |
294 | /* kernel uses private struct tm definition to be self contained */ |
295 | rtc_tm.tm_sec = new_broken_time->tm_sec; | |
296 | rtc_tm.tm_min = new_broken_time->tm_min; | |
297 | rtc_tm.tm_hour = new_broken_time->tm_hour; | |
298 | rtc_tm.tm_mday = new_broken_time->tm_mday; | |
299 | rtc_tm.tm_mon = new_broken_time->tm_mon; | |
300 | rtc_tm.tm_year = new_broken_time->tm_year; | |
301 | rtc_tm.tm_wday = new_broken_time->tm_wday; | |
302 | rtc_tm.tm_yday = new_broken_time->tm_yday; | |
303 | rtc_tm.tm_isdst = new_broken_time->tm_isdst; | |
304 | ||
305 | rc = ioctl(rtc_fd, RTC_SET_TIME, &rtc_tm); | |
fc35f2db | 306 | |
364cda48 | 307 | if (rc == -1) { |
bed96b1c TW |
308 | warn(_("ioctl(RTC_SET_TIME) to %s to set the time failed"), |
309 | rtc_dev_name); | |
c47a8f2a | 310 | hwclock_exit(ctl, EXIT_FAILURE); |
364cda48 KZ |
311 | } |
312 | ||
de4568f7 | 313 | if (ctl->verbose) |
bed96b1c | 314 | printf(_("ioctl(RTC_SET_TIME) was successful.\n")); |
364cda48 | 315 | |
364cda48 | 316 | return 0; |
7eda085c KZ |
317 | } |
318 | ||
ef71b8f1 SK |
319 | static int get_permissions_rtc(void) |
320 | { | |
7eda085c KZ |
321 | return 0; |
322 | } | |
323 | ||
df4f1a66 KZ |
324 | static const char *get_device_path(void) |
325 | { | |
326 | return rtc_dev_name; | |
327 | } | |
328 | ||
e08dddbc | 329 | static const struct clock_ops rtc_interface = { |
8f729d60 | 330 | N_("Using the rtc interface to the clock."), |
7eda085c KZ |
331 | get_permissions_rtc, |
332 | read_hardware_clock_rtc, | |
333 | set_hardware_clock_rtc, | |
334 | synchronize_to_clock_tick_rtc, | |
df4f1a66 | 335 | get_device_path, |
7eda085c KZ |
336 | }; |
337 | ||
338 | /* return &rtc if /dev/rtc can be opened, NULL otherwise */ | |
e08dddbc | 339 | const struct clock_ops *probe_for_rtc_clock(const struct hwclock_control *ctl) |
ef71b8f1 | 340 | { |
bd078689 SK |
341 | const int rtc_fd = open_rtc(ctl); |
342 | ||
343 | if (rtc_fd < 0) | |
344 | return NULL; | |
345 | return &rtc_interface; | |
7eda085c KZ |
346 | } |
347 | ||
bd078689 | 348 | #ifdef __alpha__ |
ef71b8f1 SK |
349 | /* |
350 | * Get the Hardware Clock epoch setting from the kernel. | |
351 | */ | |
af68bd01 | 352 | int get_epoch_rtc(const struct hwclock_control *ctl, unsigned long *epoch_p) |
ef71b8f1 SK |
353 | { |
354 | int rtc_fd; | |
355 | ||
336f7c5f | 356 | rtc_fd = open_rtc(ctl); |
ef71b8f1 | 357 | if (rtc_fd < 0) { |
cbc36f79 | 358 | warn(_("cannot open %s"), rtc_dev_name); |
ef71b8f1 SK |
359 | return 1; |
360 | } | |
361 | ||
362 | if (ioctl(rtc_fd, RTC_EPOCH_READ, epoch_p) == -1) { | |
f613c3c2 WP |
363 | warn(_("ioctl(%d, RTC_EPOCH_READ, epoch_p) to %s failed"), |
364 | rtc_fd, rtc_dev_name); | |
ef71b8f1 SK |
365 | return 1; |
366 | } | |
7eda085c | 367 | |
de4568f7 | 368 | if (ctl->verbose) |
f613c3c2 WP |
369 | printf(_("ioctl(%d, RTC_EPOCH_READ, epoch_p) to %s succeeded.\n"), |
370 | rtc_fd, rtc_dev_name); | |
7eda085c | 371 | |
ef71b8f1 | 372 | return 0; |
7eda085c KZ |
373 | } |
374 | ||
ef71b8f1 SK |
375 | /* |
376 | * Set the Hardware Clock epoch in the kernel. | |
377 | */ | |
336f7c5f | 378 | int set_epoch_rtc(const struct hwclock_control *ctl) |
ef71b8f1 SK |
379 | { |
380 | int rtc_fd; | |
f7599b4f | 381 | unsigned long epoch; |
ef71b8f1 | 382 | |
9bf8088f | 383 | errno = 0; |
f7599b4f WP |
384 | epoch = strtoul(ctl->epoch_option, NULL, 10); |
385 | ||
386 | /* There were no RTC clocks before 1900. */ | |
9bf8088f | 387 | if (errno || epoch < 1900 || epoch == ULONG_MAX) { |
f7599b4f | 388 | warnx(_("invalid epoch '%s'."), ctl->epoch_option); |
ef71b8f1 SK |
389 | return 1; |
390 | } | |
391 | ||
336f7c5f | 392 | rtc_fd = open_rtc(ctl); |
ef71b8f1 | 393 | if (rtc_fd < 0) { |
cbc36f79 | 394 | warn(_("cannot open %s"), rtc_dev_name); |
ef71b8f1 SK |
395 | return 1; |
396 | } | |
7eda085c | 397 | |
f7599b4f | 398 | if (ioctl(rtc_fd, RTC_EPOCH_SET, epoch) == -1) { |
f613c3c2 WP |
399 | warn(_("ioctl(%d, RTC_EPOCH_SET, %lu) to %s failed"), |
400 | rtc_fd, epoch, rtc_dev_name); | |
ef71b8f1 SK |
401 | return 1; |
402 | } | |
7eda085c | 403 | |
de4568f7 | 404 | if (ctl->verbose) |
f613c3c2 WP |
405 | printf(_("ioctl(%d, RTC_EPOCH_SET, %lu) to %s succeeded.\n"), |
406 | rtc_fd, epoch, rtc_dev_name); | |
407 | ||
ef71b8f1 | 408 | return 0; |
7eda085c | 409 | } |
bd078689 | 410 | #endif /* __alpha__ */ |
6097b12d | 411 | |
63d81834 KZ |
412 | |
413 | ||
c7fc54b9 | 414 | static int resolve_rtc_param_alias(const char *alias, __u64 *value) |
6097b12d BK |
415 | { |
416 | const struct hwclock_param *param = &hwclock_params[0]; | |
417 | ||
418 | while (param->name) { | |
419 | if (!strcmp(alias, param->name)) { | |
420 | *value = param->id; | |
421 | return 0; | |
422 | } | |
423 | param++; | |
424 | } | |
425 | ||
426 | return 1; | |
427 | } | |
428 | ||
c7fc54b9 TW |
429 | /* kernel uapi __u64 can be defined differently than uint64_t */ |
430 | static int strtoku64(const char *str, __u64 *num, int base) | |
431 | { | |
432 | return ul_strtou64(str, (uint64_t *) &num, base); | |
433 | } | |
434 | ||
6097b12d BK |
435 | /* |
436 | * Get the Hardware Clock parameter setting from the kernel. | |
437 | */ | |
511a5126 KZ |
438 | int get_param_rtc(const struct hwclock_control *ctl, |
439 | const char *name, uint64_t *id, uint64_t *value) | |
6097b12d BK |
440 | { |
441 | int rtc_fd; | |
511a5126 | 442 | struct rtc_param param = { .param = 0 }; |
6097b12d BK |
443 | |
444 | /* handle name */ | |
511a5126 | 445 | if (resolve_rtc_param_alias(name, ¶m.param) != 0 |
c7fc54b9 | 446 | && strtoku64(name, ¶m.param, 0) != 0) { |
511a5126 KZ |
447 | warnx(_("could not convert parameter name to number")); |
448 | return 1; | |
6097b12d BK |
449 | } |
450 | ||
451 | /* get parameter */ | |
452 | rtc_fd = open_rtc(ctl); | |
453 | if (rtc_fd < 0) { | |
454 | warn(_("cannot open %s"), rtc_dev_name); | |
455 | return 1; | |
456 | } | |
457 | ||
511a5126 | 458 | if (ioctl(rtc_fd, RTC_PARAM_GET, ¶m) == -1) { |
6097b12d BK |
459 | warn(_("ioctl(%d, RTC_PARAM_GET, param) to %s failed"), |
460 | rtc_fd, rtc_dev_name); | |
461 | return 1; | |
462 | } | |
463 | ||
511a5126 KZ |
464 | if (id) |
465 | *id = param.param; | |
47577bb5 | 466 | if (value) |
511a5126 KZ |
467 | *value = param.uvalue; |
468 | ||
6097b12d BK |
469 | if (ctl->verbose) |
470 | printf(_("ioctl(%d, RTC_PARAM_GET, param) to %s succeeded.\n"), | |
471 | rtc_fd, rtc_dev_name); | |
472 | ||
473 | return 0; | |
474 | } | |
b22b78b1 BK |
475 | |
476 | /* | |
477 | * Set the Hardware Clock parameter in the kernel. | |
478 | */ | |
511a5126 | 479 | int set_param_rtc(const struct hwclock_control *ctl, const char *opt0) |
b22b78b1 | 480 | { |
511a5126 KZ |
481 | int rtc_fd, rc = 1; |
482 | struct rtc_param param = { .param = 0 }; | |
483 | char *tok, *opt = xstrdup(opt0); | |
b22b78b1 BK |
484 | |
485 | /* handle name */ | |
511a5126 KZ |
486 | tok = strtok(opt, "="); |
487 | if (resolve_rtc_param_alias(tok, ¶m.param) != 0 | |
c7fc54b9 | 488 | && strtoku64(tok, ¶m.param, 0) != 0) { |
511a5126 KZ |
489 | warnx(_("could not convert parameter name to number")); |
490 | goto done; | |
b22b78b1 BK |
491 | } |
492 | ||
493 | /* handle value */ | |
494 | tok = strtok(NULL, "="); | |
495 | if (!tok) { | |
496 | warnx(_("expected <param>=<value>")); | |
511a5126 | 497 | goto done; |
b22b78b1 | 498 | } |
c7fc54b9 | 499 | if (strtoku64(tok, ¶m.uvalue, 0) != 0) { |
b22b78b1 | 500 | warnx(_("could not convert parameter value to number")); |
511a5126 | 501 | goto done; |
b22b78b1 BK |
502 | } |
503 | ||
504 | /* set parameter */ | |
505 | rtc_fd = open_rtc(ctl); | |
506 | if (rtc_fd < 0) { | |
507 | warnx(_("cannot open %s"), rtc_dev_name); | |
508 | return 1; | |
509 | } | |
510 | ||
511 | if (ioctl(rtc_fd, RTC_PARAM_SET, ¶m) == -1) { | |
512 | warn(_("ioctl(%d, RTC_PARAM_SET, param) to %s failed"), | |
513 | rtc_fd, rtc_dev_name); | |
511a5126 | 514 | goto done; |
b22b78b1 BK |
515 | } |
516 | ||
517 | if (ctl->verbose) | |
518 | printf(_("ioctl(%d, RTC_PARAM_SET, param) to %s succeeded.\n"), | |
519 | rtc_fd, rtc_dev_name); | |
520 | ||
511a5126 KZ |
521 | rc = 0; |
522 | done: | |
523 | free(opt); | |
524 | return rc; | |
b22b78b1 | 525 | } |
76cf1753 RV |
526 | |
527 | #ifndef RTC_VL_DATA_INVALID | |
528 | #define RTC_VL_DATA_INVALID 0x1 | |
529 | #endif | |
530 | #ifndef RTC_VL_BACKUP_LOW | |
531 | #define RTC_VL_BACKUP_LOW 0x2 | |
532 | #endif | |
533 | #ifndef RTC_VL_BACKUP_EMPTY | |
534 | #define RTC_VL_BACKUP_EMPTY 0x4 | |
535 | #endif | |
536 | #ifndef RTC_VL_ACCURACY_LOW | |
537 | #define RTC_VL_ACCURACY_LOW 0x8 | |
538 | #endif | |
539 | #ifndef RTC_VL_BACKUP_SWITCH | |
540 | #define RTC_VL_BACKUP_SWITCH 0x10 | |
541 | #endif | |
542 | ||
543 | int rtc_vl_read(const struct hwclock_control *ctl) | |
544 | { | |
545 | unsigned int vl; | |
546 | int rtc_fd; | |
547 | size_t i; | |
548 | static const struct vl_bit { | |
549 | unsigned int bit; | |
550 | const char *desc; | |
551 | } vl_bits[] = { | |
552 | { RTC_VL_DATA_INVALID, N_("Voltage too low, RTC data is invalid") }, | |
553 | { RTC_VL_BACKUP_LOW, N_("Backup voltage is low") }, | |
554 | { RTC_VL_BACKUP_EMPTY, N_("Backup empty or not present") }, | |
555 | { RTC_VL_ACCURACY_LOW, N_("Voltage is low, RTC accuracy is reduced") }, | |
556 | { RTC_VL_BACKUP_SWITCH, N_("Backup switchover happened") }, | |
557 | }; | |
558 | ||
559 | rtc_fd = open_rtc(ctl); | |
560 | if (rtc_fd < 0) { | |
561 | warnx(_("cannot open %s"), rtc_dev_name); | |
562 | return 1; | |
563 | } | |
564 | ||
565 | if (ioctl(rtc_fd, RTC_VL_READ, &vl) == -1) { | |
566 | warn(_("ioctl(%d, RTC_VL_READ) on %s failed"), | |
567 | rtc_fd, rtc_dev_name); | |
568 | return 1; | |
569 | } | |
570 | ||
571 | if (ctl->verbose) { | |
572 | printf(_("ioctl(%d, RTC_VL_READ) on %s returned 0x%x\n"), | |
573 | rtc_fd, rtc_dev_name, vl); | |
574 | } | |
575 | ||
576 | for (i = 0; i < ARRAY_SIZE(vl_bits); ++i) { | |
577 | const struct vl_bit *vlb = &vl_bits[i]; | |
578 | ||
579 | if (vl & vlb->bit) { | |
580 | printf("0x%02x - %s\n", vlb->bit, vlb->desc); | |
581 | vl &= ~vlb->bit; | |
582 | } | |
583 | } | |
584 | if (vl) | |
585 | printf("0x%02x - unknown bit(s)\n", vl); | |
586 | ||
587 | return 0; | |
588 | } | |
589 | ||
590 | int rtc_vl_clear(const struct hwclock_control *ctl) | |
591 | { | |
592 | int rtc_fd; | |
593 | ||
594 | rtc_fd = open_rtc(ctl); | |
595 | if (rtc_fd < 0) { | |
596 | warnx(_("cannot open %s"), rtc_dev_name); | |
597 | return 1; | |
598 | } | |
599 | ||
600 | if (ioctl(rtc_fd, RTC_VL_CLR) == -1) { | |
601 | warn(_("ioctl(%d, RTC_VL_CLEAR) on %s failed"), | |
602 | rtc_fd, rtc_dev_name); | |
603 | return 1; | |
604 | } | |
605 | ||
606 | if (ctl->verbose) | |
607 | printf(_("ioctl(%d, RTC_VL_CLEAR) on %s succeeded.\n"), | |
608 | rtc_fd, rtc_dev_name); | |
609 | ||
610 | return 0; | |
611 | } |