blob: 511f2f9a11e56fb6213a2ed0c0d58d1562284c3b [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
#include <fcntl.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <linux/rtc.h>
#define NSEC_PER_SEC 1000000000L
int set_realtime_priority(void)
{
int ret;
pthread_t this_thread = pthread_self();
struct sched_param params;
params.sched_priority = sched_get_priority_max(SCHED_FIFO);
ret = pthread_setschedparam(this_thread, SCHED_FIFO, &params);
if (ret != 0)
fprintf(stderr, "Unable to set realtime priority\n");
return ret;
}
void timespec_diff(struct timespec *start, struct timespec *stop,
struct timespec *result)
{
if ((stop->tv_nsec - start->tv_nsec) < 0) {
result->tv_sec = stop->tv_sec - start->tv_sec - 1;
result->tv_nsec = stop->tv_nsec - start->tv_nsec + NSEC_PER_SEC;
} else {
result->tv_sec = stop->tv_sec - start->tv_sec;
result->tv_nsec = stop->tv_nsec - start->tv_nsec;
}
return;
}
int get_offset_uie(struct timespec *diff, int rtc)
{
unsigned long data;
struct timespec now;
struct tm stm;
int rc;
int i;
rc = ioctl(rtc, RTC_UIE_ON, 0);
if (rc < 0) {
perror("RTC_UIE_ON");
return rc;
}
for (i = 0; i < 5; i++) {
rc = read(rtc, &data, sizeof(data));
if (rc < 0) {
perror("read");
return rc;
}
clock_gettime(CLOCK_REALTIME, &now);
ioctl(rtc, RTC_RD_TIME, &stm);
if (rc < 0) {
perror("RTC_RD_TIME");
break;
}
printf("%d %d.%09d\n", timegm(&stm), now.tv_sec, now.tv_nsec);
}
rc = ioctl(rtc, RTC_UIE_OFF, 0);
if (rc < 0) {
perror("RTC_UIE_OFF");
return rc;
}
diff->tv_sec = now.tv_sec - timegm(&stm);
diff->tv_nsec = now.tv_nsec;
return 0;
}
int get_offset_alarm(struct timespec *diff, int rtc)
{
struct rtc_wkalrm alarm = { 0 };
struct timespec now;
struct tm stm;
time_t secs;
unsigned long data;
int rc;
//TODO: save/restore surrent alarm
rc = ioctl(rtc, RTC_RD_TIME, &stm);
if (rc < 0) {
perror("RTC_RD_TIME");
return rc;
}
secs = timegm(&stm);
secs++;
gmtime_r(&secs, &stm);
alarm.time.tm_sec = stm.tm_sec;
alarm.time.tm_min = stm.tm_min;
alarm.time.tm_hour = stm.tm_hour;
alarm.time.tm_mday = stm.tm_mday;
alarm.time.tm_mon = stm.tm_mon;
alarm.time.tm_year = stm.tm_year;
alarm.time.tm_wday = -1;
alarm.time.tm_yday = -1;
alarm.time.tm_isdst = -1;
alarm.enabled = 1;
rc = ioctl(rtc, RTC_WKALM_SET, &alarm);
if (rc < 0) {
perror("RTC_WKALM_SET");
return rc;
}
rc = read(rtc, &data, sizeof(data));
if (rc < 0) {
perror("read");
return rc;
}
clock_gettime(CLOCK_REALTIME, &now);
rc = ioctl(rtc, RTC_RD_TIME, &stm);
if (rc < 0) {
perror("RTC_RD_TIME");
return rc;
}
secs = timegm(&stm);
printf("%d %d.%09d\n", secs, now.tv_sec, now.tv_nsec);
diff->tv_sec = now.tv_sec - secs;
diff->tv_nsec = now.tv_nsec;
return 0;
}
int get_offset_poll(struct timespec *diff, int rtc)
{
struct tm stm;
struct timespec now;
int rc;
int i, secs;
unsigned long m = 0;
struct timespec b, a, d;
for (i = 0; i < 100; i++) {
clock_gettime(CLOCK_MONOTONIC, &b);
rc = ioctl(rtc, RTC_RD_TIME, &stm);
if (rc < 0) {
perror("RTC_RD_TIME");
return rc;
}
clock_gettime(CLOCK_MONOTONIC, &a);
timespec_diff(&b, &a, &d);
m += d.tv_nsec/100 + d.tv_sec * 10000000;
}
printf("POLL: Mean time to read: %lu\n", m);
rc = ioctl(rtc, RTC_RD_TIME, &stm);
if (rc < 0) {
perror("RTC_RD_TIME");
return rc;
}
secs = stm.tm_sec;
do {
clock_gettime(CLOCK_MONOTONIC, &b);
ioctl(rtc, RTC_RD_TIME, &stm);
if (rc < 0) {
perror("RTC_RD_TIME");
return rc;
}
clock_gettime(CLOCK_MONOTONIC, &a);
} while (stm.tm_sec == secs);
clock_gettime(CLOCK_REALTIME, &now);
printf("POLL: corrected: %d %d.%09d\n", timegm(&stm), now.tv_sec, now.tv_nsec - m);
timespec_diff(&b, &a, &d);
printf("POLL: Last time to read: %lu %d\n", d.tv_nsec, i);
diff->tv_sec = now.tv_sec - timegm(&stm);
diff->tv_nsec = now.tv_nsec - m;
return 0;
}
int main(void)
{
struct timespec now, ts, diff;
struct tm stm;
time_t secs;
int rtc;
int rc;
int (*get_offset)(struct timespec *diff, int rtc);
get_offset = get_offset_alarm;
clock_getres(CLOCK_REALTIME, &ts);
printf("CLOCK_REALTIME %d.%09d\n", ts.tv_sec, ts.tv_nsec);
clock_getres(CLOCK_MONOTONIC, &ts);
printf("CLOCK_MONOTONIC %d.%09d\n", ts.tv_sec, ts.tv_nsec);
rtc = open("/dev/rtc0", O_RDONLY);
if (rtc < 0) {
perror("open");
return rtc;
}
set_realtime_priority();
rc = get_offset(&diff, rtc);
if (rc)
return rc;
printf("Current offset: %ds + %09dns = %dns\n", diff.tv_sec, diff.tv_nsec, diff.tv_sec * NSEC_PER_SEC + diff.tv_nsec);
clock_gettime(CLOCK_REALTIME, &now);
/* sleep to the next second unless now is too close */
ts.tv_nsec = 0;
ts.tv_sec = now.tv_sec + 1;
if (ts.tv_nsec > 900000000)
ts.tv_sec++;
gmtime_r(&ts.tv_sec, &stm);
printf("setting %d at %d.%09d\n", ts.tv_sec, ts.tv_sec, ts.tv_nsec);
rc = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, NULL);
if (rc) {
perror("clock_nanosleep");
return rc;
}
rc = ioctl(rtc, RTC_SET_TIME, &stm);
if (rc < 0) {
perror("RTC_SET_TIME");
return rc;
}
rc = get_offset(&diff, rtc);
if (rc)
return rc;
printf("Set offset: %ds + %09dns = %dns\n", diff.tv_sec, diff.tv_nsec, diff.tv_sec * NSEC_PER_SEC + diff.tv_nsec);
clock_gettime(CLOCK_REALTIME, &now);
/* Calculate the next full second */
if (diff.tv_sec < 0) {
/*
* RTC is set earlier than system time unless system time is going
* back, this will never be more than 1s
*/
secs = now.tv_sec;
ts.tv_sec = now.tv_sec;
ts.tv_nsec = NSEC_PER_SEC - diff.tv_nsec;
} else {
/*
* RTC is late so at the next second minus diff.tv_nsec, we need to set
* the RTC to next second + diff.tv_sec
*/
ts.tv_sec = now.tv_sec;
ts.tv_nsec = NSEC_PER_SEC - diff.tv_nsec;
secs = ts.tv_sec + 1 + diff.tv_sec;
}
/* too close, use next second */
if (ts.tv_nsec - now.tv_nsec < 100000000) {
ts.tv_sec++;
secs++;
}
gmtime_r(&secs, &stm);
printf("setting %d at %d.%09d\n", secs, ts.tv_sec, ts.tv_nsec);
clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, NULL);
rc = ioctl(rtc, RTC_SET_TIME, &stm);
if (rc < 0) {
perror("RTC_SET_TIME");
return rc;
}
rc = get_offset(&diff, rtc);
if (rc)
return rc;
printf("New offset: %ds + %09dns = %dns\n", diff.tv_sec, diff.tv_nsec, diff.tv_sec * NSEC_PER_SEC + diff.tv_nsec);
return 0;
}