Pulse per Second (PPS) support for Linux.
Signed-off-by: Rodolfo Giometti <giometti@enneenne.com>
---
Please, note that this PPS implementation is not RFC 2783 fully
compatible since, IMHO, the RFC simply doesn't consider PPS devices
connected with special GPIOs or other ports different from serial
ports and parallele ports.
RFC considerations
------------------
While implementing a PPS API as RFC 2783 defines and using an embedded
CPU GPIO-Pin as physical link to the signal, I encountered a deeper
problem:
At startup it needs a file descriptor as argument for the function
time_pps_create().
This implies that the source has a /dev/... entry. This assumption is
ok for the serial and parallel port, where you can do something
usefull beside(!) the gathering of timestamps as it is the central
task for a PPS-API. But this assumption does not work for a single
purpose GPIO line. In this case even basic file-related functionality
(like read() and write()) makes no sense at all and should not be a
precondition for the use of a PPS-API.
The problem can be simply solved if you change the original RFC 2783:
pps_handle_t type is an opaque __scalar type__ used to represent a
PPS source within the API
into a modified:
pps_handle_t type is an opaque __variable__ used to represent a PPS
source within the API and programs should not access it directly to
it due to its opacity.
This change seems to be neglibile because even the original RFC 2783
does not encourage programs to check (read: use) the pps_handle_t
variable before calling the time_pps_*() functions, since each
function should do this job internally.
If I intentionally separate the concept of "file descriptor" from the
concept of the "PPS source" I'm obliged to provide a solution to find
and register a PPS-source without using a file descriptor: it's done
by the functions time_pps_findsource() and time_pps_findpath() now.
According to this current NTPD drivers' code should be ...Yuck. Please. No. Doing it this way means you have to modify every
Did you not look to see what's in this helper? You'll find within here
the following code:
#ifdef CONFIG_HARD_PPS
if ((port->flags & UPF_HARDPPS_CD) && status)
hardpps();
#endif
which should've been a big sign lit up in bright lights in Times Square
Why not continue to leave it as a decision of the administrator - if
you want ports to default to having PPS support enabled, change all
the registration to set UPF_HARDPPS_CD. But leave the admin with
This means that PPS support is not available for any port which wasn't
autoprobed at device discovery time. That seems quite restrictive.
Maybe it needs to be coupled with the setting/clearing of UPF_HARDPPS_CD ?
--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of:
-
What do you think about? I should enable the PPS support only if the userland sets the UPF_HARDPPS_CD flag? Thanks for your comments, Rodolfo -- GNU/Linux Solutions e-mail: giometti@enneenne.com Linux Device Driver giometti@gnudd.com Embedded Systems giometti@linux.it UNIX programming phone: +39 349 2432127 -
You can't because it doesn't go through the interfaces you're hooking into. Existing interfaces are "changed" to point at the UARTs using Not specifically only userland - if it happens to be set when the port is registered then enable PPS support then as well. So: 1. uart_configure_port - if UPF_HARDPPS_CD is set, register the port for PPS support. 2. uart_remove_one_port - if UPF_HARDPPS_CD is set, unregister the port for PPS support. 3. uart_set_info - if changing UPF_HARDPPS_CD, appropriately register or unregister the port for PPS support. PS, linuxpps@ml.enneenne.com dropped from the cc: since it rejects my postings. -- Russell King Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/ maintainer of: -
I just fixed my spam filter (at least I hope so :). Thanks, Rodolfo -- GNU/Linux Solutions e-mail: giometti@enneenne.com Linux Device Driver giometti@gnudd.com Embedded Systems giometti@linux.it UNIX programming phone: +39 349 2432127 -
Some non political comments Add to MAINTAINERS Your way to hook into lp and 8250 is pretty gross. It should at least be possible to deactivate it via the kernel command line, but it would be a lot nicer to have pps_lp and pps_8250 modules which you can load. Also what happens if you've multiple lp ports? How do you control which to grab? - don't implement your own dbg() stuff, use dprintk and friends Perhaps just implement empty defines for the none pps cases and get rid of the ifdefs? But this should really be controllabe via I think you can drop the volatiles, there was a discussion some time ago This one looks pretty fishy. After the check you normally want Function in .h? Jan -
It's a must? I can leave procfs for backward compatibility with old I think it's not possible... however the Russell's suggestions should No way... I can add a specific flag as for uart lines or a kernel It's easier! :P Also it's very difficult having more that 3 or 4 PPS sources in a Why not? =:-o Also locking instructions may add extra code and delay the timestamp I'm going to check it. Thanks for your suggestions, Rodolfo -- GNU/Linux Solutions e-mail: giometti@enneenne.com Linux Device Driver giometti@gnudd.com Embedded Systems giometti@linux.it UNIX programming phone: +39 349 2432127 -
Hmm, as this is a new feature with regard to the mainline kernel, old utilities don't count (if you can install a new kernel you can also If it's of general use put it in the appropriate header file. If it's Hmm, think about x86_64 with 64-bit kernel and 32-bit userspace, probably having got different padding in the struct. Read LDD3, chapter 11, especially 11.4 . Jan -
Hello, after yours suggestions I modified my code. Please see the patch below. However some issues are still not modified. Here my reasons (lines Serial support has "setserial" and I used it to enable/disable PPS support at runtime. Here the patch for setserial: --- setserial.c.old 2007-02-17 08:47:45.000000000 +0100 +++ setserial.c 2007-02-17 08:57:26.000000000 +0100 @@ -127,6 +127,7 @@ CMD_FLAG, "hup_notify", ASYNC_HUP_NOTIFY, ASYNC_HUP_NOTIFY, 0, FLAG_CAN_INVERT, CMD_FLAG, "skip_test", ASYNC_SKIP_TEST,ASYNC_SKIP_TEST,2, FLAG_CAN_INVERT, CMD_FLAG, "auto_irq", ASYNC_AUTO_IRQ, ASYNC_AUTO_IRQ, 2, FLAG_CAN_INVERT, + CMD_FLAG, "hardpps", ASYNC_HARDPPS_CD, ASYNC_HARDPPS_CD, 2, FLAG_CAN_INVERT, CMD_FLAG, "split_termios", ASYNC_SPLIT_TERMIOS, ASYNC_SPLIT_TERMIOS, 2, FLAG_CAN_INVERT, CMD_FLAG, "session_lockout", ASYNC_SESSION_LOCKOUT, ASYNC_SESSION_LOCKOUT, 2, FLAG_CAN_INVERT, CMD_FLAG, "pgrp_lockout", ASYNC_PGRP_LOCKOUT, ASYNC_PGRP_LOCKOUT, 2, FLAG_CAN_INVERT, @@ -725,6 +726,7 @@ fprintf(stderr, "\t^ fourport\tconfigure the port as an AST Fourport\n"); fprintf(stderr, "\t autoconfig\tautomatically configure the serial port\n"); fprintf(stderr, "\t^ auto_irq\ttry to determine irq during autoconfiguration\n"); + fprintf(stderr, "\t^ hardpps\tmanage PPS signal when CD changes status\n"); fprintf(stderr, "\t^ skip_test\tskip UART test during autoconfiguration\n"); fprintf(stderr, "\n"); fprintf(stderr, "\t^ sak\t\tset the break key as the Secure Attention Key\n"); For parallel support I just cleaned up the code but I didn't find something similar to setserial so I decided to leave the code as is since at interrupt time we just register a timestamp without touching My own dbg() stuff are for specific debugging string for PPS code. If I use pr_dbg & friends I also enable several (and unwanted) kernel I see. However it is not a problem at all since if the device is a PPS source we have just one interrupt at second, if not we just register ...
It's not a precondition for a file descriptor, either. There are plenty of ioctl-only device drivers in existence. Furthermore, a file descriptor doesn't imply a device entry. Consider pipe(2), for example. As far as the kernel is concerned, a file handle is a nice, uniform system for providing communication between the kernel and user space. It doesn't matter if one can read() or write() on it; it's perfectly normal to support only a subset of the normal operations. -hpa -
The problem is that sometimes you cannot have a filedescriptor at all. Think about a PPS source connected with a CPU's GPIO pin. You have no filedes to use and defining one just for a PPS source or for a class of PPS sources, I think, is a non sense. RFC simply doesn't consider the fact that you can have a PPS source __without__ a filedes connected with, and a single filedes is considered __always__ connected with a single PPS source. Ciao, Rodolfo -- GNU/Linux Solutions e-mail: giometti@enneenne.com Linux Device Driver giometti@gnudd.com Embedded Systems giometti@linux.it UNIX programming phone: +39 349 2432127 -
If you have a kernel driver at all, then it makes perfect sense. If you don't have a kernel driver at all, then it's irrelevant to the That's the Unix way. -hpa -
??? So you are told me that if my PPS source is connected with a single CPU GPIO I have to add a new driver to the kernel? I have to choose a But not PPS one. In several embedded systems the GPS antenna is connected with the serial port and the PPS source, which cames from the __same__ GPS antenna, is connected with a GPIO. How I should manage such case? Which fildes I should use? Also consider that NTPD currently manage only one filedes which should support both GPS antenna's data and PPS source. My patch can solve this problem. Ciao, Rodolfo -- GNU/Linux Solutions e-mail: giometti@enneenne.com Linux Device Driver giometti@gnudd.com Embedded Systems giometti@linux.it UNIX programming phone: +39 349 2432127 -
Hi, That's not entirely true. It doesn't say that pps_handle_t must be a file descriptor and it leaves the option for the argument to time_pps_create() not to be a file descriptor as well. bye, Roman -
Yes. In fact that's my solution! The problem is that "pps_handle_t" is forced by the RFC to be a scalar and _not_ a generic (and opaque) type. I suppose that since right now such handler was simply the filedes of the serial/parallel port connected with the GPS antenna. But this is not always the case. As already told the GPS antenna can be connected with the serial line while the PPS signal is not, so NTPD should always open the serial port to read GPS data but it must not use such filedes for the time_pps_create(). My support try to resolve this problem with minor changes in both RFC and NTPD code. Ciao, Rodolfo -- GNU/Linux Solutions e-mail: giometti@enneenne.com Linux Device Driver giometti@gnudd.com Embedded Systems giometti@linux.it UNIX programming phone: +39 349 2432127 -
No new /proc files, please. Pavel -- (english) http://www.livejournal.com/~pavelmachek (cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html -
Already removed. As soon as possible I'll repost a new patch. Thanks! Rodolfo -- GNU/Linux Solutions e-mail: giometti@enneenne.com Linux Device Driver giometti@gnudd.com Embedded Systems giometti@linux.it UNIX programming phone: +39 349 2432127 -
Hello, here my new patch for PPS support in Linux. I tried to follow your suggestions as much possible! Please let me know if this new version could be more acceptable. Thanks in advance, Rodolfo -- GNU/Linux Solutions e-mail: giometti@enneenne.com Linux Device Driver giometti@gnudd.com Embedded Systems giometti@linux.it UNIX programming phone: +39 349 2432127
I have tried out 3.0.0-rc2 which seems to work pretty well so far (when
combined with the patches to the jsm driver I just posted). It took soe
work to get ntp's refclock_nmea to work though, since the patch that is
linked to from the linuxpps page seems out of date. Here is the patch
that seems to be working for me, although I am still testing it. Given
you know the linuxpps code better perhaps you can see if it looks sane
to you.
--- ntpd/refclock_nmea.c.ori 2007-03-13 18:38:01.000000000 -0400
+++ ntpd/refclock_nmea.c 2007-03-13 18:44:47.000000000 -0400
@@ -79,6 +79,7 @@
#define RANGEGATE 500000 /* range gate (ns) */
#define LENNMEA 75 /* min timecode length */
+#define LENPPS PPS_MAX_NAME_LEN
/*
* Tables to compute the ddd of year form icky dd/mm timecode. Viva la
@@ -99,6 +100,7 @@
pps_params_t pps_params; /* pps parameters */
pps_info_t pps_info; /* last pps data */
pps_handle_t handle; /* pps handlebars */
+ int handle_created; /* pps handle created flag */
#endif /* HAVE_PPSAPI */
};
@@ -147,6 +149,11 @@
register struct nmeaunit *up;
struct refclockproc *pp;
int fd;
+#ifdef PPS_HAVE_FINDPATH
+ char id[LENPPS] = "",
+ path[LENPPS],
+ mylink[LENPPS] = ""; /* just a default device */
+#endif /* PPS_HAVE_FINDPATH */
char device[20];
/*
@@ -201,7 +208,20 @@
#else
return (0);
#endif
- }
+ } else {
+ struct serial_struct ss;
+ if (ioctl(fd, TIOCGSERIAL, &ss) < 0 ||
+ (
+ ss.flags |= ASYNC_HARDPPS_CD,
+ ioctl(fd, TIOCSSERIAL, &ss)) < 0) {
+ msyslog(LOG_NOTICE, "refclock_nmea: TIOCSSERIAL fd %d, %m", fd);
+ msyslog(LOG_NOTICE,
+ "refclock_nmea: optional PPS processing not available");
+ } else {
+ msyslog(LOG_INFO,
+ "refclock_nmea: PPS detection on");
+ }
+ }
/*
* Allocate and initialize unit ...Thanks. I just posted to the linux kernel ML the last release You should use "setserial" here. Keep in mind that the PPS source Test the return value (see the wiki at Please, rewiev and test the code and I'll publish it. Ciao, Rodolfo -- GNU/Linux Solutions e-mail: giometti@enneenne.com Linux Device Driver giometti@gnudd.com Embedded Systems giometti@linux.it UNIX programming phone: +39 349 2432127 -
I will grab the last couple of commits and try although they didn't I couldn't find any way to do that with setserial (at least not the version I have), and I would rather not have to install setserial just to do that. Which version of setserial is needed and what arguments does it need to do it? If it is NOT connected to the same device, then how would you specify it? The ntp configuration is rather sparse when it comes to specifying anything and seems to rely in symlinks to hardcoded device names for finding everything. I suppose one could have gps# for the nmea messages and pps# for the associated pps device name symlink (which may point to something that doesn't even exist if there is an internal source of that name with no associated device). Does that seem reasonable? I can certainly change it to do that. Certainly refclock_atom already uses Oops. Missed that one. I was changing it to the below line to reuse the same serial device already specified for the NMEA messages. I actually find the way it determines the pps device a bit annoying. Right now I have to do this: cd /dev ln -s ttyn0 jsm0 ln -s jsm0 gps0 This way gps0 is the symlink the ntp refclock looks for when asked for device 0, and readlink turns that into jsm0 (since the internal driver name for ttyn0 is jsm, that is what the pps code insists it must be named), which then is another symlink to the real device name. Same for ttyS3 <- serial3 <- gps0. Now it would be nice if the internal driver name matched the device name, but apparently that really never seems to OK, I will do that too. Apparently the current refclock_nmea patch Will do. -- Len Sorensen -
Unluckely you need a patched version of setserial (see the patch on my site). On the same site you can find a precompiled version which I use This is a specific problem of NTPD not of LinuxPPS itself. I wrote some letters about this problem into NTP list but with no results. The sysadm shoulkd use setserial to enable a serial port to become a PPS source and then NTPD should verify if such PPS source exists Did you read this example on the wiki? giometti@jeeg:~/linuxpps$ cat /sys/class/pps/01/name serial1 giometti@jeeg:~/linuxpps$ cat /sys/class/pps/01/path /dev/ttyS1 giometti at jeeg:~/linuxpps/test$ sudo ln -sf /dev/ttyS1 /dev/gps0 giometti at jeeg:~/linuxpps/test$ sudo ./ppstest /dev/gps0 found PPS source #2 "serial1" on "/dev/ttyS1" giometti at jeeg:~/linuxpps/test$ sudo ln -sf ktimer /dev/gps0 giometti at jeeg:~/linuxpps/test$ sudo ./ppstest /dev/gps0 found PPS source #0 "ktimer" on "" Thanks a lot, Rodolfo -- GNU/Linux Solutions e-mail: giometti@enneenne.com Linux Device Driver giometti@gnudd.com Embedded Systems giometti@linux.it UNIX programming phone: +39 349 2432127 -
I looked at those, and they didn't sound important. I will grab it Well I think I may just write a small tool to do the ioctl to enable it I will have to try that again to be sure. I may have got myself confused when I first tried it. One symlink I can deal with (that's ntpd sillyness after all). -- Len Sorensen -
Yes, this could be a good solution while waiting for a new setserial. Ciao, Rodolfo -- GNU/Linux Solutions e-mail: giometti@enneenne.com Linux Device Driver giometti@gnudd.com Embedded Systems giometti@linux.it UNIX programming phone: +39 349 2432127 -
Now you said to check the return value of time_pps_readlink. Well it returns void so that isn't much good, and the stuff the wiki says to do, appears to be done internally by the function. So I guess that means I should undo that change so that I can actually get ntpd to compile again. If the call was to readlink directly it needs to be done, while your time_pps_readlink is just a handy wrapper that does all that for you, right? -- Len Sorensen -
I refere to readlink(), not to time_pps_readlink(). I'm sorry for Right. Ciao, Rodolfo -- GNU/Linux Solutions e-mail: giometti@enneenne.com Linux Device Driver giometti@gnudd.com Embedded Systems giometti@linux.it UNIX programming phone: +39 349 2432127 -
Well here is my current version of the refclock_nmea.c.patch for
LinuxPPS. It now uses /dev/gps# for the nmea messages and /dev/pps# for
the PPS device (which in my case is of course the same device). I am
running some more tests on it, but I think it is OK.
--- refclock_nmea.c.ori 2007-03-14 11:24:14.000000000 -0400
+++ refclock_nmea.c 2007-03-14 11:31:04.000000000 -0400
@@ -69,6 +69,7 @@
# define DEVICE "COM%d:" /* COM 1 - 3 supported */
#else
# define DEVICE "/dev/gps%d" /* name of radio device */
+# define PPSDEVICE "/dev/pps%d" /* name of pps device */
#endif
#define SPEED232 B4800 /* uart speed (4800 bps) */
#define PRECISION (-9) /* precision assumed (about 2 ms) */
@@ -79,6 +80,7 @@
#define RANGEGATE 500000 /* range gate (ns) */
#define LENNMEA 75 /* min timecode length */
+#define LENPPS PPS_MAX_NAME_LEN
/*
* Tables to compute the ddd of year form icky dd/mm timecode. Viva la
@@ -99,6 +101,7 @@
pps_params_t pps_params; /* pps parameters */
pps_info_t pps_info; /* last pps data */
pps_handle_t handle; /* pps handlebars */
+ int handle_created; /* pps handle created flag */
#endif /* HAVE_PPSAPI */
};
@@ -147,6 +150,11 @@
register struct nmeaunit *up;
struct refclockproc *pp;
int fd;
+#ifdef PPS_HAVE_FINDPATH
+ char id[LENPPS] = "",
+ path[LENPPS],
+ ppsdevice[LENPPS] = ""; /* just a default device */
+#endif /* PPS_HAVE_FINDPATH */
char device[20];
/*
@@ -238,12 +246,26 @@
* Start the PPSAPI interface if it is there. Default to use
* the assert edge and do not enable the kernel hardpps.
*/
+#ifdef PPS_HAVE_FINDPATH
+ /* Get the PPS source's real name */
+ (void)sprintf(ppsdevice, PPSDEVICE, unit);
+ time_pps_readlink(ppsdevice, LENPPS, path, LENPPS);
+
+ /* Try to find the source */
+ fd = time_pps_findpath(path, LENPPS, id, LENPPS);
+ if (fd < 0) {
+ msyslog(LOG_ERR, "refclock_nmea: cannot find PPS path \"%s\" in the system", path);
+ return (0);
+ }
+ msyslog(LOG_INFO, ...Thanks, published. :) Rodolfo -- GNU/Linux Solutions e-mail: giometti@enneenne.com Linux Device Driver giometti@gnudd.com Embedded Systems giometti@linux.it UNIX programming phone: +39 349 2432127 -
Well it does work for our GPS receiver at least. Of course I have to
change the baud rate in the driver since our unit doens't use the NNEA
standard 4800. And the configure script for ntp doesn't recognize the
v2 PPSAPI, so I have to manually explain to it that I have the PPS API
and it should actually include it.
Here is my utility for enabling PPS on my serial port now. Seems less
of a pain that patching setserial and including that (with 256MB flash
space, setserial seems wasteful).
ppsctl.c
--------
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <linux/serial.h>
void usage(char *name) {
fprintf(stderr,"Usage: %s /dev/port [0|1]\n",name);
fprintf(stderr,"Where 0 means disable PPS and 1 means enable PPS\n");
}
int main(int argc, char *argv[]) {
int fd;
char *state;
if(argc<3) {
usage(argv[0]);
return 1;
}
fd = open(argv[1],O_RDWR);
if (fd>=0) {
struct serial_struct ss;
if (ioctl(fd, TIOCGSERIAL, &ss) < 0 ) {
perror("TIOCGSERIAL");
return 1;
} else {
if(strcmp(argv[2],"1")==0) {
ss.flags |= ASYNC_HARDPPS_CD;
state="enabled";
} else if(strcmp(argv[2],"0")==0) {
ss.flags &= ~ASYNC_HARDPPS_CD;
state="disabled";
} else {
fprintf(stderr,"Invalid state argument \"%s\"\n",argv[2]);
return 1;
}
if (ioctl(fd, TIOCSSERIAL, &ss) < 0) {
perror("TIOCSSERIAL");
} else {
fprintf(stderr,"PPS on %s is now %s\n",argv[1],state);
}
}
} else {
fprintf(stderr,"Can't open \"%s\":",argv[1]);
perror("");
}
return 0;
}
/* vim:ts=4:shiftwidth=4:
* */
--
Len Sorensen
-
Can you please provide a little help about it? A patch against current I modify your code add inquiry functionality. Can you please test it? See http://ftp.enneenne.com/pub/misc/linuxpps/test/ and the wiki at http://wiki.enneenne.com/index.php/LinuxPPS_support#Compiling_the_code. Ciao, Rodolfo -- GNU/Linux Solutions e-mail: giometti@enneenne.com Linux Device Driver giometti@gnudd.com Embedded Systems giometti@linux.it UNIX programming phone: +39 349 2432127
Well all I actually did was simply stick #define HAVE_PPSAPI at the tome of the refclock_nmea.c file. The configure script in ntp is explcitly checking for PPS API version 1 so version 2 doesn't match and it claims you don't have a timepps.h file it can use. Unfortunately I really have trouble understanding autoconf scripts and can't figure out where the code is that tells it how to do the check for timepps.h and the API I will try out the ppstest tool. How come none of the .patch files in http://ftp.enneenne.com/pub/misc/linuxpps/refclocks/nmea/ can be accessed? Does your web server not like serving up .patch files? -- Len Sorensen -
Sorry. I set wrong file permissions. :) Try now. Ciao, Rodolfo -- GNU/Linux Solutions e-mail: giometti@enneenne.com Linux Device Driver giometti@gnudd.com Embedded Systems giometti@linux.it UNIX programming phone: +39 349 2432127 -
