Subject: Time Synchronisation
To: None <tech-kern@sun-lamp.cs.berkeley.edu>
From: None <davidb@melb.cpr.itg.telecom.com.au>
List: tech-kern
Date: 12/16/1993 18:35:45
		Time Synchronisation in NetBSD

			David Burren
		davidb@melb.cpr.itg.telecom.com.au
			December 1993


1. Introduction
   ============

In BSD ports there are typically two "real-time" clocks:  what I will
refer to as the "softclock" (referenced by the timeval "time"); and what
I will refer to as the RTC (the battery-backed clock maintained in
hardware).

The maintenance and synchronisation of these clocks is currently rather
simple-minded.  While it handles the most common scenarios, I have
encountered some that can (and do) cause problems.

This document is split into the following sections:

	2. The Current Clock-Management Scheme
	3. Where This Doesn't Work
	4. How To Fix It
	5. Changes Required To The Port-Specific Code
	6. Summary

As will be seen, section 6 outlines the choices we have to make from
here.  Some form of kernel interface is required, and the only real
choice is of the format of that interface.

This document is to provide a background for these choices, and to invite
comment from interested parties before I change anything further.
I started a discussion about this some time ago, but a lot of things have
happened since then, and I believe this document should be a little clearer
than the last.


2. The Current Clock-Management Scheme
   ===================================

Two architecture-specific functions in clock.c provide the low-level
interface:

inittodr()
	This reads the RTC (if present) and typically  uses that time in
	conjunction with the time extracted from the root superblock to
	initialise the softclock.
	inittodr() is invoked at kernel initialisation.

resettodr()
	This typically writes the RTC with the current value of the softclock.
	resettodr() is called from settimeofday() and typically also from the
	boot() function (prior to halt/reboot).

After inittodr() sets the softclock, it and the RTC run in parallel.
At settimeofday() the two clocks are synchronised again, but otherwise
adjustments made to the softclock through adjtime() are not reflected in
the RTC until the system is shutdown and resettodr() is invoked.

In cases where machines sync their time to an external (network) clock
through timed or NTP, this scheme works as expected.  The RTC is simply
used to initialise the softclock at boot time.  However, where machines
are isolated from external time sources several flaws arise.


3. Where This Doesn't Work
   =======================

Typically the RTC hardware is of higher accuracy (but lower precision) than
the softclock.  On some platforms this is due to softclock interrupts being
lost occasionally, on others due simply to the resolution of the interrupt
period.  In an extreme but not uncommon case, a machine (typically a
notebook PC) may have its softclock suspended for a time.  Some machines
have a console halt function that has the same side-effect.

Thus without external synchronisation the softclock can drift from the RTC,
in some cases by hours.  When the machine is shut down, resettodr() will
save this (incorrect) time back to the RTC, exacerbating the problem.

We can either leave the administrator with the responsibility of checking
and setting the time before shutting down or after "unfreezing", or provide
a mechanism to correct the problem automagically.  In the former case one
might as well do without the RTC altogether...


4. How To Fix It
   =============

What is required is a mechanism that allows the softclock to be resynced
to the RTC as appropriate.  This can either be done in kernel space or
user space:

kernel space
	I understand SunOS implements this by simply syncing the softclock
	to the RTC periodically.  The UofT BSD4.2 port does the same on
	the ICM-3216, running an rtc_check() every 30 seconds.

	While this can be done quite simply, by setting the softclock to
	the RTC periodically, that is only acceptable if the softclock is
	slower than the RTC (the common case) and if the difference in the
	clocks is slight and gradual (not the case when a machine is
	suspended).
	An internal adjtime() interface is required to cope with this.

	As this can conflict with user-level processes manipulating the
	clock (eg. timed, xntpd) some mechanism is required to enable/disable
	the in-kernel syncing.  Under SunOS this is done by manipulating
	"synctodr" through /dev/kmem.

	If in-kernel syncing is implemented in *BSD, I suggest it should be
	*off* by default.  A mechanism to manipulate it would be open for
	discussion, as many regard Sun's synctodr as an ugly hack.

user space
	Two new user-level interfaces are required: one to read the current
	RTC time, and one to set the softclock without calling resettodr().
	A user-level task reads the RTC and calls adjtime() or
	settimeofday-without-resettodr() to resync the clocks.

	This can either be a special-purpose daemon or a function within
	timed/ntpd to provide seamless operation (the RTC can be classed
	as a high-stratum clock in NTP).

	I have been doing something similar under NetBSD/i386 for some time
	on my notebook, although I'm not especially happy with the interface
	I'm using (I got it going and left it).

	The interfaces to the user-level functions (which should pass
	timevals) would need to be agreed upon.  As these functions would
	exist on all *BSD architectures, new system calls akin to
	{get,set}timeofday may be appropriate, although I'm sure that
	suggestion will lead to argument.

There are pros and cons for each approach.


5. Changes Required To The Port-Specific Code (Porters Take Note)
   ==============================================================

The following code changes are required to a port's clock.c to support
either of the above schemes.  Making these extensions does not _require_
any other changes to your kernel.  A reference implementation is available
in the current NetBSD/i386(magnum) sources (sys/arch/i386/isa/clock.c).

int bad_clock
	The name of this flag variable is unimportant.  Local to clock.c,
	it needs to be shared between {read,init,reset}todr().

int readtodr(struct timeval *)
	readtodr() contains code previously in inittodr(), and simply
	reads and parses the RTC.
	If the read fails or bad_clock is set, readtodr() should fail.

void inittodr(time_t)
	inittodr() clears bad_clock, then calls readtodr().
	If it decides that the RTC should not be trusted (ala. "lost
	battery-backed clock - check and reset the date"), it sets
	bad_clock so that later time-synchronisation does not use the
	RTC.

void resettodr()
	This clears bad_clock after writing the softclock to the RTC.


6. Summary
   =======

As previously discussed, RTC/softclock synchronisation can be done either
in kernel or user space.  With the above low-level kernel changes, the
interface in either case will be common across all NetBSD ports.

kernel space
	The synchronisation code is invoked periodically (every 30 secs?)
	and calls readtodr() and compares that to "time".
	It then executes code out of adjtime() to change the timedelta and/or
	sets time directly.

	An interface to enable/disable this functionality would be required.

	Effects:
		Given that it would be off by default, no changes to
		timed/xntpd/etc are required.  Non-networked machines
		can enable it during boot.

		Timed/ntpd/etc _can_ be extended to enable the functionality
		while network time is lost (calling settimeofday() first to
		invoke resettodr() with the latest network time).

user space
	I suspect many people will prefer this option, as it doesn't add
	too much complexity to the kernel, being closer to the model
	of a microkernel.

	As discussed, two user-level (root-only) interfaces would be
	required, both passing a timeval:
		- Set the softclock without invoking resettodr().
		- Return the current RTC value.
	Along with {get,set}timeofday() and adjtime(), these provide
	enough functions that an external program can examine the clocks
	and manipulate them to provide RTC synchronisation.

	These interfaces would need careful specification.  How to do so?
	Would they be syscalls or ioctls?  This is the major stumbling
	block of the user-space option.

	Effects:
		By definition clock synchronisation is off until an
		external program performs it.  This external program
		would either be an "rtcd" or a function of timed/ntpd/etc.

Thank you for reading this far.  I look forward to your comments.

- David B.

------------------------------------------------------------------------------