| /* |
| chronyd/chronyc - Programs for keeping computer clocks accurate. |
| |
| ********************************************************************** |
| * Copyright (C) Miroslav Lichvar 2013, 2017 |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of version 2 of the GNU General Public License as |
| * published by the Free Software Foundation. |
| * |
| * 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., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| * |
| ********************************************************************** |
| |
| ======================================================================= |
| |
| PTP hardware clock (PHC) refclock driver. |
| |
| */ |
| |
| #include "config.h" |
| |
| #include "refclock.h" |
| |
| #ifdef FEAT_PHC |
| |
| #include "sysincl.h" |
| |
| #include "refclock.h" |
| #include "hwclock.h" |
| #include "local.h" |
| #include "logging.h" |
| #include "memory.h" |
| #include "util.h" |
| #include "sched.h" |
| #include "sys_linux.h" |
| |
| struct phc_instance { |
| int fd; |
| int mode; |
| int nocrossts; |
| int extpps; |
| int pin; |
| int channel; |
| HCL_Instance clock; |
| }; |
| |
| static void read_ext_pulse(int sockfd, int event, void *anything); |
| |
| static int phc_initialise(RCL_Instance instance) |
| { |
| const char *options[] = {"nocrossts", "extpps", "pin", "channel", "clear", NULL}; |
| struct phc_instance *phc; |
| int phc_fd, rising_edge; |
| char *path, *s; |
| |
| RCL_CheckDriverOptions(instance, options); |
| |
| path = RCL_GetDriverParameter(instance); |
| |
| phc_fd = SYS_Linux_OpenPHC(path, 0); |
| if (phc_fd < 0) |
| LOG_FATAL("Could not open PHC"); |
| |
| phc = MallocNew(struct phc_instance); |
| phc->fd = phc_fd; |
| phc->mode = 0; |
| phc->nocrossts = RCL_GetDriverOption(instance, "nocrossts") ? 1 : 0; |
| phc->extpps = RCL_GetDriverOption(instance, "extpps") ? 1 : 0; |
| |
| if (phc->extpps) { |
| s = RCL_GetDriverOption(instance, "pin"); |
| phc->pin = s ? atoi(s) : 0; |
| s = RCL_GetDriverOption(instance, "channel"); |
| phc->channel = s ? atoi(s) : 0; |
| rising_edge = RCL_GetDriverOption(instance, "clear") ? 0 : 1; |
| phc->clock = HCL_CreateInstance(0, 16, UTI_Log2ToDouble(RCL_GetDriverPoll(instance))); |
| |
| if (!SYS_Linux_SetPHCExtTimestamping(phc->fd, phc->pin, phc->channel, |
| rising_edge, !rising_edge, 1)) |
| LOG_FATAL("Could not enable external PHC timestamping"); |
| |
| SCH_AddFileHandler(phc->fd, SCH_FILE_INPUT, read_ext_pulse, instance); |
| } else { |
| phc->pin = phc->channel = 0; |
| phc->clock = NULL; |
| } |
| |
| RCL_SetDriverData(instance, phc); |
| return 1; |
| } |
| |
| static void phc_finalise(RCL_Instance instance) |
| { |
| struct phc_instance *phc; |
| |
| phc = (struct phc_instance *)RCL_GetDriverData(instance); |
| |
| if (phc->extpps) { |
| SCH_RemoveFileHandler(phc->fd); |
| SYS_Linux_SetPHCExtTimestamping(phc->fd, phc->pin, phc->channel, 0, 0, 0); |
| HCL_DestroyInstance(phc->clock); |
| } |
| |
| close(phc->fd); |
| Free(phc); |
| } |
| |
| static void read_ext_pulse(int fd, int event, void *anything) |
| { |
| RCL_Instance instance; |
| struct phc_instance *phc; |
| struct timespec phc_ts, local_ts; |
| double local_err; |
| int channel; |
| |
| instance = anything; |
| phc = RCL_GetDriverData(instance); |
| |
| if (!SYS_Linux_ReadPHCExtTimestamp(phc->fd, &phc_ts, &channel)) |
| return; |
| |
| if (channel != phc->channel) { |
| DEBUG_LOG("Unexpected extts channel %d\n", channel); |
| return; |
| } |
| |
| if (!HCL_CookTime(phc->clock, &phc_ts, &local_ts, &local_err)) |
| return; |
| |
| RCL_AddCookedPulse(instance, &local_ts, 1.0e-9 * local_ts.tv_nsec, local_err, |
| UTI_DiffTimespecsToDouble(&phc_ts, &local_ts)); |
| } |
| |
| static int phc_poll(RCL_Instance instance) |
| { |
| struct phc_instance *phc; |
| struct timespec phc_ts, sys_ts, local_ts; |
| double offset, phc_err, local_err; |
| |
| phc = (struct phc_instance *)RCL_GetDriverData(instance); |
| |
| if (!SYS_Linux_GetPHCSample(phc->fd, phc->nocrossts, RCL_GetPrecision(instance), |
| &phc->mode, &phc_ts, &sys_ts, &phc_err)) |
| return 0; |
| |
| if (phc->extpps) { |
| LCL_CookTime(&sys_ts, &local_ts, &local_err); |
| HCL_AccumulateSample(phc->clock, &phc_ts, &local_ts, phc_err + local_err); |
| return 0; |
| } |
| |
| offset = UTI_DiffTimespecsToDouble(&phc_ts, &sys_ts); |
| |
| DEBUG_LOG("PHC offset: %+.9f err: %.9f", offset, phc_err); |
| |
| return RCL_AddSample(instance, &sys_ts, offset, LEAP_Normal); |
| } |
| |
| RefclockDriver RCL_PHC_driver = { |
| phc_initialise, |
| phc_finalise, |
| phc_poll |
| }; |
| |
| #else |
| |
| RefclockDriver RCL_PHC_driver = { NULL, NULL, NULL }; |
| |
| #endif |