/** * @file clockadj.c * @note Copyright (C) 2013 Richard Cochran * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include "clockadj.h" #include "missing.h" #include "print.h" #define NS_PER_SEC 1000000000LL static int realtime_leap_bit; static long realtime_hz; static long realtime_nominal_tick; void clockadj_init(clockid_t clkid) { #ifdef _SC_CLK_TCK if (clkid == CLOCK_REALTIME) { /* This is USER_HZ in the kernel. */ realtime_hz = sysconf(_SC_CLK_TCK); if (realtime_hz > 0) { /* This is TICK_USEC in the kernel. */ realtime_nominal_tick = (1000000 + realtime_hz / 2) / realtime_hz; } } #endif } void clockadj_set_freq(clockid_t clkid, double freq) { struct timex tx; memset(&tx, 0, sizeof(tx)); /* With system clock set also the tick length. */ if (clkid == CLOCK_REALTIME && realtime_nominal_tick) { tx.modes |= ADJ_TICK; tx.tick = round(freq / 1e3 / realtime_hz) + realtime_nominal_tick; freq -= 1e3 * realtime_hz * (tx.tick - realtime_nominal_tick); } tx.modes |= ADJ_FREQUENCY; tx.freq = (long) (freq * 65.536); if (clock_adjtime(clkid, &tx) < 0) pr_err("failed to adjust the clock: %m"); } double clockadj_get_freq(clockid_t clkid) { double f = 0.0; struct timex tx; memset(&tx, 0, sizeof(tx)); if (clock_adjtime(clkid, &tx) < 0) { pr_err("failed to read out the clock frequency adjustment: %m"); } else { f = tx.freq / 65.536; if (clkid == CLOCK_REALTIME && realtime_nominal_tick && tx.tick) f += 1e3 * realtime_hz * (tx.tick - realtime_nominal_tick); } return f; } void clockadj_step(clockid_t clkid, int64_t step) { struct timex tx; int sign = 1; if (step < 0) { sign = -1; step *= -1; } memset(&tx, 0, sizeof(tx)); tx.modes = ADJ_SETOFFSET | ADJ_NANO; tx.time.tv_sec = sign * (step / NS_PER_SEC); tx.time.tv_usec = sign * (step % NS_PER_SEC); /* * The value of a timeval is the sum of its fields, but the * field tv_usec must always be non-negative. */ if (tx.time.tv_usec < 0) { tx.time.tv_sec -= 1; tx.time.tv_usec += 1000000000; } if (clock_adjtime(clkid, &tx) < 0) pr_err("failed to step clock: %m"); } void sysclk_set_leap(int leap) { clockid_t clkid = CLOCK_REALTIME; struct timex tx; const char *m = NULL; memset(&tx, 0, sizeof(tx)); tx.modes = ADJ_STATUS; switch (leap) { case -1: tx.status = STA_DEL; m = "clock set to delete leap second at midnight (UTC)"; break; case 1: tx.status = STA_INS; m = "clock set to insert leap second at midnight (UTC)"; break; default: tx.status = 0; } if (clock_adjtime(clkid, &tx) < 0) pr_err("failed to set the clock status: %m"); else if (m) pr_notice("%s", m); realtime_leap_bit = tx.status; } void sysclk_set_tai_offset(int offset) { clockid_t clkid = CLOCK_REALTIME; struct timex tx; memset(&tx, 0, sizeof(tx)); tx.modes = ADJ_TAI; tx.constant = offset; if (clock_adjtime(clkid, &tx) < 0) pr_err("failed to set TAI offset: %m"); } int sysclk_max_freq(void) { clockid_t clkid = CLOCK_REALTIME; int f = 0; struct timex tx; memset(&tx, 0, sizeof(tx)); if (clock_adjtime(clkid, &tx) < 0) pr_err("failed to read out the clock maximum adjustment: %m"); else f = tx.tolerance / 65.536; if (!f) f = 500000; /* The kernel allows the tick length to be adjusted up to 10%. But use it only if the overall frequency of the clock can be adjusted continuously with the tick and freq fields (i.e. hz <= 1000). */ if (realtime_nominal_tick && 2 * f >= 1000 * realtime_hz) f = realtime_nominal_tick / 10 * 1000 * realtime_hz; return f; } void sysclk_set_sync(void) { clockid_t clkid = CLOCK_REALTIME; struct timex tx; memset(&tx, 0, sizeof(tx)); /* Clear the STA_UNSYNC flag from the status and keep the maxerror value (which is increased automatically by 500 ppm) below 16 seconds to avoid getting the STA_UNSYNC flag back. */ tx.modes = ADJ_STATUS | ADJ_MAXERROR; tx.status = realtime_leap_bit; if (clock_adjtime(clkid, &tx) < 0) pr_err("failed to set clock status and maximum error: %m"); }