437 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Real Time Clock Driver Test Program4 *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 322#define ALARM_DELTA 323#define READ_LOOP_DURATION_SEC 3024#define READ_LOOP_SLEEP_MS 1125 26static char *rtc_file = "/dev/rtc0";27 28FIXTURE(rtc) {29 int fd;30};31 32FIXTURE_SETUP(rtc) {33 self->fd = open(rtc_file, O_RDONLY);34}35 36FIXTURE_TEARDOWN(rtc) {37 close(self->fd);38}39 40TEST_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 57static 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 71static 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 85TEST_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 125TEST_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 154TEST_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 193TEST_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 250TEST_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 303TEST_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 360TEST_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 413int main(int argc, char **argv)414{415 int ret = -1;416 417 switch (argc) {418 case 2:419 rtc_file = argv[1];420 /* FALLTHROUGH */421 case 1:422 break;423 default:424 fprintf(stderr, "usage: %s [rtcdev]\n", argv[0]);425 return 1;426 }427 428 /* Run the test if rtc_file is accessible */429 if (access(rtc_file, R_OK) == 0)430 ret = test_harness_run(argc, argv);431 else432 ksft_exit_skip("[SKIP]: Cannot access rtc file %s - Exiting\n",433 rtc_file);434 435 return ret;436}437