amiga-ntimed/time_unix.c

258 lines
6.4 KiB
C

/*-
* Copyright (c) 2014 Poul-Henning Kamp
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* UNIX timebase
* =============
*
* Implement the timebase functions on top of a modern UNIX kernel which
* has the some version of the Mills/Kamp kernel PLL code and either
* [gs]ettimeofday(2) or better: clock_[gs]ettime(2) API.
*
*/
#include "ntimed_platform.h"
#include "ntimed.h"
static double adj_offset = 0;
static double adj_duration = 0;
static double adj_freq = 0;
static uintptr_t ticker;
static struct todolist *kt_tdl;
// #undef CLOCK_REALTIME /* Test old unix code */
/**********************************************************************
* The NTP-pll in UNIX kernels apply the offset correction in an
* exponential-decay fashion for historical and wrong reasons.
*
* The short explanation is that this ends up confusing all PLLs I have
* ever seen, by introducing mainly odd harmonics of the PLL update period
* into all time-measurements in the system.
*
* A much more sane mode would be to tell the kernel "I want this much
* offset accumulated over this many seconds", giving a constant frequency
* over the PLL update period while still falling back to the frequency
* estimate should the time-steering userland process fail.
*
* I will add such a mode to the FreeBSD kernel as a reference implementation
* at a later date, in the mean time this code implements it by updating the
* kernel frequency from userland as needed.
*
* XXX: Optimise to only wake up when truly needed, rather than every second.
* XXX: Requires TODO cancellation.
*/
static void
kt_setfreq(struct ocx *ocx, double frequency)
{
/*
struct timex tx;
int i;
assert(isfinite(frequency));
memset(&tx, 0, sizeof tx);
tx.modes = MOD_STATUS;
#if defined(MOD_NANO)
tx.modes |= MOD_NANO;
#elif defined(MOD_MICRO)
tx.modes |= MOD_MICRO;
#endif
tx.status = STA_PLL | STA_FREQHOLD;
tx.modes = MOD_FREQUENCY;
tx.freq = (long)floor(frequency * (65536 * 1e6));
errno = 0;
i = ntp_adjtime(&tx);
*/
Put(ocx, OCX_TRACE, "KERNPLL %.6e %d\n", frequency, 0);
/* XXX: what is the correct error test here ? */
// assert(i >= 0);
}
static enum todo_e __match_proto__(todo_f)
kt_ticker(struct ocx *ocx, struct todolist *tdl, void *priv)
{
(void)ocx;
AN(tdl);
AZ(priv);
kt_setfreq(ocx, adj_freq);
ticker = 0;
return (TODO_OK);
}
static void __match_proto__(tb_adjust_f)
kt_adjust(struct ocx *ocx, double offset, double duration, double frequency)
{
double freq;
(void)ocx;
assert(duration >= 0.0);
if (ticker)
TODO_Cancel(kt_tdl, &ticker);
adj_offset = offset;
adj_duration = floor(duration);
if (adj_offset > 0.0 && adj_duration == 0.0)
adj_duration = 1.0;
adj_freq = frequency;
freq = adj_freq;
if (adj_duration > 0.0)
freq += adj_offset / adj_duration;
kt_setfreq(ocx, freq);
if (adj_duration > 0.0)
ticker = TODO_ScheduleRel(kt_tdl, kt_ticker, NULL,
adj_duration, 0.0, "KT_TICK");
}
/**********************************************************************/
#ifdef CLOCK_REALTIME
static void __match_proto__(tb_step_f)
kt_step(struct ocx *ocx, double offset)
{
double d;
struct timespec ts;
Put(ocx, OCX_TRACE, "KERNTIME_STEP %.3e\n", offset);
d = floor(offset);
offset -= d;
AZ(clock_gettime(CLOCK_REALTIME, &ts));
ts.tv_sec += (long)d;
ts.tv_nsec += (long)floor(offset * 1e9);
if (ts.tv_nsec < 0) {
ts.tv_sec -= 1;
ts.tv_nsec += 1000000000;
} else if (ts.tv_nsec >= 1000000000) {
ts.tv_sec += 1;
ts.tv_nsec -= 1000000000;
}
AZ(clock_settime(CLOCK_REALTIME, &ts));
TB_generation++;
}
#else
static void __match_proto__(tb_step_f)
kt_step(struct ocx *ocx, double offset)
{
double d;
struct timeval tv;
Put(ocx, OCX_TRACE, "KERNTIME_STEP %.3e\n", offset);
d = floor(offset);
offset -= d;
AZ(gettimeofday(&tv, NULL));
tv.tv_sec += (long)d;
tv.tv_usec += (long)floor(offset * 1e6);
if (tv.tv_usec < 0) {
tv.tv_sec -= 1;
tv.tv_usec += 1000000;
} else if (tv.tv_usec >= 1000000) {
tv.tv_sec += 1;
tv.tv_usec -= 1000000;
}
AZ(settimeofday(&tv, NULL));
TB_generation++;
}
#endif
/**********************************************************************/
#if defined (CLOCK_REALTIME)
static struct timestamp * __match_proto__(tb_now_f)
kt_now(struct timestamp *storage)
{
struct timespec ts;
AZ(clock_gettime(CLOCK_REALTIME, &ts));
return (TS_Nanosec(storage, ts.tv_sec, ts.tv_nsec));
}
#else
static struct timestamp * __match_proto__(tb_now_f)
kt_now(struct timestamp *storage)
{
struct timeval tv;
AZ(gettimeofday(&tv, NULL));
return (TS_Nanosec(storage, tv.tv_sec, tv.tv_usec * 1000LL));
}
#endif
/**********************************************************************/
static int __match_proto__(tb_sleep_f)
kt_sleep(double dur)
{
struct pollfd fds[1];
int i;
i = poll(fds, 0, (int)floor(dur * 1e3));
if (i < 0 && errno == EINTR)
return (1);
AZ(i);
return (0);
}
/**********************************************************************/
void
Time_Unix(struct todolist *tdl)
{
AN(tdl);
TB_Step = kt_step;
TB_Adjust = kt_adjust;
TB_Sleep = kt_sleep;
TB_Now = kt_now;
kt_tdl = tdl;
/* XXX: test if we have perms */
}
/**********************************************************************
* Non-tweaking subset.
*/
void
Time_Unix_Passive(void)
{
TB_Sleep = kt_sleep;
TB_Now = kt_now;
}