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 *);
 

Reply via email to