opnsense-src/sys/sys/timetc.h
Colin Percival c2705ceaeb x86: Speed up clock calibration
Prior to this commit, the TSC and local APIC frequencies were calibrated
at boot time by measuring the clocks before and after a one-second sleep.
This was simple and effective, but had the disadvantage of *requiring a
one-second sleep*.

Rather than making two clock measurements (before and after sleeping) we
now perform many measurements; and rather than simply subtracting the
starting count from the ending count, we calculate a best-fit regression
between the target clock and the reference clock (for which the current
best available timecounter is used). While we do this, we keep track
of an estimate of the uncertainty in the regression slope (aka. the ratio
of clock speeds), and stop measuring when we believe the uncertainty is
less than 1 PPM.

In order to avoid the risk of aliasing resulting from the data-gathering
loop synchronizing with (a multiple of) the frequency of the reference
clock, we add some additional spinning depending upon the iteration number.

For numerical stability and simplicity of implementation, we make use of
floating-point arithmetic for the statistical calculations.

On the author's Dell laptop, this reduces the time spent in calibration
from 2000 ms to 29 ms; on an EC2 c5.xlarge instance, it is reduced from
2000 ms to 2.5 ms.

Reviewed by:	bde (previous version), kib
MFC after:	1 month
Sponsored by:	https://www.patreon.com/cperciva
Differential Revision:	https://reviews.freebsd.org/D33802
2022-01-12 12:34:07 -08:00

106 lines
3.4 KiB
C

/*-
* SPDX-License-Identifier: Beerware
*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <phk@FreeBSD.ORG> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
* ----------------------------------------------------------------------------
*
* $FreeBSD$
*/
#ifndef _SYS_TIMETC_H_
#define _SYS_TIMETC_H_
#ifndef _KERNEL
#error "no user-serviceable parts inside"
#endif
/*-
* `struct timecounter' is the interface between the hardware which implements
* a timecounter and the MI code which uses this to keep track of time.
*
* A timecounter is a binary counter which has two properties:
* * it runs at a fixed, known frequency.
* * it has sufficient bits to not roll over in less than approximately
* max(2 msec, 2/HZ seconds). (The value 2 here is really 1 + delta,
* for some indeterminate value of delta.)
*/
struct timecounter;
struct vdso_timehands;
struct vdso_timehands32;
typedef u_int timecounter_get_t(struct timecounter *);
typedef void timecounter_pps_t(struct timecounter *);
typedef uint32_t timecounter_fill_vdso_timehands_t(struct vdso_timehands *,
struct timecounter *);
typedef uint32_t timecounter_fill_vdso_timehands32_t(struct vdso_timehands32 *,
struct timecounter *);
struct timecounter {
timecounter_get_t *tc_get_timecount;
/*
* This function reads the counter. It is not required to
* mask any unimplemented bits out, as long as they are
* constant.
*/
timecounter_pps_t *tc_poll_pps;
/*
* This function is optional. It will be called whenever the
* timecounter is rewound, and is intended to check for PPS
* events. Normal hardware does not need it but timecounters
* which latch PPS in hardware (like sys/pci/xrpu.c) do.
*/
u_int tc_counter_mask;
/* This mask should mask off any unimplemented bits. */
uint64_t tc_frequency;
/* Frequency of the counter in Hz. */
const char *tc_name;
/* Name of the timecounter. */
int tc_quality;
/*
* Used to determine if this timecounter is better than
* another timecounter higher means better. Negative
* means "only use at explicit request".
*/
u_int tc_flags;
#define TC_FLAGS_C2STOP 1 /* Timer dies in C2+. */
#define TC_FLAGS_SUSPEND_SAFE 2 /*
* Timer functional across
* suspend/resume.
*/
void *tc_priv;
/* Pointer to the timecounter's private parts. */
struct timecounter *tc_next;
/* Pointer to the next timecounter. */
timecounter_fill_vdso_timehands_t *tc_fill_vdso_timehands;
timecounter_fill_vdso_timehands32_t *tc_fill_vdso_timehands32;
};
extern struct timecounter *timecounter;
extern int tc_min_ticktock_freq; /*
* Minimal tc_ticktock() call frequency,
* required to handle counter wraps.
*/
u_int64_t tc_getfrequency(void);
void tc_init(struct timecounter *tc);
void tc_setclock(struct timespec *ts);
void tc_ticktock(int cnt);
void cpu_tick_calibration(void);
#ifdef SYSCTL_DECL
SYSCTL_DECL(_kern_timecounter);
#endif
/**
* clockcalib(clk, clkname):
* Return the frequency of the provided timer, as calibrated against the
* current best-available timecounter.
*/
uint64_t clockcalib(uint64_t (*)(void), const char *);
#endif /* !_SYS_TIMETC_H_ */