Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F103200057
D11484.id30420.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
D11484.id30420.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D11484: Support multiple realtime clocks, and remove locking/sleeping restrictions on clock drivers.
Attached
Detach File
Event Timeline
Log In to Comment