[LinuxPPS] [PATCH 10/11] PPS: Make PPS parameters per-reader.

George Spelvin linux at horizon.com
Fri Feb 6 14:35:04 CET 2009


This is a fairly significant cleanup, moving the PPS parameters from
the global PPS structure to a per-fd structure.  Now each PPS reader
can specify a different capture mask and offset value.  (And timestamp
format, if that's ever supported.)

Only the PPS_ECHO* flags are now global.

The fasync handling is decidedly ugly.  Can anyone suggest a
better way?
---
 drivers/pps/kapi.c  |   39 ++-----------
 drivers/pps/pps.c   |  156 +++++++++++++++++++++++++++++++++++++++++++++------
 include/linux/pps.h |    8 +--
 3 files changed, 148 insertions(+), 55 deletions(-)

diff --git a/drivers/pps/kapi.c b/drivers/pps/kapi.c
index fa8ec89..81e2439 100644
--- a/drivers/pps/kapi.c
+++ b/drivers/pps/kapi.c
@@ -38,24 +38,6 @@ DEFINE_SPINLOCK(pps_idr_lock);
 DEFINE_IDR(pps_idr);
 
 /*
- * Local functions
- */
-
-static void pps_add_offset(struct pps_ktime *ts, struct pps_ktime *offset)
-{
-	ts->nsec += offset->nsec;
-	while (ts->nsec >= NSEC_PER_SEC) {
-		ts->nsec -= NSEC_PER_SEC;
-		ts->sec++;
-	}
-	while (ts->nsec < 0) {
-		ts->nsec += NSEC_PER_SEC;
-		ts->sec--;
-	}
-	ts->sec += offset->sec;
-}
-
-/*
  * Exported functions
  */
 
@@ -152,8 +134,8 @@ pps_register_source(struct pps_source_info *info, int default_params)
 		goto pps_register_source_exit;
 	}
 
-	pps->params.api_version = PPS_API_VERS;
-	pps->params.mode = default_params;
+	pps->default_mode = default_params;
+	pps->echo_mode = default_params & (PPS_ECHOASSERT | PPS_ECHOCLEAR);
 	pps->info = *info;
 
 	init_waitqueue_head(&pps->queue);
@@ -265,13 +247,10 @@ pps_event(struct pps_device *pps, struct pps_ktime *ts, int event, void *data)
 			pps->id, (unsigned long long) ts->sec, ts->nsec);
 
 	/* Check the event */
-	mode = pps->params.mode;
+	mode = pps->echo_mode;
 	if (event & PPS_CAPTUREASSERT) {
 		if (mode & PPS_ECHOASSERT)
 			pps->info.echo(pps->id, PPS_CAPTUREASSERT, data);
-		/* We have to add an offset? */
-		if (mode & PPS_OFFSETASSERT)
-			pps_add_offset(ts, &pps->params.assert_off_tu);
 
 		/* Save the time stamp */
 		seq = pps->assert_sequence + 1;
@@ -280,13 +259,11 @@ pps_event(struct pps_device *pps, struct pps_ktime *ts, int event, void *data)
 		pps->assert_sequence = seq;
 		pr_debug("capture assert seq #%u for source %d\n",
 			seq, pps->id);
+		kill_fasync(&pps->fasync_queue[0], SIGIO, POLL_IN);
 	}
 	if (event & PPS_CAPTURECLEAR) {
 		if (mode & PPS_ECHOCLEAR)
 			pps->info.echo(pps->id, PPS_CAPTURECLEAR, data);
-		/* We have to add an offset? */
-		if (mode & PPS_OFFSETCLEAR)
-			pps_add_offset(ts, &pps->params.clear_off_tu);
 
 		/* Save the time stamp */
 		seq = pps->clear_sequence + 1;
@@ -295,12 +272,10 @@ pps_event(struct pps_device *pps, struct pps_ktime *ts, int event, void *data)
 		pps->clear_sequence = seq;
 		pr_debug("capture clear seq #%u for source %d\n",
 			seq, pps->id);
+		kill_fasync(&pps->fasync_queue[1], SIGIO, POLL_IN);
 	}
 
-	if (event & mode) {
-		pps->go = ~0;
-		wake_up_interruptible(&pps->queue);
-		kill_fasync(&pps->async_queue, SIGIO, POLL_IN);
-	}
+	wake_up_interruptible(&pps->queue);
+	kill_fasync(&pps->fasync_queue[2], SIGIO, POLL_IN);
 }
 EXPORT_SYMBOL(pps_event);
diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c
index 1f0da51..8e3ef56 100644
--- a/drivers/pps/pps.c
+++ b/drivers/pps/pps.c
@@ -38,6 +38,46 @@ static dev_t pps_devt;
 static struct class *pps_class;
 
 /*
+ * Local functions
+ */
+
+/* Copy a pps_ktime, normalizing the nsec field */
+static void pps_norm_offset(struct pps_ktime *dst, struct pps_ktime const *src)
+{
+	__s32 nsec = src->nsec;
+	dst->sec = src->sec;
+	while (nsec >= NSEC_PER_SEC) {
+		nsec -= NSEC_PER_SEC;
+		dst->sec++;
+	}
+	while (nsec < 0) {
+		nsec += NSEC_PER_SEC;
+		dst->sec--;
+	}
+	dst->flags = 0;
+}
+
+/* Add a (normalized) offset to a timestamp. */
+static void pps_add_offset(struct pps_ktime *ts, struct pps_ktime const *offset)
+{
+	ts->nsec += offset->nsec;
+	if (ts->nsec >= NSEC_PER_SEC) {
+		ts->nsec -= NSEC_PER_SEC;
+		ts->sec++;
+	}
+	ts->sec += offset->sec;
+}
+
+static bool __pure
+pps_is_ready(struct pps_device const *pps, struct pps_kinfo const *info)
+{
+	return (info->current_mode & PPS_CAPTUREASSERT &&
+	        pps->assert_sequence != info->assert_sequence) ||
+	       (info->current_mode & PPS_CAPTURECLEAR &&
+	        pps->clear_sequence != info->clear_sequence);
+}
+
+/*
  * Char device methods
  */
 
@@ -45,17 +85,62 @@ static unsigned int pps_cdev_poll(struct file *file, poll_table *wait)
 {
 	struct pps_device *pps = container_of(file->f_dentry->d_inode->i_cdev,
 						struct pps_device, cdev);
+	struct pps_kinfo const *info = file->private_data;
 
 	poll_wait(file, &pps->queue, wait);
 
-	return POLLIN | POLLRDNORM;
+	return pps_is_ready(pps, info) ? POLLIN | POLLRDNORM : 0;
 }
 
+/*
+ * PPS is a very unusual device in that different readers can have
+ * different wakeup conditions, depending on their individual mode bits.
+ * Unfortunately, without rewriting kill_fasync(), there's no way to
+ * include a test when it iterates over the queued tasks.  Fortunately,
+ * there are only three cases, so we maintain three wakeup lists and
+ * put the reader on the correct one.
+ *
+ * The hairy part is that we have to maintain the illusion that there's
+ * a single list that the reader is being added to (if on == 1) or
+ * removed from (if on == 0).  But the mode might have changed between
+ * the previous call and this one.
+ *
+ * Here, we remove from the wrong lists before adding to the right one.
+ * By reversing the test, we could simplify the code; fasync_helper
+ * returns a flag indicating if the reader was already on the list which
+ * we could use to elide the removal from the other lists in the common
+ * ca flag indicating if the reader was already on the list which we
+ * could use to elide the removal from the other lists in the common
+ * case that the mode hasn't changed since last call.
+ *
+ * (Alternatively, we could just remember the list explicitly in the
+ * private data structure.  Or can there be multiples?)
+ */
+#if PPS_CAPTUREBOTH != 3
+# error fasync_queue logic needs rewriting!
+#endif
 static int pps_cdev_fasync(int fd, struct file *file, int on)
 {
 	struct pps_device *pps = container_of(file->f_dentry->d_inode->i_cdev,
 						struct pps_device, cdev);
-	return fasync_helper(fd, file, on, &pps->async_queue);
+	struct pps_kinfo const *info = file->private_data;
+	int i, mode = on ? info->current_mode & PPS_CAPTUREBOTH : 0;
+	int err, result = 0;
+
+	/* Remove from wrong list */
+	for (i = 0; i < 3; i++)
+		if (mode != i+1)
+			result |= fasync_helper(fd, file, 0,
+						&pps->fasync_queue[i]);
+
+	/* Add to right list */
+	if (mode > 0) {
+		err = fasync_helper(fd, file, on, &pps->fasync_queue[mode-1]);
+		if (err < 0)
+			return err;
+		result |= err;
+	}
+	return result;
 }
 
 static long pps_cdev_ioctl(struct file *file,
@@ -64,10 +149,11 @@ static long pps_cdev_ioctl(struct file *file,
 	struct pps_device *pps = container_of(file->f_dentry->d_inode->i_cdev,
 						struct pps_device, cdev);
 	struct pps_kparams params;
+	struct pps_kinfo *info;
 	struct pps_fdata fdata;
 	unsigned long ticks;
 	void __user *uarg = (void __user *) arg;
-	int __user *iuarg = (int __user *) arg;
+	struct pps_fdata __user *fuarg = uarg;
 	u32 seq1, seq2;
 	int err;
 
@@ -82,9 +168,17 @@ static long pps_cdev_ioctl(struct file *file,
 		pr_debug("PPS_GETPARAMS: source %d\n", pps->id);
 
 		/* Return current parameters */
+		info = file->private_data;
+		params.mode = info->current_mode &
+				~(PPS_ECHOASSERT | PPS_ECHOCLEAR);
+		params.assert_off_tu = info->assert_tu;
+		params.clear_off_tu = info->clear_tu;
+
 		spin_lock(&pps->lock);
-		err = copy_to_user(uarg, &pps->params, sizeof pps->params);
+		params.api_version = PPS_API_VERS;
+		params.mode |= pps->echo_mode;
 		spin_unlock(&pps->lock);
+		err = copy_to_user(uarg, &params, sizeof params);
 		if (err)
 			return -EFAULT;
 
@@ -123,13 +217,17 @@ static long pps_cdev_ioctl(struct file *file,
 								params.mode);
 			params.mode |= PPS_TSFMT_TSPEC;
 		}
-		if (pps->info.mode & PPS_CANWAIT)
-			params.mode |= PPS_CANWAIT;
-		params.api_version = PPS_API_VERS;
+		params.mode |= pps->info.mode & (PPS_CANWAIT | PPS_CANPOLL);
 
-		/* Save the new parameters */
+		/* Save the new parameters in the local version */
+		info = file->private_data;
+		pps_norm_offset(&info->assert_tu, &params.assert_off_tu);
+		pps_norm_offset(&info->clear_tu, &params.clear_off_tu);
+		info->current_mode = params.mode;
+
+		/* Save the global parameters (only the echo bits count) */
 		spin_lock(&pps->lock);
-		pps->params = params;
+		pps->echo_mode = params.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR);
 		spin_unlock(&pps->lock);
 
 		break;
@@ -137,7 +235,7 @@ static long pps_cdev_ioctl(struct file *file,
 	case PPS_GETCAP:
 		pr_debug("PPS_GETCAP: source %d\n", pps->id);
 
-		err = put_user(pps->info.mode, iuarg);
+		err = put_user(pps->info.mode, (int __user *)arg);
 		if (err)
 			return -EFAULT;
 
@@ -148,15 +246,17 @@ static long pps_cdev_ioctl(struct file *file,
 
 		if (!uarg)
 			return -EINVAL;
-		err = copy_from_user(&fdata, uarg, sizeof fdata);
+		err = copy_from_user(&fdata.timeout, &fuarg->timeout,
+			sizeof fdata.timeout);
 		if (err)
 			return -EFAULT;
 
-		pps->go = 0;
+		info = file->private_data;
 
 		/* Manage the timeout */
 		if (fdata.timeout.flags & PPS_TIME_INVALID)
-			err = wait_event_interruptible(pps->queue, pps->go);
+			err = wait_event_interruptible(pps->queue,
+						pps_is_ready(pps, info));
 		else {
 			pr_debug("timeout %lld.%09d\n",
 					(long long) fdata.timeout.sec,
@@ -166,7 +266,9 @@ static long pps_cdev_ioctl(struct file *file,
 
 			if (ticks != 0) {
 				err = wait_event_interruptible_timeout(
-						pps->queue, pps->go, ticks);
+						pps->queue,
+						pps_is_ready(pps, info),
+						ticks);
 				if (err == 0)
 					return -ETIMEDOUT;
 			}
@@ -179,14 +281,22 @@ static long pps_cdev_ioctl(struct file *file,
 		}
 
 		/* Return the fetched timestamp */
-		fdata.info.assert_sequence = seq1 = pps->assert_sequence;
-		fdata.info.clear_sequence = seq2 = pps->clear_sequence;
+		fdata.info.assert_sequence = info->assert_sequence = seq1 =
+			pps->assert_sequence;
+		fdata.info.clear_sequence = info->clear_sequence = seq2 =
+			pps->clear_sequence;
+
 		read_barrier_depends();
+
 		fdata.info.assert_tu = pps->assert_tu[seq1 % 4];
+		if (info->current_mode & PPS_OFFSETASSERT)
+			pps_add_offset(&fdata.info.assert_tu, &info->assert_tu);
 		fdata.info.clear_tu = pps->clear_tu[seq2 % 4];
-		fdata.info.current_mode = pps->current_mode;
+		if (info->current_mode & PPS_OFFSETCLEAR)
+			pps_add_offset(&fdata.info.clear_tu, &info->clear_tu);
+		fdata.info.current_mode = info->current_mode;
 
-		err = copy_to_user(uarg, &fdata, sizeof fdata);
+		err = copy_to_user(&fuarg->info, &fdata.info, sizeof fdata.info);
 		if (err)
 			return -EFAULT;
 
@@ -205,10 +315,19 @@ static int pps_cdev_open(struct inode *inode, struct file *file)
 	struct pps_device *pps = container_of(inode->i_cdev,
 						struct pps_device, cdev);
 	int found;
+	struct pps_kinfo *info;
 
 	found = pps_get_source(pps->id) != 0;	/* Bump refcount */
 	if (!found)
 		return -ENODEV;
+	info = kzalloc(sizeof *info, GFP_KERNEL);
+	if (!info) {
+		pps_put_source(pps);
+		return -ENOMEM;
+	}
+	info->current_mode = pps->default_mode;
+	/* Offsets remain zero */
+	file->private_data = info;
 
 	return 0;
 }
@@ -218,6 +337,7 @@ static int pps_cdev_release(struct inode *inode, struct file *file)
 	struct pps_device *pps = container_of(inode->i_cdev,
 						struct pps_device, cdev);
 
+	kfree(file->private_data);
 	/* Free the PPS source and wake up (possible) deregistration */
 	pps_put_source(pps);
 
diff --git a/include/linux/pps.h b/include/linux/pps.h
index 2609708..deb739c 100644
--- a/include/linux/pps.h
+++ b/include/linux/pps.h
@@ -147,22 +147,20 @@ struct pps_source_info {
 struct pps_device {
 	struct pps_source_info info;		/* PSS source info */
 
-	struct pps_kparams params;		/* PPS's current params */
-
 	__u32 assert_sequence;			/* PPS' assert event seq # */
 	__u32 clear_sequence;			/* PPS' clear event seq # */
 	struct pps_ktime assert_tu[4];
 	struct pps_ktime clear_tu[4];
-	int current_mode;			/* PPS mode at event time */
+	int default_mode;			/* Default mode */
+	int echo_mode;				/* Current PPS_ECHO bits */
 
-	int go;					/* PPS event is arrived? */
 	wait_queue_head_t queue;		/* PPS event queue */
 
 	unsigned int id;			/* PPS source unique ID */
 	struct cdev cdev;
 	struct device *dev;
 	int devno;
-	struct fasync_struct *async_queue;	/* fasync method */
+	struct fasync_struct *fasync_queue[3];	/* fasync method */
 	spinlock_t lock;
 
 	atomic_t usage;				/* usage count */
-- 
1.6.0.6




More information about the LinuxPPS mailing list