2013-03-08 00:27:29 +08:00
|
|
|
/**
|
|
|
|
* @file clockadj.c
|
|
|
|
* @note Copyright (C) 2013 Richard Cochran <richardcochran@gmail.com>
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2013-12-20 01:39:11 +08:00
|
|
|
#include <math.h>
|
2013-03-08 00:27:29 +08:00
|
|
|
#include <string.h>
|
2013-12-20 01:39:11 +08:00
|
|
|
#include <unistd.h>
|
2013-03-08 00:27:29 +08:00
|
|
|
|
|
|
|
#include "clockadj.h"
|
|
|
|
#include "missing.h"
|
|
|
|
#include "print.h"
|
|
|
|
|
|
|
|
#define NS_PER_SEC 1000000000LL
|
|
|
|
|
2013-04-08 21:44:10 +08:00
|
|
|
static int realtime_leap_bit;
|
2013-12-20 01:39:11 +08:00
|
|
|
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
|
|
|
|
}
|
2013-04-08 21:44:10 +08:00
|
|
|
|
2013-03-08 00:27:29 +08:00
|
|
|
void clockadj_set_freq(clockid_t clkid, double freq)
|
|
|
|
{
|
|
|
|
struct timex tx;
|
|
|
|
memset(&tx, 0, sizeof(tx));
|
2013-12-20 01:39:11 +08:00
|
|
|
|
|
|
|
/* 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;
|
2013-03-08 00:27:29 +08:00
|
|
|
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));
|
2013-12-20 01:39:11 +08:00
|
|
|
if (clock_adjtime(clkid, &tx) < 0) {
|
2013-03-08 00:27:29 +08:00
|
|
|
pr_err("failed to read out the clock frequency adjustment: %m");
|
2013-12-20 01:39:11 +08:00
|
|
|
} else {
|
2013-03-08 00:27:29 +08:00
|
|
|
f = tx.freq / 65.536;
|
2017-06-17 00:58:33 +08:00
|
|
|
if (clkid == CLOCK_REALTIME && realtime_nominal_tick && tx.tick)
|
2013-12-20 01:39:11 +08:00
|
|
|
f += 1e3 * realtime_hz * (tx.tick - realtime_nominal_tick);
|
|
|
|
}
|
2013-03-08 00:27:29 +08:00
|
|
|
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");
|
|
|
|
}
|
2013-03-08 00:27:30 +08:00
|
|
|
|
2018-11-08 22:05:12 +08:00
|
|
|
int clockadj_max_freq(clockid_t clkid)
|
|
|
|
{
|
|
|
|
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 (clkid == CLOCK_REALTIME && (realtime_nominal_tick && 2 * f >=
|
|
|
|
1000 * realtime_hz))
|
|
|
|
f = realtime_nominal_tick / 10 * 1000 * realtime_hz;
|
|
|
|
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
|
2013-04-08 21:44:09 +08:00
|
|
|
void sysclk_set_leap(int leap)
|
2013-03-08 00:27:30 +08:00
|
|
|
{
|
2013-04-08 21:44:09 +08:00
|
|
|
clockid_t clkid = CLOCK_REALTIME;
|
2013-03-08 00:27:30 +08:00
|
|
|
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)
|
2014-02-11 21:38:35 +08:00
|
|
|
pr_notice("%s", m);
|
2013-04-08 21:44:10 +08:00
|
|
|
realtime_leap_bit = tx.status;
|
2013-03-08 00:27:30 +08:00
|
|
|
}
|
2013-04-04 23:28:23 +08:00
|
|
|
|
2014-06-18 21:44:49 +08:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2013-04-05 15:47:21 +08:00
|
|
|
int sysclk_max_freq(void)
|
2013-04-04 23:28:23 +08:00
|
|
|
{
|
2018-11-08 22:05:12 +08:00
|
|
|
return clockadj_max_freq(CLOCK_REALTIME);
|
2013-04-04 23:28:23 +08:00
|
|
|
}
|
2013-04-08 21:44:10 +08:00
|
|
|
|
|
|
|
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");
|
|
|
|
}
|