[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, ¶ms, 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, ¶ms.assert_off_tu);
+ pps_norm_offset(&info->clear_tu, ¶ms.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