Estimate offset correction error in Linux driver
This commit is contained in:
parent
0f70959d8e
commit
fd375ca55b
1 changed files with 170 additions and 54 deletions
224
sys_linux.c
224
sys_linux.c
|
@ -189,6 +189,99 @@ static int slew_delta_tick;
|
||||||
(sys_adjtimex() in the kernel bounds this to 10%) */
|
(sys_adjtimex() in the kernel bounds this to 10%) */
|
||||||
static int max_tick_bias;
|
static int max_tick_bias;
|
||||||
|
|
||||||
|
/* The latest time at which system clock may still be slewed by previous
|
||||||
|
adjtime() call and maximum offset correction error it can cause */
|
||||||
|
static struct timeval slow_slew_error_end;
|
||||||
|
static int slow_slew_error;
|
||||||
|
|
||||||
|
/* The latest time at which 'tick' in kernel may be actually updated
|
||||||
|
and maximum offset correction error it can cause */
|
||||||
|
static struct timeval fast_slew_error_end;
|
||||||
|
static double fast_slew_error;
|
||||||
|
|
||||||
|
/* The rate at which frequency and tick values are updated in kernel. */
|
||||||
|
static int tick_update_hz;
|
||||||
|
|
||||||
|
/* ================================================== */
|
||||||
|
/* These routines are used to estimate maximum error in offset correction */
|
||||||
|
|
||||||
|
static void
|
||||||
|
update_slow_slew_error(int offset)
|
||||||
|
{
|
||||||
|
struct timezone tz;
|
||||||
|
struct timeval now, newend;
|
||||||
|
|
||||||
|
if (offset == 0 && slow_slew_error == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (gettimeofday(&now, &tz) < 0) {
|
||||||
|
CROAK("gettimeofday() failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < 0)
|
||||||
|
offset = -offset;
|
||||||
|
|
||||||
|
/* assume 500ppm rate and one sec delay, plus 10 percent for fast slewing */
|
||||||
|
UTI_AddDoubleToTimeval(&now, (offset + 999) / 500 * 1.1, &newend);
|
||||||
|
|
||||||
|
if (offset > 500)
|
||||||
|
offset = 500;
|
||||||
|
|
||||||
|
if (slow_slew_error > offset) {
|
||||||
|
double previous_left;
|
||||||
|
|
||||||
|
UTI_DiffTimevalsToDouble(&previous_left, &slow_slew_error_end, &now);
|
||||||
|
if (previous_left > 0.0) {
|
||||||
|
if (offset == 0)
|
||||||
|
newend = slow_slew_error_end;
|
||||||
|
offset = slow_slew_error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slow_slew_error = offset;
|
||||||
|
slow_slew_error_end = newend;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double
|
||||||
|
get_slow_slew_error(struct timeval *now)
|
||||||
|
{
|
||||||
|
double left;
|
||||||
|
|
||||||
|
if (slow_slew_error == 0)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
UTI_DiffTimevalsToDouble(&left, &slow_slew_error_end, now);
|
||||||
|
return left > 0.0 ? slow_slew_error / 1e6 : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
update_fast_slew_error(struct timeval *now)
|
||||||
|
{
|
||||||
|
double max_tick;
|
||||||
|
|
||||||
|
max_tick = current_total_tick +
|
||||||
|
(delta_total_tick > 0.0 ? delta_total_tick : 0.0);
|
||||||
|
|
||||||
|
UTI_AddDoubleToTimeval(now, 1e6 * max_tick / nominal_tick / tick_update_hz,
|
||||||
|
&fast_slew_error_end);
|
||||||
|
fast_slew_error = fabs(1e6 * delta_total_tick / nominal_tick / tick_update_hz);
|
||||||
|
}
|
||||||
|
|
||||||
|
static double
|
||||||
|
get_fast_slew_error(struct timeval *now)
|
||||||
|
{
|
||||||
|
double left;
|
||||||
|
|
||||||
|
if (fast_slew_error == 0.0)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
UTI_DiffTimevalsToDouble(&left, &fast_slew_error_end, now);
|
||||||
|
if (left < -10.0)
|
||||||
|
fast_slew_error = 0.0;
|
||||||
|
|
||||||
|
return left > 0.0 ? fast_slew_error : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ================================================== */
|
/* ================================================== */
|
||||||
/* This routine stops a fast slew, determines how long the slew has
|
/* This routine stops a fast slew, determines how long the slew has
|
||||||
been running for, and consequently how much adjustment has actually
|
been running for, and consequently how much adjustment has actually
|
||||||
|
@ -198,12 +291,10 @@ static int max_tick_bias;
|
||||||
static void
|
static void
|
||||||
stop_fast_slew(void)
|
stop_fast_slew(void)
|
||||||
{
|
{
|
||||||
struct timeval T1, T1d, T1a;
|
struct timeval T1;
|
||||||
struct timezone tz;
|
struct timezone tz;
|
||||||
double end_window;
|
|
||||||
double fast_slew_done;
|
double fast_slew_done;
|
||||||
double slew_duration;
|
double slew_duration;
|
||||||
double introduced_dispersion;
|
|
||||||
|
|
||||||
/* Should never get here unless this is true */
|
/* Should never get here unless this is true */
|
||||||
if (!fast_slewing) {
|
if (!fast_slewing) {
|
||||||
|
@ -218,24 +309,17 @@ stop_fast_slew(void)
|
||||||
if (TMX_SetTick(current_tick) < 0) {
|
if (TMX_SetTick(current_tick) < 0) {
|
||||||
CROAK("adjtimex() failed in stop_fast_slew");
|
CROAK("adjtimex() failed in stop_fast_slew");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gettimeofday(&T1d, &tz) < 0) {
|
|
||||||
CROAK("gettimeofday() failed in stop_fast_slew");
|
|
||||||
}
|
|
||||||
|
|
||||||
fast_slewing = 0;
|
fast_slewing = 0;
|
||||||
|
|
||||||
UTI_AverageDiffTimevals(&T1, &T1d, &T1a, &end_window);
|
UTI_DiffTimevalsToDouble(&slew_duration, &T1, &slew_start_tv);
|
||||||
UTI_DiffTimevalsToDouble(&slew_duration, &T1a, &slew_start_tv);
|
|
||||||
|
|
||||||
/* Compute the dispersion we have introduced by changing tick this
|
/* Compute the dispersion we have introduced by changing tick this
|
||||||
way. If the two samples of gettimeofday differ, there is an
|
way. We handle this by adding dispersion to all statistics held
|
||||||
uncertainty window wrt when the frequency change actually applies
|
|
||||||
from. We handle this by adding dispersion to all statistics held
|
|
||||||
at higher levels in the system. */
|
at higher levels in the system. */
|
||||||
|
|
||||||
introduced_dispersion = end_window * delta_total_tick;
|
update_fast_slew_error(&T1);
|
||||||
lcl_InvokeDispersionNotifyHandlers(introduced_dispersion);
|
lcl_InvokeDispersionNotifyHandlers(fast_slew_error);
|
||||||
|
|
||||||
fast_slew_done = delta_total_tick * slew_duration /
|
fast_slew_done = delta_total_tick * slew_duration /
|
||||||
(current_total_tick + delta_total_tick);
|
(current_total_tick + delta_total_tick);
|
||||||
|
@ -244,6 +328,40 @@ stop_fast_slew(void)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ================================================== */
|
||||||
|
/* This routine reschedules fast slew timeout after frequency was changed */
|
||||||
|
|
||||||
|
static void
|
||||||
|
adjust_fast_slew(double old_tick, double old_delta_tick)
|
||||||
|
{
|
||||||
|
struct timeval tv, end_of_slew;
|
||||||
|
struct timezone tz;
|
||||||
|
double fast_slew_done, slew_duration, dseconds;
|
||||||
|
|
||||||
|
assert(fast_slewing);
|
||||||
|
|
||||||
|
if (gettimeofday(&tv, &tz) < 0) {
|
||||||
|
CROAK("gettimeofday() failed in stop_fast_slew");
|
||||||
|
}
|
||||||
|
|
||||||
|
UTI_DiffTimevalsToDouble(&slew_duration, &tv, &slew_start_tv);
|
||||||
|
|
||||||
|
fast_slew_done = old_delta_tick * slew_duration / (old_tick + old_delta_tick);
|
||||||
|
offset_register += fast_slew_wanted + fast_slew_done;
|
||||||
|
|
||||||
|
dseconds = -offset_register * (current_total_tick + delta_total_tick) / delta_total_tick;
|
||||||
|
|
||||||
|
if (dseconds > 3600 * 24 * 7)
|
||||||
|
dseconds = 3600 * 24 * 7;
|
||||||
|
UTI_AddDoubleToTimeval(&tv, dseconds, &end_of_slew);
|
||||||
|
|
||||||
|
slew_start_tv = tv;
|
||||||
|
fast_slew_wanted = offset_register;
|
||||||
|
offset_register = 0.0;
|
||||||
|
|
||||||
|
SCH_RemoveTimeout(slew_timeout_id);
|
||||||
|
slew_timeout_id = SCH_AddTimeout(&end_of_slew, handle_end_of_slew, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
/* ================================================== */
|
/* ================================================== */
|
||||||
/* This routine is called to start a clock offset adjustment */
|
/* This routine is called to start a clock offset adjustment */
|
||||||
|
@ -254,11 +372,9 @@ initiate_slew(void)
|
||||||
double dseconds;
|
double dseconds;
|
||||||
long tick_adjust;
|
long tick_adjust;
|
||||||
long offset;
|
long offset;
|
||||||
struct timeval T0, T0d, T0a;
|
struct timeval T0;
|
||||||
struct timeval end_of_slew;
|
struct timeval end_of_slew;
|
||||||
struct timezone tz;
|
struct timezone tz;
|
||||||
double start_window;
|
|
||||||
double introduced_dispersion;
|
|
||||||
|
|
||||||
/* Don't want to get here if we already have an adjust on the go! */
|
/* Don't want to get here if we already have an adjust on the go! */
|
||||||
if (fast_slewing) {
|
if (fast_slewing) {
|
||||||
|
@ -277,6 +393,7 @@ initiate_slew(void)
|
||||||
}
|
}
|
||||||
offset_register -= (double) offset / 1.0e6;
|
offset_register -= (double) offset / 1.0e6;
|
||||||
slow_slewing = 0;
|
slow_slewing = 0;
|
||||||
|
update_slow_slew_error(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fabs(offset_register) < MAX_ADJUST_WITH_ADJTIME) {
|
if (fabs(offset_register) < MAX_ADJUST_WITH_ADJTIME) {
|
||||||
|
@ -290,6 +407,7 @@ initiate_slew(void)
|
||||||
CROAK("adjtimex() failed in initiate_slew");
|
CROAK("adjtimex() failed in initiate_slew");
|
||||||
}
|
}
|
||||||
slow_slewing = 1;
|
slow_slewing = 1;
|
||||||
|
update_slow_slew_error(offset);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
@ -332,31 +450,20 @@ initiate_slew(void)
|
||||||
CROAK("adjtimex() failed to start big slew");
|
CROAK("adjtimex() failed to start big slew");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gettimeofday(&T0d, &tz) < 0) {
|
|
||||||
CROAK("gettimeofday() failed in initiate_slew");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Now work out the uncertainty in when we actually started the
|
|
||||||
slew. */
|
|
||||||
|
|
||||||
UTI_AverageDiffTimevals(&T0, &T0d, &T0a, &start_window);
|
|
||||||
|
|
||||||
/* Compute the dispersion we have introduced by changing tick this
|
/* Compute the dispersion we have introduced by changing tick this
|
||||||
way. If the two samples of gettimeofday differ, there is an
|
way. We handle this by adding dispersion to all statistics held
|
||||||
uncertainty window wrt when the frequency change actually applies
|
|
||||||
from. We handle this by adding dispersion to all statistics held
|
|
||||||
at higher levels in the system. */
|
at higher levels in the system. */
|
||||||
|
|
||||||
introduced_dispersion = start_window * delta_total_tick;
|
update_fast_slew_error(&T0);
|
||||||
lcl_InvokeDispersionNotifyHandlers(introduced_dispersion);
|
lcl_InvokeDispersionNotifyHandlers(fast_slew_error);
|
||||||
|
|
||||||
fast_slewing = 1;
|
fast_slewing = 1;
|
||||||
slew_start_tv = T0a;
|
slew_start_tv = T0;
|
||||||
|
|
||||||
/* Set up timeout for end of slew, limit to one week */
|
/* Set up timeout for end of slew, limit to one week */
|
||||||
if (dseconds > 3600 * 24 * 7)
|
if (dseconds > 3600 * 24 * 7)
|
||||||
dseconds = 3600 * 24 * 7;
|
dseconds = 3600 * 24 * 7;
|
||||||
UTI_AddDoubleToTimeval(&T0a, dseconds, &end_of_slew);
|
UTI_AddDoubleToTimeval(&T0, dseconds, &end_of_slew);
|
||||||
|
|
||||||
slew_timeout_id = SCH_AddTimeout(&end_of_slew, handle_end_of_slew, NULL);
|
slew_timeout_id = SCH_AddTimeout(&end_of_slew, handle_end_of_slew, NULL);
|
||||||
|
|
||||||
|
@ -448,24 +555,17 @@ apply_step_offset(double offset)
|
||||||
clock runs fast when uncompensated. */
|
clock runs fast when uncompensated. */
|
||||||
|
|
||||||
static void
|
static void
|
||||||
set_frequency(double freq_ppm) {
|
set_frequency(double freq_ppm)
|
||||||
|
{
|
||||||
long required_tick;
|
long required_tick;
|
||||||
double required_freq; /* what we use */
|
double required_freq; /* what we use */
|
||||||
double scaled_freq; /* what adjtimex & the kernel use */
|
double scaled_freq; /* what adjtimex & the kernel use */
|
||||||
|
double old_total_tick;
|
||||||
int required_delta_tick;
|
int required_delta_tick;
|
||||||
int neg; /* True if estimate is that local clock runs slow,
|
int neg; /* True if estimate is that local clock runs slow,
|
||||||
i.e. positive frequency correction required */
|
i.e. positive frequency correction required */
|
||||||
|
|
||||||
|
|
||||||
/* If we in the middle of slewing the time by having the value of
|
|
||||||
tick altered, we have to stop doing that, because the timeout
|
|
||||||
expiry etc will change if we don't. */
|
|
||||||
|
|
||||||
if (fast_slewing) {
|
|
||||||
abort_slew();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (freq_ppm < 0.0) {
|
if (freq_ppm < 0.0) {
|
||||||
neg = 1;
|
neg = 1;
|
||||||
freq_ppm = -freq_ppm;
|
freq_ppm = -freq_ppm;
|
||||||
|
@ -486,20 +586,28 @@ set_frequency(double freq_ppm) {
|
||||||
scaled_freq = -freq_scale * required_freq;
|
scaled_freq = -freq_scale * required_freq;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TMX_SetFrequency(scaled_freq, required_tick) < 0) {
|
|
||||||
char buffer[1024];
|
|
||||||
sprintf(buffer, "adjtimex failed for set_frequency, freq_ppm=%10.4e scaled_freq=%10.4e required_tick=%ld",
|
|
||||||
freq_ppm, scaled_freq, required_tick);
|
|
||||||
CROAK(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
current_tick = required_tick;
|
current_tick = required_tick;
|
||||||
|
old_total_tick = current_total_tick;
|
||||||
current_total_tick = ((double)current_tick + required_freq/dhz) / 1.0e6 ;
|
current_total_tick = ((double)current_tick + required_freq/dhz) / 1.0e6 ;
|
||||||
|
|
||||||
initiate_slew(); /* Restart any slews that need to be restarted */
|
/* Don't change tick if we are fast slewing, just reschedule the timeout */
|
||||||
|
if (fast_slewing) {
|
||||||
|
required_tick = slewing_tick;
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
if (TMX_SetFrequency(scaled_freq, required_tick) < 0) {
|
||||||
|
LOG_FATAL(LOGF_SysLinux, "adjtimex failed for set_frequency, freq_ppm=%10.4e scaled_freq=%10.4e required_tick=%ld",
|
||||||
|
freq_ppm, scaled_freq, required_tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fast_slewing) {
|
||||||
|
double old_delta_tick;
|
||||||
|
|
||||||
|
old_delta_tick = delta_total_tick;
|
||||||
|
delta_total_tick = ((double)slewing_tick + required_freq/dhz) / 1.0e6 -
|
||||||
|
current_total_tick;
|
||||||
|
adjust_fast_slew(old_total_tick, old_delta_tick);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ================================================== */
|
/* ================================================== */
|
||||||
|
@ -590,6 +698,8 @@ again:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_slow_slew_error(offset);
|
||||||
|
|
||||||
adjtime_left = (double)offset / 1.0e6;
|
adjtime_left = (double)offset / 1.0e6;
|
||||||
|
|
||||||
if (fast_slewing) {
|
if (fast_slewing) {
|
||||||
|
@ -602,7 +712,7 @@ again:
|
||||||
}
|
}
|
||||||
|
|
||||||
*corr = - (offset_register + fast_slew_remaining) + adjtime_left;
|
*corr = - (offset_register + fast_slew_remaining) + adjtime_left;
|
||||||
*err = 0.0;
|
*err = get_slow_slew_error(raw) + get_fast_slew_error(raw);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -701,6 +811,7 @@ get_version_specific_details(void)
|
||||||
nominal_tick = (1000000L + (hz/2))/hz; /* Mirror declaration in kernel */
|
nominal_tick = (1000000L + (hz/2))/hz; /* Mirror declaration in kernel */
|
||||||
slew_delta_tick = nominal_tick / 12;
|
slew_delta_tick = nominal_tick / 12;
|
||||||
max_tick_bias = nominal_tick / 10;
|
max_tick_bias = nominal_tick / 10;
|
||||||
|
tick_update_hz = hz;
|
||||||
|
|
||||||
LOG(LOGS_INFO, LOGF_SysLinux, "set_config_hz=%d hz=%d shift_hz=%d basic_freq_scale=%.8f nominal_tick=%d slew_delta_tick=%d max_tick_bias=%d",
|
LOG(LOGS_INFO, LOGF_SysLinux, "set_config_hz=%d hz=%d shift_hz=%d basic_freq_scale=%.8f nominal_tick=%d slew_delta_tick=%d max_tick_bias=%d",
|
||||||
set_config_hz, hz, shift_hz, basic_freq_scale, nominal_tick, slew_delta_tick, max_tick_bias);
|
set_config_hz, hz, shift_hz, basic_freq_scale, nominal_tick, slew_delta_tick, max_tick_bias);
|
||||||
|
@ -815,6 +926,11 @@ get_version_specific_details(void)
|
||||||
/* These don't need scaling */
|
/* These don't need scaling */
|
||||||
freq_scale = 1.0;
|
freq_scale = 1.0;
|
||||||
have_readonly_adjtime = 2;
|
have_readonly_adjtime = 2;
|
||||||
|
if (minor == 6 && patch < 33) {
|
||||||
|
/* Tickless kernels before 2.6.33 accumulated ticks only in
|
||||||
|
half-second intervals. */
|
||||||
|
tick_update_hz = 2;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
LOG_FATAL(LOGF_SysLinux, "Kernel version not supported yet, sorry.");
|
LOG_FATAL(LOGF_SysLinux, "Kernel version not supported yet, sorry.");
|
||||||
|
|
Loading…
Reference in a new issue