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