mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	Unfortunately, some RTC don't have a second resolution for alarm so also test for alarm on a minute boundary. Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com> Signed-off-by: Shuah Khan <shuah@kernel.org>
		
			
				
	
	
		
			337 lines
		
	
	
	
		
			7.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			337 lines
		
	
	
	
		
			7.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// 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
 | 
						|
 | 
						|
static char *rtc_file = "/dev/rtc0";
 | 
						|
 | 
						|
FIXTURE(rtc) {
 | 
						|
	int fd;
 | 
						|
};
 | 
						|
 | 
						|
FIXTURE_SETUP(rtc) {
 | 
						|
	self->fd = open(rtc_file, O_RDONLY);
 | 
						|
	ASSERT_NE(-1, self->fd);
 | 
						|
}
 | 
						|
 | 
						|
FIXTURE_TEARDOWN(rtc) {
 | 
						|
	close(self->fd);
 | 
						|
}
 | 
						|
 | 
						|
TEST_F(rtc, date_read) {
 | 
						|
	int rc;
 | 
						|
	struct rtc_time rtc_tm;
 | 
						|
 | 
						|
	/* 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);
 | 
						|
}
 | 
						|
 | 
						|
TEST_F(rtc, uie_read) {
 | 
						|
	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;
 | 
						|
	}
 | 
						|
 | 
						|
	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;
 | 
						|
 | 
						|
	/* 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;
 | 
						|
 | 
						|
	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;
 | 
						|
 | 
						|
	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(rtc, alarm_alm_set_minute) {
 | 
						|
	struct timeval tv = { .tv_sec = 62 };
 | 
						|
	unsigned long data;
 | 
						|
	struct rtc_time tm;
 | 
						|
	fd_set readfds;
 | 
						|
	time_t secs, new;
 | 
						|
	int rc;
 | 
						|
 | 
						|
	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(rtc, alarm_wkalm_set_minute) {
 | 
						|
	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;
 | 
						|
 | 
						|
	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);
 | 
						|
}
 |