svn commit: r323447 - in stable/11: share/man/man9 sys/amd64/amd64 sys/isa sys/kern sys/sys sys/x86/isa

Ian Lepore ian at FreeBSD.org
Mon Sep 11 17:32:29 UTC 2017


Author: ian
Date: Mon Sep 11 17:32:26 2017
New Revision: 323447
URL: https://svnweb.freebsd.org/changeset/base/323447

Log:
  MFC r320901-r320902, r320996-r320997, r321002, r321048, r321400, r321743,
      r321745
  
  r320901:
  Protect access to the AT realtime clock with its own mutex.
  
  The mutex protecting access to the registered realtime clock should not be
  overloaded to protect access to the atrtc hardware, which might not even be
  the registered rtc. More importantly, the resettodr mutex needs to be
  eliminated to remove locking/sleeping restrictions on clock drivers, and
  that can't happen if MD code for amd64 depends on it. This change moves the
  protection into what's really being protected: access to the atrtc date and
  time registers.
  
  This change also adds protection when the clock is accessed from
  xentimer_settime(), which bypasses the resettodr locking.
  
  Differential Revision:	https://reviews.freebsd.org/D11483
  
  r320902:
  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
  
  r320996:
  Allow setting debug.clocktime as a tunable.  Print 64-bit time_t correctly
  on 32-bit systems.
  
  r320997:
  Minor optimization: instead of converting between days and years using
  loops that start in 1970, assume most conversions are going to be for recent
  dates and use a precomputed number of days through the end of 2016.
  
  r321002:
  Revert r320997.  There are reports of it getting the wrong results, so
  clearly my testing was insuffficent, and it's best to just revert it
  until I get it straightened out.
  
  r321048:
  Minor optimization: instead of converting between days and years using loops
  that start in 1970, assume most conversions are going to be for recent dates
  and use a precomputed number of days through the end of 2016.
  
  This is a do-over of r320997, hopefully this time with 100% more workiness.
  
  The first attempt had an off-by-one error, but instead of just adding
  another mysterious +1 adjustment, this rearranges the relationship between
  recent_base_year and recent_base_days so that the latter is the number of
  days that occurred before the start of the associated year (instead of the
  count thru the end of that year).  This makes the recent_base stuff work
  more like the original loop logic that didn't need any +1 adjustments.
  
  r321400:
  Add common code to support realtime clocks that store year without century.
  
  Most realtime clocks store the year as 2 BCD digits.  Some add a century bit
  to extend the range another hundred years.  Every clock driver has its own
  code to determine the century and pass a full year value to clock_ct_to_ts().
  Now clock drivers can just convert BCD to bin and store the result in the
  clocktime struct and let the common code figure out the century.  Clocks
  with a century bit can just add 100 to year if the century bit is on.
  
  r321743:
  Add taskqueue_enqueue_timeout_sbt(), because sometimes you want more control
  over the scheduling precision than 'ticks' can offer, and because sometimes
  you're already working with sbintime_t units and it's dumb to convert them
  to ticks just so they can get converted back to sbintime_t under the hood.
  
  r321745:
  Add clock_schedule(), a feature that allows realtime clock drivers to
  request that their clock_settime() methods be called at a given offset
  from top-of-second.  This adds a timeout_task to the rtc_instance so that
  each clock can be separately added to taskqueue_thread with the scheduling
  it prefers, instead of looping through all the clocks at once with a
  single task on taskqueue_thread.  If a driver doesn't call clock_schedule()
  the default is the old behavior: clock_settime() is queued immediately.

Modified:
  stable/11/share/man/man9/taskqueue.9
  stable/11/sys/amd64/amd64/efirt.c
  stable/11/sys/isa/rtc.h
  stable/11/sys/kern/subr_clock.c
  stable/11/sys/kern/subr_rtc.c
  stable/11/sys/kern/subr_taskqueue.c
  stable/11/sys/sys/clock.h
  stable/11/sys/sys/taskqueue.h
  stable/11/sys/x86/isa/atrtc.c
Directory Properties:
  stable/11/   (props changed)

Modified: stable/11/share/man/man9/taskqueue.9
==============================================================================
--- stable/11/share/man/man9/taskqueue.9	Mon Sep 11 17:01:26 2017	(r323446)
+++ stable/11/share/man/man9/taskqueue.9	Mon Sep 11 17:32:26 2017	(r323447)
@@ -28,7 +28,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd March 1, 2016
+.Dd July 30, 2017
 .Dt TASKQUEUE 9
 .Os
 .Sh NAME
@@ -82,6 +82,8 @@ struct timeout_task;
 .Ft int
 .Fn taskqueue_enqueue_timeout "struct taskqueue *queue" "struct timeout_task *timeout_task" "int ticks"
 .Ft int
+.Fn taskqueue_enqueue_timeout_sbt "struct taskqueue *queue" "struct timeout_task *timeout_task" "sbintime_t sbt" "sbintime_t pr" "int flags"
+.Ft int
 .Fn taskqueue_cancel "struct taskqueue *queue" "struct task *task" "u_int *pendp"
 .Ft int
 .Fn taskqueue_cancel_timeout "struct taskqueue *queue" "struct timeout_task *timeout_task" "u_int *pendp"
@@ -211,8 +213,17 @@ is called on the task pointer passed to
 .Pp
 The
 .Fn taskqueue_enqueue_timeout
-is used to schedule the enqueue after the specified amount of
+function is used to schedule the enqueue after the specified number of
 .Va ticks .
+The
+.Fn taskqueue_enqueue_timeout_sbt
+function provides finer control over the scheduling based on
+.Va sbt ,
+.Va pr ,
+and
+.Va flags ,
+as detailed in
+.Xr timeout 9 .
 Only non-fast task queues can be used for
 .Va timeout_task
 scheduling.
@@ -483,6 +494,7 @@ be created with a dedicated processing thread.
 .Xr ithread 9 ,
 .Xr kthread 9 ,
 .Xr swi 9
+.Xr timeout 9
 .Sh HISTORY
 This interface first appeared in
 .Fx 5.0 .

Modified: stable/11/sys/amd64/amd64/efirt.c
==============================================================================
--- stable/11/sys/amd64/amd64/efirt.c	Mon Sep 11 17:01:26 2017	(r323446)
+++ stable/11/sys/amd64/amd64/efirt.c	Mon Sep 11 17:32:26 2017	(r323447)
@@ -45,6 +45,7 @@ __FBSDID("$FreeBSD$");
 #include <sys/sched.h>
 #include <sys/sysctl.h>
 #include <sys/systm.h>
+#include <isa/rtc.h>
 #include <machine/fpu.h>
 #include <machine/efi.h>
 #include <machine/metadata.h>
@@ -444,7 +445,7 @@ efi_get_time_locked(struct efi_tm *tm)
 	efi_status status;
 	int error;
 
-	mtx_assert(&resettodr_lock, MA_OWNED);
+	mtx_assert(&atrtc_time_lock, MA_OWNED);
 	error = efi_enter();
 	if (error != 0)
 		return (error);
@@ -461,9 +462,9 @@ efi_get_time(struct efi_tm *tm)
 
 	if (efi_runtime == NULL)
 		return (ENXIO);
-	mtx_lock(&resettodr_lock);
+	mtx_lock(&atrtc_time_lock);
 	error = efi_get_time_locked(tm);
-	mtx_unlock(&resettodr_lock);
+	mtx_unlock(&atrtc_time_lock);
 	return (error);
 }
 
@@ -486,7 +487,7 @@ efi_set_time_locked(struct efi_tm *tm)
 	efi_status status;
 	int error;
 
-	mtx_assert(&resettodr_lock, MA_OWNED);
+	mtx_assert(&atrtc_time_lock, MA_OWNED);
 	error = efi_enter();
 	if (error != 0)
 		return (error);
@@ -503,9 +504,9 @@ efi_set_time(struct efi_tm *tm)
 
 	if (efi_runtime == NULL)
 		return (ENXIO);
-	mtx_lock(&resettodr_lock);
+	mtx_lock(&atrtc_time_lock);
 	error = efi_set_time_locked(tm);
-	mtx_unlock(&resettodr_lock);
+	mtx_unlock(&atrtc_time_lock);
 	return (error);
 }
 

Modified: stable/11/sys/isa/rtc.h
==============================================================================
--- stable/11/sys/isa/rtc.h	Mon Sep 11 17:01:26 2017	(r323446)
+++ stable/11/sys/isa/rtc.h	Mon Sep 11 17:32:26 2017	(r323447)
@@ -113,6 +113,7 @@
 
 #ifdef _KERNEL
 extern  struct mtx clock_lock;
+extern  struct mtx atrtc_time_lock;
 extern	int atrtcclock_disable;
 int	rtcin(int reg);
 void	atrtc_restore(void);

Modified: stable/11/sys/kern/subr_clock.c
==============================================================================
--- stable/11/sys/kern/subr_clock.c	Mon Sep 11 17:01:26 2017	(r323446)
+++ stable/11/sys/kern/subr_clock.c	Mon Sep 11 17:32:26 2017	(r323447)
@@ -72,7 +72,7 @@ SYSCTL_PROC(_machdep, OID_AUTO, adjkerntz, CTLTYPE_INT
     "Local offset from UTC in seconds");
 
 static int ct_debug;
-SYSCTL_INT(_debug, OID_AUTO, clocktime, CTLFLAG_RW,
+SYSCTL_INT(_debug, OID_AUTO, clocktime, CTLFLAG_RWTUN,
     &ct_debug, 0, "Enable printing of clocktime debugging");
 
 static int wall_cmos_clock;
@@ -97,6 +97,13 @@ static const int month_days[12] = {
 	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
 };
 
+/*
+ * Optimization: using a precomputed count of days between POSIX_BASE_YEAR and
+ * some recent year avoids lots of unnecessary loop iterations in conversion.
+ * recent_base_days is the number of days before the start of recent_base_year.
+ */
+static const int recent_base_year = 2017;
+static const int recent_base_days = 17167;
 
 /*
  * This inline avoids some unnecessary modulo operations
@@ -135,18 +142,27 @@ clock_ct_to_ts(struct clocktime *ct, struct timespec *
 {
 	int i, year, days;
 
-	year = ct->year;
-
 	if (ct_debug) {
 		printf("ct_to_ts(");
 		print_ct(ct);
 		printf(")");
 	}
 
+	/*
+	 * Many realtime clocks store the year as 2-digit BCD; pivot on 70 to
+	 * determine century.  Some clocks have a "century bit" and drivers do
+	 * year += 100, so interpret values between 70-199 as relative to 1900.
+	 */
+	year = ct->year;
+	if (year < 70)
+		year += 2000;
+	else if (year < 200)
+		year += 1900;
+
 	/* Sanity checks. */
 	if (ct->mon < 1 || ct->mon > 12 || ct->day < 1 ||
 	    ct->day > days_in_month(year, ct->mon) ||
-	    ct->hour > 23 ||  ct->min > 59 || ct->sec > 59 ||
+	    ct->hour > 23 ||  ct->min > 59 || ct->sec > 59 || year < 1970 ||
 	    (sizeof(time_t) == 4 && year > 2037)) {	/* time_t overflow */
 		if (ct_debug)
 			printf(" = EINVAL\n");
@@ -157,8 +173,14 @@ clock_ct_to_ts(struct clocktime *ct, struct timespec *
 	 * Compute days since start of time
 	 * First from years, then from months.
 	 */
-	days = 0;
-	for (i = POSIX_BASE_YEAR; i < year; i++)
+	if (year >= recent_base_year) {
+		i = recent_base_year;
+		days = recent_base_days;
+	} else {
+		i = POSIX_BASE_YEAR;
+		days = 0;
+	}
+	for (; i < year; i++)
 		days += days_in_year(i);
 
 	/* Months */
@@ -171,7 +193,7 @@ clock_ct_to_ts(struct clocktime *ct, struct timespec *
 	ts->tv_nsec = ct->nsec;
 
 	if (ct_debug)
-		printf(" = %ld.%09ld\n", (long)ts->tv_sec, (long)ts->tv_nsec);
+		printf(" = %jd.%09ld\n", (intmax_t)ts->tv_sec, ts->tv_nsec);
 	return (0);
 }
 
@@ -188,8 +210,14 @@ clock_ts_to_ct(struct timespec *ts, struct clocktime *
 
 	ct->dow = day_of_week(days);
 
-	/* Subtract out whole years, counting them in i. */
-	for (year = POSIX_BASE_YEAR; days >= days_in_year(year); year++)
+	/* Subtract out whole years. */
+	if (days >= recent_base_days) {
+		year = recent_base_year;
+		days -= recent_base_days;
+	} else {
+		year = POSIX_BASE_YEAR;
+	}
+	for (; days >= days_in_year(year); year++)
 		days -= days_in_year(year);
 	ct->year = year;
 
@@ -209,8 +237,8 @@ clock_ts_to_ct(struct timespec *ts, struct clocktime *
 	ct->sec  = rsec;
 	ct->nsec = ts->tv_nsec;
 	if (ct_debug) {
-		printf("ts_to_ct(%ld.%09ld) = ",
-		    (long)ts->tv_sec, (long)ts->tv_nsec);
+		printf("ts_to_ct(%jd.%09ld) = ",
+		    (intmax_t)ts->tv_sec, ts->tv_nsec);
 		print_ct(ct);
 		printf("\n");
 	}

Modified: stable/11/sys/kern/subr_rtc.c
==============================================================================
--- stable/11/sys/kern/subr_rtc.c	Mon Sep 11 17:01:26 2017	(r323446)
+++ stable/11/sys/kern/subr_rtc.c	Mon Sep 11 17:32:26 2017	(r323447)
@@ -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,249 @@ __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;
+	u_int		schedns;
+	struct timespec resadj;
+	struct timeout_task
+			stask;
+	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);
+
+/*
+ * 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 the clock.  Do so
+ * holding no locks, 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;
+
+	rtc = arg;
+	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);
+}
+
 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->schedns = 0;
+	newrtc->resadj.tv_sec  = newrtc->resolution / 2 / 1000000;
+	newrtc->resadj.tv_nsec = newrtc->resolution / 2 % 1000000 * 1000;
+	TIMEOUT_TASK_INIT(taskqueue_thread, &newrtc->stask, 0,
+		    settime_task_func, newrtc);
+
+	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);
+			break;
+		}
+	}
+	sx_xunlock(&rtc_list_lock);
+	if (rtc != NULL) {
+		taskqueue_cancel_timeout(taskqueue_thread, &rtc->stask, NULL);
+		taskqueue_drain_timeout(taskqueue_thread, &rtc->stask);
+                free(rtc, M_DEVBUF);
+	}
+}
+
+void
+clock_schedule(device_t clockdev, u_int offsetns)
+{
+	struct rtc_instance *rtc;
+
+	sx_xlock(&rtc_list_lock);
+	LIST_FOREACH(rtc, &rtc_list, rtc_entries) {
+		if (rtc->clockdev == clockdev) {
+			rtc->schedns = offsetns;
+			break;
+		}
+	}
+	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;
+	struct timespec now;
+	struct rtc_instance *rtc;
+	sbintime_t sbt;
+	long waitns;
 
-	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);
+	sx_xlock(&rtc_list_lock);
+	LIST_FOREACH(rtc, &rtc_list, rtc_entries) {
+		if (rtc->schedns != 0) {
+			getnanotime(&now);
+			waitns = rtc->schedns - now.tv_nsec;
+			if (waitns < 0)
+				waitns += 1000000000;
+			sbt = nstosbt(waitns);
+		} else
+			sbt = 0;
+		taskqueue_enqueue_timeout_sbt(taskqueue_thread,
+		    &rtc->stask, -sbt, 0, C_PREL(31));
+	}
+	sx_xunlock(&rtc_list_lock);
 }

Modified: stable/11/sys/kern/subr_taskqueue.c
==============================================================================
--- stable/11/sys/kern/subr_taskqueue.c	Mon Sep 11 17:01:26 2017	(r323446)
+++ stable/11/sys/kern/subr_taskqueue.c	Mon Sep 11 17:32:26 2017	(r323447)
@@ -289,8 +289,8 @@ taskqueue_timeout_func(void *arg)
 }
 
 int
-taskqueue_enqueue_timeout(struct taskqueue *queue,
-    struct timeout_task *timeout_task, int ticks)
+taskqueue_enqueue_timeout_sbt(struct taskqueue *queue,
+    struct timeout_task *timeout_task, sbintime_t sbt, sbintime_t pr, int flags)
 {
 	int res;
 
@@ -304,7 +304,7 @@ taskqueue_enqueue_timeout(struct taskqueue *queue,
 		/* Do nothing */
 		TQ_UNLOCK(queue);
 		res = -1;
-	} else if (ticks == 0) {
+	} else if (sbt == 0) {
 		taskqueue_enqueue_locked(queue, &timeout_task->t);
 		/* The lock is released inside. */
 	} else {
@@ -313,16 +313,25 @@ taskqueue_enqueue_timeout(struct taskqueue *queue,
 		} else {
 			queue->tq_callouts++;
 			timeout_task->f |= DT_CALLOUT_ARMED;
-			if (ticks < 0)
-				ticks = -ticks; /* Ignore overflow. */
+			if (sbt < 0)
+				sbt = -sbt; /* Ignore overflow. */
 		}
-		if (ticks > 0) {
-			callout_reset(&timeout_task->c, ticks,
-			    taskqueue_timeout_func, timeout_task);
+		if (sbt > 0) {
+			callout_reset_sbt(&timeout_task->c, sbt, pr,
+			    taskqueue_timeout_func, timeout_task, flags);
 		}
 		TQ_UNLOCK(queue);
 	}
 	return (res);
+}
+
+int
+taskqueue_enqueue_timeout(struct taskqueue *queue,
+    struct timeout_task *ttask, int ticks)
+{
+
+	return (taskqueue_enqueue_timeout_sbt(queue, ttask, ticks * tick_sbt,
+	    0, 0));
 }
 
 static void

Modified: stable/11/sys/sys/clock.h
==============================================================================
--- stable/11/sys/sys/clock.h	Mon Sep 11 17:01:26 2017	(r323446)
+++ stable/11/sys/sys/clock.h	Mon Sep 11 17:32:26 2017	(r323447)
@@ -54,7 +54,6 @@
  */
 extern int tz_minuteswest;
 extern int tz_dsttime;
-extern struct mtx resettodr_lock;
 
 int utc_offset(void);
 
@@ -76,7 +75,42 @@ 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 functions and flags.  These functions might sleep.
+ *
+ * clock_register and clock_unregister() do what they say.  Upon return from
+ * unregister, the clock's methods are not running and will not be called again.
+ *
+ * clock_schedule() requests that a registered clock's clock_settime() calls
+ * happen at the given offset into the second.  The default is 0, meaning no
+ * specific scheduling.  To schedule the call as soon after top-of-second as
+ * possible, specify 1.  Each clock has its own schedule, but taskqueue_thread
+ * is shared by many tasks; the timing of the call is not guaranteed.
+ *
+ * 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_schedule(device_t clockdev, u_int _offsetns);
+void clock_unregister(device_t _clockdev);
 
 /*
  * BCD to decimal and decimal to BCD.

Modified: stable/11/sys/sys/taskqueue.h
==============================================================================
--- stable/11/sys/sys/taskqueue.h	Mon Sep 11 17:01:26 2017	(r323446)
+++ stable/11/sys/sys/taskqueue.h	Mon Sep 11 17:32:26 2017	(r323447)
@@ -79,6 +79,9 @@ int	taskqueue_start_threads_cpuset(struct taskqueue **
 int	taskqueue_enqueue(struct taskqueue *queue, struct task *task);
 int	taskqueue_enqueue_timeout(struct taskqueue *queue,
 	    struct timeout_task *timeout_task, int ticks);
+int	taskqueue_enqueue_timeout_sbt(struct taskqueue *queue,
+	    struct timeout_task *timeout_task, sbintime_t sbt, sbintime_t pr,
+	    int flags);
 int	taskqueue_poll_is_busy(struct taskqueue *queue, struct task *task);
 int	taskqueue_cancel(struct taskqueue *queue, struct task *task,
 	    u_int *pendp);

Modified: stable/11/sys/x86/isa/atrtc.c
==============================================================================
--- stable/11/sys/x86/isa/atrtc.c	Mon Sep 11 17:01:26 2017	(r323446)
+++ stable/11/sys/x86/isa/atrtc.c	Mon Sep 11 17:32:26 2017	(r323447)
@@ -53,9 +53,17 @@ __FBSDID("$FreeBSD$");
 #include <machine/intr_machdep.h>
 #include "clock_if.h"
 
+/*
+ * clock_lock protects low-level access to individual hardware registers.
+ * atrtc_time_lock protects the entire sequence of accessing multiple registers
+ * to read or write the date and time.
+ */
 #define	RTC_LOCK	do { if (!kdb_active) mtx_lock_spin(&clock_lock); } while (0)
 #define	RTC_UNLOCK	do { if (!kdb_active) mtx_unlock_spin(&clock_lock); } while (0)
 
+struct mtx atrtc_time_lock;
+MTX_SYSINIT(atrtc_lock_init, &atrtc_time_lock, "atrtc", MTX_DEF);
+
 int	atrtcclock_disable = 0;
 
 static	int	rtc_reg = -1;
@@ -158,6 +166,8 @@ atrtc_set(struct timespec *ts)
 
 	clock_ts_to_ct(ts, &ct);
 
+	mtx_lock(&atrtc_time_lock);
+
 	/* Disable RTC updates and interrupts. */
 	writertc(RTC_STATUSB, RTCSB_HALT | RTCSB_24HR);
 
@@ -176,6 +186,8 @@ atrtc_set(struct timespec *ts)
 	/* Re-enable RTC updates and interrupts. */
 	writertc(RTC_STATUSB, rtc_statusb);
 	rtcin(RTC_INTR);
+
+	mtx_unlock(&atrtc_time_lock);
 }
 
 /**********************************************************************
@@ -347,6 +359,7 @@ atrtc_gettime(device_t dev, struct timespec *ts)
 	 * to make sure that no more than 240us pass after we start reading,
 	 * and try again if so.
 	 */
+	mtx_lock(&atrtc_time_lock);
 	while (rtcin(RTC_STATUSA) & RTCSA_TUP)
 		continue;
 	critical_enter();
@@ -364,6 +377,7 @@ atrtc_gettime(device_t dev, struct timespec *ts)
 	ct.year += (ct.year < 80 ? 2000 : 1900);
 #endif
 	critical_exit();
+	mtx_unlock(&atrtc_time_lock);
 	/* Set dow = -1 because some clocks don't set it correctly. */
 	ct.dow = -1;
 	return (clock_ct_to_ts(&ct, ts));


More information about the svn-src-all mailing list