svn commit: r320902 - in head/sys: kern sys

Ian Lepore ian at FreeBSD.org
Wed Jul 12 02:53:56 UTC 2017


Author: ian
Date: Wed Jul 12 02:53:54 2017
New Revision: 320902
URL: https://svnweb.freebsd.org/changeset/base/320902

Log:
  Support multiple realtime clocks, and remove locking/sleeping restrictions
  on clock drivers.
  
  This tracks multiple concurrent realtime clock drivers in a list sorted by
  clock resolution.  When system time changes (and periodically) the
  clock_settime() methods of all registered clocks are invoked.
  
  To initialize system time, each driver is tried in turn from best to worst
  resolution, until one succesfully returns a valid time.
  
  The code no longer holds a mutex while calling the clock_settime() and
  clock_gettime() methods of the registered clocks. This allows clock drivers
  to do whatever kind of locking or sleeping is necessary (this is especially
  important for i2c clock chips since i2c drivers often need to sleep).
  
  A new clock_register_flags() function allows the clock driver to pass
  flags. The flags currently defined help support drivers that use their own
  techniques to avoid roundoff errors (prevents the 4/5 rounding done by the
  subr_rtc code). A driver which may need to wait for resources (such as bus
  ownership) may pass a flag to indicate that it will obtain system time for
  itself after waiting for resources; this is merely an optimization to avoid
  the common code retrieving a timespec that will never get used.
  
  Relnotes:	yes
  Differential Revision:	https://reviews.freebsd.org/D11484

Modified:
  head/sys/kern/subr_rtc.c
  head/sys/sys/clock.h

Modified: head/sys/kern/subr_rtc.c
==============================================================================
--- head/sys/kern/subr_rtc.c	Wed Jul 12 02:42:57 2017	(r320901)
+++ head/sys/kern/subr_rtc.c	Wed Jul 12 02:53:54 2017	(r320902)
@@ -63,8 +63,10 @@ __FBSDID("$FreeBSD$");
 #include <sys/bus.h>
 #include <sys/clock.h>
 #include <sys/lock.h>
-#include <sys/mutex.h>
+#include <sys/malloc.h>
+#include <sys/sx.h>
 #include <sys/sysctl.h>
+#include <sys/taskqueue.h>
 #ifdef FFCLOCK
 #include <sys/timeffc.h>
 #endif
@@ -72,116 +74,210 @@ __FBSDID("$FreeBSD$");
 
 #include "clock_if.h"
 
-static device_t clock_dev = NULL;
-static long clock_res;
-static struct timespec clock_adj;
-struct mtx resettodr_lock;
-MTX_SYSINIT(resettodr_init, &resettodr_lock, "tod2rl", MTX_DEF);
-
 /* XXX: should be kern. now, it's no longer machdep.  */
 static int disable_rtc_set;
 SYSCTL_INT(_machdep, OID_AUTO, disable_rtc_set, CTLFLAG_RW, &disable_rtc_set,
     0, "Disallow adjusting time-of-day clock");
 
+/*
+ * An instance of a realtime clock.  A list of these tracks all the registered
+ * clocks in the system.
+ *
+ * The resadj member is used to apply a "resolution adjustment" equal to half
+ * the clock's resolution, which is useful mainly on clocks with a whole-second
+ * resolution.  Because the clock truncates the fractional part, adding half the
+ * resolution performs 4/5 rounding.  The same adjustment is applied to the
+ * times returned from clock_gettime(), because the fraction returned will
+ * always be zero, but on average the actual fraction at the time of the call
+ * should be about .5.
+ */
+struct rtc_instance {
+	device_t	clockdev;
+	int		resolution;
+	int		flags;
+	struct timespec resadj;
+	LIST_ENTRY(rtc_instance)
+			rtc_entries;
+};
+
+/*
+ * Clocks are updated using a task running on taskqueue_thread.
+ */
+static void settime_task_func(void *arg, int pending);
+static struct task settime_task = TASK_INITIALIZER(0, settime_task_func, NULL);
+
+/*
+ * Registered clocks are kept in a list which is sorted by resolution; the more
+ * accurate clocks get the first shot at providing the time.
+ */
+LIST_HEAD(rtc_listhead, rtc_instance);
+static struct rtc_listhead rtc_list = LIST_HEAD_INITIALIZER(rtc_list);
+static struct sx rtc_list_lock;
+SX_SYSINIT(rtc_list_lock_init, &rtc_list_lock, "rtc list");
+
+/*
+ * On the task thread, invoke the clock_settime() method of each registered
+ * clock.  Do so holding only an sxlock, so that clock drivers are free to do
+ * whatever kind of locking or sleeping they need to.
+ */
+static void
+settime_task_func(void *arg, int pending)
+{
+	struct timespec ts;
+	struct rtc_instance *rtc;
+
+	sx_xlock(&rtc_list_lock);
+	LIST_FOREACH(rtc, &rtc_list, rtc_entries) {
+		if (!(rtc->flags & CLOCKF_SETTIME_NO_TS)) {
+			getnanotime(&ts);
+			if (!(rtc->flags & CLOCKF_SETTIME_NO_ADJ)) {
+				ts.tv_sec -= utc_offset();
+				timespecadd(&ts, &rtc->resadj);
+			}
+		} else {
+			ts.tv_sec  = 0;
+			ts.tv_nsec = 0;
+		}
+		CLOCK_SETTIME(rtc->clockdev, &ts);
+	}
+	sx_xunlock(&rtc_list_lock);
+}
+
 void
-clock_register(device_t dev, long res)	/* res has units of microseconds */
+clock_register_flags(device_t clockdev, long resolution, int flags)
 {
+	struct rtc_instance *rtc, *newrtc;
 
-	if (clock_dev != NULL) {
-		if (clock_res <= res) {
-			if (bootverbose)
-				device_printf(dev, "not installed as "
-				    "time-of-day clock: clock %s has higher "
-				    "resolution\n", device_get_name(clock_dev));
-			return;
+	newrtc = malloc(sizeof(*newrtc), M_DEVBUF, M_WAITOK);
+	newrtc->clockdev = clockdev;
+	newrtc->resolution = (int)resolution;
+	newrtc->flags = flags;
+	newrtc->resadj.tv_sec  = newrtc->resolution / 2 / 1000000;
+	newrtc->resadj.tv_nsec = newrtc->resolution / 2 % 1000000 * 1000;
+
+	sx_xlock(&rtc_list_lock);
+	if (LIST_EMPTY(&rtc_list)) {
+		LIST_INSERT_HEAD(&rtc_list, newrtc, rtc_entries);
+	} else {
+		LIST_FOREACH(rtc, &rtc_list, rtc_entries) {
+			if (rtc->resolution > newrtc->resolution) {
+				LIST_INSERT_BEFORE(rtc, newrtc, rtc_entries);
+				break;
+			} else if (LIST_NEXT(rtc, rtc_entries) == NULL) {
+				LIST_INSERT_AFTER(rtc, newrtc, rtc_entries);
+				break;
+			}
 		}
-		if (bootverbose)
-			device_printf(clock_dev, "removed as "
-			    "time-of-day clock: clock %s has higher "
-			    "resolution\n", device_get_name(dev));
 	}
-	clock_dev = dev;
-	clock_res = res;
-	clock_adj.tv_sec = res / 2 / 1000000;
-	clock_adj.tv_nsec = res / 2 % 1000000 * 1000;
-	if (bootverbose)
-		device_printf(dev, "registered as a time-of-day clock "
-		    "(resolution %ldus, adjustment %jd.%09jds)\n", res,
-		    (intmax_t)clock_adj.tv_sec, (intmax_t)clock_adj.tv_nsec);
+	sx_xunlock(&rtc_list_lock);
+
+	device_printf(clockdev, 
+	    "registered as a time-of-day clock, resolution %d.%6.6ds\n",
+	    newrtc->resolution / 1000000, newrtc->resolution % 1000000);
 }
 
-/*
- * inittodr and settodr derived from the i386 versions written
- * by Christoph Robitschko <chmr at edvz.tu-graz.ac.at>,  reintroduced and
- * updated by Chris Stenton <chris at gnome.co.uk> 8/10/94
- */
+void
+clock_register(device_t dev, long res)
+{
 
+	clock_register_flags(dev, res, 0);
+}
+
+void
+clock_unregister(device_t clockdev)
+{
+	struct rtc_instance *rtc, *tmp;
+
+	sx_xlock(&rtc_list_lock);
+	LIST_FOREACH_SAFE(rtc, &rtc_list, rtc_entries, tmp) {
+		if (rtc->clockdev == clockdev) {
+			LIST_REMOVE(rtc, rtc_entries);
+			free(rtc, M_DEVBUF);
+		}
+	}
+	sx_xunlock(&rtc_list_lock);
+}
+
 /*
- * Initialize the time of day register, based on the time base which is, e.g.
- * from a filesystem.
+ * Initialize the system time.  Must be called from a context which does not
+ * restrict any locking or sleeping that clock drivers may need to do.
+ *
+ * First attempt to get the time from a registered realtime clock.  The clocks
+ * are queried in order of resolution until one provides the time.  If no clock
+ * can provide the current time, use the 'base' time provided by the caller, if
+ * non-zero.  The 'base' time is potentially highly inaccurate, such as the last
+ * known good value of the system clock, or even a filesystem last-updated
+ * timestamp.  It is used to prevent system time from appearing to move
+ * backwards in logs.
  */
 void
 inittodr(time_t base)
 {
 	struct timespec ts;
+	struct rtc_instance *rtc;
 	int error;
 
-	if (clock_dev == NULL) {
-		printf("warning: no time-of-day clock registered, system time "
-		    "will not be set accurately\n");
-		goto wrong_time;
+	error = ENXIO;
+	sx_xlock(&rtc_list_lock);
+	LIST_FOREACH(rtc, &rtc_list, rtc_entries) {
+		if ((error = CLOCK_GETTIME(rtc->clockdev, &ts)) != 0)
+			continue;
+		if (ts.tv_sec < 0 || ts.tv_nsec < 0) {
+			error = EINVAL;
+			continue;
+		}
+		if (!(rtc->flags & CLOCKF_GETTIME_NO_ADJ)) {
+			timespecadd(&ts, &rtc->resadj);
+			ts.tv_sec += utc_offset();
+		}
+		if (bootverbose)
+			device_printf(rtc->clockdev,
+			    "providing initial system time\n");
+		break;
 	}
-	/* XXX: We should poll all registered RTCs in case of failure */
-	mtx_lock(&resettodr_lock);
-	error = CLOCK_GETTIME(clock_dev, &ts);
-	mtx_unlock(&resettodr_lock);
-	if (error != 0 && error != EINVAL) {
-		printf("warning: clock_gettime failed (%d), the system time "
-		    "will not be set accurately\n", error);
-		goto wrong_time;
+	sx_xunlock(&rtc_list_lock);
+
+	/*
+	 * Do not report errors from each clock; it is expected that some clocks
+	 * cannot provide results in some situations.  Only report problems when
+	 * no clocks could provide the time.
+	 */
+	if (error != 0) {
+		switch (error) {
+		case ENXIO:
+			printf("Warning: no time-of-day clock registered, ");
+			break;
+		case EINVAL:
+			printf("Warning: bad time from time-of-day clock, ");
+			break;
+		default:
+			printf("Error reading time-of-day clock (%d), ", error);
+			break;
+		}
+		printf("system time will not be set accurately\n");
+		ts.tv_sec  = (base > 0) ? base : -1;
+		ts.tv_nsec = 0;
 	}
-	if (error == EINVAL || ts.tv_sec < 0) {
-		printf("Invalid time in real time clock.\n"
-		    "Check and reset the date immediately!\n");
-		goto wrong_time;
-	}
 
-	ts.tv_sec += utc_offset();
-	timespecadd(&ts, &clock_adj);
-	tc_setclock(&ts);
+	if (ts.tv_sec >= 0) {
+		tc_setclock(&ts);
 #ifdef FFCLOCK
-	ffclock_reset_clock(&ts);
+		ffclock_reset_clock(&ts);
 #endif
-	return;
-
-wrong_time:
-	if (base > 0) {
-		ts.tv_sec = base;
-		ts.tv_nsec = 0;
-		tc_setclock(&ts);
 	}
 }
 
 /*
- * Write system time back to RTC
+ * Write system time back to all registered clocks, unless disabled by admin.
+ * This can be called from a context that restricts locking and/or sleeping; the
+ * actual updating is done asynchronously on a task thread.
  */
 void
 resettodr(void)
 {
-	struct timespec ts;
-	int error;
 
-	if (disable_rtc_set || clock_dev == NULL)
+	if (disable_rtc_set)
 		return;
 
-	getnanotime(&ts);
-	timespecadd(&ts, &clock_adj);
-	ts.tv_sec -= utc_offset();
-	/* XXX: We should really set all registered RTCs */
-	mtx_lock(&resettodr_lock);
-	error = CLOCK_SETTIME(clock_dev, &ts);
-	mtx_unlock(&resettodr_lock);
-	if (error != 0)
-		printf("warning: clock_settime failed (%d), time-of-day clock "
-		    "not adjusted to system time\n", error);
+	taskqueue_enqueue(taskqueue_thread, &settime_task);
 }

Modified: head/sys/sys/clock.h
==============================================================================
--- head/sys/sys/clock.h	Wed Jul 12 02:42:57 2017	(r320901)
+++ head/sys/sys/clock.h	Wed Jul 12 02:53:54 2017	(r320902)
@@ -54,7 +54,6 @@
  */
 extern int tz_minuteswest;
 extern int tz_dsttime;
-extern struct mtx resettodr_lock;
 
 int utc_offset(void);
 
@@ -76,7 +75,34 @@ struct clocktime {
 
 int clock_ct_to_ts(struct clocktime *, struct timespec *);
 void clock_ts_to_ct(struct timespec *, struct clocktime *);
-void clock_register(device_t, long);
+
+/*
+ * Time-of-day clock register/unregister functions, and associated flags.  These
+ * functions can sleep.  Upon return from unregister, the clock's methods are
+ * not running and will not be called again.
+ *
+ * Flags:
+ *
+ *  CLOCKF_SETTIME_NO_TS
+ *    Do not pass a timespec to clock_settime(), the driver obtains its own time
+ *    and applies its own adjustments (this flag implies CLOCKF_SETTIME_NO_ADJ).
+ *
+ *  CLOCKF_SETTIME_NO_ADJ
+ *    Do not apply utc offset and resolution/accuracy adjustments to the value
+ *    passed to clock_settime(), the driver applies them itself.
+ *
+ *  CLOCKF_GETTIME_NO_ADJ
+ *    Do not apply utc offset and resolution/accuracy adjustments to the value
+ *    returned from clock_gettime(), the driver has already applied them.
+ */
+
+#define	CLOCKF_SETTIME_NO_TS	0x00000001
+#define	CLOCKF_SETTIME_NO_ADJ	0x00000002
+#define	CLOCKF_GETTIME_NO_ADJ	0x00000004
+
+void clock_register(device_t _clockdev, long _resolution_us);
+void clock_register_flags(device_t _clockdev, long _resolution_us, int _flags);
+void clock_unregister(device_t _clockdev);
 
 /*
  * BCD to decimal and decimal to BCD.


More information about the svn-src-head mailing list