]>
Commit | Line | Data |
---|---|---|
7eda085c KZ |
1 | /* rtc.c - Use /dev/rtc for clock access */ |
2 | #include <unistd.h> /* for close() */ | |
3 | #include <fcntl.h> /* for O_RDONLY */ | |
5213517f | 4 | #include <errno.h> |
63cccae4 | 5 | #include <sysexits.h> |
7eda085c | 6 | #include <sys/ioctl.h> |
d03dd608 | 7 | #include <sys/time.h> /* for struct timeval */ |
7eda085c KZ |
8 | |
9 | #include "clock.h" | |
10 | #include "nls.h" | |
11 | ||
12 | /* | |
13 | * Get defines for rtc stuff. | |
14 | * | |
15 | * Getting the rtc defines is nontrivial. | |
16 | * The obvious way is by including <linux/mc146818rtc.h> | |
17 | * but that again includes <asm/io.h> which again includes ... | |
18 | * and on sparc and alpha this gives compilation errors for | |
19 | * many kernel versions. So, we give the defines ourselves here. | |
20 | * Moreover, some Sparc person decided to be incompatible, and | |
21 | * used a struct rtc_time different from that used in mc146818rtc.h. | |
22 | */ | |
23 | ||
24 | /* On Sparcs, there is a <asm/rtc.h> that defines different ioctls | |
25 | (that are required on my machine). However, this include file | |
26 | does not exist on other architectures. */ | |
27 | /* One might do: | |
28 | #ifdef __sparc__ | |
29 | #include <asm/rtc.h> | |
30 | #endif | |
31 | */ | |
32 | /* The following is roughly equivalent */ | |
33 | struct sparc_rtc_time | |
34 | { | |
35 | int sec; /* Seconds (0-59) */ | |
36 | int min; /* Minutes (0-59) */ | |
37 | int hour; /* Hour (0-23) */ | |
38 | int dow; /* Day of the week (1-7) */ | |
39 | int dom; /* Day of the month (1-31) */ | |
40 | int month; /* Month of year (1-12) */ | |
41 | int year; /* Year (0-99) */ | |
42 | }; | |
43 | ||
44 | #define RTCGET _IOR('p', 20, struct sparc_rtc_time) | |
45 | #define RTCSET _IOW('p', 21, struct sparc_rtc_time) | |
46 | ||
47 | ||
48 | /* non-sparc stuff */ | |
49 | #if 0 | |
50 | #include <linux/version.h> | |
51 | /* Check if the /dev/rtc interface is available in this version of | |
52 | the system headers. 131072 is linux 2.0.0. */ | |
53 | #if LINUX_VERSION_CODE >= 131072 | |
54 | #include <linux/mc146818rtc.h> | |
55 | #endif | |
56 | #endif | |
57 | ||
58 | /* struct rtc_time is present since 1.3.99 */ | |
59 | /* Earlier (since 1.3.89), a struct tm was used. */ | |
60 | struct linux_rtc_time { | |
61 | int tm_sec; | |
62 | int tm_min; | |
63 | int tm_hour; | |
64 | int tm_mday; | |
65 | int tm_mon; | |
66 | int tm_year; | |
67 | int tm_wday; | |
68 | int tm_yday; | |
69 | int tm_isdst; | |
70 | }; | |
71 | ||
72 | /* RTC_RD_TIME etc have this definition since 1.99.9 (pre2.0-9) */ | |
73 | #ifndef RTC_RD_TIME | |
74 | #define RTC_RD_TIME _IOR('p', 0x09, struct linux_rtc_time) | |
75 | #define RTC_SET_TIME _IOW('p', 0x0a, struct linux_rtc_time) | |
76 | #define RTC_UIE_ON _IO('p', 0x03) /* Update int. enable on */ | |
77 | #define RTC_UIE_OFF _IO('p', 0x04) /* Update int. enable off */ | |
78 | #endif | |
79 | /* RTC_EPOCH_READ and RTC_EPOCH_SET are present since 2.0.34 and 2.1.89 */ | |
80 | #ifndef RTC_EPOCH_READ | |
81 | #define RTC_EPOCH_READ _IOR('p', 0x0d, unsigned long) /* Read epoch */ | |
82 | #define RTC_EPOCH_SET _IOW('p', 0x0e, unsigned long) /* Set epoch */ | |
83 | #endif | |
84 | ||
88681c5f KZ |
85 | /* /dev/rtc is conventionally chardev 10/135 |
86 | * ia64 uses /dev/efirtc, chardev 10/136 | |
87 | * devfs (obsolete) used /dev/misc/... for miscdev | |
88 | * new RTC framework + udev uses dynamic major and /dev/rtc0.../dev/rtcN | |
89 | * ... so we need an overridable default | |
90 | */ | |
7eda085c | 91 | |
88681c5f KZ |
92 | /* default or user defined dev (by hwclock --rtc=<path>) */ |
93 | char *rtc_dev_name; | |
364cda48 | 94 | |
27f9db17 KZ |
95 | static int rtc_dev_fd = -1; |
96 | ||
97 | static void | |
98 | close_rtc(void) { | |
99 | if (rtc_dev_fd != -1) | |
100 | close(rtc_dev_fd); | |
101 | rtc_dev_fd = -1; | |
102 | } | |
103 | ||
364cda48 KZ |
104 | static int |
105 | open_rtc(void) { | |
88681c5f KZ |
106 | char *fls[] = { |
107 | #ifdef __ia64__ | |
108 | "/dev/efirtc", | |
109 | "/dev/misc/efirtc", | |
110 | #endif | |
111 | "/dev/rtc", | |
112 | "/dev/rtc0", | |
113 | "/dev/misc/rtc", | |
114 | NULL | |
115 | }; | |
5d1f6bae MK |
116 | char **p; |
117 | ||
27f9db17 KZ |
118 | if (rtc_dev_fd != -1) |
119 | return rtc_dev_fd; | |
120 | ||
5d1f6bae MK |
121 | /* --rtc option has been given */ |
122 | if (rtc_dev_name) | |
27f9db17 KZ |
123 | rtc_dev_fd = open(rtc_dev_name, O_RDONLY); |
124 | else { | |
125 | for (p=fls; *p; ++p) { | |
126 | rtc_dev_fd = open(*p, O_RDONLY); | |
127 | ||
128 | if (rtc_dev_fd < 0 && (errno == ENOENT || errno == ENODEV)) | |
129 | continue; | |
130 | rtc_dev_name = *p; | |
131 | break; | |
132 | } | |
133 | if (rtc_dev_fd < 0) | |
134 | rtc_dev_name = *fls; /* default for error messages */ | |
5d1f6bae | 135 | } |
27f9db17 KZ |
136 | |
137 | if (rtc_dev_fd != 1) | |
138 | atexit(close_rtc); | |
139 | return rtc_dev_fd; | |
63cccae4 KZ |
140 | } |
141 | ||
142 | static int | |
143 | open_rtc_or_exit(void) { | |
144 | int rtc_fd = open_rtc(); | |
145 | ||
146 | if (rtc_fd < 0) { | |
147 | outsyserr(_("open() of %s failed"), rtc_dev_name); | |
88058a71 | 148 | hwclock_exit(EX_OSFILE); |
364cda48 KZ |
149 | } |
150 | return rtc_fd; | |
151 | } | |
152 | ||
7eda085c KZ |
153 | static int |
154 | do_rtc_read_ioctl(int rtc_fd, struct tm *tm) { | |
364cda48 KZ |
155 | int rc = -1; |
156 | char *ioctlname; | |
7eda085c | 157 | |
7eda085c | 158 | #ifdef __sparc__ |
364cda48 KZ |
159 | /* some but not all sparcs use a different ioctl and struct */ |
160 | struct sparc_rtc_time stm; | |
161 | ||
162 | ioctlname = "RTCGET"; | |
163 | rc = ioctl(rtc_fd, RTCGET, &stm); | |
164 | if (rc == 0) { | |
165 | tm->tm_sec = stm.sec; | |
166 | tm->tm_min = stm.min; | |
167 | tm->tm_hour = stm.hour; | |
168 | tm->tm_mday = stm.dom; | |
169 | tm->tm_mon = stm.month - 1; | |
170 | tm->tm_year = stm.year - 1900; | |
171 | tm->tm_wday = stm.dow - 1; | |
172 | tm->tm_yday = -1; /* day in the year */ | |
173 | } | |
7eda085c | 174 | #endif |
364cda48 KZ |
175 | if (rc == -1) { /* no sparc, or RTCGET failed */ |
176 | ioctlname = "RTC_RD_TIME"; | |
177 | rc = ioctl(rtc_fd, RTC_RD_TIME, tm); | |
178 | } | |
179 | if (rc == -1) { | |
180 | perror(ioctlname); | |
181 | fprintf(stderr, _("ioctl() to %s to read the time failed.\n"), | |
182 | rtc_dev_name); | |
cdedde03 | 183 | return -1; |
364cda48 KZ |
184 | } |
185 | ||
186 | tm->tm_isdst = -1; /* don't know whether it's dst */ | |
187 | return 0; | |
188 | } | |
7eda085c KZ |
189 | |
190 | static int | |
191 | busywait_for_rtc_clock_tick(const int rtc_fd) { | |
192 | /*---------------------------------------------------------------------------- | |
193 | Wait for the top of a clock tick by reading /dev/rtc in a busy loop until | |
9abb2685 | 194 | we see it. |
7eda085c KZ |
195 | -----------------------------------------------------------------------------*/ |
196 | struct tm start_time; | |
197 | /* The time when we were called (and started waiting) */ | |
198 | struct tm nowtime; | |
7eda085c | 199 | int rc; |
982a4a5d | 200 | struct timeval begin, now; |
7eda085c KZ |
201 | |
202 | if (debug) | |
364cda48 KZ |
203 | printf(_("Waiting in loop for time from %s to change\n"), |
204 | rtc_dev_name); | |
7eda085c KZ |
205 | |
206 | rc = do_rtc_read_ioctl(rtc_fd, &start_time); | |
207 | if (rc) | |
208 | return 1; | |
209 | ||
210 | /* Wait for change. Should be within a second, but in case something | |
982a4a5d KZ |
211 | * weird happens, we have a time limit (1.5s) on this loop to reduce the |
212 | * impact of this failure. | |
213 | */ | |
214 | gettimeofday(&begin, NULL); | |
215 | do { | |
216 | rc = do_rtc_read_ioctl(rtc_fd, &nowtime); | |
217 | if (rc || start_time.tm_sec != nowtime.tm_sec) | |
218 | break; | |
219 | gettimeofday(&now, NULL); | |
220 | if (time_diff(now, begin) > 1.5) { | |
7eda085c KZ |
221 | fprintf(stderr, _("Timed out waiting for time change.\n")); |
222 | return 2; | |
223 | } | |
982a4a5d | 224 | } while(1); |
7eda085c KZ |
225 | |
226 | if (rc) | |
227 | return 3; | |
228 | return 0; | |
229 | } | |
230 | ||
7eda085c KZ |
231 | static int |
232 | synchronize_to_clock_tick_rtc(void) { | |
233 | /*---------------------------------------------------------------------------- | |
234 | Same as synchronize_to_clock_tick(), but just for /dev/rtc. | |
235 | -----------------------------------------------------------------------------*/ | |
236 | int rtc_fd; /* File descriptor of /dev/rtc */ | |
237 | int ret; | |
238 | ||
27f9db17 | 239 | rtc_fd = open_rtc(); |
7eda085c | 240 | if (rtc_fd == -1) { |
364cda48 | 241 | outsyserr(_("open() of %s failed"), rtc_dev_name); |
7eda085c KZ |
242 | ret = 1; |
243 | } else { | |
244 | int rc; /* Return code from ioctl */ | |
245 | /* Turn on update interrupts (one per second) */ | |
442d61c6 | 246 | #if defined(__alpha__) || defined(__sparc__) |
7eda085c KZ |
247 | /* Not all alpha kernels reject RTC_UIE_ON, but probably they should. */ |
248 | rc = -1; | |
249 | errno = EINVAL; | |
250 | #else | |
251 | rc = ioctl(rtc_fd, RTC_UIE_ON, 0); | |
252 | #endif | |
63cccae4 | 253 | if (rc == -1 && (errno == ENOTTY || errno == EINVAL)) { |
7eda085c KZ |
254 | /* This rtc device doesn't have interrupt functions. This is typical |
255 | on an Alpha, where the Hardware Clock interrupts are used by the | |
256 | kernel for the system clock, so aren't at the user's disposal. | |
257 | */ | |
66ee8158 | 258 | if (debug) |
364cda48 KZ |
259 | printf(_("%s does not have interrupt functions. "), |
260 | rtc_dev_name); | |
7eda085c | 261 | ret = busywait_for_rtc_clock_tick(rtc_fd); |
66ee8158 | 262 | } else if (rc == 0) { |
d03dd608 | 263 | #ifdef Wait_until_update_interrupt |
7eda085c KZ |
264 | unsigned long dummy; |
265 | ||
266 | /* this blocks until the next update interrupt */ | |
267 | rc = read(rtc_fd, &dummy, sizeof(dummy)); | |
d03dd608 KZ |
268 | ret = 1; |
269 | if (rc == -1) | |
364cda48 KZ |
270 | outsyserr(_("read() to %s to wait for clock tick failed"), |
271 | rtc_dev_name); | |
d03dd608 | 272 | else |
7eda085c | 273 | ret = 0; |
d03dd608 KZ |
274 | #else |
275 | /* Just reading rtc_fd fails on broken hardware: no update | |
276 | interrupt comes and a bootscript with a hwclock call hangs */ | |
277 | fd_set rfds; | |
278 | struct timeval tv; | |
279 | ||
280 | /* Wait up to five seconds for the next update interrupt */ | |
281 | FD_ZERO(&rfds); | |
282 | FD_SET(rtc_fd, &rfds); | |
283 | tv.tv_sec = 5; | |
284 | tv.tv_usec = 0; | |
285 | rc = select(rtc_fd + 1, &rfds, NULL, NULL, &tv); | |
286 | ret = 1; | |
287 | if (rc == -1) | |
288 | outsyserr(_("select() to %s to wait for clock tick failed"), | |
289 | rtc_dev_name); | |
290 | else if (rc == 0) | |
291 | fprintf(stderr, _("select() to %s to wait for clock tick timed out\n"), | |
292 | rtc_dev_name); | |
293 | else | |
294 | ret = 0; | |
295 | #endif | |
296 | ||
7eda085c KZ |
297 | /* Turn off update interrupts */ |
298 | rc = ioctl(rtc_fd, RTC_UIE_OFF, 0); | |
299 | if (rc == -1) | |
364cda48 KZ |
300 | outsyserr(_("ioctl() to %s to turn off update interrupts failed"), |
301 | rtc_dev_name); | |
7eda085c | 302 | } else { |
66ee8158 | 303 | outsyserr(_("ioctl() to %s to turn on update interrupts " |
364cda48 | 304 | "failed unexpectedly"), rtc_dev_name); |
7eda085c KZ |
305 | ret = 1; |
306 | } | |
7eda085c KZ |
307 | } |
308 | return ret; | |
309 | } | |
310 | ||
311 | ||
312 | static int | |
313 | read_hardware_clock_rtc(struct tm *tm) { | |
55a4a75c | 314 | int rtc_fd, rc; |
7eda085c | 315 | |
63cccae4 | 316 | rtc_fd = open_rtc_or_exit(); |
7eda085c | 317 | |
364cda48 | 318 | /* Read the RTC time/date, return answer via tm */ |
55a4a75c | 319 | rc = do_rtc_read_ioctl(rtc_fd, tm); |
7eda085c | 320 | |
55a4a75c | 321 | return rc; |
7eda085c KZ |
322 | } |
323 | ||
324 | ||
325 | static int | |
326 | set_hardware_clock_rtc(const struct tm *new_broken_time) { | |
364cda48 | 327 | /*------------------------------------------------------------------------- |
7eda085c KZ |
328 | Set the Hardware Clock to the broken down time <new_broken_time>. |
329 | Use ioctls to "rtc" device /dev/rtc. | |
364cda48 KZ |
330 | -------------------------------------------------------------------------*/ |
331 | int rc = -1; | |
332 | int rtc_fd; | |
333 | char *ioctlname; | |
334 | ||
63cccae4 KZ |
335 | rtc_fd = open_rtc_or_exit(); |
336 | ||
7eda085c | 337 | #ifdef __sparc__ |
364cda48 KZ |
338 | { |
339 | struct sparc_rtc_time stm; | |
340 | ||
341 | stm.sec = new_broken_time->tm_sec; | |
342 | stm.min = new_broken_time->tm_min; | |
343 | stm.hour = new_broken_time->tm_hour; | |
344 | stm.dom = new_broken_time->tm_mday; | |
345 | stm.month = new_broken_time->tm_mon + 1; | |
346 | stm.year = new_broken_time->tm_year + 1900; | |
347 | stm.dow = new_broken_time->tm_wday + 1; | |
348 | ||
349 | ioctlname = "RTCSET"; | |
350 | rc = ioctl(rtc_fd, RTCSET, &stm); | |
351 | } | |
7eda085c | 352 | #endif |
364cda48 KZ |
353 | if (rc == -1) { /* no sparc, or RTCSET failed */ |
354 | ioctlname = "RTC_SET_TIME"; | |
355 | rc = ioctl(rtc_fd, RTC_SET_TIME, new_broken_time); | |
356 | } | |
357 | ||
358 | if (rc == -1) { | |
359 | perror(ioctlname); | |
360 | fprintf(stderr, _("ioctl() to %s to set the time failed.\n"), | |
361 | rtc_dev_name); | |
88058a71 | 362 | hwclock_exit(EX_IOERR); |
364cda48 KZ |
363 | } |
364 | ||
365 | if (debug) | |
366 | printf(_("ioctl(%s) was successful.\n"), ioctlname); | |
367 | ||
364cda48 | 368 | return 0; |
7eda085c KZ |
369 | } |
370 | ||
371 | ||
372 | static int | |
373 | get_permissions_rtc(void) { | |
374 | return 0; | |
375 | } | |
376 | ||
377 | static struct clock_ops rtc = { | |
88681c5f | 378 | "/dev interface to clock", |
7eda085c KZ |
379 | get_permissions_rtc, |
380 | read_hardware_clock_rtc, | |
381 | set_hardware_clock_rtc, | |
382 | synchronize_to_clock_tick_rtc, | |
383 | }; | |
384 | ||
385 | /* return &rtc if /dev/rtc can be opened, NULL otherwise */ | |
386 | struct clock_ops * | |
387 | probe_for_rtc_clock(){ | |
364cda48 | 388 | int rtc_fd = open_rtc(); |
27f9db17 | 389 | if (rtc_fd >= 0) |
364cda48 | 390 | return &rtc; |
364cda48 KZ |
391 | if (debug) |
392 | outsyserr(_("Open of %s failed"), rtc_dev_name); | |
393 | return NULL; | |
7eda085c KZ |
394 | } |
395 | ||
396 | ||
397 | ||
398 | int | |
399 | get_epoch_rtc(unsigned long *epoch_p, int silent) { | |
400 | /*---------------------------------------------------------------------------- | |
401 | Get the Hardware Clock epoch setting from the kernel. | |
402 | ----------------------------------------------------------------------------*/ | |
403 | int rtc_fd; | |
404 | ||
364cda48 | 405 | rtc_fd = open_rtc(); |
7eda085c KZ |
406 | if (rtc_fd < 0) { |
407 | if (!silent) { | |
9abb2685 | 408 | if (errno == ENOENT) |
7eda085c KZ |
409 | fprintf(stderr, _( |
410 | "To manipulate the epoch value in the kernel, we must " | |
411 | "access the Linux 'rtc' device driver via the device special " | |
364cda48 KZ |
412 | "file %s. This file does not exist on this system.\n"), |
413 | rtc_dev_name); | |
9abb2685 | 414 | else |
364cda48 | 415 | outsyserr(_("Unable to open %s"), rtc_dev_name); |
7eda085c KZ |
416 | } |
417 | return 1; | |
418 | } | |
419 | ||
420 | if (ioctl(rtc_fd, RTC_EPOCH_READ, epoch_p) == -1) { | |
421 | if (!silent) | |
364cda48 | 422 | outsyserr(_("ioctl(RTC_EPOCH_READ) to %s failed"), rtc_dev_name); |
7eda085c KZ |
423 | return 1; |
424 | } | |
425 | ||
426 | if (debug) | |
66ee8158 | 427 | printf(_("we have read epoch %ld from %s " |
364cda48 | 428 | "with RTC_EPOCH_READ ioctl.\n"), *epoch_p, rtc_dev_name); |
7eda085c | 429 | |
7eda085c KZ |
430 | return 0; |
431 | } | |
432 | ||
433 | ||
434 | ||
435 | int | |
436 | set_epoch_rtc(unsigned long epoch) { | |
437 | /*---------------------------------------------------------------------------- | |
438 | Set the Hardware Clock epoch in the kernel. | |
439 | ----------------------------------------------------------------------------*/ | |
440 | int rtc_fd; | |
441 | ||
442 | if (epoch < 1900) { | |
443 | /* kernel would not accept this epoch value */ | |
444 | /* Hmm - bad habit, deciding not to do what the user asks | |
445 | just because one believes that the kernel might not like it. */ | |
446 | fprintf(stderr, _("The epoch value may not be less than 1900. " | |
447 | "You requested %ld\n"), epoch); | |
448 | return 1; | |
449 | } | |
450 | ||
364cda48 | 451 | rtc_fd = open_rtc(); |
7eda085c | 452 | if (rtc_fd < 0) { |
9abb2685 | 453 | if (errno == ENOENT) |
7eda085c KZ |
454 | fprintf(stderr, _("To manipulate the epoch value in the kernel, we must " |
455 | "access the Linux 'rtc' device driver via the device special " | |
364cda48 KZ |
456 | "file %s. This file does not exist on this system.\n"), |
457 | rtc_dev_name); | |
7eda085c | 458 | else |
364cda48 | 459 | outsyserr(_("Unable to open %s"), rtc_dev_name); |
7eda085c KZ |
460 | return 1; |
461 | } | |
462 | ||
463 | if (debug) | |
464 | printf(_("setting epoch to %ld " | |
364cda48 | 465 | "with RTC_EPOCH_SET ioctl to %s.\n"), epoch, rtc_dev_name); |
7eda085c KZ |
466 | |
467 | if (ioctl(rtc_fd, RTC_EPOCH_SET, epoch) == -1) { | |
468 | if (errno == EINVAL) | |
66ee8158 | 469 | fprintf(stderr, _("The kernel device driver for %s " |
364cda48 | 470 | "does not have the RTC_EPOCH_SET ioctl.\n"), rtc_dev_name); |
9abb2685 | 471 | else |
364cda48 | 472 | outsyserr(_("ioctl(RTC_EPOCH_SET) to %s failed"), rtc_dev_name); |
7eda085c KZ |
473 | return 1; |
474 | } | |
475 | ||
7eda085c KZ |
476 | return 0; |
477 | } |