Skip to content

Commit

Permalink
Adds Linux "fake PPS" detection and avoidance.
Browse files Browse the repository at this point in the history
If the Linux kernel is built with CONFIG_PPS_CLIENT_KTIMER=y, then a
synthetic PPS source is added to allow testing without a real PPS
source.  However, this source doesn't even run at exactly 1Hz, so any
attempt to use it for real time synchronization is disastrous.

To make matters worse, this is usually the first PPS driver
configured, causing it to appear as /dev/pps0, which is the implied
PPS device used by the "Magic HAT" kludge.

This change adds detection for the fake source (based on its name),
both to provide a warning if it is configured explicitly, and to skip
over it when applying the "Magic HAT" kludge.

TESTED:
Tested both gpsmon and gpsd on a Beaglebone Black with added symlinks
for testing "Magic HAT".  Did this with kernels with and without the
KTIMER enabled.  Verified that the proper PPS device is selected by
"Magic HAT", and that a warning is generated when expected.
  • Loading branch information
fhgwright committed Dec 23, 2016
1 parent 79c69b2 commit e2471dd
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 21 deletions.
14 changes: 8 additions & 6 deletions gpsmon.c
Original file line number Diff line number Diff line change
Expand Up @@ -1322,14 +1322,16 @@ int main(int argc, char **argv)
/*
* The HAT kludge. If we're using the HAT GPS on a
* Raspberry Pi or a workalike like the ODROIDC2, and
* there is a static /dev/pps0, and we have access because
* there is a static "first PPS", and we have access because
* we're root, assume we want to use KPPS.
*/
if ((strcmp(session.pps_thread.devicename, MAGIC_HAT_GPS) == 0
|| strcmp(session.pps_thread.devicename, MAGIC_LINK_GPS) == 0)
&& access("/dev/pps0", R_OK | W_OK) == 0)
session.pps_thread.devicename = "/dev/pps0";
#endif /* MAGIC_HAT_GPS && MAGIC_LINK_GPS */
if (strcmp(session.pps_thread.devicename, MAGIC_HAT_GPS) == 0
|| strcmp(session.pps_thread.devicename, MAGIC_LINK_GPS) == 0) {
char *first_pps = pps_get_first();
if (access(first_pps, R_OK | W_OK) == 0)
session.pps_thread.devicename = first_pps;
}
#endif /* MAGIC_HAT_ENABLE */
pps_thread_activate(&session.pps_thread);
}
#endif /* PPS_ENABLE */
Expand Down
58 changes: 49 additions & 9 deletions ppsthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,15 @@
* CONFIG_PPS=y
* CONFIG_PPS_DEBUG=y [optional to kernel log pulses]
* CONFIG_PPS_CLIENT_LDISC=y
*
* Also beware that setting
* CONFIG_PPS_CLIENT_KTIMER=y
* adds a fake software-generated PPS intended for testing. This
* doesn't even run at exactly 1Hz, so any attempt to use it for
* real timing is disastrous. Hence we try to avoid it.
*/
#define FAKE_PPS_NAME "ktimer"

#if defined(HAVE_SYS_TIMEPPS_H)
// include unistd.h here as it is missing on older pps-tools releases.
// 'close' is not defined otherwise.
Expand Down Expand Up @@ -156,6 +164,39 @@ static void thread_unlock(volatile struct pps_thread_t *pps_thread)
}

#if defined(HAVE_SYS_TIMEPPS_H)
#ifdef __linux__
/* Obtain contents of specified sysfs variable; null string if failure */
static void get_sysfs_var(const char *path, char *buf, size_t bufsize)
{
buf[0] = '\0';
int fd = open(path, O_RDONLY);
if ( 0 <= fd ) {
ssize_t r = read( fd, buf, bufsize -1);
if ( 0 < r ) {
buf[r - 1] = '\0'; /* remove trailing \x0a */
}
(void)close(fd);
}
}

/* Check to see whether the named PPS source is the fake one */
int pps_check_fake(const char *name) {
char path[PATH_MAX] = "";
char buf[32] = "";
snprintf(path, sizeof(path), "/sys/devices/virtual/pps/%s/name", name);
get_sysfs_var(path, buf, sizeof(buf));
return strcmp(buf, FAKE_PPS_NAME) == 0;
}

/* Get first "real" PPS device, skipping the fake, if any */
char *pps_get_first(void)
{
if (pps_check_fake("pps0"))
return "/dev/pps1";
return "/dev/pps0";
}
#endif /* __linux__ */

static int init_kernel_pps(struct inner_context_t *inner_context)
/* return handle for kernel pps, or -1; requires root privileges */
{
Expand Down Expand Up @@ -189,8 +230,14 @@ static int init_kernel_pps(struct inner_context_t *inner_context)
* (We use strncpy() here because this might be compiled where
* strlcpy() is not available.)
*/
if (strncmp(pps_thread->devicename, "/dev/pps", 8) == 0)
if (strncmp(pps_thread->devicename, "/dev/pps", 8) == 0) {
if (pps_check_fake(pps_thread->devicename + 5))
pps_thread->log_hook(pps_thread, THREAD_WARN,
"KPPS:%s is fake PPS,"
" timing will be inaccurate\n",
pps_thread->devicename);
(void)strncpy(path, pps_thread->devicename, sizeof(path)-1);
}
else {
char pps_num = '\0'; /* /dev/pps[pps_num] is our device */
size_t i; /* to match type of globbuf.gl_pathc */
Expand Down Expand Up @@ -227,14 +274,7 @@ static int init_kernel_pps(struct inner_context_t *inner_context)

memset( (void *)&path, 0, sizeof(path));
for ( i = 0; i < globbuf.gl_pathc; i++ ) {
int fd = open(globbuf.gl_pathv[i], O_RDONLY);
if ( 0 <= fd ) {
ssize_t r = read( fd, path, sizeof(path) -1);
if ( 0 < r ) {
path[r - 1] = '\0'; /* remove trailing \x0a */
}
(void)close(fd);
}
get_sysfs_var(globbuf.gl_pathv[i], path, sizeof(path));
pps_thread->log_hook(pps_thread, THREAD_PROG,
"KPPS:%s checking %s, %s\n",
pps_thread->devicename,
Expand Down
2 changes: 2 additions & 0 deletions ppsthread.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,7 @@ extern void pps_thread_fixin(volatile struct pps_thread_t *,
volatile struct timedelta_t *);
extern int pps_thread_ppsout(volatile struct pps_thread_t *,
volatile struct timedelta_t *);
int pps_check_fake(const char *);
char *pps_get_first(void);

#endif /* PPSTHREAD_H */
15 changes: 9 additions & 6 deletions timehint.c
Original file line number Diff line number Diff line change
Expand Up @@ -463,14 +463,17 @@ void ntpshm_link_activate(struct gps_device_t *session)
/*
* The HAT kludge. If we're using the HAT GPS on a
* Raspberry Pi or a workalike like the ODROIDC2, and
* there is a static /dev/pps0, and we have access because
* there is a static "first PPS", and we have access because
* we're root, assume we want to use KPPS.
*/
if ((strcmp(session->pps_thread.devicename, MAGIC_HAT_GPS) == 0
|| strcmp(session->pps_thread.devicename, MAGIC_LINK_GPS) == 0)
&& access("/dev/pps0", R_OK | W_OK) == 0)
session->pps_thread.devicename = "/dev/pps0";
#endif /* MAGIC_HAT_GPS && MAGIC_LINK_GPS */
if (strcmp(session->pps_thread.devicename, MAGIC_HAT_GPS) == 0
|| strcmp(session->pps_thread.devicename,
MAGIC_LINK_GPS) == 0) {
char *first_pps = pps_get_first();
if (access(first_pps, R_OK | W_OK) == 0)
session->pps_thread.devicename = first_pps;
}
#endif /* MAGIC_HAT_ENABLE */
pps_thread_activate(&session->pps_thread);
}
}
Expand Down

0 comments on commit e2471dd

Please sign in to comment.