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