public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: Rodolfo Giometti <giometti@enneenne.com>
To: Alexander Gordeev <lasaine@lvk.cs.msu.su>
Cc: linux-kernel@vger.kernel.org, linuxpps@ml.enneenne.com,
	"Nikita V. Youshchenko" <yoush@cs.msu.su>,
	stas@lvk.cs.msu.su, john stultz <johnstul@us.ibm.com>,
	Andrew Morton <akpm@linux-foundation.org>,
	Alan Cox <alan@lxorguk.ukuu.org.uk>, Ingo Molnar <mingo@elte.hu>,
	Bernhard Schiffner <bernhard@schiffner-limbach.de>,
	Rik van Riel <riel@redhat.com>,
	Thomas Gleixner <tglx@linutronix.de>,
	Martin Schwidefsky <schwidefsky@de.ibm.com>
Subject: Re: [PATCHv2 1/6] ntp: add hardpps implementation
Date: Mon, 1 Mar 2010 09:14:59 +0100	[thread overview]
Message-ID: <20100301081458.GG3671@enneenne.com> (raw)
In-Reply-To: <eb18684a471acd332c1d6be46f5bd89b1c935095.1267008049.git.lasaine@lvk.cs.msu.su>

On Wed, Feb 24, 2010 at 03:28:12PM +0300, Alexander Gordeev wrote:
> This commit adds hardpps() implementation based upon the original one
> from the NTPv4 reference kernel code from David Mills. However, it is
> highly optimized towards very fast syncronization and maximum stickness
> to PPS signal. The typical error is less then a microsecond.
> To make it sync faster I had to throw away exponential phase filter so
> that the full phase offset is corrected immediately. Then I also had to
> throw away median phase filter because it gives a bigger error itself
> if used without exponential filter.
> Maybe we will find an appropriate filtering scheme in the future but
> it's not necessary if the signal quality is ok.
> 
> Signed-off-by: Alexander Gordeev <lasaine@lvk.cs.msu.su>
> ---
>  drivers/pps/Kconfig   |    1 +
>  include/linux/timex.h |    1 +
>  kernel/time/Kconfig   |    7 +
>  kernel/time/ntp.c     |  420 +++++++++++++++++++++++++++++++++++++++++++++++--
>  4 files changed, 414 insertions(+), 15 deletions(-)
> 
> diff --git a/drivers/pps/Kconfig b/drivers/pps/Kconfig
> index cc2eb8e..2bd4f65 100644
> --- a/drivers/pps/Kconfig
> +++ b/drivers/pps/Kconfig
> @@ -7,6 +7,7 @@ menu "PPS support"
>  config PPS
>  	tristate "PPS support"
>  	depends on EXPERIMENTAL
> +	select NTP_PPS
>  	---help---
>  	  PPS (Pulse Per Second) is a special pulse provided by some GPS
>  	  antennae. Userland can use it to get a high-precision time
> diff --git a/include/linux/timex.h b/include/linux/timex.h
> index e6967d1..5a93cd3 100644
> --- a/include/linux/timex.h
> +++ b/include/linux/timex.h
> @@ -274,6 +274,7 @@ extern u64 tick_length;
>  extern void second_overflow(void);
>  extern void update_ntp_one_tick(void);
>  extern int do_adjtimex(struct timex *);
> +extern void hardpps(const struct timespec *, const struct timespec *);
>  
>  /* Don't use! Compatibility define for existing users. */
>  #define tickadj	(500/HZ ? : 1)
> diff --git a/kernel/time/Kconfig b/kernel/time/Kconfig
> index 95ed429..2da4900 100644
> --- a/kernel/time/Kconfig
> +++ b/kernel/time/Kconfig
> @@ -27,3 +27,10 @@ config GENERIC_CLOCKEVENTS_BUILD
>  	default y
>  	depends on GENERIC_CLOCKEVENTS || GENERIC_CLOCKEVENTS_MIGR
>  
> +config NTP_PPS
> +	bool "PPS kernel consumer support"
> +	depends on PPS
> +	help
> +	  This option adds support for direct in-kernel time
> +	  syncronization using an external PPS signal.
> +

This patch is both PPS and NTP related but I suppose is better moving
this setting into drivers/pps directory since people whose want to use
thise supports can go into same place and find whetever they want...

> diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
> index 4800f93..6db097a 100644
> --- a/kernel/time/ntp.c
> +++ b/kernel/time/ntp.c
> @@ -14,6 +14,7 @@
>  #include <linux/timex.h>
>  #include <linux/time.h>
>  #include <linux/mm.h>
> +#include <linux/module.h>
>  
>  /*
>   * NTP timekeeping variables:
> @@ -74,6 +75,161 @@ long				time_adjust;
>  /* constant (boot-param configurable) NTP tick adjustment (upscaled)	*/
>  static s64			ntp_tick_adj;
>  
> +#ifdef CONFIG_NTP_PPS
> +
> +/*
> + * The following variables are used when a pulse-per-second (PPS) signal
> + * is available. They establish the engineering parameters of the clock
> + * discipline loop when controlled by the PPS signal.
> + */
> +#define PPS_VALID	120	/* PPS signal watchdog max (s) */
> +#define PPS_POPCORN	4	/* popcorn spike threshold (shift) */
> +#define PPS_INTMIN	2	/* min freq interval (s) (shift) */
> +#define PPS_INTMAX	8	/* max freq interval (s) (shift) */
> +#define PPS_INTCOUNT	4	/* number of consecutive good intervals to
> +				   increase pps_shift or consecutive bad
> +				   intervals to decrease it */
> +#define PPS_MAXWANDER	100000	/* max PPS freq wander (ns/s) */
> +
> +static int pps_valid;		/* signal watchdog counter */
> +static long pps_tf[3];		/* phase median filter */
> +static long pps_jitter;		/* current jitter (ns) */
> +static struct timespec pps_fbase; /* beginning of the last freq interval */
> +static int pps_shift;		/* current interval duration (s) (shift) */
> +static int pps_intcnt;		/* interval counter */
> +static s64 pps_freq;		/* frequency offset (scaled ns/s) */
> +static long pps_stabil;		/* current stability (scaled ns/s) */
> +
> +/*
> + * PPS signal quality monitors
> + */
> +static long pps_calcnt;		/* calibration intervals */
> +static long pps_jitcnt;		/* jitter limit exceeded */
> +static long pps_stbcnt;		/* stability limit exceeded */
> +static long pps_errcnt;		/* calibration errors */
> +
> +
> +/**
> + * pps_clear - Clears the PPS state variables
> + *
> + * Must be called while holding a write on the xtime_lock
> + */
> +static inline void pps_clear(void)
> +{
> +	pps_shift = PPS_INTMIN;
> +	pps_tf[0] = 0;
> +	pps_tf[1] = 0;
> +	pps_tf[2] = 0;
> +	pps_fbase.tv_sec = pps_fbase.tv_nsec = 0;
> +	pps_freq = 0;
> +}
> +
> +/* PPS kernel consumer compensates the whole phase error immediately.
> + * Otherwise, reduce the offset by a fixed factor times the time constant.
> + */
> +static inline s64 ntp_offset_chunk(s64 offset)
> +{
> +	if (time_status & STA_PPSTIME && time_status & STA_PPSSIGNAL)
> +		return offset;
> +	else
> +		return shift_right(offset, SHIFT_PLL + time_constant);
> +}
> +
> +/* Decrease pps_valid to indicate that another second has passed since
> + * the last PPS signal. When it reaches 0, indicate that PPS signal is
> + * missing.
> + *
> + * Must be called while holding a write on the xtime_lock
> + */
> +static inline void pps_dec_valid(void)
> +{
> +	if (pps_valid > 0)
> +		pps_valid--;
> +	else
> +		time_status &= ~(STA_PPSSIGNAL | STA_PPSJITTER |
> +				 STA_PPSWANDER | STA_PPSERROR);
> +}
> +
> +static inline void pps_reset_freq_interval(void)
> +{
> +	/* the PPS calibration interval may end
> +	   surprisingly early */
> +	pps_shift = PPS_INTMIN;
> +	pps_intcnt = 0;
> +}
> +
> +static inline void pps_set_freq(s64 freq)
> +{
> +	pps_freq = freq;
> +}
> +
> +static inline int is_error_status(int status)
> +{
> +	return (time_status & (STA_UNSYNC|STA_CLOCKERR))
> +		/* PPS signal lost when either PPS time or
> +		 * PPS frequency synchronization requested
> +		 */
> +		|| ((time_status & (STA_PPSFREQ|STA_PPSTIME))
> +			&& !(time_status & STA_PPSSIGNAL))
> +		/* PPS jitter exceeded when
> +		 * PPS time synchronization requested */
> +		|| ((time_status & (STA_PPSTIME|STA_PPSJITTER))
> +			== (STA_PPSTIME|STA_PPSJITTER))
> +		/* PPS wander exceeded or calibration error when
> +		 * PPS frequency synchronization requested
> +		 */
> +		|| ((time_status & STA_PPSFREQ)
> +			&& (time_status & (STA_PPSWANDER|STA_PPSERROR)));
> +}
> +
> +static inline void pps_fill_timex(struct timex *txc)
> +{
> +	txc->ppsfreq	   = shift_right((pps_freq >> PPM_SCALE_INV_SHIFT) *
> +					 PPM_SCALE_INV, NTP_SCALE_SHIFT);
> +	txc->jitter	   = pps_jitter;
> +	if (!(time_status & STA_NANO))
> +		txc->jitter /= NSEC_PER_USEC;
> +	txc->shift	   = pps_shift;
> +	txc->stabil	   = pps_stabil;
> +	txc->jitcnt	   = pps_jitcnt;
> +	txc->calcnt	   = pps_calcnt;
> +	txc->errcnt	   = pps_errcnt;
> +	txc->stbcnt	   = pps_stbcnt;
> +}
> +
> +#else /* !CONFIG_NTP_PPS */
> +
> +static inline void pps_clear(void) {}
> +
> +static inline s64 ntp_offset_chunk(s64 offset)
> +{
> +	return shift_right(offset, SHIFT_PLL + time_constant);
> +}
> +
> +static inline void pps_dec_valid(void) {}
> +static inline void pps_reset_freq_interval(void) {}
> +static inline void pps_set_freq(s64 freq) {}
> +
> +static inline int is_error_status(int status)
> +{
> +	return status & (STA_UNSYNC|STA_CLOCKERR);
> +}
> +
> +static inline void pps_fill_timex(struct timex *txc)
> +{
> +	/* PPS is not implemented, so these are zero */
> +	txc->ppsfreq	   = 0;
> +	txc->jitter	   = 0;
> +	txc->shift	   = 0;
> +	txc->stabil	   = 0;
> +	txc->jitcnt	   = 0;
> +	txc->calcnt	   = 0;
> +	txc->errcnt	   = 0;
> +	txc->stbcnt	   = 0;
> +}
> +
> +#endif /* CONFIG_NTP_PPS */
> +
>  /*
>   * NTP methods:
>   */
> @@ -177,6 +333,9 @@ void ntp_clear(void)
>  
>  	tick_length	= tick_length_base;
>  	time_offset	= 0;
> +
> +	/* Clear PPS state variables */
> +	pps_clear();
>  }
>  
>  /*
> @@ -242,16 +401,16 @@ void second_overflow(void)
>  		time_status |= STA_UNSYNC;
>  	}
>  
> -	/*
> -	 * Compute the phase adjustment for the next second. The offset is
> -	 * reduced by a fixed factor times the time constant.
> -	 */
> +	/* Compute the phase adjustment for the next second */
>  	tick_length	 = tick_length_base;
>  
> -	delta		 = shift_right(time_offset, SHIFT_PLL + time_constant);
> +	delta		 = ntp_offset_chunk(time_offset);
>  	time_offset	-= delta;
>  	tick_length	+= delta;
>  
> +	/* Check PPS signal */
> +	pps_dec_valid();
> +
>  	if (!time_adjust)
>  		return;
>  
> @@ -361,6 +520,8 @@ static inline void process_adj_status(struct timex *txc, struct timespec *ts)
>  	if ((time_status & STA_PLL) && !(txc->status & STA_PLL)) {
>  		time_state = TIME_OK;
>  		time_status = STA_UNSYNC;
> +		/* restart PPS frequency calibration */
> +		pps_reset_freq_interval();
>  	}
>  
>  	/*
> @@ -410,6 +571,8 @@ static inline void process_adjtimex_modes(struct timex *txc, struct timespec *ts
>  		time_freq = txc->freq * PPM_SCALE;
>  		time_freq = min(time_freq, MAXFREQ_SCALED);
>  		time_freq = max(time_freq, -MAXFREQ_SCALED);
> +		/* update pps_freq */
> +		pps_set_freq(time_freq);
>  	}
>  
>  	if (txc->modes & ADJ_MAXERROR)
> @@ -500,7 +663,8 @@ int do_adjtimex(struct timex *txc)
>  	}
>  
>  	result = time_state;	/* mostly `TIME_OK' */
> -	if (time_status & (STA_UNSYNC|STA_CLOCKERR))
> +	/* check for errors */
> +	if (is_error_status(time_status))
>  		result = TIME_ERROR;
>  
>  	txc->freq	   = shift_right((time_freq >> PPM_SCALE_INV_SHIFT) *
> @@ -514,15 +678,8 @@ int do_adjtimex(struct timex *txc)
>  	txc->tick	   = tick_usec;
>  	txc->tai	   = time_tai;
>  
> -	/* PPS is not implemented, so these are zero */
> -	txc->ppsfreq	   = 0;
> -	txc->jitter	   = 0;
> -	txc->shift	   = 0;
> -	txc->stabil	   = 0;
> -	txc->jitcnt	   = 0;
> -	txc->calcnt	   = 0;
> -	txc->errcnt	   = 0;
> -	txc->stbcnt	   = 0;
> +	/* fill PPS status fields */
> +	pps_fill_timex(txc);
>  
>  	write_sequnlock_irq(&xtime_lock);
>  
> @@ -536,6 +693,239 @@ int do_adjtimex(struct timex *txc)
>  	return result;
>  }
>  
> +#ifdef	CONFIG_NTP_PPS
> +
> +struct pps_normtime {
> +	__kernel_time_t	sec;	/* seconds */
> +	long		nsec;	/* nanoseconds */
> +};
> +
> +/* normalize the timestamp so that nsec is in the
> +   ( -NSEC_PER_SEC / 2, NSEC_PER_SEC / 2 ] interval */
> +static inline struct pps_normtime pps_normalize_ts(struct timespec ts)
> +{
> +	struct pps_normtime norm = {
> +		.sec = ts.tv_sec,
> +		.nsec = ts.tv_nsec
> +	};
> +
> +	if (norm.nsec > (NSEC_PER_SEC >> 1)) {
> +		norm.nsec -= NSEC_PER_SEC;
> +		norm.sec++;
> +	}
> +
> +	return norm;
> +}
> +
> +/* get current phase correction and jitter */
> +static inline long pps_phase_filter_get(long *jitter)
> +{
> +	*jitter = pps_tf[0] - pps_tf[1];
> +	if (*jitter < 0)
> +		*jitter = -*jitter;
> +
> +	/* TODO: test various filters */
> +	return pps_tf[0];
> +}
> +
> +/* add the sample to the phase filter */
> +static inline void pps_phase_filter_add(long err)
> +{
> +	pps_tf[2] = pps_tf[1];
> +	pps_tf[1] = pps_tf[0];
> +	pps_tf[0] = err;
> +}
> +
> +/* decrease frequency calibration interval length.
> + * It is halved after four consecutive unstable intervals.
> + */
> +static inline void pps_dec_freq_interval(void)
> +{
> +	if (--pps_intcnt <= -PPS_INTCOUNT) {
> +		pps_intcnt = -PPS_INTCOUNT;
> +		if (pps_shift > PPS_INTMIN) {
> +			pps_shift--;
> +			pps_intcnt = 0;
> +		}
> +	}
> +}
> +
> +/* increase frequency calibration interval length.
> + * It is doubled after four consecutive stable intervals.
> + */
> +static inline void pps_inc_freq_interval(void)
> +{
> +	if (++pps_intcnt >= PPS_INTCOUNT) {
> +		pps_intcnt = PPS_INTCOUNT;
> +		if (pps_shift < PPS_INTMAX) {
> +			pps_shift++;
> +			pps_intcnt = 0;
> +		}
> +	}
> +}
> +
> +/* update clock frequency based on MONOTONIC_RAW clock PPS signal
> + * timestamps
> + *
> + * At the end of the calibration interval the difference between the
> + * first and last MONOTONIC_RAW clock timestamps divided by the length
> + * of the interval becomes the frequency update. If the interval was
> + * too long, the data are discarded.
> + * Returns the difference between old and new frequency values.
> + */
> +static long hardpps_update_freq(struct pps_normtime freq_norm)
> +{
> +	long delta, delta_mod;
> +	s64 ftemp;
> +
> +	/* check if the frequency interval was too long */
> +	if (freq_norm.sec > (2 << pps_shift)) {
> +		time_status |= STA_PPSERROR;
> +		pps_errcnt++;
> +		pps_dec_freq_interval();
> +		pr_err("hardpps: PPSERROR: interval too long - %ld s\n",
> +				freq_norm.sec);
> +		return 0;
> +	}
> +
> +	/* here the raw frequency offset and wander (stability) is
> +	 * calculated. If the wander is less than the wander threshold
> +	 * the interval is increased; otherwise it is decreased.
> +	 */
> +	ftemp = div_s64(((s64)(-freq_norm.nsec)) << NTP_SCALE_SHIFT,
> +			freq_norm.sec);
> +	delta = shift_right(ftemp - pps_freq, NTP_SCALE_SHIFT);
> +	pps_freq = ftemp;
> +	if (delta > PPS_MAXWANDER || delta < -PPS_MAXWANDER) {
> +		pr_warning("hardpps: PPSWANDER: change=%ld\n", delta);
> +		time_status |= STA_PPSWANDER;
> +		pps_stbcnt++;
> +		pps_dec_freq_interval();
> +	} else {	/* good sample */
> +		pps_inc_freq_interval();
> +	}
> +
> +	/* the stability metric is calculated as the average of recent
> +	 * frequency changes, but is used only for performance
> +	 * monitoring
> +	 */
> +	delta_mod = delta;
> +	if (delta_mod < 0)
> +		delta_mod = -delta_mod;
> +	pps_stabil += (div_s64(((s64)delta_mod) <<
> +				(NTP_SCALE_SHIFT - SHIFT_USEC),
> +				NSEC_PER_USEC) - pps_stabil) >> PPS_INTMIN;
> +
> +	/* if enabled, the system clock frequency is updated */
> +	if ((time_status & STA_PPSFREQ) != 0 &&
> +	    (time_status & STA_FREQHOLD) == 0) {
> +		time_freq = pps_freq;
> +		ntp_update_frequency();
> +	}
> +
> +	return delta;
> +}
> +
> +/* correct REALTIME clock phase error against PPS signal */
> +static void hardpps_update_phase(long error)
> +{
> +	long correction = -error;
> +	long jitter;
> +
> +	/* add the sample to the median filter */
> +	pps_phase_filter_add(correction);
> +	correction = pps_phase_filter_get(&jitter);
> +
> +	/* Nominal jitter is due to PPS signal noise. If it exceeds the
> +	 * threshold, the sample is discarded; otherwise, if so enabled,
> +	 * the time offset is updated.
> +	 */
> +	if (jitter > (pps_jitter << PPS_POPCORN)) {
> +		pr_warning("hardpps: PPSJITTER: jitter=%ld, limit=%ld\n",
> +		       jitter, (pps_jitter << PPS_POPCORN));
> +		time_status |= STA_PPSJITTER;
> +		pps_jitcnt++;
> +	} else if (time_status & STA_PPSTIME) {
> +		/* correct the time using the phase offset */
> +		time_offset = div_s64(((s64)correction) << NTP_SCALE_SHIFT,
> +				NTP_INTERVAL_FREQ);
> +		/* cancel running adjtime() */
> +		time_adjust = 0;
> +	}
> +	/* update jitter */
> +	pps_jitter += (jitter - pps_jitter) >> PPS_INTMIN;
> +}
> +
> +/*
> + * hardpps() - discipline CPU clock oscillator to external PPS signal
> + *
> + * This routine is called at each PPS signal arrival in order to
> + * discipline the CPU clock oscillator to the PPS signal. It takes two
> + * parameters: REALTIME and MONOTONIC_RAW clock timestamps. The former
> + * is used to correct clock phase error and the latter is used to
> + * correct the frequency.
> + *
> + * This code is based on David Mills's reference nanokernel
> + * implementation. It was mostly rewritten but keeps the same idea.
> + */
> +void hardpps(const struct timespec *phase_ts, const struct timespec *raw_ts)
> +{
> +	struct pps_normtime pts_norm, freq_norm;
> +	unsigned long flags;
> +
> +	pts_norm = pps_normalize_ts(*phase_ts);
> +
> +	write_seqlock_irqsave(&xtime_lock, flags);
> +
> +	/* clear the error bits, they will be set again if needed */
> +	time_status &= ~(STA_PPSJITTER | STA_PPSWANDER | STA_PPSERROR);
> +
> +	/* indicate signal presence */
> +	time_status |= STA_PPSSIGNAL;
> +	pps_valid = PPS_VALID;
> +
> +	/* when called for the first time,
> +	 * just start the frequency interval */
> +	if (unlikely(pps_fbase.tv_sec == 0)) {
> +		pps_fbase = *raw_ts;
> +		write_sequnlock_irq(&xtime_lock);
> +		return;
> +	}
> +
> +	/* ok, now we have a base for frequency calculation */
> +	freq_norm = pps_normalize_ts(timespec_sub(*raw_ts, pps_fbase));
> +
> +	/* check that the signal is in the range
> +	 * [1s - MAXFREQ us, 1s + MAXFREQ us], otherwise reject it */
> +	if ((freq_norm.sec == 0) ||
> +			(freq_norm.nsec > MAXFREQ * freq_norm.sec) ||
> +			(freq_norm.nsec < -MAXFREQ * freq_norm.sec)) {
> +		time_status |= STA_PPSJITTER;
> +		/* restart the frequency calibration interval */
> +		pps_fbase = *raw_ts;
> +		write_sequnlock_irqrestore(&xtime_lock, flags);
> +		pr_err("hardpps: PPSJITTER: bad pulse\n");
> +		return;
> +	}
> +
> +	/* signal is ok */
> +
> +	/* check if the current frequency interval is finished */
> +	if (freq_norm.sec >= (1 << pps_shift)) {
> +		pps_calcnt++;
> +		/* restart the frequency calibration interval */
> +		pps_fbase = *raw_ts;
> +		hardpps_update_freq(freq_norm);
> +	}
> +
> +	hardpps_update_phase(pts_norm.nsec);
> +
> +	write_sequnlock_irqrestore(&xtime_lock, flags);
> +}
> +EXPORT_SYMBOL(hardpps);
> +
> +#endif	/* CONFIG_NTP_PPS */
> +
>  static int __init ntp_tick_adj_setup(char *str)
>  {
>  	ntp_tick_adj = simple_strtol(str, NULL, 0);
> -- 
> 1.6.6.1
> 

-- 

GNU/Linux Solutions                  e-mail: giometti@enneenne.com
Linux Device Driver                          giometti@linux.it
Embedded Systems                     phone:  +39 349 2432127
UNIX programming                     skype:  rodolfo.giometti
Freelance ICT Italia - Consulente ICT Italia - www.consulenti-ict.it

  reply	other threads:[~2010-03-01  8:15 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2010-02-24 12:28 [PATCHv2 0/6] pps: time synchronization over LPT Alexander Gordeev
2010-02-24 12:28 ` [PATCHv2 1/6] ntp: add hardpps implementation Alexander Gordeev
2010-03-01  8:14   ` Rodolfo Giometti [this message]
2010-03-01 10:43     ` Alexander Gordeev
2010-02-24 12:28 ` [PATCHv2 2/6] pps: unify timestamp gathering Alexander Gordeev
2010-03-01  8:18   ` Rodolfo Giometti
2010-02-24 12:28 ` [PATCHv2 3/6] pps: capture MONOTONIC_RAW timestamps as well Alexander Gordeev
2010-03-01  8:19   ` Rodolfo Giometti
2010-02-24 12:28 ` [PATCHv2 4/6] pps: add kernel consumer support Alexander Gordeev
2010-03-01  8:29   ` Rodolfo Giometti
2010-03-01 10:48     ` Alexander Gordeev
2010-02-24 12:28 ` [PATCHv2 5/6] pps: add parallel port PPS signal generator Alexander Gordeev
2010-03-01  8:38   ` Rodolfo Giometti
2010-03-01 10:51     ` Alexander Gordeev
2010-02-24 12:28 ` [PATCHv2 6/6] pps: add parallel port PPS client Alexander Gordeev
2010-03-01  8:42   ` Rodolfo Giometti
2010-03-09  3:25 ` [PATCHv2 0/6] pps: time synchronization over LPT john stultz
2010-03-22 20:42   ` Alexander Gordeev
2010-03-22 21:01     ` john stultz
2010-03-22 21:37       ` Alexander Gordeev

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20100301081458.GG3671@enneenne.com \
    --to=giometti@enneenne.com \
    --cc=akpm@linux-foundation.org \
    --cc=alan@lxorguk.ukuu.org.uk \
    --cc=bernhard@schiffner-limbach.de \
    --cc=johnstul@us.ibm.com \
    --cc=lasaine@lvk.cs.msu.su \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linuxpps@ml.enneenne.com \
    --cc=mingo@elte.hu \
    --cc=riel@redhat.com \
    --cc=schwidefsky@de.ibm.com \
    --cc=stas@lvk.cs.msu.su \
    --cc=tglx@linutronix.de \
    --cc=yoush@cs.msu.su \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox