Implement CLOCK_TAI

Provide a clock through clock_gettime() that returns the current TAI
time (UTC without leap seconds) as a complement to CLOCK_REALTIME. This
provides compatibility with Linux, which also provides a CLOCK_TAI since
kernel 2.6.26, and this seems to be becoming the standard way to acquire
TAI time. Unlike Linux, this code will return EINVAL if the TAI offset
(set by ntpd, ptpd, etc.) is not known since it seems pathological for
CLOCK_TAI to silently give the wrong (UTC) time if the offset is not
known as it does on Linux.

Reviewed by: imp
Differential Revision:	https://reviews.freebsd.org/D46268
This commit is contained in:
Nathan Whitehorn 2025-06-12 11:52:30 -06:00 committed by Warner Losh
parent 4deb9760a9
commit 7b7ba7857c
13 changed files with 77 additions and 26 deletions

View file

@ -210,6 +210,8 @@ Valid clock identifiers are a subset of those for
.It
.Dv CLOCK_SECOND
.It
.Dv CLOCK_TAI
.It
.Dv CLOCK_UPTIME
.It
.Dv CLOCK_UPTIME_FAST

View file

@ -27,7 +27,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd June 28, 2024
.Dd August 10, 2024
.Dt CLOCK_GETTIME 2
.Os
.Sh NAME
@ -99,11 +99,20 @@ query, using an in-kernel cached value of the current second.
Returns the execution time of the calling process.
.It Dv CLOCK_THREAD_CPUTIME_ID
Returns the execution time of the calling thread.
.It Dv CLOCK_TAI
Increments in SI seconds like a wall clock.
It uses a 1970 epoch and implements the TAI timescale.
Similar to CLOCK_REALTIME, but without leap seconds.
It will increase monotonically during a leap second.
Will return EINVAL if the current offset between TAI and UTC is not known,
which may be the case early in boot before NTP or other time daemon has
synchronized.
.El
.Pp
The clock IDs
.Dv CLOCK_BOOTTIME ,
.Dv CLOCK_REALTIME ,
.Dv CLOCK_TAI ,
.Dv CLOCK_MONOTONIC ,
and
.Dv CLOCK_UPTIME
@ -202,7 +211,8 @@ The clock IDs
.Dv CLOCK_MONOTONIC_PRECISE ,
.Dv CLOCK_REALTIME_FAST ,
.Dv CLOCK_REALTIME_PRECISE ,
.Dv CLOCK_SECOND
.Dv CLOCK_SECOND ,
.Dv CLOCK_TAI ,
.Dv CLOCK_UPTIME ,
.Dv CLOCK_UPTIME_FAST ,
and

View file

@ -27,7 +27,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd April 29, 2025
.Dd May 3, 2025
.Dt NANOSLEEP 2
.Os
.Sh NAME
@ -116,6 +116,8 @@ CLOCK_REALTIME_PRECISE
.It
CLOCK_SECOND
.It
CLOCK_TAI
.It
CLOCK_UPTIME
.It
CLOCK_UPTIME_FAST

View file

@ -126,7 +126,8 @@ the value of the timer ID.
This implementation supports a
.Fa clock_id
of
.Dv CLOCK_REALTIME
.Dv CLOCK_REALTIME ,
.Dv CLOCK_TAI ,
or
.Dv CLOCK_MONOTONIC .
.Pp

View file

@ -94,6 +94,7 @@ _pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id)
if (attr == NULL || *attr == NULL)
return (EINVAL);
if (clock_id != CLOCK_REALTIME &&
clock_id != CLOCK_TAI &&
clock_id != CLOCK_VIRTUAL &&
clock_id != CLOCK_PROF &&
clock_id != CLOCK_MONOTONIC) {

View file

@ -85,7 +85,8 @@ in
.Xr pthread_cond_timedwait 3
and may be set to
.Dv CLOCK_REALTIME
(default)
(default),
.Dv CLOCK_TAI ,
or
.Dv CLOCK_MONOTONIC .
.Pp

View file

@ -504,7 +504,7 @@ sys_ntp_adjtime(struct thread *td, struct ntp_adjtime_args *uap)
* simulation.
*/
void
ntp_update_second(int64_t *adjustment, time_t *newsec)
ntp_update_second(int64_t *adjustment, time_t *newsec, long *tai_off)
{
int tickrate;
l_fp ftemp; /* 32/64-bit temporary */
@ -624,6 +624,7 @@ ntp_update_second(int64_t *adjustment, time_t *newsec)
L_ADD(time_adj, ftemp);
}
*adjustment = time_adj;
*tai_off = time_tai;
#ifdef PPS_SYNC
if (pps_valid > 0)

View file

@ -72,6 +72,7 @@ struct timehands {
uint64_t th_scale;
u_int th_large_delta;
u_int th_offset_count;
long th_tai_offset;
struct bintime th_offset;
struct bintime th_bintime;
struct timeval th_microtime;
@ -1066,6 +1067,7 @@ sysclock_getsnapshot(struct sysclock_snap *clock_snap, int fast)
th = timehands;
gen = atomic_load_acq_int(&th->th_generation);
fbi->th_scale = th->th_scale;
fbi->th_tai_offset = th->th_tai_offset;
fbi->tick_time = th->th_offset;
#ifdef FFCLOCK
ffth = fftimehands;
@ -1139,6 +1141,11 @@ sysclock_snap2bintime(struct sysclock_snap *cs, struct bintime *bt,
getboottimebin(&boottimebin);
bintime_add(bt, &boottimebin);
}
if (!(flags & FBCLOCK_LEAPSEC)) {
if (cs->fb_info.th_tai_offset == 0)
return (EINVAL);
bt->sec += cs->fb_info.th_tai_offset;
}
break;
#ifdef FFCLOCK
case SYSCLOCK_FFWD:
@ -1433,7 +1440,8 @@ tc_windup(struct bintime *new_boottimebin)
do {
t = bt.sec;
ntp_update_second(&th->th_adjustment, &bt.sec);
ntp_update_second(&th->th_adjustment, &bt.sec,
&th->th_tai_offset);
if (bt.sec != t)
th->th_boottime.sec += bt.sec - t;
--i;

View file

@ -49,6 +49,7 @@
#include <sys/proc.h>
#include <sys/posix4.h>
#include <sys/time.h>
#include <sys/timeffc.h>
#include <sys/timers.h>
#include <sys/timetc.h>
#include <sys/vnode.h>
@ -60,7 +61,7 @@
#include <vm/vm_extern.h>
#include <vm/uma.h>
#define MAX_CLOCKS (CLOCK_MONOTONIC+1)
#define MAX_CLOCKS (CLOCK_TAI+1)
#define CPUCLOCK_BIT 0x80000000
#define CPUCLOCK_PROCESS_BIT 0x40000000
#define CPUCLOCK_ID_MASK (~(CPUCLOCK_BIT|CPUCLOCK_PROCESS_BIT))
@ -101,7 +102,6 @@ static int realtimer_gettime(struct itimer *, struct itimerspec *);
static int realtimer_settime(struct itimer *, int,
struct itimerspec *, struct itimerspec *);
static int realtimer_delete(struct itimer *);
static void realtimer_clocktime(clockid_t, struct timespec *);
static void realtimer_expire(void *);
static void realtimer_expire_l(struct itimer *it, bool proc_locked);
@ -318,7 +318,10 @@ int
kern_clock_gettime(struct thread *td, clockid_t clock_id, struct timespec *ats)
{
struct timeval sys, user;
struct sysclock_snap clk;
struct bintime bt;
struct proc *p;
int err;
p = td->td_proc;
switch (clock_id) {
@ -329,6 +332,14 @@ kern_clock_gettime(struct thread *td, clockid_t clock_id, struct timespec *ats)
case CLOCK_REALTIME_FAST:
getnanotime(ats);
break;
case CLOCK_TAI:
sysclock_getsnapshot(&clk, 0);
err = sysclock_snap2bintime(&clk, &bt, clk.sysclock_active,
(clk.sysclock_active == SYSCLOCK_FFWD) ? FFCLOCK_LERP : 0);
if (err)
return (err);
bintime2timespec(&bt, ats);
break;
case CLOCK_VIRTUAL:
PROC_LOCK(p);
PROC_STATLOCK(p);
@ -451,6 +462,7 @@ kern_clock_getres(struct thread *td, clockid_t clock_id, struct timespec *ts)
case CLOCK_REALTIME:
case CLOCK_REALTIME_FAST:
case CLOCK_REALTIME_PRECISE:
case CLOCK_TAI:
case CLOCK_MONOTONIC:
case CLOCK_MONOTONIC_FAST:
case CLOCK_MONOTONIC_PRECISE:
@ -516,6 +528,7 @@ kern_clock_nanosleep(struct thread *td, clockid_t clock_id, int flags,
return (EINVAL);
switch (clock_id) {
case CLOCK_REALTIME:
case CLOCK_TAI:
precise = nanosleep_precise;
is_abs_real = (flags & TIMER_ABSTIME) != 0;
break;
@ -1167,6 +1180,7 @@ itimer_start(void)
NULL, NULL, itimer_init, itimer_fini, UMA_ALIGN_PTR, 0);
register_posix_clock(CLOCK_REALTIME, &rt_clock);
register_posix_clock(CLOCK_MONOTONIC, &rt_clock);
register_posix_clock(CLOCK_TAI, &rt_clock);
p31b_setcfg(CTL_P1003_1B_TIMERS, 200112L);
p31b_setcfg(CTL_P1003_1B_DELAYTIMER_MAX, INT_MAX);
p31b_setcfg(CTL_P1003_1B_TIMER_MAX, TIMER_MAX);
@ -1327,6 +1341,7 @@ kern_ktimer_create(struct thread *td, clockid_t clock_id, struct sigevent *evp,
switch (clock_id) {
default:
case CLOCK_REALTIME:
case CLOCK_TAI:
it->it_sigev.sigev_signo = SIGALRM;
break;
case CLOCK_VIRTUAL:
@ -1570,10 +1585,14 @@ static int
realtimer_gettime(struct itimer *it, struct itimerspec *ovalue)
{
struct timespec cts;
int err;
mtx_assert(&it->it_mtx, MA_OWNED);
realtimer_clocktime(it->it_clockid, &cts);
err = kern_clock_gettime(curthread, it->it_clockid, &cts);
if (err)
return (err);
*ovalue = it->it_time;
if (ovalue->it_value.tv_sec != 0 || ovalue->it_value.tv_nsec != 0) {
timespecsub(&ovalue->it_value, &cts, &ovalue->it_value);
@ -1594,6 +1613,7 @@ realtimer_settime(struct itimer *it, int flags, struct itimerspec *value,
struct timespec cts, ts;
struct timeval tv;
struct itimerspec val;
int err;
mtx_assert(&it->it_mtx, MA_OWNED);
@ -1613,7 +1633,10 @@ realtimer_settime(struct itimer *it, int flags, struct itimerspec *value,
it->it_time = val;
if (timespecisset(&val.it_value)) {
realtimer_clocktime(it->it_clockid, &cts);
err = kern_clock_gettime(curthread, it->it_clockid, &cts);
if (err)
return (err);
ts = val.it_value;
if ((flags & TIMER_ABSTIME) == 0) {
/* Convert to absolute time. */
@ -1636,15 +1659,6 @@ realtimer_settime(struct itimer *it, int flags, struct itimerspec *value,
return (0);
}
static void
realtimer_clocktime(clockid_t id, struct timespec *ts)
{
if (id == CLOCK_REALTIME)
getnanotime(ts);
else /* CLOCK_MONOTONIC */
getnanouptime(ts);
}
int
itimer_accept(struct proc *p, int timerid, ksiginfo_t *ksi)
{
@ -1689,10 +1703,12 @@ realtimer_expire_l(struct itimer *it, bool proc_locked)
struct timeval tv;
struct proc *p;
uint64_t interval, now, overruns, value;
int err;
err = kern_clock_gettime(curthread, it->it_clockid, &cts);
realtimer_clocktime(it->it_clockid, &cts);
/* Only fire if time is reached. */
if (timespeccmp(&cts, &it->it_time.it_value, >=)) {
if (err == 0 && timespeccmp(&cts, &it->it_time.it_value, >=)) {
if (timespecisset(&it->it_time.it_interval)) {
timespecadd(&it->it_time.it_value,
&it->it_time.it_interval,

View file

@ -693,6 +693,7 @@ umtx_abs_timeout_init(struct umtx_abs_timeout *timo, int clockid,
timo->is_abs_real = clockid == CLOCK_REALTIME ||
clockid == CLOCK_REALTIME_FAST ||
clockid == CLOCK_REALTIME_PRECISE ||
clockid == CLOCK_TAI ||
clockid == CLOCK_SECOND;
}
}
@ -787,6 +788,7 @@ umtx_abs_timeout_getsbt(struct umtx_abs_timeout *timo, sbintime_t *sbt,
case CLOCK_PROF:
case CLOCK_THREAD_CPUTIME_ID:
case CLOCK_PROCESS_CPUTIME_ID:
case CLOCK_TAI: /* Boot time is not necessarily stable in TAI */
default:
kern_clock_gettime(curthread, timo->clockid, &timo->cur);
if (timespeccmp(&timo->end, &timo->cur, <=))
@ -2953,8 +2955,9 @@ do_cv_wait(struct thread *td, struct ucond *cv, struct umutex *m,
umtx_key_release(&uq->uq_key);
return (EFAULT);
}
if (clockid < CLOCK_REALTIME ||
clockid >= CLOCK_THREAD_CPUTIME_ID) {
if ((clockid < CLOCK_REALTIME ||
clockid >= CLOCK_THREAD_CPUTIME_ID) &&
clockid != CLOCK_TAI) {
/* hmm, only HW clock id will work. */
umtx_key_release(&uq->uq_key);
return (EINVAL);

View file

@ -74,6 +74,10 @@
#define CLOCK_PROCESS_CPUTIME_ID 15
#endif /* __POSIX_VISIBLE >= 199309 */
#ifdef __BSD_VISIBLE
#define CLOCK_TAI 16
#endif
/*
* Linux compatible names.
*/

View file

@ -89,7 +89,7 @@ extern int sysclock_active;
* of the kernel tick timer (1/hz [s]).
* FFCLOCK_LERP: Linear interpolation of ffclock time to guarantee
* monotonic time.
* FFCLOCK_LEAPSEC: Include leap seconds.
* {FB|FF}CLOCK_LEAPSEC: Include leap seconds.
* {FB|FF}CLOCK_UPTIME: Time stamp should be relative to system boot, not epoch.
*/
#define FFCLOCK_FAST 0x00000001
@ -100,6 +100,7 @@ extern int sysclock_active;
#define FBCLOCK_FAST 0x00010000 /* Currently unused. */
#define FBCLOCK_UPTIME 0x00020000
#define FBCLOCK_LEAPSEC 0x00040000
#define FBCLOCK_MASK 0xffff0000
/*
@ -111,6 +112,7 @@ struct fbclock_info {
struct bintime error;
struct bintime tick_time;
uint64_t th_scale;
long th_tai_offset;
int status;
};

View file

@ -154,7 +154,7 @@ struct timex {
#ifdef __FreeBSD__
#ifdef _KERNEL
void ntp_update_second(int64_t *adjustment, time_t *newsec);
void ntp_update_second(int64_t *adjustment, time_t *newsec, long *tai_off);
#else /* !_KERNEL */
#include <sys/cdefs.h>