Module Name: src
Committed By: kardel
Date: Sun May 26 18:07:42 UTC 2013
Modified Files:
src/sys/conf: files
src/sys/kern: kern_tc.c
src/sys/sys: timepps.h
Log Message:
Extend kernel PPS api with pps_ref_event().
pps_ref_event() allows capturing PPS time stamps
that are not generated at precisely 1Hz (e. g.
by reading a precision clock via callout()).
This extension allows clock drivers to supply PPS
time-stamps and drive the kernel NTP PLL
without the overhead of interrupt-handling and
-processing.
To generate a diff of this commit:
cvs rdiff -u -r1.1070 -r1.1071 src/sys/conf/files
cvs rdiff -u -r1.44 -r1.45 src/sys/kern/kern_tc.c
cvs rdiff -u -r1.20 -r1.21 src/sys/sys/timepps.h
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/sys/conf/files
diff -u src/sys/conf/files:1.1070 src/sys/conf/files:1.1071
--- src/sys/conf/files:1.1070 Sun Apr 28 03:11:32 2013
+++ src/sys/conf/files Sun May 26 18:07:42 2013
@@ -1,4 +1,4 @@
-# $NetBSD: files,v 1.1070 2013/04/28 03:11:32 christos Exp $
+# $NetBSD: files,v 1.1071 2013/05/26 18:07:42 kardel Exp $
# @(#)files.newconf 7.5 (Berkeley) 5/10/93
version 20100430
@@ -64,7 +64,7 @@ defflag opt_dtrace.h KDTRACE_HOOKS
defflag opt_sysv.h SYSVMSG SYSVSEM SYSVSHM
defparam opt_sysvparam.h SHMMAXPGS SEMMNI SEMMNS SEMUME SEMMNU
-defflag opt_ntp.h PPS_SYNC NTP
+defflag opt_ntp.h PPS_SYNC PPS_DEBUG NTP
defflag opt_ptm.h NO_DEV_PTM COMPAT_BSDPTY
Index: src/sys/kern/kern_tc.c
diff -u src/sys/kern/kern_tc.c:1.44 src/sys/kern/kern_tc.c:1.45
--- src/sys/kern/kern_tc.c:1.44 Tue Nov 13 20:10:02 2012
+++ src/sys/kern/kern_tc.c Sun May 26 18:07:42 2013
@@ -1,4 +1,4 @@
-/* $NetBSD: kern_tc.c,v 1.44 2012/11/13 20:10:02 pooka Exp $ */
+/* $NetBSD: kern_tc.c,v 1.45 2013/05/26 18:07:42 kardel Exp $ */
/*-
* Copyright (c) 2008, 2009 The NetBSD Foundation, Inc.
@@ -40,7 +40,7 @@
#include <sys/cdefs.h>
/* __FBSDID("$FreeBSD: src/sys/kern/kern_tc.c,v 1.166 2005/09/19 22:16:31 andre Exp $"); */
-__KERNEL_RCSID(0, "$NetBSD: kern_tc.c,v 1.44 2012/11/13 20:10:02 pooka Exp $");
+__KERNEL_RCSID(0, "$NetBSD: kern_tc.c,v 1.45 2013/05/26 18:07:42 kardel Exp $");
#ifdef _KERNEL_OPT
#include "opt_ntp.h"
@@ -688,7 +688,8 @@ tc_setclock(const struct timespec *ts)
if (timestepwarnings) {
bintime2timespec(&bt2, &ts2);
- log(LOG_INFO, "Time stepped from %lld.%09ld to %lld.%09ld\n",
+ log(LOG_INFO,
+ "Time stepped from %lld.%09ld to %lld.%09ld\n",
(long long)ts2.tv_sec, ts2.tv_nsec,
(long long)ts->tv_sec, ts->tv_nsec);
}
@@ -859,7 +860,8 @@ pps_ioctl(u_long cmd, void *data, struct
KASSERT(mutex_owned(&timecounter_lock));
- KASSERT(pps != NULL); /* XXX ("NULL pps pointer in pps_ioctl") */
+ KASSERT(pps != NULL);
+
switch (cmd) {
case PPS_IOC_CREATE:
return (0);
@@ -913,6 +915,9 @@ pps_init(struct pps_state *pps)
pps->ppscap |= PPS_OFFSETCLEAR;
}
+/*
+ * capture a timetamp in the pps structure
+ */
void
pps_capture(struct pps_state *pps)
{
@@ -929,72 +934,223 @@ pps_capture(struct pps_state *pps)
pps->capgen = 0;
}
+#ifdef PPS_DEBUG
+int ppsdebug = 0;
+#endif
+
+/*
+ * process a pps_capture()ed event
+ */
void
pps_event(struct pps_state *pps, int event)
{
- struct bintime bt;
+ pps_ref_event(pps, event, NULL, PPS_REFEVNT_PPS|PPS_REFEVNT_CAPTURE);
+}
+
+/*
+ * extended pps api / kernel pll/fll entry point
+ *
+ * feed reference time stamps to PPS engine
+ *
+ * will simulate a PPS event and feed
+ * the NTP PLL/FLL if requested.
+ *
+ * the ref time stamps should be roughly once
+ * a second but do not need to be exactly in phase
+ * with the UTC second but should be close to it.
+ * this relaxation of requirements allows callout
+ * driven timestamping mechanisms to feed to pps
+ * capture/kernel pll logic.
+ *
+ * calling pattern is:
+ * pps_capture() (for PPS_REFEVNT_{CAPTURE|CAPCUR})
+ * read timestamp from reference source
+ * pps_ref_event()
+ *
+ * supported refmodes:
+ * PPS_REFEVNT_CAPTURE
+ * use system timestamp of pps_capture()
+ * PPS_REFEVNT_CURRENT
+ * use system timestamp of this call
+ * PPS_REFEVNT_CAPCUR
+ * use average of read capture and current system time stamp
+ * PPS_REFEVNT_PPS
+ * assume timestamp on second mark - ref_ts is ignored
+ *
+ */
+
+void
+pps_ref_event(struct pps_state *pps,
+ int event,
+ struct bintime *ref_ts,
+ int refmode
+ )
+{
+ struct bintime bt; /* current time */
+ struct bintime btd; /* time difference */
+ struct bintime bt_ref; /* reference time */
struct timespec ts, *tsp, *osp;
- u_int64_t tcount, *pcount;
- int foff;
-#ifdef PPS_SYNC
- int fhard;
-#endif
+ struct timehands *th;
+ u_int64_t tcount, acount, dcount, *pcount;
+ int foff, fhard, gen;
pps_seq_t *pseq;
KASSERT(mutex_owned(&timecounter_lock));
- KASSERT(pps != NULL); /* XXX ("NULL pps pointer in pps_event") */
- /* If the timecounter was wound up underneath us, bail out. */
- if (pps->capgen == 0 || pps->capgen != pps->capth->th_generation)
- return;
+ KASSERT(pps != NULL);
+
+ /* pick up current time stamp if needed */
+ if (refmode & (PPS_REFEVNT_CURRENT|PPS_REFEVNT_CAPCUR)) {
+ /* pick up current time stamp */
+ th = timehands;
+ gen = th->th_generation;
+ tcount = (u_int64_t)tc_delta(th) + th->th_offset_count;
+ if (gen != th->th_generation)
+ gen = 0;
+
+ /* If the timecounter was wound up underneath us, bail out. */
+ if (pps->capgen == 0 ||
+ pps->capgen != pps->capth->th_generation ||
+ gen == 0 ||
+ gen != pps->capgen) {
+#ifdef PPS_DEBUG
+ if (ppsdebug & 0x1) {
+ log(LOG_DEBUG,
+ "pps_ref_event(pps=%p, event=%d, ...): DROP (wind-up)\n",
+ pps, event);
+ }
+#endif
+ return;
+ }
+ } else {
+ tcount = 0; /* keep GCC happy */
+ }
+
+#ifdef PPS_DEBUG
+ if (ppsdebug & 0x1) {
+ struct timespec tmsp;
+
+ if (ref_ts == NULL) {
+ tmsp.tv_sec = 0;
+ tmsp.tv_nsec = 0;
+ } else {
+ bintime2timespec(ref_ts, &tmsp);
+ }
- /* Things would be easier with arrays. */
+ log(LOG_DEBUG,
+ "pps_ref_event(pps=%p, event=%d, ref_ts=%"PRIi64
+ ".%09"PRIi32", refmode=0x%1x)\n",
+ pps, event, tmsp.tv_sec, (int32_t)tmsp.tv_nsec, refmode);
+ }
+#endif
+
+ /* setup correct event references */
if (event == PPS_CAPTUREASSERT) {
tsp = &pps->ppsinfo.assert_timestamp;
osp = &pps->ppsparam.assert_offset;
foff = pps->ppsparam.mode & PPS_OFFSETASSERT;
-#ifdef PPS_SYNC
fhard = pps->kcmode & PPS_CAPTUREASSERT;
-#endif
pcount = &pps->ppscount[0];
pseq = &pps->ppsinfo.assert_sequence;
} else {
tsp = &pps->ppsinfo.clear_timestamp;
osp = &pps->ppsparam.clear_offset;
foff = pps->ppsparam.mode & PPS_OFFSETCLEAR;
-#ifdef PPS_SYNC
fhard = pps->kcmode & PPS_CAPTURECLEAR;
-#endif
pcount = &pps->ppscount[1];
pseq = &pps->ppsinfo.clear_sequence;
}
+ /* determine system time stamp according to refmode */
+ dcount = 0; /* keep GCC happy */
+ switch (refmode & PPS_REFEVNT_RMASK) {
+ case PPS_REFEVNT_CAPTURE:
+ acount = pps->capcount; /* use capture timestamp */
+ break;
+
+ case PPS_REFEVNT_CURRENT:
+ acount = tcount; /* use current timestamp */
+ break;
+
+ case PPS_REFEVNT_CAPCUR:
+ /*
+ * calculate counter value between pps_capture() and
+ * pps_ref_event()
+ */
+ dcount = tcount - pps->capcount;
+ acount = (dcount / 2) + pps->capcount;
+ break;
+
+ default: /* ignore call error silently */
+ return;
+ }
+
/*
* If the timecounter changed, we cannot compare the count values, so
* we have to drop the rest of the PPS-stuff until the next event.
*/
if (pps->ppstc != pps->capth->th_counter) {
pps->ppstc = pps->capth->th_counter;
- *pcount = pps->capcount;
- pps->ppscount[2] = pps->capcount;
+ pps->capcount = acount;
+ *pcount = acount;
+ pps->ppscount[2] = acount;
+#ifdef PPS_DEBUG
+ if (ppsdebug & 0x1) {
+ log(LOG_DEBUG,
+ "pps_ref_event(pps=%p, event=%d, ...): DROP (time-counter change)\n",
+ pps, event);
+ }
+#endif
return;
}
- /* Convert the count to a timespec. */
- tcount = pps->capcount - pps->capth->th_offset_count;
+ pps->capcount = acount;
+
+ /* Convert the count to a bintime. */
bt = pps->capth->th_offset;
- bintime_addx(&bt, pps->capth->th_scale * tcount);
+ bintime_addx(&bt, pps->capth->th_scale * (acount - pps->capth->th_offset_count));
bintime_add(&bt, &timebasebin);
+
+ if ((refmode & PPS_REFEVNT_PPS) == 0) {
+ /* determine difference to reference time stamp */
+ bt_ref = *ref_ts;
+
+ btd = bt;
+ bintime_sub(&btd, &bt_ref);
+
+ /*
+ * simulate a PPS timestamp by dropping the fraction
+ * and applying the offset
+ */
+ if (bt.frac >= (uint64_t)1<<63) /* skip to nearest second */
+ bt.sec++;
+ bt.frac = 0;
+ bintime_add(&bt, &btd);
+ } else {
+ /*
+ * create ref_ts from current time -
+ * we are supposed to be called on
+ * the second mark
+ */
+ bt_ref = bt;
+ if (bt_ref.frac >= (uint64_t)1<<63) /* skip to nearest second */
+ bt_ref.sec++;
+ bt_ref.frac = 0;
+ }
+
+ /* convert bintime to timestamp */
bintime2timespec(&bt, &ts);
/* If the timecounter was wound up underneath us, bail out. */
if (pps->capgen != pps->capth->th_generation)
return;
+ /* store time stamp */
*pcount = pps->capcount;
(*pseq)++;
*tsp = ts;
+ /* add offset correction */
if (foff) {
timespecadd(tsp, osp, tsp);
if (tsp->tv_nsec < 0) {
@@ -1002,26 +1158,107 @@ pps_event(struct pps_state *pps, int eve
tsp->tv_sec -= 1;
}
}
+
+#ifdef PPS_DEBUG
+ if (ppsdebug & 0x2) {
+ struct timespec ts2;
+ struct timespec ts3;
+
+ bintime2timespec(&bt_ref, &ts2);
+
+ bt.sec = 0;
+ bt.frac = 0;
+
+ if (refmode & PPS_REFEVNT_CAPCUR) {
+ bintime_addx(&bt, pps->capth->th_scale * dcount);
+ }
+ bintime2timespec(&bt, &ts3);
+
+ log(LOG_DEBUG, "ref_ts=%"PRIi64".%09"PRIi32
+ ", ts=%"PRIi64".%09"PRIi32", read latency=%"PRIi64" ns\n",
+ ts2.tv_sec, (int32_t)ts2.tv_nsec,
+ tsp->tv_sec, (int32_t)tsp->tv_nsec,
+ timespec2ns(&ts3));
+ }
+#endif
+
#ifdef PPS_SYNC
if (fhard) {
- u_int64_t scale;
+ uint64_t scale;
+ uint64_t div;
/*
* Feed the NTP PLL/FLL.
* The FLL wants to know how many (hardware) nanoseconds
- * elapsed since the previous event.
+ * elapsed since the previous event (mod 1 second) thus
+ * we are actually looking at the frequency difference scaled
+ * in nsec.
+ * As the counter time stamps are not truly at 1Hz
+ * we need to scale the count by the elapsed
+ * reference time.
+ * valid sampling interval: [0.5..2[ sec
*/
+
+ /* calculate elapsed raw count */
tcount = pps->capcount - pps->ppscount[2];
pps->ppscount[2] = pps->capcount;
tcount &= pps->capth->th_counter->tc_counter_mask;
- scale = (u_int64_t)1 << 63;
- scale /= pps->capth->th_counter->tc_frequency;
+
+ /* calculate elapsed ref time */
+ btd = bt_ref;
+ bintime_sub(&btd, &pps->ref_time);
+ pps->ref_time = bt_ref;
+
+ /* check that we stay below 2 sec */
+ if (btd.sec < 0 || btd.sec > 1)
+ return;
+
+ /* we want at least 0.5 sec between samples */
+ if (btd.sec == 0 && btd.frac < (uint64_t)1<<63)
+ return;
+
+ /*
+ * calculate cycles per period by multiplying
+ * the frequency with the elapsed period
+ * we pick a fraction of 30 bits
+ * ~1ns resolution for elapsed time
+ */
+ div = (uint64_t)btd.sec << 30;
+ div |= (btd.frac >> 34) & (((uint64_t)1 << 30) - 1);
+ div *= pps->capth->th_counter->tc_frequency;
+ div >>= 30;
+
+ if (div == 0) /* safeguard */
+ return;
+
+ scale = (uint64_t)1 << 63;
+ scale /= div;
scale *= 2;
+
bt.sec = 0;
bt.frac = 0;
bintime_addx(&bt, scale * tcount);
bintime2timespec(&bt, &ts);
- hardpps(tsp, ts.tv_nsec + 1000000000 * ts.tv_sec);
+
+#ifdef PPS_DEBUG
+ if (ppsdebug & 0x4) {
+ struct timespec ts2;
+ int64_t df;
+
+ bintime2timespec(&bt_ref, &ts2);
+ df = timespec2ns(&ts);
+ if (df > 500000000)
+ df -= 1000000000;
+ log(LOG_DEBUG, "hardpps: ref_ts=%"PRIi64
+ ".%09"PRIi32", ts=%"PRIi64".%09"PRIi32
+ ", freqdiff=%"PRIi64" ns/s\n",
+ ts2.tv_sec, (int32_t)ts2.tv_nsec,
+ tsp->tv_sec, (int32_t)tsp->tv_nsec,
+ df);
+ }
+#endif
+
+ hardpps(tsp, timespec2ns(&ts));
}
#endif
}
Index: src/sys/sys/timepps.h
diff -u src/sys/sys/timepps.h:1.20 src/sys/sys/timepps.h:1.21
--- src/sys/sys/timepps.h:1.20 Wed Mar 21 05:42:26 2012
+++ src/sys/sys/timepps.h Sun May 26 18:07:42 2013
@@ -1,4 +1,4 @@
-/* $NetBSD: timepps.h,v 1.20 2012/03/21 05:42:26 matt Exp $ */
+/* $NetBSD: timepps.h,v 1.21 2013/05/26 18:07:42 kardel Exp $ */
/*
* Copyright (c) 1998 Jonathan Stone
@@ -133,6 +133,15 @@ typedef struct {
#include <sys/mutex.h>
+/* flags for pps_ref_event() - bitmask but only 1 bit allowed */
+#define PPS_REFEVNT_CAPTURE 0x01 /* use captume time stamp */
+#define PPS_REFEVNT_CURRENT 0x02 /* use current time stamp */
+#define PPS_REFEVNT_CAPCUR 0x04 /* use average of above */
+#define PPS_REFEVNT_RMASK 0x0F /* mask reference bits */
+
+#define PPS_REFEVNT_PPS 0x10 /* guess PPS second from */
+ /* capture timestamp */
+
extern kmutex_t timecounter_lock;
struct pps_state {
@@ -140,6 +149,7 @@ struct pps_state {
struct timehands *capth;
unsigned capgen;
u_int64_t capcount;
+ struct bintime ref_time;
/* State information. */
pps_params_t ppsparam;
@@ -152,6 +162,7 @@ struct pps_state {
void pps_capture(struct pps_state *);
void pps_event(struct pps_state *, int);
+void pps_ref_event(struct pps_state *, int, struct bintime *, int);
void pps_init(struct pps_state *);
int pps_ioctl(unsigned long, void *, struct pps_state *);