509 lines · 12.3 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Real Time Clock Driver Test Program * * Copyright (c) 2018 Alexandre Belloni <alexandre.belloni@bootlin.com> */ #include <errno.h>#include <fcntl.h>#include <linux/rtc.h>#include <stdio.h>#include <stdlib.h>#include <sys/ioctl.h>#include <sys/time.h>#include <sys/types.h>#include <time.h>#include <unistd.h> #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"; enum rtc_alarm_state {	RTC_ALARM_UNKNOWN,	RTC_ALARM_ENABLED,	RTC_ALARM_DISABLED,	RTC_ALARM_RES_MINUTE,}; 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;	}} static enum rtc_alarm_state get_rtc_alarm_state(int fd, int need_seconds){	struct rtc_param param = { 0 };	int rc; 	/* Validate kernel reflects unsupported RTC alarm state */	param.param = RTC_PARAM_FEATURES;	param.index = 0;	rc = ioctl(fd, RTC_PARAM_GET, &param);	if (rc < 0)		return RTC_ALARM_UNKNOWN; 	if ((param.uvalue & _BITUL(RTC_FEATURE_ALARM)) == 0)		return RTC_ALARM_DISABLED; 	/* Check if alarm has desired granularity */	if (need_seconds && (param.uvalue & _BITUL(RTC_FEATURE_ALARM_RES_MINUTE)))		return RTC_ALARM_RES_MINUTE; 	return RTC_ALARM_ENABLED;} 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;	enum rtc_alarm_state alarm_state = RTC_ALARM_UNKNOWN; 	if (self->fd == -1 && errno == ENOENT)		SKIP(return, "Skipping test since %s does not exist", rtc_file);	ASSERT_NE(-1, self->fd); 	alarm_state = get_rtc_alarm_state(self->fd, 1);	if (alarm_state == RTC_ALARM_DISABLED)		SKIP(return, "Skipping test since alarms are not supported.");	if (alarm_state == RTC_ALARM_RES_MINUTE)		SKIP(return, "Skipping test since alarms has only minute granularity."); 	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) {		/*		 * Report error if rtc alarm was enabled. Fallback to check ioctl		 * error number if rtc alarm state is unknown.		 */		ASSERT_EQ(RTC_ALARM_UNKNOWN, alarm_state);		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;	enum rtc_alarm_state alarm_state = RTC_ALARM_UNKNOWN; 	if (self->fd == -1 && errno == ENOENT)		SKIP(return, "Skipping test since %s does not exist", rtc_file);	ASSERT_NE(-1, self->fd); 	alarm_state = get_rtc_alarm_state(self->fd, 1);	if (alarm_state == RTC_ALARM_DISABLED)		SKIP(return, "Skipping test since alarms are not supported.");	if (alarm_state == RTC_ALARM_RES_MINUTE)		SKIP(return, "Skipping test since alarms has only minute granularity."); 	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) {		/*		 * Report error if rtc alarm was enabled. Fallback to check ioctl		 * error number if rtc alarm state is unknown.		 */		ASSERT_EQ(RTC_ALARM_UNKNOWN, alarm_state);		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;	enum rtc_alarm_state alarm_state = RTC_ALARM_UNKNOWN; 	if (self->fd == -1 && errno == ENOENT)		SKIP(return, "Skipping test since %s does not exist", rtc_file);	ASSERT_NE(-1, self->fd); 	alarm_state = get_rtc_alarm_state(self->fd, 0);	if (alarm_state == RTC_ALARM_DISABLED)		SKIP(return, "Skipping test since alarms are not supported."); 	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) {		/*		 * Report error if rtc alarm was enabled. Fallback to check ioctl		 * error number if rtc alarm state is unknown.		 */		ASSERT_EQ(RTC_ALARM_UNKNOWN, alarm_state);		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;	enum rtc_alarm_state alarm_state = RTC_ALARM_UNKNOWN; 	if (self->fd == -1 && errno == ENOENT)		SKIP(return, "Skipping test since %s does not exist", rtc_file);	ASSERT_NE(-1, self->fd); 	alarm_state = get_rtc_alarm_state(self->fd, 0);	if (alarm_state == RTC_ALARM_DISABLED)		SKIP(return, "Skipping test since alarms are not supported."); 	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) {		/*		 * Report error if rtc alarm was enabled. Fallback to check ioctl		 * error number if rtc alarm state is unknown.		 */		ASSERT_EQ(RTC_ALARM_UNKNOWN, alarm_state);		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);} int main(int argc, char **argv){	int ret = -1; 	switch (argc) {	case 2:		rtc_file = argv[1];		/* FALLTHROUGH */	case 1:		break;	default:		fprintf(stderr, "usage: %s [rtcdev]\n", argv[0]);		return 1;	} 

Lines 1–500 of 509.