[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