[Linuxpps] [PATCH] Diff against 2.6.17-rc5

Rodolfo Giometti giometti at linux.it
Thu May 25 19:46:04 CEST 2006


Hello,

here a patch against 2.6.17-rc5, please try it and report possible
bugs/problems.

Ciao,

Rodolfo

-- 

GNU/Linux Solutions                  e-mail:    giometti at enneenne.com
Linux Device Driver                             giometti at gnudd.com
Embedded Systems                     		giometti at linux.it
UNIX programming                     phone:     +39 349 2432127
-------------- next part --------------
diff --git a/drivers/Kconfig b/drivers/Kconfig
index aeb5ab2..a89d0c6 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -46,6 +46,8 @@ source "drivers/i2c/Kconfig"
 
 source "drivers/spi/Kconfig"
 
+source "drivers/pps/Kconfig"
+
 source "drivers/w1/Kconfig"
 
 source "drivers/hwmon/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 447d8e6..ce3caee 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -56,6 +56,7 @@ obj-$(CONFIG_INPUT)		+= input/
 obj-$(CONFIG_I2O)		+= message/
 obj-$(CONFIG_RTC_LIB)		+= rtc/
 obj-$(CONFIG_I2C)		+= i2c/
+obj-$(CONFIG_PPS)		+= pps/
 obj-$(CONFIG_W1)		+= w1/
 obj-$(CONFIG_HWMON)		+= hwmon/
 obj-$(CONFIG_PHONE)		+= telephony/
diff --git a/drivers/pps/Kconfig b/drivers/pps/Kconfig
new file mode 100644
index 0000000..20aa5f0
--- /dev/null
+++ b/drivers/pps/Kconfig
@@ -0,0 +1,43 @@
+#
+# Character device 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
+	  antennas. Userland can use it to get an high time reference.
+
+	  Some antennas' 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 antennas' PPS signals are connected with some special host
+	  inputs so you have to enable the corresponding client support.
+
+	  This PPS support can also be built as a module.  If so, the module
+	  will be called pps-core.
+
+config PPS_PROCFS
+	bool "PPS procfs support"
+	depends on PPS 
+	default y
+	help
+	  Say Y here if you want the PPS support to produce a new file into
+	  the "/proc" directory which holds useful information regarding
+	  registered PPS clients into the system.
+
+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.
+
+source drivers/pps/clients/Kconfig
+
+endmenu
diff --git a/drivers/pps/Makefile b/drivers/pps/Makefile
new file mode 100644
index 0000000..24c65f5
--- /dev/null
+++ b/drivers/pps/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for the i2c core.
+#
+
+pps_core-objs			+= pps.o kapi.o procfs.o
+obj-$(CONFIG_PPS)		+= pps_core.o
+obj-y				+= clients/
diff --git a/drivers/pps/clients/Kconfig b/drivers/pps/clients/Kconfig
new file mode 100644
index 0000000..5bd6273
--- /dev/null
+++ b/drivers/pps/clients/Kconfig
@@ -0,0 +1,23 @@
+#
+# LinuxPPS clients configuration
+#
+
+comment "PPS clients support"
+	depends on PPS
+
+config PPS_CLIENT_KTIMER
+	tristate "Kernel timer client (Testing client, use for debug)"
+	depends on PPS && EXPERIMENTAL
+	help
+	  If you say yes here you get support for a PPS debugging client
+	  which uses a kernel timer to generate the PPS signal.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called ktimer.o.
+
+config PPS_CLIENT_8250
+	bool "8250 serial support"
+	depends on PPS && SERIAL_8250 && ! ( PPS = m && SERIAL_8250 = y ) && EXPERIMENTAL
+	help
+	  If you say yes here you get support for a PPS source connected
+	  with the CD (Carrier Detect) pin of your 8250 serial line chip.
diff --git a/drivers/pps/clients/Makefile b/drivers/pps/clients/Makefile
new file mode 100644
index 0000000..3ecca41
--- /dev/null
+++ b/drivers/pps/clients/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for miscellaneous I2C chip drivers.
+#
+
+obj-$(CONFIG_PPS_CLIENT_KTIMER)	+= ktimer.o
+
diff --git a/drivers/pps/clients/ktimer.c b/drivers/pps/clients/ktimer.c
new file mode 100644
index 0000000..7820e66
--- /dev/null
+++ b/drivers/pps/clients/ktimer.c
@@ -0,0 +1,108 @@
+/*
+ * ktimer.c -- kernel timer test client
+ *
+ *
+ * Copyright (C) 2005-2006   Rodolfo Giometti <giometti at 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/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/timer.h>
+
+#include <linux/timepps.h>
+#include <linux/pps.h>
+
+/* --- Global variables ----------------------------------------------------- */
+
+static int source;
+static struct timer_list ktimer;
+
+/* --- The kernel timer ----------------------------------------------------- */
+
+static void linuxpps_ktimer_event(unsigned long ptr) {
+	PINFO("PPS event at %lu", jiffies);
+
+	linuxpps_event(source, PPS_CAPTUREASSERT);
+
+	/* Rescheduling */
+	ktimer.expires = jiffies+HZ;   /* 1 second */
+	add_timer(&ktimer);
+}
+
+/* --- The echo function ---------------------------------------------------- */
+
+static void linuxpps_ktimer_echo(int source, int event)
+{
+	PINFO("echo %s %s for source %d",
+	 event&PPS_CAPTUREASSERT ? "assert" : "",
+	 event&PPS_CAPTURECLEAR ? "clear" : "",
+	 source);
+}
+
+/* --- The PPS info struct -------------------------------------------------- */
+
+static struct linuxpps_source_info_s linuxpps_ktimer_info = {
+	name		: "ktimer",
+	path		: "",
+	mode		: PPS_CAPTUREASSERT|PPS_OFFSETASSERT|PPS_ECHOASSERT| \
+			  PPS_CANWAIT|PPS_TSFMT_TSPEC,
+	echo 	: linuxpps_ktimer_echo,
+};
+
+/* --- Module staff -------------------------------------------------------- */
+
+void __exit linuxpps_ktimer_exit(void)
+{
+	del_timer_sync(&ktimer);
+	linuxpps_unregister_source(&linuxpps_ktimer_info);
+
+	PINFO("ktimer PPS source unregistered");
+}
+
+int __init linuxpps_ktimer_init(void)
+{
+	int ret;
+
+	ret = linuxpps_register_source(&linuxpps_ktimer_info,
+				PPS_CAPTUREASSERT|PPS_OFFSETASSERT,
+				-1 /* is up to the system */);
+	if (ret < 0) {
+		PDEBUG("cannot register ktimer source");
+		return ret;
+	}
+	source = ret;
+
+	init_timer(&ktimer);
+	ktimer.function = linuxpps_ktimer_event;
+	ktimer.expires = jiffies+HZ;   /* 1 second */
+	add_timer(&ktimer);
+
+	PINFO("ktimer PPS source registered at %d", source);
+
+	return  0;
+}
+
+module_init(linuxpps_ktimer_init);
+module_exit(linuxpps_ktimer_exit);
+
+MODULE_AUTHOR("Rodolfo Giometti <giometti at linux.it>");
+MODULE_DESCRIPTION("testing PPS source by using a kernel timer (just for debug)");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pps/kapi.c b/drivers/pps/kapi.c
new file mode 100644
index 0000000..6f73d4d
--- /dev/null
+++ b/drivers/pps/kapi.c
@@ -0,0 +1,168 @@
+/*
+ * kapi.c -- kernel API
+ *
+ *
+ * Copyright (C) 2005-2006   Rodolfo Giometti <giometti at 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/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/time.h>
+
+#include <linux/timepps.h>
+#include <linux/pps.h>
+
+/* --- Local functions ----------------------------------------------------- */
+
+#ifndef NSEC_PER_SEC
+#define	NSEC_PER_SEC		1000000000
+#endif
+static void linuxpps_add_offeset(struct timespec *ts, struct timespec *offset)
+{
+	ts->tv_nsec += offset->tv_nsec;
+	if (ts->tv_nsec >= NSEC_PER_SEC) {
+		ts->tv_nsec -= NSEC_PER_SEC;
+		ts->tv_sec++;
+	} else if (ts->tv_nsec < 0) {
+		ts->tv_nsec += NSEC_PER_SEC;
+		ts->tv_sec--;
+	}
+	ts->tv_sec += offset->tv_sec;
+}
+
+/* --- Exported functions -------------------------------------------------- */
+
+int linuxpps_register_source(struct linuxpps_source_info_s *info, int default_params, int try_id)
+{
+	int i;
+
+	if (try_id >= 0) {
+		if (linuxpps_is_allocated(try_id)) {
+			PERR("source id %d busy", try_id);
+			return -EBUSY;
+		}
+		i = try_id;
+	}
+	else {
+		for (i = 0; i < LINUXPPS_MAX_SOURCES; i++)
+			if (!linuxpps_is_allocated(i))
+				break;
+		if (i >= LINUXPPS_MAX_SOURCES) {
+			PERR("no free source ids");
+			return -ENOMEM;
+		}
+	}
+
+	/* Sanity checks */
+	if ((info->mode&default_params) != default_params) {
+		PERR("unsupported default parameters");
+		return -EINVAL;
+	}
+	if ((info->mode&(PPS_ECHOASSERT|PPS_ECHOCLEAR)) != 0 && info->echo == NULL) {
+		PERR("echo function is not defined");
+		return -EINVAL;
+	}
+	if ((info->mode&(PPS_TSFMT_TSPEC|PPS_TSFMT_NTPFP)) == 0) {
+		PERR("unspecified time format");
+		return -EINVAL;
+	}
+
+	/* Allocate the PPS source */
+	memset(&linuxpps_source[i], 0, sizeof(struct linuxpps_s));
+	linuxpps_source[i].info = info;
+	linuxpps_source[i].params.api_version = PPS_API_VERS_1;
+	linuxpps_source[i].params.mode = default_params;
+	init_waitqueue_head(&linuxpps_source[i].queue);
+
+	return i;
+}
+
+void linuxpps_unregister_source(struct linuxpps_source_info_s *info)
+{  
+	int i;
+
+	for (i = 0; i < LINUXPPS_MAX_SOURCES; i++)
+		if (linuxpps_is_allocated(i) && linuxpps_source[i].info == info)
+			break;
+
+	if (i >= LINUXPPS_MAX_SOURCES) {
+		PERR("warning! Try to unregister an unknow PPS source");
+		return;
+	}
+
+	/* Deallocate the PPS source */
+	linuxpps_source[i].info = NULL; 
+} 
+
+void linuxpps_event(int source, int event)
+{
+	struct timespec ts;
+
+	/* Sanity checks */
+	if (!linuxpps_is_allocated(source)) {
+		PERR("unknow source for event!");
+		return;
+	}
+	if ((event&(PPS_CAPTUREASSERT|PPS_CAPTURECLEAR)) == 0 ) {
+		PERR("unknow event (%x) for source %d", event, source);
+		return;
+	}
+
+	/* Get the time stamp */
+	do_gettimeofday((struct timeval *) &ts);
+	ts.tv_nsec *= 1000;	  /* microseconds to nanoseconds */
+
+	/* Must call the echo function? */
+	if ((linuxpps_source[source].params.mode&(PPS_ECHOASSERT|PPS_ECHOCLEAR)) != 0)
+		linuxpps_source[source].info->echo(source, event);
+
+	/* Check the event */
+	linuxpps_source[source].current_mode = linuxpps_source[source].params.mode;
+	if (event&PPS_CAPTUREASSERT) {
+		/* We have to add an offset? */
+		if (linuxpps_source[source].params.mode&PPS_OFFSETASSERT)
+			linuxpps_add_offeset(&ts, &linuxpps_source[source].params.assert_off_tu.tspec);
+
+		/* Save the time stamp */
+		linuxpps_source[source].assert_tu.tspec = ts;
+		linuxpps_source[source].assert_sequence++;
+		PDEBUG("capture assert seq #%lu for source %d", 
+			linuxpps_source[source].assert_sequence, source);
+	}
+	if (event&PPS_CAPTURECLEAR) {
+		/* We have to add an offset? */
+		if (linuxpps_source[source].params.mode&PPS_OFFSETCLEAR)
+			linuxpps_add_offeset(&ts, &linuxpps_source[source].params.clear_off_tu.tspec);
+
+		/* Save the time stamp */
+		linuxpps_source[source].clear_tu.tspec = ts;
+		linuxpps_source[source].clear_sequence++;
+		PDEBUG("capture clear seq #%lu for source %d", 
+			linuxpps_source[source].clear_sequence, source);
+	}
+
+	wake_up_interruptible(&linuxpps_source[source].queue);
+}
+
+/* --- Exported functions -------------------------------------------------- */
+
+EXPORT_SYMBOL(linuxpps_register_source);
+EXPORT_SYMBOL(linuxpps_unregister_source);
+EXPORT_SYMBOL(linuxpps_event);
diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c
new file mode 100644
index 0000000..23590f0
--- /dev/null
+++ b/drivers/pps/pps.c
@@ -0,0 +1,343 @@
+/*
+ * main.c -- Main driver file
+ *
+ *
+ * Copyright (C) 2005-2006   Rodolfo Giometti <giometti at 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/version.h>
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+
+#include <linux/timepps.h>
+#include <linux/pps.h>
+
+/* --- Global variables ----------------------------------------------------- */
+
+struct linuxpps_s linuxpps_source[LINUXPPS_MAX_SOURCES];
+static struct sock *nl_sk = NULL;
+
+/* --- Misc functions ------------------------------------------------------ */
+
+static inline int linuxpps_find_source(int source)
+{
+	int i;
+
+	if (source >= 0) {
+		if (source >= LINUXPPS_MAX_SOURCES || !linuxpps_is_allocated(source))
+			return -EINVAL;
+		else
+			return source;
+	}
+
+	for (i = 0; i < LINUXPPS_MAX_SOURCES; i++)
+		if (linuxpps_is_allocated(i))
+			break;
+
+	if (i >= LINUXPPS_MAX_SOURCES)
+		return -EINVAL;
+
+	return i;
+}
+
+/* --- Input function ------------------------------------------------------ */
+
+static void linuxpps_nl_data_ready(struct sock *sk, int len)
+{
+	struct sk_buff *skb;
+	struct nlmsghdr *nlh;
+	struct pps_netlink_msg *nlpps;
+
+	int cmd, source;
+	wait_queue_head_t *queue;
+	unsigned long timeout;
+
+	int ret;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
+	while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) {
+#else
+	while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) {
+#endif
+		nlh = (struct nlmsghdr *) skb->data;
+		PDEBUG("New message from PID %d (flags %x)",
+			nlh->nlmsg_pid, nlh->nlmsg_flags);
+
+		/* Decode the userland command */
+		nlpps = (struct pps_netlink_msg*) NLMSG_DATA(nlh);
+		cmd = nlpps->cmd;
+		source = nlpps->source;
+
+		switch (cmd) {
+		case PPS_CREATE : {
+			PDEBUG("PPS_CREATE: source %d", source);
+
+			/* Check if the requested source is allocated */
+			ret = linuxpps_find_source(source);
+			if (ret < 0) {
+				nlpps->ret = ret;
+				break;
+			}
+
+			nlpps->source = ret;
+			nlpps->ret = 0;
+
+			break;
+		}
+
+		case PPS_DESTROY : {
+			PDEBUG("PPS_DESTROY: source %d", source);
+
+			/* Nothing to do here! Just answer ok... */
+			nlpps->ret = 0;
+
+			break;
+		}
+
+		case PPS_SETPARMS : {
+			PDEBUG("PPS_SETPARMS: source %d", source);
+
+			/* Check the capabilities */
+			if (!capable(CAP_SYS_TIME)) {
+				nlpps->ret = -EPERM;
+				break;
+		 	}
+
+			/* Sanity checks */
+			if (nlpps->source < 0 || !linuxpps_is_allocated(source)) {
+				nlpps->ret = -EINVAL;
+				break;
+			}
+			if ((nlpps->params.mode&~linuxpps_source[source].info->mode) != 0) {
+				PDEBUG("unsupported capabilities");
+				nlpps->ret = -EINVAL;
+				break;
+		 	}
+			if ((nlpps->params.mode&(PPS_CAPTUREASSERT|PPS_CAPTURECLEAR)) == 0) {
+				PDEBUG("capture mode unspecified");
+				nlpps->ret = -EINVAL;
+				break;
+			}
+			if ((nlpps->params.mode&(PPS_TSFMT_TSPEC|PPS_TSFMT_NTPFP)) == 0) {
+				/* section 3.3 of RFC 2783 interpreted */
+				PDEBUG("time format unspecified");
+				nlpps->params.mode |= PPS_TSFMT_TSPEC;
+			}
+
+			/* Save the new parameters */
+			linuxpps_source[source].params = nlpps->params;
+
+			/* Restore the read only parameters */
+			if (linuxpps_source[source].info->mode&PPS_CANWAIT)
+				linuxpps_source[source].params.mode |= PPS_CANWAIT;
+			linuxpps_source[source].params.api_version = PPS_API_VERS_1;
+
+			nlpps->ret = 0;
+
+			break;
+		}
+
+		case PPS_GETPARMS : {
+			PDEBUG("PPS_GETPARMS: source %d", source);
+
+			/* Sanity checks */
+			if (nlpps->source < 0 || !linuxpps_is_allocated(source)) {
+				nlpps->ret = -EINVAL;
+				break;
+		 	}
+
+			nlpps->params = linuxpps_source[source].params;
+			nlpps->ret = 0;
+
+			break;
+	 	}
+
+	 	case PPS_GETCAP : {
+			PDEBUG("PPS_GETCAP: source %d", source);
+
+			/* Sanity checks */
+			if (nlpps->source < 0 || !linuxpps_is_allocated(source)) {
+				nlpps->ret = -EINVAL;
+				break;
+		 	}
+
+			nlpps->mode = linuxpps_source[source].info->mode;
+			nlpps->ret = 0;
+
+			break;
+	 	}
+
+	 	case PPS_FETCH : {
+			PDEBUG("PPS_FETCH: source %d", source);
+			queue = &linuxpps_source[source].queue;
+
+			/* Sanity checks */
+			if (nlpps->source < 0 || !linuxpps_is_allocated(source)) {
+				nlpps->ret = -EINVAL;
+				break;
+		 	}
+			if ((nlpps->tsformat != PPS_TSFMT_TSPEC) != 0 ) {
+				PDEBUG("unsupported time format");
+				nlpps->ret = -EINVAL;
+				break;
+		 	}
+
+		 	/* Manage the timeout */
+			if (nlpps->timeout.tv_sec != -1) {
+				timeout = nlpps->timeout.tv_sec*HZ;
+				timeout += nlpps->timeout.tv_nsec/(1000000000/HZ);
+
+				if (timeout != 0) {
+					timeout = interruptible_sleep_on_timeout(queue, timeout);
+		  			if (timeout <= 0) {
+						PDEBUG("timeout expired");
+						nlpps->ret = -ETIMEDOUT;
+						break;
+					}
+				}
+		 	}
+			else
+				interruptible_sleep_on(queue);
+
+			/* Return the fetched timestamp */
+			nlpps->info.assert_sequence = linuxpps_source[source].assert_sequence;
+			nlpps->info.clear_sequence = linuxpps_source[source].clear_sequence;
+			nlpps->info.assert_tu = linuxpps_source[source].assert_tu;
+			nlpps->info.clear_tu = linuxpps_source[source].clear_tu;
+			nlpps->info.current_mode = linuxpps_source[source].current_mode;
+
+			nlpps->ret = 0;
+
+			break;
+	 	}
+
+	 	case PPS_KC_BIND : {
+			/* Feature currently not supported */
+			nlpps->ret = -EOPNOTSUPP;
+
+			break;
+	 	}
+
+		case PPS_FIND_SRC : {
+			source = linuxpps_find_source(source);
+			if (source < 0) {
+				PDEBUG("no PPS devices found");
+				nlpps->ret = -ENODEV;
+				break;
+		 	}
+
+			/* Found! So copy the info */
+			nlpps->source = source;
+			strncpy(nlpps->name, linuxpps_source[source].info->name,
+				LINUXPPS_MAX_NAME_LEN);
+			strncpy(nlpps->path, linuxpps_source[source].info->path,
+				LINUXPPS_MAX_NAME_LEN);
+			nlpps->ret = 0;
+
+			break;
+	 	}
+
+		default : {
+			/* Unknow command */
+			PDEBUG("unknow command %d", nlpps->cmd);
+
+			nlpps->ret = -EINVAL;
+		}
+		}
+
+		/* Send an answer to the userland */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,15)
+		NETLINK_CB(skb).groups = 0;	/* not in mcast groups */
+#endif
+		NETLINK_CB(skb).pid = 0;	/* from the kernel */
+		NETLINK_CB(skb).dst_pid = nlh->nlmsg_pid;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,15)
+		NETLINK_CB(skb).dst_groups = 0;	/* not in mcast groups */
+#else
+		NETLINK_CB(skb).dst_group = 0;	/* not in mcast groups */
+#endif
+
+		netlink_unicast(nl_sk, skb, nlh->nlmsg_pid, MSG_DONTWAIT);
+	}
+}
+
+/* --- Module staff -------------------------------------------------------- */
+
+void __exit linuxpps_exit(void)
+{
+	linuxpps_procfs_unregister();
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
+	sock_release(nl_sk->sk_socket);
+#else
+	sock_release(nl_sk->socket);
+#endif
+
+	PINFO("LinuxPPS API ver. %d removed", PPS_API_VERS_1);
+}
+
+int __init linuxpps_init(void)
+{
+	int ret;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14)
+	nl_sk = netlink_kernel_create(NETLINK_PPSAPI, linuxpps_nl_data_ready);
+#else
+	nl_sk = netlink_kernel_create(NETLINK_PPSAPI, 0,
+					linuxpps_nl_data_ready, THIS_MODULE);
+#endif
+	if (nl_sk == NULL) {
+		PERR("unable to create netlink kernel socket");
+		return -EBUSY;
+	}
+	PDEBUG("netlink protocol %d created", NETLINK_PPSAPI);
+
+	/* Init the main struct */
+	memset(linuxpps_source, 0, sizeof(struct linuxpps_s)*LINUXPPS_MAX_SOURCES);
+
+	/* Register to procfs */
+	ret = linuxpps_procfs_register();
+	if (ret < 0) {
+		PERR("unable to register procfs");
+		goto linuxpps_procfs_register_error;
+	}
+
+	PINFO("LinuxPPS API ver. %d registered", PPS_API_VERS_1);
+	PINFO("Software ver. %s - Copyright 2005-2006 Rodolfo Giometti <giometti at linux.it>", PPS_VERSION);
+
+	return  0;
+
+linuxpps_procfs_register_error :
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
+	sock_release(nl_sk->sk_socket);
+#else
+	sock_release(nl_sk->socket);
+#endif
+
+	return ret;
+}
+
+subsys_initcall(linuxpps_init);
+module_exit(linuxpps_exit);
+
+MODULE_AUTHOR("Rodolfo Giometti <giometti at linux.it>");
+MODULE_DESCRIPTION("LinuxPPS support (RFC 2783) - ver. " PPS_VERSION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/pps/procfs.c b/drivers/pps/procfs.c
new file mode 100644
index 0000000..6d76bfa
--- /dev/null
+++ b/drivers/pps/procfs.c
@@ -0,0 +1,97 @@
+/*
+ * procfs.c -- procfs support
+ *
+ *
+ * Copyright (C) 2005-2006   Rodolfo Giometti <giometti at 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/proc_fs.h>
+#include <linux/module.h>
+
+#include <linux/timepps.h>
+#include <linux/pps.h>
+
+#ifdef CONFIG_PPS_PROCFS
+/* ----- Private functions -------------------------------------------- */
+
+static int linuxpps_sources_read(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+	int i;
+	int len = 0;
+
+	len += sprintf(page+len, "id\tmode\techo\tname\n");
+	len += sprintf(page+len, "----\t------\t----\t----------------\n");
+	for (i = 0; i < LINUXPPS_MAX_SOURCES; i++)
+		if (linuxpps_is_allocated(i))
+			len += sprintf(page+len, "%2d\t%4x\t%s\t%s\n",
+					i,
+					linuxpps_source[i].info->mode,
+					linuxpps_source[i].info->echo ? "yes" : "no",
+				 	linuxpps_source[i].info->name);
+	
+	*eof = 1;
+	return len;
+}
+
+/* ----- Public functions --------------------------------------------- */
+
+void linuxpps_procfs_unregister(void)
+{
+	remove_proc_entry("pps/sources", NULL);
+
+	/* The root dir in /proc/pps */
+	remove_proc_entry("pps", NULL);
+}
+
+int linuxpps_procfs_register(void)
+{
+	struct proc_dir_entry *root_dir, *procfs_file;
+
+	/* The root dir in /proc */
+	root_dir = proc_mkdir("pps", NULL);
+	if (root_dir == NULL)
+		return -ENOMEM;
+
+	/* The file "sources" */
+	procfs_file = create_proc_entry("sources", S_IRUGO|S_IWUSR, root_dir);
+	if (procfs_file == NULL) {
+		linuxpps_procfs_unregister();
+		return -ENOMEM;
+	}
+	procfs_file->owner = THIS_MODULE;
+	procfs_file->read_proc = linuxpps_sources_read;
+	procfs_file->write_proc = NULL;	/* no write method */
+
+	PINFO("procfs enabled");
+	return 0;
+}
+
+#else	/* CONFIG_PPS_PROCFS */
+
+void linuxpps_procfs_unregister(void)
+{
+	/* Nothing to do here... */
+	return;
+}
+
+int linuxpps_procfs_register(void)
+{
+	PINFO("procfs not enabled");
+	return 0;
+}
+#endif	/* CONFIG_PPS_PROCFS */
diff --git a/drivers/serial/8250.c b/drivers/serial/8250.c
index bbf78aa..d8debd0 100644
--- a/drivers/serial/8250.c
+++ b/drivers/serial/8250.c
@@ -45,6 +45,11 @@
 #include <asm/io.h>
 #include <asm/irq.h>
 
+#ifdef CONFIG_PPS_CLIENT_8250
+#include <linux/timepps.h>
+#include <linux/pps.h>
+#endif
+
 #include "8250.h"
 
 /*
@@ -121,6 +126,10 @@ struct uart_8250_port {
 	struct uart_port	port;
 	struct timer_list	timer;		/* "no irq" timer */
 	struct list_head	list;		/* ports on this IRQ */
+#ifdef CONFIG_PPS_CLIENT_8250
+	struct linuxpps_source_info_s linuxpps_8250_info;
+	int source;
+#endif
 	unsigned short		capabilities;	/* port capabilities */
 	unsigned short		bugs;		/* port bugs */
 	unsigned int		tx_loadsz;	/* transmit fifo load size */
@@ -1283,8 +1292,19 @@ static unsigned int check_modem_status(s
 			up->port.icount.rng++;
 		if (status & UART_MSR_DDSR)
 			up->port.icount.dsr++;
-		if (status & UART_MSR_DDCD)
+		if (status & UART_MSR_DDCD) {
+#ifdef CONFIG_PPS_CLIENT_8250
+			if (status & UART_MSR_DCD) {
+				linuxpps_event(up->source, PPS_CAPTUREASSERT);
+				PDEBUG("serial8250: PPS assert event at %lu", jiffies);
+			}
+			else {
+				linuxpps_event(up->source, PPS_CAPTURECLEAR);
+				PDEBUG("serial8250: PPS clear event at %lu", jiffies);
+			}
+#endif
 			uart_handle_dcd_change(&up->port, status & UART_MSR_DCD);
+		}
 		if (status & UART_MSR_DCTS)
 			uart_handle_cts_change(&up->port, status & UART_MSR_CTS);
 
@@ -1868,6 +1888,9 @@ serial8250_set_termios(struct uart_port 
 		up->ier |= UART_IER_MSI;
 	if (up->capabilities & UART_CAP_UUE)
 		up->ier |= UART_IER_UUE | UART_IER_RTOIE;
+#ifdef CONFIG_PPS_CLIENT_8250
+	up->ier |= UART_IER_MSI;	/* enable interrupts */
+#endif
 
 	serial_out(up, UART_IER, up->ier);
 
@@ -2174,6 +2197,7 @@ static void __init
 serial8250_register_ports(struct uart_driver *drv, struct device *dev)
 {
 	int i;
+	int ret;
 
 	serial8250_isa_init_ports();
 
@@ -2182,6 +2206,25 @@ serial8250_register_ports(struct uart_dr
 
 		up->port.dev = dev;
 		uart_add_one_port(drv, &up->port);
+#ifdef CONFIG_PPS_CLIENT_8250
+		snprintf(up->linuxpps_8250_info.name, 
+			LINUXPPS_MAX_NAME_LEN, "pps_8250_%d",
+			up->port.line);
+		snprintf(up->linuxpps_8250_info.path,
+			LINUXPPS_MAX_NAME_LEN, "/dev/ttyS%d",
+			up->port.line);
+		up->linuxpps_8250_info.mode = 
+			  PPS_CAPTUREASSERT|PPS_OFFSETASSERT|
+            		  PPS_CANWAIT|PPS_TSFMT_TSPEC;
+		ret = linuxpps_register_source(
+			&(up->linuxpps_8250_info),
+    			PPS_CAPTUREASSERT|PPS_OFFSETASSERT,
+			-1 /* is up to the system */);
+		if (ret >= 0)
+			up->source = ret;
+		else
+			PERR("cannot register 8250 source %d", up->port.line);
+#endif
 	}
 }
 
@@ -2579,8 +2622,30 @@ int serial8250_register_port(struct uart
 			uart->port.dev = port->dev;
 
 		ret = uart_add_one_port(&serial8250_reg, &uart->port);
-		if (ret == 0)
+		if (ret == 0) {
+#ifdef CONFIG_PPS_CLIENT_8250
+			snprintf(uart->linuxpps_8250_info.name, 
+				LINUXPPS_MAX_NAME_LEN, "pps_8250_%d",
+				uart->port.line);
+			snprintf(uart->linuxpps_8250_info.path,
+				LINUXPPS_MAX_NAME_LEN, "/dev/ttyS%d",
+				uart->port.line);
+			uart->linuxpps_8250_info.mode = 
+				  PPS_CAPTUREASSERT|PPS_OFFSETASSERT|
+                		  PPS_CANWAIT|PPS_TSFMT_TSPEC;
+			ret = linuxpps_register_source(
+				&(uart->linuxpps_8250_info),
+	    			PPS_CAPTUREASSERT|PPS_OFFSETASSERT,
+				-1 /* is up to the system */);
+			if (ret >= 0) {
+				uart->source = ret;
+				ret = uart->port.line;
+			} else
+				PDEBUG("cannot register 8250 source");
+#else
 			ret = uart->port.line;
+#endif
+		}
 	}
 	mutex_unlock(&serial_mutex);
 
@@ -2600,6 +2665,12 @@ void serial8250_unregister_port(int line
 	struct uart_8250_port *uart = &serial8250_ports[line];
 
 	mutex_lock(&serial_mutex);
+
+#ifdef CONFIG_PPS_CLIENT_8250
+	linuxpps_unregister_source(&(uart->linuxpps_8250_info));
+	PINFO("8250 PPS source unregistered");
+#endif
+
 	uart_remove_one_port(&serial8250_reg, &uart->port);
 	if (serial8250_isa_devs) {
 		uart->port.flags &= ~UPF_BOOT_AUTOCONF;
diff --git a/include/linux/netlink.h b/include/linux/netlink.h
index 87b8a57..18719c1 100644
--- a/include/linux/netlink.h
+++ b/include/linux/netlink.h
@@ -21,6 +21,7 @@
 #define NETLINK_DNRTMSG		14	/* DECnet routing messages */
 #define NETLINK_KOBJECT_UEVENT	15	/* Kernel messages to userspace */
 #define NETLINK_GENERIC		16
+#define NETLINK_PPSAPI          17      /* linuxPPS support */
 
 #define MAX_LINKS 32		
 
diff --git a/include/linux/pps.h b/include/linux/pps.h
new file mode 100644
index 0000000..ed8a3c1
--- /dev/null
+++ b/include/linux/pps.h
@@ -0,0 +1,97 @@
+/*
+ * pps.h -- PPS API kernel header.
+ *
+ *
+ * Copyright (C) 2005-2006   Rodolfo Giometti <giometti at 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.
+ */
+
+
+/* ----- Misc macros -------------------------------------------------- */
+
+#define PPS_VERSION	"2.0.0"
+
+#undef PDEBUG           /* undef it, just in case */
+#undef PINFO            /* undef it, just in case */
+#ifdef CONFIG_PPS_DEBUG
+	#define PDEBUG(fmt, args...) printk(KERN_DEBUG "%s:%s[%d]: " fmt "\n", \
+					__stringify(KBUILD_MODNAME),	       \
+					__stringify(KBUILD_BASENAME),	       \
+					__LINE__ , ## args)
+	#define PINFO(fmt, args...) printk(KERN_INFO "%s:%s[%d]: " fmt "\n",   \
+					__stringify(KBUILD_MODNAME),	       \
+					__stringify(KBUILD_BASENAME),	       \
+					__LINE__ , ## args)
+	#define PERR(fmt, args...) printk(KERN_ERR "%s:%s[%d]: " fmt "\n",     \
+					__stringify(KBUILD_MODNAME),	       \
+					__stringify(KBUILD_BASENAME),	       \
+				       	__LINE__ , ## args)
+#else   /* CONFIG_PPS_DEBUG */
+	#define PDEBUG(fmt, args...) /* do nothing! */
+	#define PINFO(fmt, args...) printk(KERN_INFO "%s: " fmt "\n",	       \
+					__stringify(KBUILD_MODNAME) , ## args)
+	#define PERR(fmt, args...) printk(KERN_ERR "%s: " fmt "\n",	       \
+					__stringify(KBUILD_MODNAME) , ## args)
+#endif   /* CONFIG_PPS_DEBUG */
+
+/* --- Global defines ------------------------------------------------------ */
+
+#define LINUXPPS_MAX_SOURCES		16
+
+/* --- Global variables ---------------------------------------------------- */
+
+/* The specific PPS source info */
+struct linuxpps_source_info_s {
+	char name[LINUXPPS_MAX_NAME_LEN];		/* simbolic name */
+	char path[LINUXPPS_MAX_NAME_LEN];		/* path of connected device */
+	int mode;					/* PPS's allowed mode */
+
+	void (*echo)(int source, int event);		/* the PPS echo function */
+};
+
+/* The main struct */
+struct linuxpps_s {
+	struct linuxpps_source_info_s *info;		/* PSS source info */
+
+	pps_params_t params;				/* PPS's current params */
+
+	volatile pps_seq_t assert_sequence;		/* PPS' assert event seq # */
+	volatile pps_seq_t clear_sequence;		/* PPS' clear event seq # */
+	volatile pps_timeu_t assert_tu;
+	volatile pps_timeu_t clear_tu;
+	int current_mode;				/* PPS mode at event time */
+
+	wait_queue_head_t queue;			/* PPS event queue */
+};
+
+/* --- Global variables ---------------------------------------------------- */
+
+extern struct linuxpps_s linuxpps_source[LINUXPPS_MAX_SOURCES];
+
+/* --- Global functions ---------------------------------------------------- */
+
+static inline int linuxpps_is_allocated(int source) {
+	return linuxpps_source[source].info != NULL;
+}
+
+/* --- Exported functions -------------------------------------------------- */
+
+extern int linuxpps_register_source(struct linuxpps_source_info_s *info, int default_params, int try_id);
+extern void linuxpps_unregister_source(struct linuxpps_source_info_s *info);
+extern void linuxpps_event(int source, int event);
+
+extern int linuxpps_procfs_register(void);
+extern void linuxpps_procfs_unregister(void);
diff --git a/include/linux/timepps.h b/include/linux/timepps.h
new file mode 100644
index 0000000..efc3571
--- /dev/null
+++ b/include/linux/timepps.h
@@ -0,0 +1,453 @@
+/*
+ * timepps.h -- PPS API main header
+ *
+ *
+ * Copyright (C) 2005-2006   Rodolfo Giometti <giometti at 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.
+ *
+ * NOTE: this file is *strongly* based on a previous job by Ulrich Windl.
+ *       The original copyright note follows:
+ *
+ *    Interface to the PPS API described in RFC 2783 (March 2000)
+ *
+ *    Copyright (c) 1999, 2001, 2004 by Ulrich Windl,
+ * 	   based on code by Reg Clemens <reg at dwf.com>
+ *	   based on code by Poul-Henning Kamp <phk at FreeBSD.org>
+ *
+ *    ----------------------------------------------------------------------
+ *    "THE BEER-WARE LICENSE" (Revision 42):
+ *    <phk at FreeBSD.org> wrote this file.  As long as you retain this notice
+ *    you can do whatever you want with this stuff. If we meet some day, and
+ *    you think this stuff is worth it, you can buy me a beer in return.
+ *       Poul-Henning Kamp
+ *    ----------------------------------------------------------------------
+ */
+
+#ifndef _SYS_TIMEPPS_H_
+#define _SYS_TIMEPPS_H_
+
+/* Implementation note: the logical states ``assert'' and ``clear''
+ * are implemented in terms of the chip register, i.e. ``assert''
+ * means the bit is set.  */
+
+/* --- 3.2 New data structures --------------------------------------------- */
+
+#define PPS_API_VERS_1		1	/* draft-05, dated 1999-08 */
+#define NETLINK_PPSAPI		17	/* we use just one free number... */
+
+typedef struct pps_handle_s {
+	int source;
+	int socket;
+} pps_handle_t;				/* represents a PPS source */
+
+typedef unsigned long pps_seq_t;	/* sequence number */
+
+typedef struct ntp_fp {
+	unsigned int	integral;
+	unsigned int	fractional;
+} ntp_fp_t;				/* NTP-compatible time stamp */
+
+typedef union pps_timeu {
+	struct timespec tspec;
+	ntp_fp_t ntpfp;
+	unsigned long longpad[3];
+} pps_timeu_t;				/* generic data type to represent time stamps */
+
+typedef struct pps_info {
+	pps_seq_t	assert_sequence;	/* seq. num. of assert event */
+	pps_seq_t	clear_sequence;		/* seq. num. of clear event */
+	pps_timeu_t	assert_tu;		/* time of assert event */
+	pps_timeu_t	clear_tu;		/* time of clear event */
+	int		current_mode;		/* current mode bits */
+} pps_info_t;
+
+#define assert_timestamp        assert_tu.tspec
+#define clear_timestamp         clear_tu.tspec
+
+#define assert_timestamp_ntpfp  assert_tu.ntpfp
+#define clear_timestamp_ntpfp   clear_tu.ntpfp
+
+typedef struct pps_params {
+	int		api_version;	/* API version # */
+	int		mode;		/* mode bits */
+	pps_timeu_t assert_off_tu;	/* offset compensation for assert */
+	pps_timeu_t clear_off_tu;	/* offset compensation for clear */
+} pps_params_t;
+
+#define assert_offset   assert_off_tu.tspec
+#define clear_offset    clear_off_tu.tspec
+
+#define assert_offset_ntpfp     assert_off_tu.ntpfp
+#define clear_offset_ntpfp      clear_off_tu.ntpfp
+
+/* --- 3.3 Mode bit definitions -------------------------------------------- */
+
+/* Device/implementation parameters */
+#define PPS_CAPTUREASSERT	0x01	/* capture assert events */
+#define PPS_CAPTURECLEAR	0x02	/* capture clear events */
+#define PPS_CAPTUREBOTH		0x03	/* capture assert and clear events */
+
+#define PPS_OFFSETASSERT	0x10	/* apply compensation for assert ev. */
+#define PPS_OFFSETCLEAR		0x20	/* apply compensation for clear ev. */
+
+#define PPS_CANWAIT		0x100	/* Can we wait for an event? */
+#define PPS_CANPOLL		0x200	/* "This bit is reserved for
+                                           future use." */
+
+/* Kernel actions */
+#define PPS_ECHOASSERT		0x40	/* feed back assert event to output */
+#define PPS_ECHOCLEAR		0x80	/* feed back clear event to output */
+
+/* Timestamp formats */
+#define PPS_TSFMT_TSPEC		0x1000	/* select timespec format */
+#define PPS_TSFMT_NTPFP		0x2000	/* select NTP format */
+
+/* --- 3.4.4 New functions: disciplining the kernel timebase --------------- */
+
+/* Kernel consumers */
+#define PPS_KC_HARDPPS		0	/* hardpps() (or equivalent) */
+#define PPS_KC_HARDPPS_PLL	1	/* hardpps() constrained to
+					   use a phase-locked loop */
+#define PPS_KC_HARDPPS_FLL	2	/* hardpps() constrained to
+					   use a frequency-locked loop */
+
+/* --- Here begins the implementation-specific part! ----------------------- */
+
+#define LINUXPPS_MAX_NAME_LEN           32
+struct pps_netlink_msg {
+	int cmd;			  /* the command to execute */
+	int source;
+	char name[LINUXPPS_MAX_NAME_LEN]; /* symbolic name */
+	char path[LINUXPPS_MAX_NAME_LEN]; /* path of the connected device */
+	int consumer;			  /* selected kernel consumer */
+	pps_params_t params;
+	int mode;			  /* edge */
+	int tsformat;			  /* format of time stamps */
+	pps_info_t info;
+	struct timespec timeout;
+	int ret;
+};
+#define PPSAPI_MAX_PAYLOAD	sizeof(struct pps_netlink_msg)
+
+/* check Documentation/ioctl-number.txt! */
+#define PPS_CREATE		1
+#define PPS_DESTROY		2
+#define PPS_SETPARMS		3
+#define PPS_GETPARMS		4
+#define PPS_GETCAP		5
+#define PPS_FETCH		6
+#define PPS_KC_BIND		7
+#define PPS_FIND_SRC		8
+
+#ifdef __KERNEL__
+
+#include <linux/socket.h>
+#include <net/sock.h>
+#include <linux/netlink.h>
+
+struct pps_state {
+	pps_params_t	parm;		  /* PPS parameters */
+	pps_info_t info;		  /* PPS information */
+	int cap;			  /* PPS capabilities */
+	long ecount;			  /* interpolation offset of event */
+	struct timespec etime;		  /* kernel time of event */
+	wait_queue_head_t ewait;	  /* wait queue for event */
+};
+
+/* State variables to bind kernel consumer */
+/* PPS API (RFC 2783): current source and mode for ``kernel consumer'' */
+extern const struct pps *pps_kc_hardpps_dev; /* some unique pointer to device */
+extern int pps_kc_hardpps_mode;		     /* mode bits for kernel consumer */
+
+/* Return allowed mode bits for given pps struct, file's mode, and user.
+ * Bits set in `*obligatory' must be set.  Returned bits may be set. */
+extern int pps_allowed_mode(const struct pps *pps, mode_t fmode, int *obligatory);
+
+#else /* !__KERNEL__ */
+
+/* --- 3.4 Functions ------------------------------------------------------- */
+
+#include <errno.h>
+#include <asm/types.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+
+/* Private functions */
+
+static int netlink_msg(int socket, struct pps_netlink_msg *nlpps)
+{
+	struct sockaddr_nl dest_addr;
+	struct nlmsghdr *nlh;
+	struct iovec iov;
+	struct msghdr msg;
+
+	int ret;
+
+	memset(&msg, 0, sizeof(msg));
+
+	/* Create the destination address */
+	memset(&dest_addr, 0, sizeof(dest_addr));
+	dest_addr.nl_family = AF_NETLINK;
+	dest_addr.nl_pid = 0;          /* for the kernel */
+	dest_addr.nl_groups = 0;       /* not in mcast groups */
+
+	nlh = (struct nlmsghdr *) alloca(NLMSG_SPACE(PPSAPI_MAX_PAYLOAD));
+	if (nlh == NULL)
+		return -1;
+
+	/* Fill the netlink message header */
+	nlh->nlmsg_len = NLMSG_SPACE(PPSAPI_MAX_PAYLOAD);
+	nlh->nlmsg_pid = getpid();
+	nlh->nlmsg_flags = 0;
+	memcpy(NLMSG_DATA(nlh), nlpps, sizeof(struct pps_netlink_msg));
+
+	iov.iov_base = (void *) nlh;
+	iov.iov_len = nlh->nlmsg_len;
+	msg.msg_name = (void *) &dest_addr;
+	msg.msg_namelen = sizeof(dest_addr);
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+
+	/* Send the message */
+	ret = sendmsg(socket, &msg, 0);
+	if (ret < 0)
+		return ret;
+
+	/* Wait for the answer */
+	memset(nlh, 0, NLMSG_SPACE(PPSAPI_MAX_PAYLOAD));
+	ret = recvmsg(socket, &msg, 0);
+	if (ret < 0)
+		return ret;
+
+	/* Check the return value */
+	memcpy(nlpps, NLMSG_DATA(nlh), sizeof(struct pps_netlink_msg));
+	if (nlpps->ret < 0) {
+		errno = -nlpps->ret;
+		return -1;
+	}
+
+	return 0;
+}
+
+/* The PPSAPI functions */
+
+/* Create PPS handle from file descriptor */
+/* We simply ignore "filedes" */
+static __inline int time_pps_create(int filedes, pps_handle_t *handle)
+{
+	struct sockaddr_nl src_addr, dest_addr;
+	struct pps_netlink_msg nlpps;
+
+	int ret;
+
+	/* Create the netlink socket */
+	ret = socket(PF_NETLINK, SOCK_RAW, NETLINK_PPSAPI);
+	if (ret < 0)
+		return ret;
+	handle->socket = ret;
+
+	/* Bind the socket with the source address */
+	memset(&src_addr, 0, sizeof(src_addr));
+	src_addr.nl_family = AF_NETLINK;
+	src_addr.nl_pid = getpid();	/* self PID as unique ID */
+	src_addr.nl_groups = 0;		/* not in mcast groups */
+	ret = bind(handle->socket, (struct sockaddr *) &src_addr, sizeof(src_addr));
+	if (ret < 0) {
+		close(handle->socket);
+		return ret;
+	}
+
+	/* Now ask the kernel to create the PPS source */
+	nlpps.cmd = PPS_CREATE;
+	nlpps.source = filedes;
+	ret = netlink_msg(handle->socket, &nlpps);
+	if (ret < 0)
+		return ret;
+
+	/* Save the PPS source returned by the kernel */
+	handle->source = nlpps.source;
+
+	return 0;
+}
+
+/* Release PPS handle */
+static __inline int time_pps_destroy(pps_handle_t handle)
+{
+	struct pps_netlink_msg nlpps;
+
+	int ret;
+
+	/* Ask the kernel to destroy the PPS source */
+	nlpps.cmd = PPS_DESTROY;
+	nlpps.source = handle.source;
+	ret = netlink_msg(handle.socket, &nlpps);
+	if (ret < 0)
+		return ret;
+
+	/* Now we can destroy the netlink socket */
+	close(handle.socket);
+
+	return 0;
+}
+
+/* Set parameters for handle */
+static __inline int time_pps_setparams(pps_handle_t handle, const pps_params_t *ppsparams)
+{
+	struct pps_netlink_msg nlpps;
+
+	int ret;
+
+	/* Ask the kernel to set the new PPS source's parameters */
+	nlpps.cmd = PPS_SETPARMS;
+	nlpps.source = handle.source;
+	nlpps.params = *ppsparams;
+	ret = netlink_msg(handle.socket, &nlpps);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static __inline int time_pps_getparams(pps_handle_t handle, pps_params_t *ppsparams)
+{
+	struct pps_netlink_msg nlpps;
+
+	int ret;
+
+	/* Ask the kernel to return the PPS source's parameters */
+	nlpps.cmd = PPS_GETPARMS;
+	nlpps.source = handle.source;
+	ret = netlink_msg(handle.socket, &nlpps);
+	if (ret < 0)
+		return ret;
+
+	/* Return the parameters */
+	*ppsparams = nlpps.params; 
+
+	return 0;
+}
+
+/* Get capabilities for handle */
+static __inline int time_pps_getcap(pps_handle_t handle, int *mode)
+{
+	struct pps_netlink_msg nlpps;
+
+	int ret;
+
+	/* Ask the kernel to return the PPS source's capabilities */
+	nlpps.cmd = PPS_GETCAP;
+	nlpps.source = handle.source;
+	ret = netlink_msg(handle.socket, &nlpps);
+	if (ret < 0)
+		return ret;
+
+	/* Return the capabilities */
+	*mode = nlpps.mode; 
+
+	return 0;
+}
+
+/* current event for handle */
+static __inline int time_pps_fetch(pps_handle_t handle, const int tsformat, pps_info_t *ppsinfobuf, const struct timespec *timeout)
+{
+	struct pps_netlink_msg nlpps;
+
+	int ret;
+
+	/* Ask the kernel to return the PPS source's capabilities */
+	nlpps.cmd = PPS_FETCH;
+	nlpps.source = handle.source;
+	nlpps.tsformat = tsformat;
+	if (timeout)
+		nlpps.timeout = *timeout;
+	else	 /* wait forever */
+		nlpps.timeout.tv_sec = nlpps.timeout.tv_nsec = -1;
+
+	ret = netlink_msg(handle.socket, &nlpps);
+	if (ret < 0)
+		return ret;
+
+	/* Return the timestamps */
+	*ppsinfobuf = nlpps.info; 
+
+	return 0;
+}
+
+/* Specify kernel consumer */
+static __inline int time_pps_kcbind(pps_handle_t handle, const int kernel_consumer, const int edge, const int tsformat)
+{
+	struct pps_netlink_msg nlpps;
+
+	int ret;
+
+	/* Ask the kernel to destroy the PPS source */
+	nlpps.cmd = PPS_KC_BIND;
+	nlpps.source = handle.source;
+	nlpps.consumer = kernel_consumer;
+	nlpps.mode = edge;
+	nlpps.tsformat = tsformat;
+	ret = netlink_msg(handle.socket, &nlpps);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/* Find a PPS source */
+#define PPS_HAVE_FINDSOURCE	1
+static __inline int time_pps_findsource(int index, char *path, int pathlen, char *idstring, int idlen)
+{
+	int sock;
+	struct sockaddr_nl src_addr, dest_addr;
+	struct pps_netlink_msg nlpps;
+
+	int ret;
+
+	/* Create the netlink socket */
+	ret = socket(PF_NETLINK, SOCK_RAW, NETLINK_PPSAPI);
+	if (ret < 0)
+		return ret;
+	sock = ret;
+
+	/* Bind the socket with the source address */
+	memset(&src_addr, 0, sizeof(src_addr));
+	src_addr.nl_family = AF_NETLINK;
+	src_addr.nl_pid = getpid();	/* self PID as unique ID */
+	src_addr.nl_groups = 0;		/* not in mcast groups */
+	ret = bind(sock, (struct sockaddr *) &src_addr, sizeof(src_addr));
+	if (ret < 0) {
+		close(sock);
+		return ret;
+	}
+
+	/* Ask the kernel to destroy the PPS source */
+	nlpps.cmd = PPS_FIND_SRC;
+	nlpps.source = index;
+	ret = netlink_msg(sock, &nlpps);
+	if (ret < 0) {
+		close(sock);
+		return ret;
+	}
+
+	strncpy(path, nlpps.path, pathlen);
+	strncpy(idstring, nlpps.name, idlen);
+
+	close(sock);
+	return nlpps.source;
+}
+
+#endif   /* !__KERNEL__ */
+#endif   /* _SYS_TIMEPPS_H_ */


More information about the LinuxPPS mailing list