1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Real Time Clock Driver Test Program |
4 | * |
5 | * Copyright (c) 2018 Alexandre Belloni <alexandre.belloni@bootlin.com> |
6 | */ |
7 | |
8 | #include <errno.h> |
9 | #include <fcntl.h> |
10 | #include <linux/rtc.h> |
11 | #include <stdio.h> |
12 | #include <stdlib.h> |
13 | #include <sys/ioctl.h> |
14 | #include <sys/time.h> |
15 | #include <sys/types.h> |
16 | #include <time.h> |
17 | #include <unistd.h> |
18 | |
19 | #include "../kselftest_harness.h" |
20 | |
21 | #define NUM_UIE 3 |
22 | #define ALARM_DELTA 3 |
23 | #define READ_LOOP_DURATION_SEC 30 |
24 | #define READ_LOOP_SLEEP_MS 11 |
25 | |
26 | static char *rtc_file = "/dev/rtc0" ; |
27 | |
28 | FIXTURE(rtc) { |
29 | int fd; |
30 | }; |
31 | |
32 | FIXTURE_SETUP(rtc) { |
33 | self->fd = open(rtc_file, O_RDONLY); |
34 | } |
35 | |
36 | FIXTURE_TEARDOWN(rtc) { |
37 | close(self->fd); |
38 | } |
39 | |
40 | TEST_F(rtc, date_read) { |
41 | int rc; |
42 | struct rtc_time rtc_tm; |
43 | |
44 | if (self->fd == -1 && errno == ENOENT) |
45 | SKIP(return, "Skipping test since %s does not exist" , rtc_file); |
46 | ASSERT_NE(-1, self->fd); |
47 | |
48 | /* Read the RTC time/date */ |
49 | rc = ioctl(self->fd, RTC_RD_TIME, &rtc_tm); |
50 | ASSERT_NE(-1, rc); |
51 | |
52 | TH_LOG("Current RTC date/time is %02d/%02d/%02d %02d:%02d:%02d." , |
53 | rtc_tm.tm_mday, rtc_tm.tm_mon + 1, rtc_tm.tm_year + 1900, |
54 | rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec); |
55 | } |
56 | |
57 | static time_t rtc_time_to_timestamp(struct rtc_time *rtc_time) |
58 | { |
59 | struct tm tm_time = { |
60 | .tm_sec = rtc_time->tm_sec, |
61 | .tm_min = rtc_time->tm_min, |
62 | .tm_hour = rtc_time->tm_hour, |
63 | .tm_mday = rtc_time->tm_mday, |
64 | .tm_mon = rtc_time->tm_mon, |
65 | .tm_year = rtc_time->tm_year, |
66 | }; |
67 | |
68 | return mktime(&tm_time); |
69 | } |
70 | |
71 | static void nanosleep_with_retries(long ns) |
72 | { |
73 | struct timespec req = { |
74 | .tv_sec = 0, |
75 | .tv_nsec = ns, |
76 | }; |
77 | struct timespec rem; |
78 | |
79 | while (nanosleep(&req, &rem) != 0) { |
80 | req.tv_sec = rem.tv_sec; |
81 | req.tv_nsec = rem.tv_nsec; |
82 | } |
83 | } |
84 | |
85 | TEST_F_TIMEOUT(rtc, date_read_loop, READ_LOOP_DURATION_SEC + 2) { |
86 | int rc; |
87 | long iter_count = 0; |
88 | struct rtc_time rtc_tm; |
89 | time_t start_rtc_read, prev_rtc_read; |
90 | |
91 | if (self->fd == -1 && errno == ENOENT) |
92 | SKIP(return, "Skipping test since %s does not exist" , rtc_file); |
93 | ASSERT_NE(-1, self->fd); |
94 | |
95 | TH_LOG("Continuously reading RTC time for %ds (with %dms breaks after every read)." , |
96 | READ_LOOP_DURATION_SEC, READ_LOOP_SLEEP_MS); |
97 | |
98 | rc = ioctl(self->fd, RTC_RD_TIME, &rtc_tm); |
99 | ASSERT_NE(-1, rc); |
100 | start_rtc_read = rtc_time_to_timestamp(&rtc_tm); |
101 | prev_rtc_read = start_rtc_read; |
102 | |
103 | do { |
104 | time_t rtc_read; |
105 | |
106 | rc = ioctl(self->fd, RTC_RD_TIME, &rtc_tm); |
107 | ASSERT_NE(-1, rc); |
108 | |
109 | rtc_read = rtc_time_to_timestamp(&rtc_tm); |
110 | /* Time should not go backwards */ |
111 | ASSERT_LE(prev_rtc_read, rtc_read); |
112 | /* Time should not increase more then 1s at a time */ |
113 | ASSERT_GE(prev_rtc_read + 1, rtc_read); |
114 | |
115 | /* Sleep 11ms to avoid killing / overheating the RTC */ |
116 | nanosleep_with_retries(READ_LOOP_SLEEP_MS * 1000000); |
117 | |
118 | prev_rtc_read = rtc_read; |
119 | iter_count++; |
120 | } while (prev_rtc_read <= start_rtc_read + READ_LOOP_DURATION_SEC); |
121 | |
122 | TH_LOG("Performed %ld RTC time reads." , iter_count); |
123 | } |
124 | |
125 | TEST_F_TIMEOUT(rtc, uie_read, NUM_UIE + 2) { |
126 | int i, rc, irq = 0; |
127 | unsigned long data; |
128 | |
129 | if (self->fd == -1 && errno == ENOENT) |
130 | SKIP(return, "Skipping test since %s does not exist" , rtc_file); |
131 | ASSERT_NE(-1, self->fd); |
132 | |
133 | /* Turn on update interrupts */ |
134 | rc = ioctl(self->fd, RTC_UIE_ON, 0); |
135 | if (rc == -1) { |
136 | ASSERT_EQ(EINVAL, errno); |
137 | TH_LOG("skip update IRQs not supported." ); |
138 | return; |
139 | } |
140 | |
141 | for (i = 0; i < NUM_UIE; i++) { |
142 | /* This read will block */ |
143 | rc = read(self->fd, &data, sizeof(data)); |
144 | ASSERT_NE(-1, rc); |
145 | irq++; |
146 | } |
147 | |
148 | EXPECT_EQ(NUM_UIE, irq); |
149 | |
150 | rc = ioctl(self->fd, RTC_UIE_OFF, 0); |
151 | ASSERT_NE(-1, rc); |
152 | } |
153 | |
154 | TEST_F(rtc, uie_select) { |
155 | int i, rc, irq = 0; |
156 | unsigned long data; |
157 | |
158 | if (self->fd == -1 && errno == ENOENT) |
159 | SKIP(return, "Skipping test since %s does not exist" , rtc_file); |
160 | ASSERT_NE(-1, self->fd); |
161 | |
162 | /* Turn on update interrupts */ |
163 | rc = ioctl(self->fd, RTC_UIE_ON, 0); |
164 | if (rc == -1) { |
165 | ASSERT_EQ(EINVAL, errno); |
166 | TH_LOG("skip update IRQs not supported." ); |
167 | return; |
168 | } |
169 | |
170 | for (i = 0; i < NUM_UIE; i++) { |
171 | struct timeval tv = { .tv_sec = 2 }; |
172 | fd_set readfds; |
173 | |
174 | FD_ZERO(&readfds); |
175 | FD_SET(self->fd, &readfds); |
176 | /* The select will wait until an RTC interrupt happens. */ |
177 | rc = select(self->fd + 1, &readfds, NULL, NULL, &tv); |
178 | ASSERT_NE(-1, rc); |
179 | ASSERT_NE(0, rc); |
180 | |
181 | /* This read won't block */ |
182 | rc = read(self->fd, &data, sizeof(unsigned long)); |
183 | ASSERT_NE(-1, rc); |
184 | irq++; |
185 | } |
186 | |
187 | EXPECT_EQ(NUM_UIE, irq); |
188 | |
189 | rc = ioctl(self->fd, RTC_UIE_OFF, 0); |
190 | ASSERT_NE(-1, rc); |
191 | } |
192 | |
193 | TEST_F(rtc, alarm_alm_set) { |
194 | struct timeval tv = { .tv_sec = ALARM_DELTA + 2 }; |
195 | unsigned long data; |
196 | struct rtc_time tm; |
197 | fd_set readfds; |
198 | time_t secs, new; |
199 | int rc; |
200 | |
201 | if (self->fd == -1 && errno == ENOENT) |
202 | SKIP(return, "Skipping test since %s does not exist" , rtc_file); |
203 | ASSERT_NE(-1, self->fd); |
204 | |
205 | rc = ioctl(self->fd, RTC_RD_TIME, &tm); |
206 | ASSERT_NE(-1, rc); |
207 | |
208 | secs = timegm((struct tm *)&tm) + ALARM_DELTA; |
209 | gmtime_r(&secs, (struct tm *)&tm); |
210 | |
211 | rc = ioctl(self->fd, RTC_ALM_SET, &tm); |
212 | if (rc == -1) { |
213 | ASSERT_EQ(EINVAL, errno); |
214 | TH_LOG("skip alarms are not supported." ); |
215 | return; |
216 | } |
217 | |
218 | rc = ioctl(self->fd, RTC_ALM_READ, &tm); |
219 | ASSERT_NE(-1, rc); |
220 | |
221 | TH_LOG("Alarm time now set to %02d:%02d:%02d." , |
222 | tm.tm_hour, tm.tm_min, tm.tm_sec); |
223 | |
224 | /* Enable alarm interrupts */ |
225 | rc = ioctl(self->fd, RTC_AIE_ON, 0); |
226 | ASSERT_NE(-1, rc); |
227 | |
228 | FD_ZERO(&readfds); |
229 | FD_SET(self->fd, &readfds); |
230 | |
231 | rc = select(self->fd + 1, &readfds, NULL, NULL, &tv); |
232 | ASSERT_NE(-1, rc); |
233 | ASSERT_NE(0, rc); |
234 | |
235 | /* Disable alarm interrupts */ |
236 | rc = ioctl(self->fd, RTC_AIE_OFF, 0); |
237 | ASSERT_NE(-1, rc); |
238 | |
239 | rc = read(self->fd, &data, sizeof(unsigned long)); |
240 | ASSERT_NE(-1, rc); |
241 | TH_LOG("data: %lx" , data); |
242 | |
243 | rc = ioctl(self->fd, RTC_RD_TIME, &tm); |
244 | ASSERT_NE(-1, rc); |
245 | |
246 | new = timegm((struct tm *)&tm); |
247 | ASSERT_EQ(new, secs); |
248 | } |
249 | |
250 | TEST_F(rtc, alarm_wkalm_set) { |
251 | struct timeval tv = { .tv_sec = ALARM_DELTA + 2 }; |
252 | struct rtc_wkalrm alarm = { 0 }; |
253 | struct rtc_time tm; |
254 | unsigned long data; |
255 | fd_set readfds; |
256 | time_t secs, new; |
257 | int rc; |
258 | |
259 | if (self->fd == -1 && errno == ENOENT) |
260 | SKIP(return, "Skipping test since %s does not exist" , rtc_file); |
261 | ASSERT_NE(-1, self->fd); |
262 | |
263 | rc = ioctl(self->fd, RTC_RD_TIME, &alarm.time); |
264 | ASSERT_NE(-1, rc); |
265 | |
266 | secs = timegm((struct tm *)&alarm.time) + ALARM_DELTA; |
267 | gmtime_r(&secs, (struct tm *)&alarm.time); |
268 | |
269 | alarm.enabled = 1; |
270 | |
271 | rc = ioctl(self->fd, RTC_WKALM_SET, &alarm); |
272 | if (rc == -1) { |
273 | ASSERT_EQ(EINVAL, errno); |
274 | TH_LOG("skip alarms are not supported." ); |
275 | return; |
276 | } |
277 | |
278 | rc = ioctl(self->fd, RTC_WKALM_RD, &alarm); |
279 | ASSERT_NE(-1, rc); |
280 | |
281 | TH_LOG("Alarm time now set to %02d/%02d/%02d %02d:%02d:%02d." , |
282 | alarm.time.tm_mday, alarm.time.tm_mon + 1, |
283 | alarm.time.tm_year + 1900, alarm.time.tm_hour, |
284 | alarm.time.tm_min, alarm.time.tm_sec); |
285 | |
286 | FD_ZERO(&readfds); |
287 | FD_SET(self->fd, &readfds); |
288 | |
289 | rc = select(self->fd + 1, &readfds, NULL, NULL, &tv); |
290 | ASSERT_NE(-1, rc); |
291 | ASSERT_NE(0, rc); |
292 | |
293 | rc = read(self->fd, &data, sizeof(unsigned long)); |
294 | ASSERT_NE(-1, rc); |
295 | |
296 | rc = ioctl(self->fd, RTC_RD_TIME, &tm); |
297 | ASSERT_NE(-1, rc); |
298 | |
299 | new = timegm((struct tm *)&tm); |
300 | ASSERT_EQ(new, secs); |
301 | } |
302 | |
303 | TEST_F_TIMEOUT(rtc, alarm_alm_set_minute, 65) { |
304 | struct timeval tv = { .tv_sec = 62 }; |
305 | unsigned long data; |
306 | struct rtc_time tm; |
307 | fd_set readfds; |
308 | time_t secs, new; |
309 | int rc; |
310 | |
311 | if (self->fd == -1 && errno == ENOENT) |
312 | SKIP(return, "Skipping test since %s does not exist" , rtc_file); |
313 | ASSERT_NE(-1, self->fd); |
314 | |
315 | rc = ioctl(self->fd, RTC_RD_TIME, &tm); |
316 | ASSERT_NE(-1, rc); |
317 | |
318 | secs = timegm((struct tm *)&tm) + 60 - tm.tm_sec; |
319 | gmtime_r(&secs, (struct tm *)&tm); |
320 | |
321 | rc = ioctl(self->fd, RTC_ALM_SET, &tm); |
322 | if (rc == -1) { |
323 | ASSERT_EQ(EINVAL, errno); |
324 | TH_LOG("skip alarms are not supported." ); |
325 | return; |
326 | } |
327 | |
328 | rc = ioctl(self->fd, RTC_ALM_READ, &tm); |
329 | ASSERT_NE(-1, rc); |
330 | |
331 | TH_LOG("Alarm time now set to %02d:%02d:%02d." , |
332 | tm.tm_hour, tm.tm_min, tm.tm_sec); |
333 | |
334 | /* Enable alarm interrupts */ |
335 | rc = ioctl(self->fd, RTC_AIE_ON, 0); |
336 | ASSERT_NE(-1, rc); |
337 | |
338 | FD_ZERO(&readfds); |
339 | FD_SET(self->fd, &readfds); |
340 | |
341 | rc = select(self->fd + 1, &readfds, NULL, NULL, &tv); |
342 | ASSERT_NE(-1, rc); |
343 | ASSERT_NE(0, rc); |
344 | |
345 | /* Disable alarm interrupts */ |
346 | rc = ioctl(self->fd, RTC_AIE_OFF, 0); |
347 | ASSERT_NE(-1, rc); |
348 | |
349 | rc = read(self->fd, &data, sizeof(unsigned long)); |
350 | ASSERT_NE(-1, rc); |
351 | TH_LOG("data: %lx" , data); |
352 | |
353 | rc = ioctl(self->fd, RTC_RD_TIME, &tm); |
354 | ASSERT_NE(-1, rc); |
355 | |
356 | new = timegm((struct tm *)&tm); |
357 | ASSERT_EQ(new, secs); |
358 | } |
359 | |
360 | TEST_F_TIMEOUT(rtc, alarm_wkalm_set_minute, 65) { |
361 | struct timeval tv = { .tv_sec = 62 }; |
362 | struct rtc_wkalrm alarm = { 0 }; |
363 | struct rtc_time tm; |
364 | unsigned long data; |
365 | fd_set readfds; |
366 | time_t secs, new; |
367 | int rc; |
368 | |
369 | if (self->fd == -1 && errno == ENOENT) |
370 | SKIP(return, "Skipping test since %s does not exist" , rtc_file); |
371 | ASSERT_NE(-1, self->fd); |
372 | |
373 | rc = ioctl(self->fd, RTC_RD_TIME, &alarm.time); |
374 | ASSERT_NE(-1, rc); |
375 | |
376 | secs = timegm((struct tm *)&alarm.time) + 60 - alarm.time.tm_sec; |
377 | gmtime_r(&secs, (struct tm *)&alarm.time); |
378 | |
379 | alarm.enabled = 1; |
380 | |
381 | rc = ioctl(self->fd, RTC_WKALM_SET, &alarm); |
382 | if (rc == -1) { |
383 | ASSERT_EQ(EINVAL, errno); |
384 | TH_LOG("skip alarms are not supported." ); |
385 | return; |
386 | } |
387 | |
388 | rc = ioctl(self->fd, RTC_WKALM_RD, &alarm); |
389 | ASSERT_NE(-1, rc); |
390 | |
391 | TH_LOG("Alarm time now set to %02d/%02d/%02d %02d:%02d:%02d." , |
392 | alarm.time.tm_mday, alarm.time.tm_mon + 1, |
393 | alarm.time.tm_year + 1900, alarm.time.tm_hour, |
394 | alarm.time.tm_min, alarm.time.tm_sec); |
395 | |
396 | FD_ZERO(&readfds); |
397 | FD_SET(self->fd, &readfds); |
398 | |
399 | rc = select(self->fd + 1, &readfds, NULL, NULL, &tv); |
400 | ASSERT_NE(-1, rc); |
401 | ASSERT_NE(0, rc); |
402 | |
403 | rc = read(self->fd, &data, sizeof(unsigned long)); |
404 | ASSERT_NE(-1, rc); |
405 | |
406 | rc = ioctl(self->fd, RTC_RD_TIME, &tm); |
407 | ASSERT_NE(-1, rc); |
408 | |
409 | new = timegm((struct tm *)&tm); |
410 | ASSERT_EQ(new, secs); |
411 | } |
412 | |
413 | static void __attribute__((constructor)) |
414 | __constructor_order_last(void) |
415 | { |
416 | if (!__constructor_order) |
417 | __constructor_order = _CONSTRUCTOR_ORDER_BACKWARD; |
418 | } |
419 | |
420 | int main(int argc, char **argv) |
421 | { |
422 | switch (argc) { |
423 | case 2: |
424 | rtc_file = argv[1]; |
425 | /* FALLTHROUGH */ |
426 | case 1: |
427 | break; |
428 | default: |
429 | fprintf(stderr, "usage: %s [rtcdev]\n" , argv[0]); |
430 | return 1; |
431 | } |
432 | |
433 | return test_harness_run(argc, argv); |
434 | } |
435 | |