2018-04-19 14:50:30 +02:00
// SPDX-License-Identifier: GPL-2.0
2015-03-23 16:32:09 -04:00
/*
2018-04-19 14:50:30 +02:00
* Real Time Clock Driver Test Program
2015-03-23 16:32:09 -04:00
*
2018-04-19 14:50:30 +02:00
* Copyright ( c ) 2018 Alexandre Belloni < alexandre . belloni @ bootlin . com >
2015-03-23 16:32:09 -04:00
*/
2018-04-19 14:50:30 +02:00
# include <errno.h>
# include <fcntl.h>
2015-03-23 16:32:09 -04:00
# include <linux/rtc.h>
2018-04-19 14:50:30 +02:00
# include <stdio.h>
# include <stdlib.h>
2015-03-23 16:32:09 -04:00
# include <sys/ioctl.h>
# include <sys/time.h>
# include <sys/types.h>
2018-04-19 14:50:30 +02:00
# include <time.h>
2015-03-23 16:32:09 -04:00
# include <unistd.h>
2018-04-19 14:50:30 +02:00
# include "../kselftest_harness.h"
2015-03-23 16:32:09 -04:00
2018-04-19 14:50:30 +02:00
# define NUM_UIE 3
# define ALARM_DELTA 3
static char * rtc_file = " /dev/rtc0 " ;
FIXTURE ( rtc ) {
int fd ;
2017-06-19 11:36:21 +02:00
} ;
2018-04-19 14:50:30 +02:00
FIXTURE_SETUP ( rtc ) {
self - > fd = open ( rtc_file , O_RDONLY ) ;
ASSERT_NE ( - 1 , self - > fd ) ;
}
2017-06-19 11:36:21 +02:00
2018-04-19 14:50:30 +02:00
FIXTURE_TEARDOWN ( rtc ) {
close ( self - > fd ) ;
2017-06-19 11:36:21 +02:00
}
2015-03-23 16:32:09 -04:00
2018-04-19 14:50:30 +02:00
TEST_F ( rtc , date_read ) {
int rc ;
2015-03-23 16:32:09 -04:00
struct rtc_time rtc_tm ;
2018-04-19 14:50:30 +02:00
/* Read the RTC time/date */
rc = ioctl ( self - > fd , RTC_RD_TIME , & rtc_tm ) ;
ASSERT_NE ( - 1 , rc ) ;
2015-03-23 16:32:09 -04:00
2018-04-19 14:50:30 +02:00
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 ) ;
}
2015-03-23 16:32:09 -04:00
2018-04-19 14:50:30 +02:00
TEST_F ( rtc , uie_read ) {
int i , rc , irq = 0 ;
unsigned long data ;
2015-03-23 16:32:09 -04:00
2018-04-19 14:50:30 +02:00
/* 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 ;
2015-03-23 16:32:09 -04:00
}
2018-04-19 14:50:30 +02:00
for ( i = 0 ; i < NUM_UIE ; i + + ) {
2015-03-23 16:32:09 -04:00
/* This read will block */
2018-04-19 14:50:30 +02:00
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 ;
2015-03-23 16:32:09 -04:00
}
2018-04-19 14:50:30 +02:00
for ( i = 0 ; i < NUM_UIE ; i + + ) {
struct timeval tv = { . tv_sec = 2 } ;
2015-03-23 16:32:09 -04:00
fd_set readfds ;
FD_ZERO ( & readfds ) ;
2018-04-19 14:50:30 +02:00
FD_SET ( self - > fd , & readfds ) ;
2015-03-23 16:32:09 -04:00
/* The select will wait until an RTC interrupt happens. */
2018-04-19 14:50:30 +02:00
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 + + ;
2015-03-23 16:32:09 -04:00
}
2018-04-19 14:50:30 +02:00
EXPECT_EQ ( NUM_UIE , irq ) ;
2015-03-23 16:32:09 -04:00
2018-04-19 14:50:30 +02:00
rc = ioctl ( self - > fd , RTC_UIE_OFF , 0 ) ;
ASSERT_NE ( - 1 , rc ) ;
}
2015-03-23 16:32:09 -04:00
2018-04-19 14:50:30 +02:00
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 ;
2015-03-23 16:32:09 -04:00
}
2018-04-19 14:50:30 +02:00
rc = ioctl ( self - > fd , RTC_ALM_READ , & tm ) ;
ASSERT_NE ( - 1 , rc ) ;
2015-03-23 16:32:09 -04:00
2018-04-19 14:50:30 +02:00
TH_LOG ( " Alarm time now set to %02d:%02d:%02d. " ,
tm . tm_hour , tm . tm_min , tm . tm_sec ) ;
2015-03-23 16:32:09 -04:00
/* Enable alarm interrupts */
2018-04-19 14:50:30 +02:00
rc = ioctl ( self - > fd , RTC_AIE_ON , 0 ) ;
ASSERT_NE ( - 1 , rc ) ;
2015-03-23 16:32:09 -04:00
2018-04-19 14:50:30 +02:00
FD_ZERO ( & readfds ) ;
FD_SET ( self - > fd , & readfds ) ;
rc = select ( self - > fd + 1 , & readfds , NULL , NULL , & tv ) ;
ASSERT_NE ( - 1 , rc ) ;
2018-12-18 22:34:20 +01:00
ASSERT_NE ( 0 , rc ) ;
2015-03-23 16:32:09 -04:00
/* Disable alarm interrupts */
2018-04-19 14:50:30 +02:00
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 ;
2015-03-23 16:32:09 -04:00
2018-12-18 22:34:21 +01:00
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 ;
2018-04-19 14:50:30 +02:00
rc = ioctl ( self - > fd , RTC_WKALM_SET , & alarm ) ;
if ( rc = = - 1 ) {
ASSERT_EQ ( EINVAL , errno ) ;
TH_LOG ( " skip alarms are not supported. " ) ;
return ;
2017-06-19 11:36:21 +02:00
}
2015-03-23 16:32:09 -04:00
2018-04-19 14:50:30 +02:00
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 ) ;
2018-12-18 22:34:20 +01:00
ASSERT_NE ( 0 , rc ) ;
2018-04-19 14:50:30 +02:00
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 ;
}
2015-03-23 16:32:09 -04:00
2018-04-19 14:50:30 +02:00
return test_harness_run ( argc , argv ) ;
2015-03-23 16:32:09 -04:00
}