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