LinuxPPS: core support

This patch adds the kernel side of the PPS support currently named
"LinuxPPS".

PPS means "pulse per second" and a PPS source is just a device which
provides a high precision signal each second so that an application can
use it to adjust system clock time.

Common use is the combination of the NTPD as userland program with a GPS
receiver as PPS source to obtain a wallclock-time with sub-millisecond
synchronisation to UTC.

To obtain this goal the userland programs shoud use the PPS API
specification (RFC 2783 - Pulse-Per-Second API for UNIX-like Operating
Systems, Version 1.0) which in part is implemented by this patch.  It
provides a set of chars devices, one per PPS source, which can be used to
get the time signal.  The RFC's functions can be implemented by accessing
to these char devices.

Signed-off-by: Rodolfo Giometti <giometti@linux.it>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: Greg KH <greg@kroah.com>
Cc: Randy Dunlap <randy.dunlap@oracle.com>
Cc: Kay Sievers <kay.sievers@vrfy.org>
Acked-by: Alan Cox <alan@lxorguk.ukuu.org.uk>
Cc: Michael Kerrisk <mtk.manpages@googlemail.com>
Cc: Christoph Hellwig <hch@infradead.org>
Cc: Roman Zippel <zippel@linux-m68k.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Rodolfo Giometti
2009-06-17 16:28:37 -07:00
committed by Linus Torvalds
parent 8820f27ad9
commit eae9d2ba0c
14 changed files with 1249 additions and 0 deletions

33
drivers/pps/Kconfig Normal file
View File

@@ -0,0 +1,33 @@
#
# PPS support configuration
#
menu "PPS support"
config PPS
tristate "PPS support"
depends on EXPERIMENTAL
---help---
PPS (Pulse Per Second) is a special pulse provided by some GPS
antennae. Userland can use it to get a high-precision time
reference.
Some antennae's PPS signals are connected with the CD (Carrier
Detect) pin of the serial line they use to communicate with the
host. In this case use the SERIAL_LINE client support.
Some antennae's PPS signals are connected with some special host
inputs so you have to enable the corresponding client support.
To compile this driver as a module, choose M here: the module
will be called pps_core.ko.
config PPS_DEBUG
bool "PPS debugging messages"
depends on PPS
help
Say Y here if you want the PPS support to produce a bunch of debug
messages to the system log. Select this if you are having a
problem with PPS support and want to see more of what is going on.
endmenu

8
drivers/pps/Makefile Normal file
View File

@@ -0,0 +1,8 @@
#
# Makefile for the PPS core.
#
pps_core-y := pps.o kapi.o sysfs.o
obj-$(CONFIG_PPS) := pps_core.o
ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG

329
drivers/pps/kapi.c Normal file
View File

@@ -0,0 +1,329 @@
/*
* kernel API
*
*
* Copyright (C) 2005-2009 Rodolfo Giometti <giometti@linux.it>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/time.h>
#include <linux/spinlock.h>
#include <linux/idr.h>
#include <linux/fs.h>
#include <linux/pps_kernel.h>
/*
* Global variables
*/
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
*/
/* pps_get_source - find a PPS source
* @source: the PPS source ID.
*
* This function is used to find an already registered PPS source into the
* system.
*
* The function returns NULL if found nothing, otherwise it returns a pointer
* to the PPS source data struct (the refcounter is incremented by 1).
*/
struct pps_device *pps_get_source(int source)
{
struct pps_device *pps;
unsigned long flags;
spin_lock_irqsave(&pps_idr_lock, flags);
pps = idr_find(&pps_idr, source);
if (pps != NULL)
atomic_inc(&pps->usage);
spin_unlock_irqrestore(&pps_idr_lock, flags);
return pps;
}
/* pps_put_source - free the PPS source data
* @pps: a pointer to the PPS source.
*
* This function is used to free a PPS data struct if its refcount is 0.
*/
void pps_put_source(struct pps_device *pps)
{
unsigned long flags;
spin_lock_irqsave(&pps_idr_lock, flags);
BUG_ON(atomic_read(&pps->usage) == 0);
if (!atomic_dec_and_test(&pps->usage)) {
pps = NULL;
goto exit;
}
/* No more reference to the PPS source. We can safely remove the
* PPS data struct.
*/
idr_remove(&pps_idr, pps->id);
exit:
spin_unlock_irqrestore(&pps_idr_lock, flags);
kfree(pps);
}
/* pps_register_source - add a PPS source in the system
* @info: the PPS info struct
* @default_params: the default PPS parameters of the new source
*
* This function is used to add a new PPS source in the system. The new
* source is described by info's fields and it will have, as default PPS
* parameters, the ones specified into default_params.
*
* The function returns, in case of success, the PPS source ID.
*/
int pps_register_source(struct pps_source_info *info, int default_params)
{
struct pps_device *pps;
int id;
int err;
/* Sanity checks */
if ((info->mode & default_params) != default_params) {
printk(KERN_ERR "pps: %s: unsupported default parameters\n",
info->name);
err = -EINVAL;
goto pps_register_source_exit;
}
if ((info->mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)) != 0 &&
info->echo == NULL) {
printk(KERN_ERR "pps: %s: echo function is not defined\n",
info->name);
err = -EINVAL;
goto pps_register_source_exit;
}
if ((info->mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) {
printk(KERN_ERR "pps: %s: unspecified time format\n",
info->name);
err = -EINVAL;
goto pps_register_source_exit;
}
/* Allocate memory for the new PPS source struct */
pps = kzalloc(sizeof(struct pps_device), GFP_KERNEL);
if (pps == NULL) {
err = -ENOMEM;
goto pps_register_source_exit;
}
/* These initializations must be done before calling idr_get_new()
* in order to avoid reces into pps_event().
*/
pps->params.api_version = PPS_API_VERS;
pps->params.mode = default_params;
pps->info = *info;
init_waitqueue_head(&pps->queue);
spin_lock_init(&pps->lock);
atomic_set(&pps->usage, 1);
/* Get new ID for the new PPS source */
if (idr_pre_get(&pps_idr, GFP_KERNEL) == 0) {
err = -ENOMEM;
goto kfree_pps;
}
spin_lock_irq(&pps_idr_lock);
/* Now really allocate the PPS source.
* After idr_get_new() calling the new source will be freely available
* into the kernel.
*/
err = idr_get_new(&pps_idr, pps, &id);
if (err < 0) {
spin_unlock_irq(&pps_idr_lock);
goto kfree_pps;
}
id = id & MAX_ID_MASK;
if (id >= PPS_MAX_SOURCES) {
spin_unlock_irq(&pps_idr_lock);
printk(KERN_ERR "pps: %s: too many PPS sources in the system\n",
info->name);
err = -EBUSY;
goto free_idr;
}
pps->id = id;
spin_unlock_irq(&pps_idr_lock);
/* Create the char device */
err = pps_register_cdev(pps);
if (err < 0) {
printk(KERN_ERR "pps: %s: unable to create char device\n",
info->name);
goto free_idr;
}
pr_info("new PPS source %s at ID %d\n", info->name, id);
return id;
free_idr:
spin_lock_irq(&pps_idr_lock);
idr_remove(&pps_idr, id);
spin_unlock_irq(&pps_idr_lock);
kfree_pps:
kfree(pps);
pps_register_source_exit:
printk(KERN_ERR "pps: %s: unable to register source\n", info->name);
return err;
}
EXPORT_SYMBOL(pps_register_source);
/* pps_unregister_source - remove a PPS source from the system
* @source: the PPS source ID
*
* This function is used to remove a previously registered PPS source from
* the system.
*/
void pps_unregister_source(int source)
{
struct pps_device *pps;
spin_lock_irq(&pps_idr_lock);
pps = idr_find(&pps_idr, source);
if (!pps) {
BUG();
spin_unlock_irq(&pps_idr_lock);
return;
}
spin_unlock_irq(&pps_idr_lock);
pps_unregister_cdev(pps);
pps_put_source(pps);
}
EXPORT_SYMBOL(pps_unregister_source);
/* pps_event - register a PPS event into the system
* @source: the PPS source ID
* @ts: the event timestamp
* @event: the event type
* @data: userdef pointer
*
* This function is used by each PPS client in order to register a new
* PPS event into the system (it's usually called inside an IRQ handler).
*
* If an echo function is associated with the PPS source it will be called
* as:
* pps->info.echo(source, event, data);
*/
void pps_event(int source, struct pps_ktime *ts, int event, void *data)
{
struct pps_device *pps;
unsigned long flags;
if ((event & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR)) == 0) {
printk(KERN_ERR "pps: unknown event (%x) for source %d\n",
event, source);
return;
}
pps = pps_get_source(source);
if (!pps)
return;
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(source, event, data);
/* Check the event */
pps->current_mode = pps->params.mode;
if (event & PPS_CAPTUREASSERT) {
/* We have to add an offset? */
if (pps->params.mode & PPS_OFFSETASSERT)
pps_add_offset(ts, &pps->params.assert_off_tu);
/* Save the time stamp */
pps->assert_tu = *ts;
pps->assert_sequence++;
pr_debug("capture assert seq #%u for source %d\n",
pps->assert_sequence, source);
}
if (event & PPS_CAPTURECLEAR) {
/* We have to add an offset? */
if (pps->params.mode & PPS_OFFSETCLEAR)
pps_add_offset(ts, &pps->params.clear_off_tu);
/* Save the time stamp */
pps->clear_tu = *ts;
pps->clear_sequence++;
pr_debug("capture clear seq #%u for source %d\n",
pps->clear_sequence, source);
}
pps->go = ~0;
wake_up_interruptible(&pps->queue);
kill_fasync(&pps->async_queue, SIGIO, POLL_IN);
spin_unlock_irqrestore(&pps->lock, flags);
/* Now we can release the PPS source for (possible) deregistration */
pps_put_source(pps);
}
EXPORT_SYMBOL(pps_event);

312
drivers/pps/pps.c Normal file
View File

@@ -0,0 +1,312 @@
/*
* PPS core file
*
*
* Copyright (C) 2005-2009 Rodolfo Giometti <giometti@linux.it>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/idr.h>
#include <linux/cdev.h>
#include <linux/poll.h>
#include <linux/pps_kernel.h>
/*
* Local variables
*/
static dev_t pps_devt;
static struct class *pps_class;
/*
* Char device methods
*/
static unsigned int pps_cdev_poll(struct file *file, poll_table *wait)
{
struct pps_device *pps = file->private_data;
poll_wait(file, &pps->queue, wait);
return POLLIN | POLLRDNORM;
}
static int pps_cdev_fasync(int fd, struct file *file, int on)
{
struct pps_device *pps = file->private_data;
return fasync_helper(fd, file, on, &pps->async_queue);
}
static long pps_cdev_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
struct pps_device *pps = file->private_data;
struct pps_kparams params;
struct pps_fdata fdata;
unsigned long ticks;
void __user *uarg = (void __user *) arg;
int __user *iuarg = (int __user *) arg;
int err;
switch (cmd) {
case PPS_GETPARAMS:
pr_debug("PPS_GETPARAMS: source %d\n", pps->id);
/* Return current parameters */
err = copy_to_user(uarg, &pps->params,
sizeof(struct pps_kparams));
if (err)
return -EFAULT;
break;
case PPS_SETPARAMS:
pr_debug("PPS_SETPARAMS: source %d\n", pps->id);
/* Check the capabilities */
if (!capable(CAP_SYS_TIME))
return -EPERM;
err = copy_from_user(&params, uarg, sizeof(struct pps_kparams));
if (err)
return -EFAULT;
if (!(params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR))) {
pr_debug("capture mode unspecified (%x)\n",
params.mode);
return -EINVAL;
}
/* Check for supported capabilities */
if ((params.mode & ~pps->info.mode) != 0) {
pr_debug("unsupported capabilities (%x)\n",
params.mode);
return -EINVAL;
}
spin_lock_irq(&pps->lock);
/* Save the new parameters */
pps->params = params;
/* Restore the read only parameters */
if ((params.mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) {
/* section 3.3 of RFC 2783 interpreted */
pr_debug("time format unspecified (%x)\n",
params.mode);
pps->params.mode |= PPS_TSFMT_TSPEC;
}
if (pps->info.mode & PPS_CANWAIT)
pps->params.mode |= PPS_CANWAIT;
pps->params.api_version = PPS_API_VERS;
spin_unlock_irq(&pps->lock);
break;
case PPS_GETCAP:
pr_debug("PPS_GETCAP: source %d\n", pps->id);
err = put_user(pps->info.mode, iuarg);
if (err)
return -EFAULT;
break;
case PPS_FETCH:
pr_debug("PPS_FETCH: source %d\n", pps->id);
err = copy_from_user(&fdata, uarg, sizeof(struct pps_fdata));
if (err)
return -EFAULT;
pps->go = 0;
/* Manage the timeout */
if (fdata.timeout.flags & PPS_TIME_INVALID)
err = wait_event_interruptible(pps->queue, pps->go);
else {
pr_debug("timeout %lld.%09d\n",
(long long) fdata.timeout.sec,
fdata.timeout.nsec);
ticks = fdata.timeout.sec * HZ;
ticks += fdata.timeout.nsec / (NSEC_PER_SEC / HZ);
if (ticks != 0) {
err = wait_event_interruptible_timeout(
pps->queue, pps->go, ticks);
if (err == 0)
return -ETIMEDOUT;
}
}
/* Check for pending signals */
if (err == -ERESTARTSYS) {
pr_debug("pending signal caught\n");
return -EINTR;
}
/* 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.current_mode = pps->current_mode;
spin_unlock_irq(&pps->lock);
err = copy_to_user(uarg, &fdata, sizeof(struct pps_fdata));
if (err)
return -EFAULT;
break;
default:
return -ENOTTY;
break;
}
return 0;
}
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;
found = pps_get_source(pps->id) != 0;
if (!found)
return -ENODEV;
file->private_data = pps;
return 0;
}
static int pps_cdev_release(struct inode *inode, struct file *file)
{
struct pps_device *pps = file->private_data;
/* Free the PPS source and wake up (possible) deregistration */
pps_put_source(pps);
return 0;
}
/*
* Char device stuff
*/
static const struct file_operations pps_cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.poll = pps_cdev_poll,
.fasync = pps_cdev_fasync,
.unlocked_ioctl = pps_cdev_ioctl,
.open = pps_cdev_open,
.release = pps_cdev_release,
};
int pps_register_cdev(struct pps_device *pps)
{
int err;
pps->devno = MKDEV(MAJOR(pps_devt), pps->id);
cdev_init(&pps->cdev, &pps_cdev_fops);
pps->cdev.owner = pps->info.owner;
err = cdev_add(&pps->cdev, pps->devno, 1);
if (err) {
printk(KERN_ERR "pps: %s: failed to add char device %d:%d\n",
pps->info.name, MAJOR(pps_devt), pps->id);
return err;
}
pps->dev = device_create(pps_class, pps->info.dev, pps->devno, NULL,
"pps%d", pps->id);
if (err)
goto del_cdev;
dev_set_drvdata(pps->dev, pps);
pr_debug("source %s got cdev (%d:%d)\n", pps->info.name,
MAJOR(pps_devt), pps->id);
return 0;
del_cdev:
cdev_del(&pps->cdev);
return err;
}
void pps_unregister_cdev(struct pps_device *pps)
{
device_destroy(pps_class, pps->devno);
cdev_del(&pps->cdev);
}
/*
* Module stuff
*/
static void __exit pps_exit(void)
{
class_destroy(pps_class);
unregister_chrdev_region(pps_devt, PPS_MAX_SOURCES);
}
static int __init pps_init(void)
{
int err;
pps_class = class_create(THIS_MODULE, "pps");
if (!pps_class) {
printk(KERN_ERR "pps: failed to allocate class\n");
return -ENOMEM;
}
pps_class->dev_attrs = pps_attrs;
err = alloc_chrdev_region(&pps_devt, 0, PPS_MAX_SOURCES, "pps");
if (err < 0) {
printk(KERN_ERR "pps: failed to allocate char device region\n");
goto remove_class;
}
pr_info("LinuxPPS API ver. %d registered\n", PPS_API_VERS);
pr_info("Software ver. %s - Copyright 2005-2007 Rodolfo Giometti "
"<giometti@linux.it>\n", PPS_VERSION);
return 0;
remove_class:
class_destroy(pps_class);
return err;
}
subsys_initcall(pps_init);
module_exit(pps_exit);
MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
MODULE_DESCRIPTION("LinuxPPS support (RFC 2783) - ver. " PPS_VERSION);
MODULE_LICENSE("GPL");

98
drivers/pps/sysfs.c Normal file
View File

@@ -0,0 +1,98 @@
/*
* PPS sysfs support
*
*
* Copyright (C) 2007-2009 Rodolfo Giometti <giometti@linux.it>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/pps_kernel.h>
/*
* Attribute functions
*/
static ssize_t pps_show_assert(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pps_device *pps = dev_get_drvdata(dev);
if (!(pps->info.mode & PPS_CAPTUREASSERT))
return 0;
return sprintf(buf, "%lld.%09d#%d\n",
(long long) pps->assert_tu.sec, pps->assert_tu.nsec,
pps->assert_sequence);
}
static ssize_t pps_show_clear(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pps_device *pps = dev_get_drvdata(dev);
if (!(pps->info.mode & PPS_CAPTURECLEAR))
return 0;
return sprintf(buf, "%lld.%09d#%d\n",
(long long) pps->clear_tu.sec, pps->clear_tu.nsec,
pps->clear_sequence);
}
static ssize_t pps_show_mode(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pps_device *pps = dev_get_drvdata(dev);
return sprintf(buf, "%4x\n", pps->info.mode);
}
static ssize_t pps_show_echo(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pps_device *pps = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", !!pps->info.echo);
}
static ssize_t pps_show_name(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pps_device *pps = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", pps->info.name);
}
static ssize_t pps_show_path(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pps_device *pps = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", pps->info.path);
}
struct device_attribute pps_attrs[] = {
__ATTR(assert, S_IRUGO, pps_show_assert, NULL),
__ATTR(clear, S_IRUGO, pps_show_clear, NULL),
__ATTR(mode, S_IRUGO, pps_show_mode, NULL),
__ATTR(echo, S_IRUGO, pps_show_echo, NULL),
__ATTR(name, S_IRUGO, pps_show_name, NULL),
__ATTR(path, S_IRUGO, pps_show_path, NULL),
__ATTR_NULL,
};