[LinuxPPS] [PATCH 02b/12] PPS: Make pps_event capturing lockless.

George Spelvin linux at horizon.com
Tue Feb 10 04:57:48 CET 2009


Rather than using spinlock mutual exclusion between pps_event
and PPS_FETCH, store captured timestamps into a circular buffer
and atomically update the sequence number to publish them to
PPS_FETCH.

This technically has a race if more than 5 pps_events of a single type
arrive during one PPS_FETCH; given a typical 1 Hz rate, is that really
likely?
---
 drivers/pps/kapi.c  |   46 ++++++++++++++++++++++++----------------------
 drivers/pps/pps.c   |   16 +++++++---------
 drivers/pps/sysfs.c |   14 ++++++++++----
 include/linux/pps.h |    4 ++--
 4 files changed, 43 insertions(+), 37 deletions(-)

diff --git a/drivers/pps/kapi.c b/drivers/pps/kapi.c
index 4d6a2ed..77c2b96 100644
--- a/drivers/pps/kapi.c
+++ b/drivers/pps/kapi.c
@@ -258,7 +258,8 @@ EXPORT_SYMBOL(pps_unregister_source);
 void
 pps_event(struct pps_device *pps, struct pps_ktime *ts, int event, void *data)
 {
-	unsigned long flags;
+	int mode;
+	u32 seq;
 
 	if (!pps)
 		return;
@@ -273,42 +274,43 @@ pps_event(struct pps_device *pps, struct pps_ktime *ts, int event, void *data)
 	pr_debug("PPS event on source %d at %llu.%06u\n",
 			pps->id, (unsigned long long) ts->sec, ts->nsec);
 
-	spin_lock_irqsave(&pps->lock, flags);
-
-	/* Must call the echo function? */
-	if ((pps->params.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)))
-		pps->info.echo(pps, event, data);
-
 	/* Check the event */
-	pps->current_mode = pps->params.mode;
+	mode = pps->params.mode;
 	if (event & PPS_CAPTUREASSERT) {
+		if (mode & PPS_ECHOASSERT)
+			pps->info.echo(pps->id, PPS_CAPTUREASSERT, data);
 		/* We have to add an offset? */
-		if (pps->params.mode & PPS_OFFSETASSERT)
+		if (mode & PPS_OFFSETASSERT)
 			pps_add_offset(ts, &pps->params.assert_off_tu);
 
 		/* Save the time stamp */
-		pps->assert_tu = *ts;
-		pps->assert_sequence++;
+		seq = pps->assert_sequence + 1;
+		pps->assert_tu[seq % 4] = *ts;
+		wmb();
+		pps->assert_sequence = seq;
 		pr_debug("capture assert seq #%u for source %d\n",
-			pps->assert_sequence, pps->id);
+			seq, pps->id);
 	}
 	if (event & PPS_CAPTURECLEAR) {
+		if (mode & PPS_ECHOCLEAR)
+			pps->info.echo(pps->id, PPS_CAPTURECLEAR, data);
 		/* We have to add an offset? */
-		if (pps->params.mode & PPS_OFFSETCLEAR)
+		if (mode & PPS_OFFSETCLEAR)
 			pps_add_offset(ts, &pps->params.clear_off_tu);
 
 		/* Save the time stamp */
-		pps->clear_tu = *ts;
-		pps->clear_sequence++;
+		seq = pps->clear_sequence + 1;
+		pps->clear_tu[seq % 4] = *ts;
+		wmb();
+		pps->clear_sequence = seq;
 		pr_debug("capture clear seq #%u for source %d\n",
-			pps->clear_sequence, pps->id);
+			seq, pps->id);
 	}
 
-	pps->go = ~0;
-	wake_up_interruptible(&pps->queue);
-
-	kill_fasync(&pps->async_queue, SIGIO, POLL_IN);
-
-	spin_unlock_irqrestore(&pps->lock, flags);
+	if (event & mode) {
+		pps->go = ~0;
+		wake_up_interruptible(&pps->queue);
+		kill_fasync(&pps->async_queue, SIGIO, POLL_IN);
+	}
 }
 EXPORT_SYMBOL(pps_event);
diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c
index aeaa52e..92fc76a 100644
--- a/drivers/pps/pps.c
+++ b/drivers/pps/pps.c
@@ -65,6 +65,7 @@ static long pps_cdev_ioctl(struct file *file,
 	unsigned long ticks;
 	void __user *uarg = (void __user *) arg;
 	int __user *iuarg = (int __user *) arg;
+	u32 seq1, seq2;
 	int err;
 
 	switch (cmd) {
@@ -171,17 +172,14 @@ static long pps_cdev_ioctl(struct file *file,
 		}
 
 		/* Return the fetched timestamp */
-		spin_lock_irq(&pps->lock);
-
-		fdata.info.assert_sequence = pps->assert_sequence;
-		fdata.info.clear_sequence = pps->clear_sequence;
-		fdata.info.assert_tu = pps->assert_tu;
-		fdata.info.clear_tu = pps->clear_tu;
+		fdata.info.assert_sequence = seq1 = pps->assert_sequence;
+		fdata.info.clear_sequence = seq2 = pps->clear_sequence;
+		read_barrier_depends();
+		fdata.info.assert_tu = pps->assert_tu[seq1 % 4];
+		fdata.info.clear_tu = pps->clear_tu[seq2 % 4];
 		fdata.info.current_mode = pps->current_mode;
 
-		spin_unlock_irq(&pps->lock);
-
-		err = copy_to_user(uarg, &fdata, sizeof(struct pps_fdata));
+		err = copy_to_user(uarg, &fdata, sizeof fdata);
 		if (err)
 			return -EFAULT;
 
diff --git a/drivers/pps/sysfs.c b/drivers/pps/sysfs.c
index b4cc850..84a5634 100644
--- a/drivers/pps/sysfs.c
+++ b/drivers/pps/sysfs.c
@@ -33,26 +33,32 @@ static ssize_t pps_show_assert(struct device *dev,
 				struct device_attribute *attr, char *buf)
 {
 	struct pps_device *pps = dev_get_drvdata(dev);
+	u32 seq;
 
 	if (!(pps->info.mode & PPS_CAPTUREASSERT))
 		return 0;
 
+	seq = pps->assert_sequence;
+	rmb();
 	return sprintf(buf, "%lld.%09d#%d\n",
-			(long long) pps->assert_tu.sec, pps->assert_tu.nsec,
-			pps->assert_sequence);
+			(long long) pps->assert_tu[seq % 4].sec,
+			pps->assert_tu[seq % 4].nsec, (unsigned)seq);
 }
 
 static ssize_t pps_show_clear(struct device *dev,
 				struct device_attribute *attr, char *buf)
 {
 	struct pps_device *pps = dev_get_drvdata(dev);
+	u32 seq;
 
 	if (!(pps->info.mode & PPS_CAPTURECLEAR))
 		return 0;
 
+	seq = pps->clear_sequence;
+	rmb();
 	return sprintf(buf, "%lld.%09d#%d\n",
-			(long long) pps->clear_tu.sec, pps->clear_tu.nsec,
-			pps->clear_sequence);
+			(long long) pps->clear_tu[seq % 4].sec,
+			pps->clear_tu[seq % 4].nsec, (unsigned)seq);
 }
 
 static ssize_t pps_show_mode(struct device *dev,
diff --git a/include/linux/pps.h b/include/linux/pps.h
index c8cacdc..bc025f2 100644
--- a/include/linux/pps.h
+++ b/include/linux/pps.h
@@ -153,8 +153,8 @@ struct pps_device {
 
 	__u32 assert_sequence;			/* PPS' assert event seq # */
 	__u32 clear_sequence;			/* PPS' clear event seq # */
-	struct pps_ktime assert_tu;
-	struct pps_ktime clear_tu;
+	struct pps_ktime assert_tu[4];
+	struct pps_ktime clear_tu[4];
 	int current_mode;			/* PPS mode at event time */
 
 	int go;					/* PPS event is arrived? */
-- 
1.6.0.6




More information about the LinuxPPS mailing list