// SPDX-License-Identifier: GPL-2.0 /* * Real Time Clock Driver Test Program * * Copyright (c) 2018 Alexandre Belloni */ #include #include #include #include #include #include #include #include #include #include #include "../kselftest_harness.h" #define NUM_UIE 3 #define ALARM_DELTA 3 #define READ_LOOP_DURATION_SEC 30 #define READ_LOOP_SLEEP_MS 11 static char *rtc_file = "/dev/rtc0"; FIXTURE(rtc) { int fd; }; FIXTURE_SETUP(rtc) { self->fd = open(rtc_file, O_RDONLY); } FIXTURE_TEARDOWN(rtc) { close(self->fd); } TEST_F(rtc, date_read) { int rc; struct rtc_time rtc_tm; if (self->fd == -1 && errno == ENOENT) SKIP(return, "Skipping test since %s does not exist", rtc_file); ASSERT_NE(-1, self->fd); /* Read the RTC time/date */ rc = ioctl(self->fd, RTC_RD_TIME, &rtc_tm); ASSERT_NE(-1, rc); TH_LOG("Current RTC date/time is %02d/%02d/%02d %02d:%02d:%02d.", rtc_tm.tm_mday, rtc_tm.tm_mon + 1, rtc_tm.tm_year + 1900, rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec); } static time_t rtc_time_to_timestamp(struct rtc_time *rtc_time) { struct tm tm_time = { .tm_sec = rtc_time->tm_sec, .tm_min = rtc_time->tm_min, .tm_hour = rtc_time->tm_hour, .tm_mday = rtc_time->tm_mday, .tm_mon = rtc_time->tm_mon, .tm_year = rtc_time->tm_year, }; return mktime(&tm_time); } static void nanosleep_with_retries(long ns) { struct timespec req = { .tv_sec = 0, .tv_nsec = ns, }; struct timespec rem; while (nanosleep(&req, &rem) != 0) { req.tv_sec = rem.tv_sec; req.tv_nsec = rem.tv_nsec; } } TEST_F_TIMEOUT(rtc, date_read_loop, READ_LOOP_DURATION_SEC + 2) { int rc; long iter_count = 0; struct rtc_time rtc_tm; time_t start_rtc_read, prev_rtc_read; if (self->fd == -1 && errno == ENOENT) SKIP(return, "Skipping test since %s does not exist", rtc_file); ASSERT_NE(-1, self->fd); TH_LOG("Continuously reading RTC time for %ds (with %dms breaks after every read).", READ_LOOP_DURATION_SEC, READ_LOOP_SLEEP_MS); rc = ioctl(self->fd, RTC_RD_TIME, &rtc_tm); ASSERT_NE(-1, rc); start_rtc_read = rtc_time_to_timestamp(&rtc_tm); prev_rtc_read = start_rtc_read; do { time_t rtc_read; rc = ioctl(self->fd, RTC_RD_TIME, &rtc_tm); ASSERT_NE(-1, rc); rtc_read = rtc_time_to_timestamp(&rtc_tm); /* Time should not go backwards */ ASSERT_LE(prev_rtc_read, rtc_read); /* Time should not increase more then 1s at a time */ ASSERT_GE(prev_rtc_read + 1, rtc_read); /* Sleep 11ms to avoid killing / overheating the RTC */ nanosleep_with_retries(READ_LOOP_SLEEP_MS * 1000000); prev_rtc_read = rtc_read; iter_count++; } while (prev_rtc_read <= start_rtc_read + READ_LOOP_DURATION_SEC); TH_LOG("Performed %ld RTC time reads.", iter_count); } TEST_F_TIMEOUT(rtc, uie_read, NUM_UIE + 2) { int i, rc, irq = 0; unsigned long data; if (self->fd == -1 && errno == ENOENT) SKIP(return, "Skipping test since %s does not exist", rtc_file); ASSERT_NE(-1, self->fd); /* Turn on update interrupts */ rc = ioctl(self->fd, RTC_UIE_ON, 0); if (rc == -1) { ASSERT_EQ(EINVAL, errno); TH_LOG("skip update IRQs not supported."); return; } for (i = 0; i < NUM_UIE; i++) { /* This read will block */ rc = read(self->fd, &data, sizeof(data)); ASSERT_NE(-1, rc); irq++; } EXPECT_EQ(NUM_UIE, irq); rc = ioctl(self->fd, RTC_UIE_OFF, 0); ASSERT_NE(-1, rc); } TEST_F(rtc, uie_select) { int i, rc, irq = 0; unsigned long data; if (self->fd == -1 && errno == ENOENT) SKIP(return, "Skipping test since %s does not exist", rtc_file); ASSERT_NE(-1, self->fd); /* Turn on update interrupts */ rc = ioctl(self->fd, RTC_UIE_ON, 0); if (rc == -1) { ASSERT_EQ(EINVAL, errno); TH_LOG("skip update IRQs not supported."); return; } for (i = 0; i < NUM_UIE; i++) { struct timeval tv = { .tv_sec = 2 }; fd_set readfds; FD_ZERO(&readfds); FD_SET(self->fd, &readfds); /* The select will wait until an RTC interrupt happens. */ rc = select(self->fd + 1, &readfds, NULL, NULL, &tv); ASSERT_NE(-1, rc); ASSERT_NE(0, rc); /* This read won't block */ rc = read(self->fd, &data, sizeof(unsigned long)); ASSERT_NE(-1, rc); irq++; } EXPECT_EQ(NUM_UIE, irq); rc = ioctl(self->fd, RTC_UIE_OFF, 0); ASSERT_NE(-1, rc); } TEST_F(rtc, alarm_alm_set) { struct timeval tv = { .tv_sec = ALARM_DELTA + 2 }; unsigned long data; struct rtc_time tm; fd_set readfds; time_t secs, new; int rc; if (self->fd == -1 && errno == ENOENT) SKIP(return, "Skipping test since %s does not exist", rtc_file); ASSERT_NE(-1, self->fd); rc = ioctl(self->fd, RTC_RD_TIME, &tm); ASSERT_NE(-1, rc); secs = timegm((struct tm *)&tm) + ALARM_DELTA; gmtime_r(&secs, (struct tm *)&tm); rc = ioctl(self->fd, RTC_ALM_SET, &tm); if (rc == -1) { ASSERT_EQ(EINVAL, errno); TH_LOG("skip alarms are not supported."); return; } rc = ioctl(self->fd, RTC_ALM_READ, &tm); ASSERT_NE(-1, rc); TH_LOG("Alarm time now set to %02d:%02d:%02d.", tm.tm_hour, tm.tm_min, tm.tm_sec); /* Enable alarm interrupts */ rc = ioctl(self->fd, RTC_AIE_ON, 0); ASSERT_NE(-1, rc); FD_ZERO(&readfds); FD_SET(self->fd, &readfds); rc = select(self->fd + 1, &readfds, NULL, NULL, &tv); ASSERT_NE(-1, rc); ASSERT_NE(0, rc); /* Disable alarm interrupts */ rc = ioctl(self->fd, RTC_AIE_OFF, 0); ASSERT_NE(-1, rc); rc = read(self->fd, &data, sizeof(unsigned long)); ASSERT_NE(-1, rc); TH_LOG("data: %lx", data); rc = ioctl(self->fd, RTC_RD_TIME, &tm); ASSERT_NE(-1, rc); new = timegm((struct tm *)&tm); ASSERT_EQ(new, secs); } TEST_F(rtc, alarm_wkalm_set) { struct timeval tv = { .tv_sec = ALARM_DELTA + 2 }; struct rtc_wkalrm alarm = { 0 }; struct rtc_time tm; unsigned long data; fd_set readfds; time_t secs, new; int rc; if (self->fd == -1 && errno == ENOENT) SKIP(return, "Skipping test since %s does not exist", rtc_file); ASSERT_NE(-1, self->fd); rc = ioctl(self->fd, RTC_RD_TIME, &alarm.time); ASSERT_NE(-1, rc); secs = timegm((struct tm *)&alarm.time) + ALARM_DELTA; gmtime_r(&secs, (struct tm *)&alarm.time); alarm.enabled = 1; rc = ioctl(self->fd, RTC_WKALM_SET, &alarm); if (rc == -1) { ASSERT_EQ(EINVAL, errno); TH_LOG("skip alarms are not supported."); return; } rc = ioctl(self->fd, RTC_WKALM_RD, &alarm); ASSERT_NE(-1, rc); TH_LOG("Alarm time now set to %02d/%02d/%02d %02d:%02d:%02d.", alarm.time.tm_mday, alarm.time.tm_mon + 1, alarm.time.tm_year + 1900, alarm.time.tm_hour, alarm.time.tm_min, alarm.time.tm_sec); FD_ZERO(&readfds); FD_SET(self->fd, &readfds); rc = select(self->fd + 1, &readfds, NULL, NULL, &tv); ASSERT_NE(-1, rc); ASSERT_NE(0, rc); rc = read(self->fd, &data, sizeof(unsigned long)); ASSERT_NE(-1, rc); rc = ioctl(self->fd, RTC_RD_TIME, &tm); ASSERT_NE(-1, rc); new = timegm((struct tm *)&tm); ASSERT_EQ(new, secs); } TEST_F_TIMEOUT(rtc, alarm_alm_set_minute, 65) { struct timeval tv = { .tv_sec = 62 }; unsigned long data; struct rtc_time tm; fd_set readfds; time_t secs, new; int rc; if (self->fd == -1 && errno == ENOENT) SKIP(return, "Skipping test since %s does not exist", rtc_file); ASSERT_NE(-1, self->fd); rc = ioctl(self->fd, RTC_RD_TIME, &tm); ASSERT_NE(-1, rc); secs = timegm((struct tm *)&tm) + 60 - tm.tm_sec; gmtime_r(&secs, (struct tm *)&tm); rc = ioctl(self->fd, RTC_ALM_SET, &tm); if (rc == -1) { ASSERT_EQ(EINVAL, errno); TH_LOG("skip alarms are not supported."); return; } rc = ioctl(self->fd, RTC_ALM_READ, &tm); ASSERT_NE(-1, rc); TH_LOG("Alarm time now set to %02d:%02d:%02d.", tm.tm_hour, tm.tm_min, tm.tm_sec); /* Enable alarm interrupts */ rc = ioctl(self->fd, RTC_AIE_ON, 0); ASSERT_NE(-1, rc); FD_ZERO(&readfds); FD_SET(self->fd, &readfds); rc = select(self->fd + 1, &readfds, NULL, NULL, &tv); ASSERT_NE(-1, rc); ASSERT_NE(0, rc); /* Disable alarm interrupts */ rc = ioctl(self->fd, RTC_AIE_OFF, 0); ASSERT_NE(-1, rc); rc = read(self->fd, &data, sizeof(unsigned long)); ASSERT_NE(-1, rc); TH_LOG("data: %lx", data); rc = ioctl(self->fd, RTC_RD_TIME, &tm); ASSERT_NE(-1, rc); new = timegm((struct tm *)&tm); ASSERT_EQ(new, secs); } TEST_F_TIMEOUT(rtc, alarm_wkalm_set_minute, 65) { struct timeval tv = { .tv_sec = 62 }; struct rtc_wkalrm alarm = { 0 }; struct rtc_time tm; unsigned long data; fd_set readfds; time_t secs, new; int rc; if (self->fd == -1 && errno == ENOENT) SKIP(return, "Skipping test since %s does not exist", rtc_file); ASSERT_NE(-1, self->fd); rc = ioctl(self->fd, RTC_RD_TIME, &alarm.time); ASSERT_NE(-1, rc); secs = timegm((struct tm *)&alarm.time) + 60 - alarm.time.tm_sec; gmtime_r(&secs, (struct tm *)&alarm.time); alarm.enabled = 1; rc = ioctl(self->fd, RTC_WKALM_SET, &alarm); if (rc == -1) { ASSERT_EQ(EINVAL, errno); TH_LOG("skip alarms are not supported."); return; } rc = ioctl(self->fd, RTC_WKALM_RD, &alarm); ASSERT_NE(-1, rc); TH_LOG("Alarm time now set to %02d/%02d/%02d %02d:%02d:%02d.", alarm.time.tm_mday, alarm.time.tm_mon + 1, alarm.time.tm_year + 1900, alarm.time.tm_hour, alarm.time.tm_min, alarm.time.tm_sec); FD_ZERO(&readfds); FD_SET(self->fd, &readfds); rc = select(self->fd + 1, &readfds, NULL, NULL, &tv); ASSERT_NE(-1, rc); ASSERT_NE(0, rc); rc = read(self->fd, &data, sizeof(unsigned long)); ASSERT_NE(-1, rc); rc = ioctl(self->fd, RTC_RD_TIME, &tm); ASSERT_NE(-1, rc); new = timegm((struct tm *)&tm); ASSERT_EQ(new, secs); } static void __attribute__((constructor)) __constructor_order_last(void) { if (!__constructor_order) __constructor_order = _CONSTRUCTOR_ORDER_BACKWARD; } int main(int argc, char **argv) { switch (argc) { case 2: rtc_file = argv[1]; /* FALLTHROUGH */ case 1: break; default: fprintf(stderr, "usage: %s [rtcdev]\n", argv[0]); return 1; } return test_harness_run(argc, argv); }