提交 d8da8665 编写于 作者: A Alexandre Belloni 提交者: Shuah Khan (Samsung OSG)

selftests: rtc: rework rtctest

Rework rtctest to use the test harness to better handle skipping tests
(e.g. when alarms are not available). Also, it now handles timeout so it
will not block expecting an alarm that never comes.
Signed-off-by: NAlexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: NShuah Khan (Samsung OSG) <shuah@kernel.org>
上级 a12ab9e1
// SPDX-License-Identifier: GPL-2.0
/* /*
* Real Time Clock Driver Test/Example Program * Real Time Clock Driver Test Program
*
* Compile with:
* gcc -s -Wall -Wstrict-prototypes rtctest.c -o rtctest
*
* Copyright (C) 1996, Paul Gortmaker.
*
* Released under the GNU General Public License, version 2,
* included herein by reference.
* *
* Copyright (c) 2018 Alexandre Belloni <alexandre.belloni@bootlin.com>
*/ */
#include <stdio.h> #include <errno.h>
#include <fcntl.h>
#include <linux/rtc.h> #include <linux/rtc.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/types.h> #include <sys/types.h>
#include <fcntl.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#ifndef ARRAY_SIZE #include "../kselftest_harness.h"
# define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif
/* #define NUM_UIE 3
* This expects the new RTC class driver framework, working with #define ALARM_DELTA 3
* clocks that will often not be clones of what the PC-AT had.
* Use the command line to specify another RTC if you need one. static char *rtc_file = "/dev/rtc0";
*/
static const char default_rtc[] = "/dev/rtc0"; FIXTURE(rtc) {
int fd;
static struct rtc_time cutoff_dates[] = {
{
.tm_year = 70, /* 1970 -1900 */
.tm_mday = 1,
},
/* signed time_t 19/01/2038 3:14:08 */
{
.tm_year = 138,
.tm_mday = 19,
},
{
.tm_year = 138,
.tm_mday = 20,
},
{
.tm_year = 199, /* 2099 -1900 */
.tm_mday = 1,
},
{
.tm_year = 200, /* 2100 -1900 */
.tm_mday = 1,
},
/* unsigned time_t 07/02/2106 7:28:15*/
{
.tm_year = 205,
.tm_mon = 1,
.tm_mday = 7,
},
{
.tm_year = 206,
.tm_mon = 1,
.tm_mday = 8,
},
/* signed time on 64bit in nanoseconds 12/04/2262 01:47:16*/
{
.tm_year = 362,
.tm_mon = 3,
.tm_mday = 12,
},
{
.tm_year = 362, /* 2262 -1900 */
.tm_mon = 3,
.tm_mday = 13,
},
}; };
static int compare_dates(struct rtc_time *a, struct rtc_time *b) FIXTURE_SETUP(rtc) {
{ self->fd = open(rtc_file, O_RDONLY);
if (a->tm_year != b->tm_year || ASSERT_NE(-1, self->fd);
a->tm_mon != b->tm_mon || }
a->tm_mday != b->tm_mday ||
a->tm_hour != b->tm_hour ||
a->tm_min != b->tm_min ||
((b->tm_sec - a->tm_sec) > 1))
return 1;
return 0; FIXTURE_TEARDOWN(rtc) {
close(self->fd);
} }
int main(int argc, char **argv) TEST_F(rtc, date_read) {
{ int rc;
int i, fd, retval, irqcount = 0, dangerous = 0;
unsigned long data;
struct rtc_time rtc_tm; struct rtc_time rtc_tm;
const char *rtc = default_rtc;
switch (argc) {
case 3:
if (*argv[2] == 'd')
dangerous = 1;
case 2:
rtc = argv[1];
/* FALLTHROUGH */
case 1:
break;
default:
fprintf(stderr, "usage: rtctest [rtcdev] [d]\n");
return 1;
}
fd = open(rtc, O_RDONLY); /* Read the RTC time/date */
rc = ioctl(self->fd, RTC_RD_TIME, &rtc_tm);
ASSERT_NE(-1, rc);
if (fd == -1) { TH_LOG("Current RTC date/time is %02d/%02d/%02d %02d:%02d:%02d.",
perror(rtc); rtc_tm.tm_mday, rtc_tm.tm_mon + 1, rtc_tm.tm_year + 1900,
exit(errno); rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec);
} }
fprintf(stderr, "\n\t\t\tRTC Driver Test Example.\n\n"); TEST_F(rtc, uie_read) {
int i, rc, irq = 0;
unsigned long data;
/* Turn on update interrupts (one per second) */ /* Turn on update interrupts */
retval = ioctl(fd, RTC_UIE_ON, 0); rc = ioctl(self->fd, RTC_UIE_ON, 0);
if (retval == -1) { if (rc == -1) {
if (errno == EINVAL) { ASSERT_EQ(EINVAL, errno);
fprintf(stderr, TH_LOG("skip update IRQs not supported.");
"\n...Update IRQs not supported.\n"); return;
goto test_READ;
}
perror("RTC_UIE_ON ioctl");
exit(errno);
} }
fprintf(stderr, "Counting 5 update (1/sec) interrupts from reading %s:", for (i = 0; i < NUM_UIE; i++) {
rtc);
fflush(stderr);
for (i=1; i<6; i++) {
/* This read will block */ /* This read will block */
retval = read(fd, &data, sizeof(unsigned long)); rc = read(self->fd, &data, sizeof(data));
if (retval == -1) { ASSERT_NE(-1, rc);
perror("read"); irq++;
exit(errno);
} }
fprintf(stderr, " %d",i);
fflush(stderr); EXPECT_EQ(NUM_UIE, irq);
irqcount++;
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;
/* 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;
} }
fprintf(stderr, "\nAgain, from using select(2) on /dev/rtc:"); for (i = 0; i < NUM_UIE; i++) {
fflush(stderr); struct timeval tv = { .tv_sec = 2 };
for (i=1; i<6; i++) {
struct timeval tv = {5, 0}; /* 5 second timeout on select */
fd_set readfds; fd_set readfds;
FD_ZERO(&readfds); FD_ZERO(&readfds);
FD_SET(fd, &readfds); FD_SET(self->fd, &readfds);
/* The select will wait until an RTC interrupt happens. */ /* The select will wait until an RTC interrupt happens. */
retval = select(fd+1, &readfds, NULL, NULL, &tv); rc = select(self->fd + 1, &readfds, NULL, NULL, &tv);
if (retval == -1) { ASSERT_NE(-1, rc);
perror("select"); ASSERT_NE(0, rc);
exit(errno);
}
/* This read won't block unlike the select-less case above. */
retval = read(fd, &data, sizeof(unsigned long));
if (retval == -1) {
perror("read");
exit(errno);
}
fprintf(stderr, " %d",i);
fflush(stderr);
irqcount++;
}
/* Turn off update interrupts */ /* This read won't block */
retval = ioctl(fd, RTC_UIE_OFF, 0); rc = read(self->fd, &data, sizeof(unsigned long));
if (retval == -1) { ASSERT_NE(-1, rc);
perror("RTC_UIE_OFF ioctl"); irq++;
exit(errno);
} }
test_READ: EXPECT_EQ(NUM_UIE, irq);
/* Read the RTC time/date */
retval = ioctl(fd, RTC_RD_TIME, &rtc_tm);
if (retval == -1) {
perror("RTC_RD_TIME ioctl");
exit(errno);
}
fprintf(stderr, "\n\nCurrent RTC date/time is %d-%d-%d, %02d:%02d:%02d.\n", rc = ioctl(self->fd, RTC_UIE_OFF, 0);
rtc_tm.tm_mday, rtc_tm.tm_mon + 1, rtc_tm.tm_year + 1900, ASSERT_NE(-1, rc);
rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec); }
/* Set the alarm to 5 sec in the future, and check for rollover */ TEST_F(rtc, alarm_alm_set) {
rtc_tm.tm_sec += 5; struct timeval tv = { .tv_sec = ALARM_DELTA + 2 };
if (rtc_tm.tm_sec >= 60) { unsigned long data;
rtc_tm.tm_sec %= 60; struct rtc_time tm;
rtc_tm.tm_min++; fd_set readfds;
} time_t secs, new;
if (rtc_tm.tm_min == 60) { int rc;
rtc_tm.tm_min = 0;
rtc_tm.tm_hour++;
}
if (rtc_tm.tm_hour == 24)
rtc_tm.tm_hour = 0;
retval = ioctl(fd, RTC_ALM_SET, &rtc_tm);
if (retval == -1) {
if (errno == EINVAL) {
fprintf(stderr,
"\n...Alarm IRQs not supported.\n");
goto test_DATE;
}
perror("RTC_ALM_SET ioctl"); rc = ioctl(self->fd, RTC_RD_TIME, &tm);
exit(errno); ASSERT_NE(-1, rc);
}
/* Read the current alarm settings */ secs = timegm((struct tm *)&tm) + ALARM_DELTA;
retval = ioctl(fd, RTC_ALM_READ, &rtc_tm); gmtime_r(&secs, (struct tm *)&tm);
if (retval == -1) {
if (errno == EINVAL) { rc = ioctl(self->fd, RTC_ALM_SET, &tm);
fprintf(stderr, if (rc == -1) {
"\n...EINVAL reading current alarm setting.\n"); ASSERT_EQ(EINVAL, errno);
goto test_DATE; TH_LOG("skip alarms are not supported.");
} return;
perror("RTC_ALM_READ ioctl");
exit(errno);
} }
fprintf(stderr, "Alarm time now set to %02d:%02d:%02d.\n", rc = ioctl(self->fd, RTC_ALM_READ, &tm);
rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec); 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 */ /* Enable alarm interrupts */
retval = ioctl(fd, RTC_AIE_ON, 0); rc = ioctl(self->fd, RTC_AIE_ON, 0);
if (retval == -1) { ASSERT_NE(-1, rc);
if (errno == EINVAL || errno == EIO) {
fprintf(stderr,
"\n...Alarm IRQs not supported.\n");
goto test_DATE;
}
perror("RTC_AIE_ON ioctl"); FD_ZERO(&readfds);
exit(errno); FD_SET(self->fd, &readfds);
}
fprintf(stderr, "Waiting 5 seconds for alarm..."); rc = select(self->fd + 1, &readfds, NULL, NULL, &tv);
fflush(stderr); ASSERT_NE(-1, rc);
/* This blocks until the alarm ring causes an interrupt */ EXPECT_NE(0, rc);
retval = read(fd, &data, sizeof(unsigned long));
if (retval == -1) {
perror("read");
exit(errno);
}
irqcount++;
fprintf(stderr, " okay. Alarm rang.\n");
/* Disable alarm interrupts */ /* Disable alarm interrupts */
retval = ioctl(fd, RTC_AIE_OFF, 0); rc = ioctl(self->fd, RTC_AIE_OFF, 0);
if (retval == -1) { ASSERT_NE(-1, rc);
perror("RTC_AIE_OFF ioctl");
exit(errno);
}
test_DATE: if (rc == 0)
if (!dangerous) return;
goto done;
fprintf(stderr, "\nTesting problematic dates\n"); rc = read(self->fd, &data, sizeof(unsigned long));
ASSERT_NE(-1, rc);
TH_LOG("data: %lx", data);
for (i = 0; i < ARRAY_SIZE(cutoff_dates); i++) { rc = ioctl(self->fd, RTC_RD_TIME, &tm);
struct rtc_time current; ASSERT_NE(-1, rc);
/* Write the new date in RTC */ new = timegm((struct tm *)&tm);
retval = ioctl(fd, RTC_SET_TIME, &cutoff_dates[i]); ASSERT_EQ(new, secs);
if (retval == -1) { }
perror("RTC_SET_TIME ioctl");
close(fd);
exit(errno);
}
/* Read back */ TEST_F(rtc, alarm_wkalm_set) {
retval = ioctl(fd, RTC_RD_TIME, &current); struct timeval tv = { .tv_sec = ALARM_DELTA + 2 };
if (retval == -1) { struct rtc_wkalrm alarm = { 0 };
perror("RTC_RD_TIME ioctl"); struct rtc_time tm;
exit(errno); unsigned long data;
} fd_set readfds;
time_t secs, new;
int rc;
if(compare_dates(&cutoff_dates[i], &current)) { rc = ioctl(self->fd, RTC_RD_TIME, &alarm.time);
fprintf(stderr,"Setting date %d failed\n", ASSERT_NE(-1, rc);
cutoff_dates[i].tm_year + 1900);
goto done;
}
cutoff_dates[i].tm_sec += 5; secs = timegm((struct tm *)&alarm.time) + ALARM_DELTA;
gmtime_r(&secs, (struct tm *)&alarm.time);
/* Write the new alarm in RTC */ alarm.enabled = 1;
retval = ioctl(fd, RTC_ALM_SET, &cutoff_dates[i]);
if (retval == -1) {
perror("RTC_ALM_SET ioctl");
close(fd);
exit(errno);
}
/* Read back */ rc = ioctl(self->fd, RTC_WKALM_SET, &alarm);
retval = ioctl(fd, RTC_ALM_READ, &current); if (rc == -1) {
if (retval == -1) { ASSERT_EQ(EINVAL, errno);
perror("RTC_ALM_READ ioctl"); TH_LOG("skip alarms are not supported.");
exit(errno); return;
} }
if(compare_dates(&cutoff_dates[i], &current)) { rc = ioctl(self->fd, RTC_WKALM_RD, &alarm);
fprintf(stderr,"Setting alarm %d failed\n", ASSERT_NE(-1, rc);
cutoff_dates[i].tm_year + 1900);
goto done;
}
fprintf(stderr, "Setting year %d is OK \n", TH_LOG("Alarm time now set to %02d/%02d/%02d %02d:%02d:%02d.",
cutoff_dates[i].tm_year + 1900); alarm.time.tm_mday, alarm.time.tm_mon + 1,
} alarm.time.tm_year + 1900, alarm.time.tm_hour,
done: alarm.time.tm_min, alarm.time.tm_sec);
fprintf(stderr, "\n\n\t\t\t *** Test complete ***\n");
FD_ZERO(&readfds);
FD_SET(self->fd, &readfds);
rc = select(self->fd + 1, &readfds, NULL, NULL, &tv);
ASSERT_NE(-1, rc);
EXPECT_NE(0, rc);
close(fd); 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 0; return test_harness_run(argc, argv);
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册