| /** |
| * @file ts2phc_pps_sink.c |
| * @brief Utility program to synchronize the PHC clock to external events |
| * @note Copyright (C) 2019 Balint Ferencz <fernya@sch.bme.hu> |
| * @note SPDX-License-Identifier: GPL-2.0+ |
| */ |
| #include <errno.h> |
| #include <linux/ptp_clock.h> |
| #include <poll.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <sys/ioctl.h> |
| #include <sys/queue.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include "clockadj.h" |
| #include "config.h" |
| #include "missing.h" |
| #include "phc.h" |
| #include "print.h" |
| #include "servo.h" |
| #include "ts2phc.h" |
| #include "util.h" |
| |
| struct ts2phc_pps_sink { |
| char *name; |
| STAILQ_ENTRY(ts2phc_pps_sink) list; |
| struct ptp_pin_desc pin_desc; |
| unsigned int polarity; |
| tmv_t correction; |
| uint32_t pulsewidth; |
| struct ts2phc_clock *clock; |
| }; |
| |
| struct ts2phc_sink_array { |
| struct ts2phc_pps_sink **sink; |
| int *collected_events; |
| struct pollfd *pfd; |
| }; |
| |
| enum extts_result { |
| EXTTS_ERROR = -1, |
| EXTTS_OK = 0, |
| EXTTS_IGNORE = 1, |
| }; |
| |
| static int ts2phc_pps_sink_array_create(struct ts2phc_private *priv) |
| { |
| struct ts2phc_sink_array *polling_array; |
| struct ts2phc_pps_sink *sink; |
| unsigned int i; |
| |
| polling_array = malloc(sizeof(*polling_array)); |
| if (!polling_array) |
| goto err_alloc_array; |
| |
| polling_array->sink = malloc(priv->n_sinks * |
| sizeof(*polling_array->sink)); |
| if (!polling_array->sink) |
| goto err_alloc_sinks; |
| |
| polling_array->pfd = malloc(priv->n_sinks * |
| sizeof(*polling_array->pfd)); |
| if (!polling_array->pfd) |
| goto err_alloc_pfd; |
| |
| polling_array->collected_events = malloc(priv->n_sinks * sizeof(int)); |
| if (!polling_array->collected_events) |
| goto err_alloc_events; |
| |
| i = 0; |
| STAILQ_FOREACH(sink, &priv->sinks, list) { |
| polling_array->sink[i] = sink; |
| i++; |
| } |
| for (i = 0; i < priv->n_sinks; i++) { |
| sink = polling_array->sink[i]; |
| |
| polling_array->pfd[i].events = POLLIN | POLLPRI; |
| polling_array->pfd[i].fd = sink->clock->fd; |
| } |
| |
| priv->polling_array = polling_array; |
| |
| return 0; |
| |
| err_alloc_events: |
| free(polling_array->pfd); |
| err_alloc_pfd: |
| free(polling_array->sink); |
| err_alloc_sinks: |
| free(polling_array); |
| err_alloc_array: |
| pr_err("low memory"); |
| return -1; |
| } |
| |
| static void ts2phc_pps_sink_array_destroy(struct ts2phc_private *priv) |
| { |
| struct ts2phc_sink_array *polling_array = priv->polling_array; |
| |
| /* Allow sloppy calls of ts2phc_cleanup(), without having previously |
| * called ts2phc_pps_sink_array_create(). |
| */ |
| if (!priv->polling_array) |
| return; |
| |
| free(polling_array->collected_events); |
| free(polling_array->sink); |
| free(polling_array->pfd); |
| free(polling_array); |
| } |
| |
| static int ts2phc_pps_sink_clear_fifo(struct ts2phc_pps_sink *sink) |
| { |
| struct pollfd pfd = { |
| .events = POLLIN | POLLPRI, |
| .fd = sink->clock->fd, |
| }; |
| struct ptp_extts_event event; |
| int cnt, size; |
| |
| while (1) { |
| cnt = poll(&pfd, 1, 0); |
| if (cnt < 0) { |
| if (EINTR == errno) { |
| continue; |
| } else { |
| pr_emerg("poll failed"); |
| return -1; |
| } |
| } else if (!cnt) { |
| break; |
| } |
| size = read(pfd.fd, &event, sizeof(event)); |
| if (size != sizeof(event)) { |
| pr_err("read failed"); |
| return -1; |
| } |
| pr_debug("%s SKIP extts index %u at %lld.%09u", |
| sink->name, event.index, event.t.sec, event.t.nsec); |
| } |
| |
| return 0; |
| } |
| |
| static struct ts2phc_pps_sink *ts2phc_pps_sink_create(struct ts2phc_private *priv, |
| const char *device) |
| { |
| struct config *cfg = priv->cfg; |
| struct ptp_extts_request extts; |
| struct ts2phc_pps_sink *sink; |
| int32_t correction; |
| int err; |
| |
| sink = calloc(1, sizeof(*sink)); |
| if (!sink) { |
| pr_err("low memory"); |
| return NULL; |
| } |
| sink->name = strdup(device); |
| if (!sink->name) { |
| pr_err("low memory"); |
| free(sink); |
| return NULL; |
| } |
| sink->pin_desc.index = config_get_int(cfg, device, "ts2phc.pin_index"); |
| sink->pin_desc.func = PTP_PF_EXTTS; |
| sink->pin_desc.chan = config_get_int(cfg, device, "ts2phc.channel"); |
| sink->polarity = config_get_int(cfg, device, "ts2phc.extts_polarity"); |
| correction = config_get_int(cfg, device, "ts2phc.extts_correction"); |
| sink->correction = nanoseconds_to_tmv(correction); |
| |
| sink->pulsewidth = config_get_int(cfg, device, "ts2phc.pulsewidth"); |
| if (sink->pulsewidth > 500000000) |
| sink->pulsewidth = 1000000000 - sink->pulsewidth; |
| |
| sink->clock = ts2phc_clock_add(priv, device); |
| if (!sink->clock) { |
| pr_err("failed to open clock"); |
| goto no_posix_clock; |
| } |
| sink->clock->is_target = true; |
| |
| pr_debug("PPS sink %s has ptp index %d", device, |
| sink->clock->phc_index); |
| |
| if (phc_number_pins(sink->clock->clkid) > 0) { |
| err = phc_pin_setfunc(sink->clock->clkid, &sink->pin_desc); |
| if (err < 0) { |
| pr_err("PTP_PIN_SETFUNC request failed"); |
| goto no_pin_func; |
| } |
| } |
| |
| /* |
| * Disable external time stamping, and then read out any stale |
| * time stamps. |
| */ |
| memset(&extts, 0, sizeof(extts)); |
| extts.index = sink->pin_desc.chan; |
| extts.flags = 0; |
| if (ioctl(sink->clock->fd, PTP_EXTTS_REQUEST2, &extts)) { |
| pr_err(PTP_EXTTS_REQUEST_FAILED); |
| } |
| if (ioctl(sink->clock->fd, PTP_MASK_CLEAR_ALL)) { |
| pr_debug("PTP_MASK_CLEAR_ALL_FAILED request failed"); |
| } else if (ioctl(sink->clock->fd, PTP_MASK_EN_SINGLE, &extts.index)) { |
| pr_debug("PTP_MASK_EN_SINGLE request failed for channel: %d", extts.index); |
| } |
| if (ts2phc_pps_sink_clear_fifo(sink)) { |
| goto no_ext_ts; |
| } |
| |
| return sink; |
| no_ext_ts: |
| no_pin_func: |
| ts2phc_clock_destroy(sink->clock); |
| no_posix_clock: |
| free(sink->name); |
| free(sink); |
| return NULL; |
| } |
| |
| static void ts2phc_pps_sink_destroy(struct ts2phc_pps_sink *sink) |
| { |
| struct ptp_extts_request extts; |
| |
| memset(&extts, 0, sizeof(extts)); |
| extts.index = sink->pin_desc.chan; |
| extts.flags = 0; |
| if (ioctl(sink->clock->fd, PTP_EXTTS_REQUEST2, &extts)) { |
| pr_err(PTP_EXTTS_REQUEST_FAILED); |
| } |
| ts2phc_clock_destroy(sink->clock); |
| free(sink->name); |
| free(sink); |
| } |
| |
| static bool ts2phc_pps_sink_ignore(struct ts2phc_private *priv, |
| struct ts2phc_pps_sink *sink, |
| struct timespec source_ts) |
| { |
| tmv_t source_tmv = timespec_to_tmv(source_ts); |
| uint32_t ignore_lower, ignore_upper; |
| |
| source_tmv = tmv_sub(source_tmv, priv->perout_phase); |
| source_ts = tmv_to_timespec(source_tmv); |
| |
| if (ts2phc_pps_source_get_type(priv->src) == TS2PHC_PPS_SOURCE_NMEA) { |
| ignore_upper = sink->pulsewidth; |
| ignore_lower = 0; |
| } else { |
| ignore_upper = 1000000000 - sink->pulsewidth / 2; |
| ignore_lower = sink->pulsewidth / 2; |
| } |
| |
| return source_ts.tv_nsec > ignore_lower && |
| source_ts.tv_nsec < ignore_upper; |
| } |
| |
| static enum extts_result ts2phc_pps_sink_event(struct ts2phc_private *priv, |
| struct ts2phc_pps_sink *sink) |
| { |
| enum extts_result result = EXTTS_OK; |
| struct ptp_extts_event event; |
| struct timespec source_ts; |
| int err, cnt; |
| tmv_t ts; |
| |
| cnt = read(sink->clock->fd, &event, sizeof(event)); |
| if (cnt != sizeof(event)) { |
| pr_err("read extts event failed: %m"); |
| result = EXTTS_ERROR; |
| goto out; |
| } |
| if (event.index != sink->pin_desc.chan) { |
| pr_err("extts on unexpected channel"); |
| result = EXTTS_ERROR; |
| goto out; |
| } |
| |
| if (sink->polarity == (PTP_RISING_EDGE | PTP_FALLING_EDGE)) { |
| err = ts2phc_pps_source_getppstime(priv->src, &source_ts); |
| if (err < 0) { |
| pr_debug("source ts not valid"); |
| return 0; |
| } |
| |
| if (ts2phc_pps_sink_ignore(priv, sink, source_ts)) { |
| pr_debug("%s SKIP extts index %u at %lld.%09u src %" PRIi64 ".%ld", |
| sink->name, event.index, event.t.sec, |
| event.t.nsec, (int64_t)source_ts.tv_sec, |
| source_ts.tv_nsec); |
| |
| result = EXTTS_IGNORE; |
| goto out; |
| } |
| } |
| |
| out: |
| if (result == EXTTS_ERROR || result == EXTTS_IGNORE) |
| return result; |
| |
| ts = pct_to_tmv(event.t); |
| ts = tmv_add(ts, sink->correction); |
| ts2phc_clock_add_tstamp(sink->clock, ts); |
| |
| return EXTTS_OK; |
| } |
| |
| /* public methods */ |
| |
| int ts2phc_pps_sink_add(struct ts2phc_private *priv, const char *name) |
| { |
| struct ts2phc_pps_sink *sink; |
| |
| /* Create each interface only once. */ |
| STAILQ_FOREACH(sink, &priv->sinks, list) { |
| if (0 == strcmp(name, sink->name)) { |
| return 0; |
| } |
| } |
| sink = ts2phc_pps_sink_create(priv, name); |
| if (!sink) { |
| pr_err("failed to create sink"); |
| return -1; |
| } |
| STAILQ_INSERT_TAIL(&priv->sinks, sink, list); |
| priv->n_sinks++; |
| |
| return 0; |
| } |
| |
| int ts2phc_pps_sink_arm(struct ts2phc_private *priv) |
| { |
| struct ptp_extts_request extts; |
| struct ts2phc_pps_sink *sink; |
| int err; |
| |
| memset(&extts, 0, sizeof(extts)); |
| |
| STAILQ_FOREACH(sink, &priv->sinks, list) { |
| extts.index = sink->pin_desc.chan; |
| extts.flags = sink->polarity | PTP_ENABLE_FEATURE; |
| err = ioctl(sink->clock->fd, PTP_EXTTS_REQUEST2, &extts); |
| if (err < 0) { |
| pr_err(PTP_EXTTS_REQUEST_FAILED); |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| int ts2phc_pps_sinks_init(struct ts2phc_private *priv) |
| { |
| int err; |
| |
| err = ts2phc_pps_sink_array_create(priv); |
| if (err) |
| return err; |
| |
| return ts2phc_pps_sink_arm(priv); |
| } |
| |
| void ts2phc_pps_sink_cleanup(struct ts2phc_private *priv) |
| { |
| struct ts2phc_pps_sink *sink; |
| |
| ts2phc_pps_sink_array_destroy(priv); |
| |
| while ((sink = STAILQ_FIRST(&priv->sinks))) { |
| STAILQ_REMOVE_HEAD(&priv->sinks, list); |
| ts2phc_pps_sink_destroy(sink); |
| priv->n_sinks--; |
| } |
| } |
| |
| int ts2phc_pps_sink_poll(struct ts2phc_private *priv) |
| { |
| struct ts2phc_sink_array *polling_array = priv->polling_array; |
| bool all_sinks_have_events = false; |
| bool ignore_any = false; |
| unsigned int i; |
| int cnt; |
| |
| for (i = 0; i < priv->n_sinks; i++) |
| polling_array->collected_events[i] = 0; |
| |
| while (!all_sinks_have_events) { |
| struct ts2phc_pps_sink *sink; |
| |
| if (!is_running()) |
| return 0; |
| |
| cnt = poll(polling_array->pfd, priv->n_sinks, 2000); |
| if (cnt < 0) { |
| if (errno == EINTR) { |
| return 0; |
| } else { |
| pr_emerg("poll failed"); |
| return -1; |
| } |
| } else if (!cnt) { |
| pr_debug("poll returns zero, no events"); |
| return 0; |
| } |
| |
| for (i = 0; i < priv->n_sinks; i++) { |
| if (polling_array->pfd[i].revents & POLLERR) { |
| sink = polling_array->sink[i]; |
| |
| pr_err("%s: error polling on pfd[%d]\n", |
| sink->name, i); |
| return -EIO; |
| } |
| |
| if (polling_array->pfd[i].revents & (POLLIN|POLLPRI)) { |
| enum extts_result result; |
| |
| sink = polling_array->sink[i]; |
| |
| result = ts2phc_pps_sink_event(priv, sink); |
| if (result == EXTTS_ERROR) |
| return -EIO; |
| if (result == EXTTS_IGNORE) |
| ignore_any = true; |
| |
| /* |
| * Collect the events anyway, even if we'll |
| * ignore this source edge later. We don't |
| * want sink events from different edges |
| * to pile up and mix. |
| */ |
| polling_array->collected_events[i]++; |
| } |
| } |
| |
| all_sinks_have_events = true; |
| |
| for (i = 0; i < priv->n_sinks; i++) { |
| /* |
| * In the external PPS mode don't require non-target |
| * clocks to be receiving PPS to allow switching the |
| * PPS direction to synchronize the external clock. |
| */ |
| if (priv->external_pps && |
| !polling_array->sink[i]->clock->is_target) |
| continue; |
| |
| if (!polling_array->collected_events[i]) { |
| all_sinks_have_events = false; |
| break; |
| } |
| } |
| } |
| |
| if (ignore_any) |
| return 0; |
| |
| return 1; |
| } |