// SPDX-License-Identifier: GPL-2.0 #include #include #include #include #include #include #include #include #include #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, ¶ms); 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; }