Page MenuHomeFreeBSD

D11484.id30420.diff
No OneTemporary

D11484.id30420.diff

Index: sys/kern/subr_rtc.c
===================================================================
--- sys/kern/subr_rtc.c
+++ sys/kern/subr_rtc.c
@@ -63,8 +63,10 @@
#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,208 @@
#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 | M_ZERO);
+ 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@edvz.tu-graz.ac.at>, reintroduced and
- * updated by Chris Stenton <chris@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;
- }
- /* 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;
- }
- 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);
-#ifdef FFCLOCK
- ffclock_reset_clock(&ts);
-#endif
- return;
+ 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();
+ }
+ device_printf(rtc->clockdev, "providing initial system time\n");
+ break;
+ }
+ sx_xunlock(&rtc_list_lock);
-wrong_time:
- if (base > 0) {
- ts.tv_sec = base;
+ /*
+ * 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 (ts.tv_sec >= 0) {
tc_setclock(&ts);
+#ifdef FFCLOCK
+ ffclock_reset_clock(&ts);
+#endif
}
}
/*
- * 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);
}
Index: sys/sys/clock.h
===================================================================
--- sys/sys/clock.h
+++ sys/sys/clock.h
@@ -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 @@
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.

File Metadata

Mime Type
text/plain
Expires
Sat, Nov 23, 4:28 AM (2 h, 32 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
14788134
Default Alt Text
D11484.id30420.diff (10 KB)

Event Timeline