Thread: [Linuxptp-devel] [PATCH v2 2/2] ts2phc: Add PTP as a source of ToD
PTP IEEE 1588 stack for Linux
Brought to you by:
rcochran
From: Maciek M. <ma...@ma...> - 2023-04-07 15:15:39
|
Add PPS ToD source that extracts ToD from master timestamps from a slave event monitor of the running ptp4l instance. It enables scenarios where reliable ToD sources, such as nmea, may not be available, but a system is connected to different ptp masters. It can be used in the APTS deployment with the PPS from the local GNSS receiver that doesn't supply the ToD information, but PTP service is enabled in the backhaul. To use the servo ts2phc -s ptp -c eth0 -m --slave_event_monitor /var/run/ts2phc-mon And run the ptp4l with slave event monitor enabled ptp4l -i eth0 -m --free_running 1 --slave_event_monitor /var/run/ts2phc-mon Note: Do not enable ptp4l control of the clock on the same port. Signed-off-by: Maciek Machnikowski <ma...@ma...> --- makefile | 3 +- ts2phc.8 | 6 ++ ts2phc.c | 2 + ts2phc_pps_source.c | 4 + ts2phc_pps_source.h | 1 + ts2phc_ptp_pps_source.c | 200 ++++++++++++++++++++++++++++++++++++++++ ts2phc_ptp_pps_source.h | 15 +++ 7 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 ts2phc_ptp_pps_source.c create mode 100644 ts2phc_ptp_pps_source.h diff --git a/makefile b/makefile index 3e3b8b3..e15c22d 100644 --- a/makefile +++ b/makefile @@ -27,7 +27,8 @@ FILTERS = filter.o mave.o mmedian.o SERVOS = linreg.o ntpshm.o nullf.o pi.o refclock_sock.o servo.o TRANSP = raw.o transport.o udp.o udp6.o uds.o TS2PHC = ts2phc.o lstab.o nmea.o serial.o sock.o ts2phc_generic_pps_source.o \ - ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o ts2phc_pps_source.o + ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o ts2phc_pps_source.o \ + ts2phc_ptp_pps_source.o OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o designated_fsm.o \ e2e_tc.o fault.o $(FILTERS) fsm.o hash.o interface.o monitor.o msg.o phc.o \ port.o port_signaling.o pqueue.o print.o ptp4l.o p2p_tc.o rtnl.o $(SERVOS) \ diff --git a/ts2phc.8 b/ts2phc.8 index 3c71d47..f3abb32 100644 --- a/ts2phc.8 +++ b/ts2phc.8 @@ -72,6 +72,8 @@ device (like /dev/ptp0) or its associated network interface (like eth0). Use the key word "nmea" for an external 1-PPS from a GPS providing ToD information via the RMC NMEA sentence. +Use the key word "ptp" for the ToD source from the running ptp4l instance pushing +timestamps through the slave event monitor. .TP .B \-v Prints the software version and exits. @@ -259,6 +261,10 @@ Some PHC devices feature programmable pins, and this option allows configuration of a particular pin for the external time stamping or periodic output function. The default is pin index 0. +.TP +.B slave_event_monitor +Used with ptp ToD source, specifies the UNIX domain socket to read +SLAVE_RX_SYNC_TIMING_DATA from. .SH WARNING diff --git a/ts2phc.c b/ts2phc.c index 4393059..b998c7d 100644 --- a/ts2phc.c +++ b/ts2phc.c @@ -733,6 +733,8 @@ int main(int argc, char *argv[]) pps_type = TS2PHC_PPS_SOURCE_GENERIC; } else if (!strcasecmp(tod_source, "nmea")) { pps_type = TS2PHC_PPS_SOURCE_NMEA; + } else if (!strcasecmp(tod_source, "ptp")) { + pps_type = TS2PHC_PPS_SOURCE_PTP; } else { pps_type = TS2PHC_PPS_SOURCE_PHC; } diff --git a/ts2phc_pps_source.c b/ts2phc_pps_source.c index c333f65..87b0ffa 100644 --- a/ts2phc_pps_source.c +++ b/ts2phc_pps_source.c @@ -8,6 +8,7 @@ #include "ts2phc_nmea_pps_source.h" #include "ts2phc_phc_pps_source.h" #include "ts2phc_pps_source_private.h" +#include "ts2phc_ptp_pps_source.h" struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv, const char *dev, @@ -25,6 +26,9 @@ struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv, case TS2PHC_PPS_SOURCE_PHC: src = ts2phc_phc_pps_source_create(priv, dev); break; + case TS2PHC_PPS_SOURCE_PTP: + src = ts2phc_ptp_pps_source_create(priv, dev); + break; } return src; } diff --git a/ts2phc_pps_source.h b/ts2phc_pps_source.h index 293c693..3a66219 100644 --- a/ts2phc_pps_source.h +++ b/ts2phc_pps_source.h @@ -23,6 +23,7 @@ enum ts2phc_pps_source_type { TS2PHC_PPS_SOURCE_GENERIC, TS2PHC_PPS_SOURCE_NMEA, TS2PHC_PPS_SOURCE_PHC, + TS2PHC_PPS_SOURCE_PTP, }; /** diff --git a/ts2phc_ptp_pps_source.c b/ts2phc_ptp_pps_source.c new file mode 100644 index 0000000..cc385ce --- /dev/null +++ b/ts2phc_ptp_pps_source.c @@ -0,0 +1,200 @@ +/** + * @file ts2phc_ptp_pps_source.c + * @note Copyright (C) 2023 Maciek Machnikowski <ma...@ma...> + * @note SPDX-License-Identifier: GPL-2.0+ + */ +#include <pthread.h> +#include <stdlib.h> +#include <time.h> + +#include "missing.h" +#include "pmc_agent.h" +#include "print.h" +#include "ts2phc_pps_source_private.h" +#include "ts2phc_ptp_pps_source.h" +#include "util.h" + + +#define TIMESTAMP_SEC(ts) (((uint64_t)ts.seconds_lsb) | (((uint64_t)ts.seconds_msb) << 32)) +#define MAX_PTP_AGE 5000000000ULL + +struct ts2phc_ptp_pps_source { + struct ts2phc_pps_source pps_source; + struct ts2phc_private *priv; + struct pmc_agent *agent; + + pthread_t worker; + /* Protects anonymous struct fields, below, from concurrent access. */ + pthread_mutex_t mutex; + struct { + struct timespec local_monotime; + struct timespec ptp_time; + bool ptp_time_valid; + }; +}; + +static void ts2phc_ptp_pps_source_destroy(struct ts2phc_pps_source *src) +{ + struct ts2phc_ptp_pps_source *s = + container_of(src, struct ts2phc_ptp_pps_source, pps_source); + + pthread_join(s->worker, NULL); + pthread_mutex_destroy(&s->mutex); + + pmc_agent_destroy(s->agent); + + free(s); +} + +static int ts2phc_ptp_pps_source_getppstime(struct ts2phc_pps_source *src, + struct timespec *ts) +{ + struct ts2phc_ptp_pps_source *s = + container_of(src, struct ts2phc_ptp_pps_source, pps_source); + tmv_t duration_since_ptp, local_t1, local_t2, sync_time; + struct timespec now; + bool data_valid; + + clock_gettime(CLOCK_MONOTONIC, &now); + local_t2 = timespec_to_tmv(now); + + pthread_mutex_lock(&s->mutex); + + local_t1 = timespec_to_tmv(s->local_monotime); + sync_time = timespec_to_tmv(s->ptp_time); + data_valid = s->ptp_time_valid; + + pthread_mutex_unlock(&s->mutex); + + if (!data_valid) { + pr_debug("ptp: no valid PTP timestamp received"); + return -1; + } + + duration_since_ptp = tmv_sub(local_t2, local_t1); + if (tmv_to_nanoseconds(duration_since_ptp) > MAX_PTP_AGE) { + /* Invalidate the timestamp */ + pthread_mutex_lock(&s->mutex); + s->ptp_time_valid = false; + pthread_mutex_unlock(&s->mutex); + + pr_err("ptp: time stamp stale"); + return -1; + } + sync_time = tmv_add(sync_time, duration_since_ptp); + + *ts = tmv_to_timespec(sync_time); + + return 0; +} + +static void *monitor_ptp_signaling_msg(void *arg) +{ + struct ts2phc_ptp_pps_source *s = arg; + + while (is_running()) { + sleep(0.01); + pmc_agent_update(s->agent); + } + + return NULL; +} + +static int ts2phc_recv_signaling_subscribed(void *context, + struct ptp_message *msg, + int excluded) +{ + struct slave_rx_sync_timing_record *sync_record; + struct slave_rx_sync_timing_data_tlv *srstd; + struct ts2phc_ptp_pps_source *s = context; + struct tlv_extra *extra; + struct timespec rxtime; + int i, cnt; + + TAILQ_FOREACH(extra, &msg->tlv_list, list) { + if (extra->tlv->type != TLV_SLAVE_RX_SYNC_TIMING_DATA) { + continue; + } + + /* Read the time from the Sync/FollowUp message */ + srstd = (struct slave_rx_sync_timing_data_tlv *)extra->tlv; + cnt = (srstd->length - sizeof(srstd->sourcePortIdentity)) / + sizeof(*sync_record); + sync_record = srstd->record; + + for (i = 0; i < cnt; i++) { + clock_gettime(CLOCK_MONOTONIC, &rxtime); + + pthread_mutex_lock(&s->mutex); + + s->local_monotime = rxtime; + s->ptp_time.tv_sec = TIMESTAMP_SEC(sync_record->syncOriginTimestamp); + s->ptp_time.tv_nsec = sync_record->syncOriginTimestamp.nanoseconds; + s->ptp_time_valid = true; + + pthread_mutex_unlock(&s->mutex); + + pr_debug("%ld.%ld Seq: %d\n", s->ptp_time.tv_sec, + s->ptp_time.tv_nsec, sync_record->sequenceId); + + sync_record++; + } + } + + return 1; +} + +struct ts2phc_pps_source *ts2phc_ptp_pps_source_create(struct ts2phc_private *priv, + const char *dev) +{ + char uds_local[MAX_IFNAME_SIZE + 1]; + struct ts2phc_ptp_pps_source *s; + const char *path; + int err; + + s = calloc(1, sizeof(*s)); + if (!s) { + return NULL; + } + path = config_get_string(priv->cfg, NULL, "slave_event_monitor"); + if (!path) { + snprintf(uds_local, sizeof(uds_local), "/var/run/ts2phc-ptpmon"); + } else { + snprintf(uds_local, sizeof(uds_local), path); + } + + s->agent = pmc_agent_create(); + if (!s->agent) { + pr_err("failed to start pmc agent"); + goto err_agent; + } + + err = init_pmc_node(priv->cfg, s->agent, uds_local, + ts2phc_recv_signaling_subscribed, s); + if (err) { + pr_err("failed to create PMC agent: %s", strerror(err)); + goto err_pmc_init; + } + pmc_agent_enable_signaling_cb(s->agent, true); + + s->pps_source.destroy = ts2phc_ptp_pps_source_destroy; + s->pps_source.getppstime = ts2phc_ptp_pps_source_getppstime; + s->priv = priv; + + pthread_mutex_init(&s->mutex, NULL); + err = pthread_create(&s->worker, NULL, monitor_ptp_signaling_msg, s); + if (err) { + pr_err("failed to create worker thread: %s", strerror(err)); + goto err_thread; + } + + return &s->pps_source; + +err_thread: + pthread_mutex_destroy(&s->mutex); +err_pmc_init: + pmc_agent_destroy(s->agent); +err_agent: + free(s); + return NULL; +} diff --git a/ts2phc_ptp_pps_source.h b/ts2phc_ptp_pps_source.h new file mode 100644 index 0000000..45bf7ae --- /dev/null +++ b/ts2phc_ptp_pps_source.h @@ -0,0 +1,15 @@ +/** + * @file ts2phc_ptp_pps_source.h + * @note Copyright (C) 2023 Maciek Machnikowski <ma...@ma...> + * @note SPDX-License-Identifier: GPL-2.0+ + */ +#ifndef HAVE_TS2PHC_PTP_PPS_SOURCE_H +#define HAVE_TS2PHC_PTP_PPS_SOURCE_H + +#include "ts2phc.h" +#include "ts2phc_pps_source.h" + +struct ts2phc_pps_source *ts2phc_ptp_pps_source_create(struct ts2phc_private *priv, + const char *dev); + +#endif -- 2.30.2 |
From: Maciek M. <ma...@ma...> - 2023-04-07 15:15:42
|
Add option to run callback that when the PMC agent receives signaling messages. Signed-off-by: Maciek Machnikowski <ma...@ma...> --- pmc_agent.c | 19 ++++++++++++++++++- pmc_agent.h | 7 +++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/pmc_agent.c b/pmc_agent.c index 62d1a86..7ffcec8 100644 --- a/pmc_agent.c +++ b/pmc_agent.c @@ -42,6 +42,7 @@ struct pmc_agent { bool dds_valid; int leap; int pmc_ds_requested; + bool signaling_cb_ena; bool stay_subscribed; int sync_offset; int utc_offset_traceable; @@ -127,6 +128,7 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, #define N_FD 1 struct pollfd pollfd[N_FD]; int cnt, res; + bool skip_cb; while (1) { pollfd[0].fd = pmc_get_transport_fd(node->pmc); @@ -178,7 +180,16 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, node->pmc_ds_requested = 0; return RUN_PMC_NODEV; } - if (res <= 0 || + + /* Skip callback if message is not management */ + skip_cb = (res <= 0) ? true : false; + + /* Run the callback on signaling messages if configured */ + if (node->signaling_cb_ena && (msg_type(*msg) == SIGNALING)) { + skip_cb = false; + } + + if (skip_cb || node->recv_subscribed(node->recv_context, *msg, ds_id) || management_tlv_id(*msg) != ds_id) { msg_put(*msg); @@ -430,3 +441,9 @@ bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent) { return agent->utc_offset_traceable; } + +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable) +{ + agent->signaling_cb_ena = enable; +} + diff --git a/pmc_agent.h b/pmc_agent.h index 2fb1cc8..9ae37f7 100644 --- a/pmc_agent.h +++ b/pmc_agent.h @@ -170,4 +170,11 @@ int pmc_agent_update(struct pmc_agent *agent); */ bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent); +/** + * Enables or disables callback on signaling messages + * @param agent Pointer to a PMC instance obtained via @ref pmc_agent_create(). + * @param enable - if set to true, callback will be called on signaling msgs + */ +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable); + #endif -- 2.30.2 |
From: Erez <ere...@gm...> - 2023-04-08 15:29:55
|
On Fri, 7 Apr 2023 at 17:15, Maciek Machnikowski <ma...@ma...> wrote: > Add option to run callback that when the PMC agent receives signaling > messages. > I support you! > > Signed-off-by: Maciek Machnikowski <ma...@ma...> > --- > pmc_agent.c | 19 ++++++++++++++++++- > pmc_agent.h | 7 +++++++ > 2 files changed, 25 insertions(+), 1 deletion(-) > > diff --git a/pmc_agent.c b/pmc_agent.c > index 62d1a86..7ffcec8 100644 > --- a/pmc_agent.c > +++ b/pmc_agent.c > @@ -42,6 +42,7 @@ struct pmc_agent { > bool dds_valid; > int leap; > int pmc_ds_requested; > + bool signaling_cb_ena; > bool stay_subscribed; > int sync_offset; > int utc_offset_traceable; > @@ -127,6 +128,7 @@ static int run_pmc(struct pmc_agent *node, int > timeout, int ds_id, > #define N_FD 1 > struct pollfd pollfd[N_FD]; > int cnt, res; > + bool skip_cb; > > while (1) { > pollfd[0].fd = pmc_get_transport_fd(node->pmc); > @@ -178,7 +180,16 @@ static int run_pmc(struct pmc_agent *node, int > timeout, int ds_id, > node->pmc_ds_requested = 0; > return RUN_PMC_NODEV; > } > - if (res <= 0 || > + > + /* Skip callback if message is not management */ > + skip_cb = (res <= 0) ? true : false; > + > + /* Run the callback on signaling messages if configured */ > + if (node->signaling_cb_ena && (msg_type(*msg) == > SIGNALING)) { > + skip_cb = false; > + } > + > You can do: if (res == 0 && node->signaling_cb_ena && (msg_type(*msg) == SIGNALING)) { if(node->recv_subscribed(node->recv_context, *msg, ds_id) { msg_put(*msg); *msg = NULL; continue; } } else if (node->recv_subscribed(node->recv_context, *msg, ds_id) || management_tlv_id(*msg) != ds_id) { msg_put(*msg); *msg = NULL; continue; } + if (skip_cb || > node->recv_subscribed(node->recv_context, *msg, ds_id) > || > management_tlv_id(*msg) != ds_id) { > This condition: "management_tlv_id(*msg) != ds_id" seems irrelevant to signaling messages. > msg_put(*msg); > @@ -430,3 +441,9 @@ bool pmc_agent_utc_offset_traceable(struct pmc_agent > *agent) > { > return agent->utc_offset_traceable; > } > + > +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable) > +{ > + agent->signaling_cb_ena = enable; > +} > + > diff --git a/pmc_agent.h b/pmc_agent.h > index 2fb1cc8..9ae37f7 100644 > --- a/pmc_agent.h > +++ b/pmc_agent.h > @@ -170,4 +170,11 @@ int pmc_agent_update(struct pmc_agent *agent); > */ > bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent); > > +/** > + * Enables or disables callback on signaling messages > + * @param agent Pointer to a PMC instance obtained via @ref > pmc_agent_create(). > + * @param enable - if set to true, callback will be called on signaling > msgs > + */ > +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable); > I like the name! > + > #endif > -- > 2.30.2 > > Great work Erez |
From: Erez <ere...@gm...> - 2023-04-08 15:35:48
|
On Sat, 8 Apr 2023 at 17:29, Erez <ere...@gm...> wrote: > > > On Fri, 7 Apr 2023 at 17:15, Maciek Machnikowski <ma...@ma...> > wrote: > >> Add option to run callback that when the PMC agent receives signaling >> messages. >> > > I support you! > > >> >> Signed-off-by: Maciek Machnikowski <ma...@ma...> >> --- >> pmc_agent.c | 19 ++++++++++++++++++- >> pmc_agent.h | 7 +++++++ >> 2 files changed, 25 insertions(+), 1 deletion(-) >> >> diff --git a/pmc_agent.c b/pmc_agent.c >> index 62d1a86..7ffcec8 100644 >> --- a/pmc_agent.c >> +++ b/pmc_agent.c >> @@ -42,6 +42,7 @@ struct pmc_agent { >> bool dds_valid; >> int leap; >> int pmc_ds_requested; >> + bool signaling_cb_ena; >> bool stay_subscribed; >> int sync_offset; >> int utc_offset_traceable; >> @@ -127,6 +128,7 @@ static int run_pmc(struct pmc_agent *node, int >> timeout, int ds_id, >> #define N_FD 1 >> struct pollfd pollfd[N_FD]; >> int cnt, res; >> + bool skip_cb; >> >> while (1) { >> pollfd[0].fd = pmc_get_transport_fd(node->pmc); >> @@ -178,7 +180,16 @@ static int run_pmc(struct pmc_agent *node, int >> timeout, int ds_id, >> node->pmc_ds_requested = 0; >> return RUN_PMC_NODEV; >> } >> - if (res <= 0 || >> + >> + /* Skip callback if message is not management */ >> + skip_cb = (res <= 0) ? true : false; >> + >> + /* Run the callback on signaling messages if configured */ >> + if (node->signaling_cb_ena && (msg_type(*msg) == >> SIGNALING)) { >> + skip_cb = false; >> + } >> + >> > > You can do: > if (res == 0 && node->signaling_cb_ena && (msg_type(*msg) > == SIGNALING)) { > if(node->recv_subscribed(node->recv_context, *msg, > ds_id) { > msg_put(*msg); > *msg = NULL; > continue; > } > } > else if (node->recv_subscribed(node->recv_context, *msg, > ds_id) || > management_tlv_id(*msg) != ds_id) { > msg_put(*msg); > *msg = NULL; > continue; > } > > My code is wrong, but you should call "management_tlv_id(*msg)" only if the message is a management. > + if (skip_cb || >> node->recv_subscribed(node->recv_context, *msg, >> ds_id) || >> management_tlv_id(*msg) != ds_id) { >> > > This condition: "management_tlv_id(*msg) != ds_id" seems irrelevant to > signaling messages. > > >> msg_put(*msg); >> @@ -430,3 +441,9 @@ bool pmc_agent_utc_offset_traceable(struct pmc_agent >> *agent) >> { >> return agent->utc_offset_traceable; >> } >> + >> +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable) >> +{ >> + agent->signaling_cb_ena = enable; >> +} >> + >> diff --git a/pmc_agent.h b/pmc_agent.h >> index 2fb1cc8..9ae37f7 100644 >> --- a/pmc_agent.h >> +++ b/pmc_agent.h >> @@ -170,4 +170,11 @@ int pmc_agent_update(struct pmc_agent *agent); >> */ >> bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent); >> >> +/** >> + * Enables or disables callback on signaling messages >> + * @param agent Pointer to a PMC instance obtained via @ref >> pmc_agent_create(). >> + * @param enable - if set to true, callback will be called on signaling >> msgs >> + */ >> +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable); >> > > I like the name! > > >> + >> #endif >> -- >> 2.30.2 >> >> > Great work > Erez > |
From: Erez <ere...@gm...> - 2023-04-08 15:43:04
|
On Fri, 7 Apr 2023 at 17:15, Maciek Machnikowski <ma...@ma...> wrote: > Add option to run callback that when the PMC agent receives signaling > messages. > > Signed-off-by: Maciek Machnikowski <ma...@ma...> > --- > pmc_agent.c | 19 ++++++++++++++++++- > pmc_agent.h | 7 +++++++ > 2 files changed, 25 insertions(+), 1 deletion(-) > > diff --git a/pmc_agent.c b/pmc_agent.c > index 62d1a86..7ffcec8 100644 > --- a/pmc_agent.c > +++ b/pmc_agent.c > @@ -42,6 +42,7 @@ struct pmc_agent { > bool dds_valid; > int leap; > int pmc_ds_requested; > + bool signaling_cb_ena; > bool stay_subscribed; > int sync_offset; > int utc_offset_traceable; > @@ -127,6 +128,7 @@ static int run_pmc(struct pmc_agent *node, int > timeout, int ds_id, > #define N_FD 1 > struct pollfd pollfd[N_FD]; > int cnt, res; > + bool skip_cb; > > while (1) { > pollfd[0].fd = pmc_get_transport_fd(node->pmc); > @@ -178,7 +180,16 @@ static int run_pmc(struct pmc_agent *node, int > timeout, int ds_id, > node->pmc_ds_requested = 0; > return RUN_PMC_NODEV; > } > - if (res <= 0 || > + > + /* Skip callback if message is not management */ > + skip_cb = (res <= 0) ? true : false; > + > + /* Run the callback on signaling messages if configured */ > + if (node->signaling_cb_ena && (msg_type(*msg) == > SIGNALING)) { > + skip_cb = false; > + } > + > + if (skip_cb || > node->recv_subscribed(node->recv_context, *msg, ds_id) > || > management_tlv_id(*msg) != ds_id) { > msg_put(*msg); > @@ -430,3 +441,9 @@ bool pmc_agent_utc_offset_traceable(struct pmc_agent > *agent) > { > return agent->utc_offset_traceable; > } > I think I have it: + /* Run the callback on signaling messages if configured */ + if (res == 0 && node->signaling_cb_ena && (msg_type(*msg) == SIGNALING)) { + res=2; + } - management_tlv_id(*msg) != ds_id) { + res == 1 && management_tlv_id(*msg) != ds_id) { Erez :-) > + > +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable) > +{ > + agent->signaling_cb_ena = enable; > +} > + > diff --git a/pmc_agent.h b/pmc_agent.h > index 2fb1cc8..9ae37f7 100644 > --- a/pmc_agent.h > +++ b/pmc_agent.h > @@ -170,4 +170,11 @@ int pmc_agent_update(struct pmc_agent *agent); > */ > bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent); > > +/** > + * Enables or disables callback on signaling messages > + * @param agent Pointer to a PMC instance obtained via @ref > pmc_agent_create(). > + * @param enable - if set to true, callback will be called on signaling > msgs > + */ > +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable); > + > #endif > -- > 2.30.2 > > |
From: Maciek M. <ma...@ma...> - 2023-04-11 08:28:26
|
On 4/8/2023 5:42 PM, Erez wrote: > > I think I have it: > > + /* Run the callback on signaling messages if configured */ > + if (res == 0 && node->signaling_cb_ena && > (msg_type(*msg) == SIGNALING)) { > + res=2; > + } > > > - management_tlv_id(*msg) != ds_id) { > + res == 1 && management_tlv_id(*msg) != ds_id) { > > Erez :-) Thanks for the effort. I started with a similar solution, but after seeing how unreadable it became - I decided to add a new variable to address the readability of this part of the code. I don't like abusing res to get fewer lines of code :) Thanks, Maciek |
From: Maciek M. <ma...@ma...> - 2023-04-20 16:48:09
|
On 4/17/2023 3:24 PM, Erez wrote: > > > On Mon, 17 Apr 2023 at 15:21, Maciek Machnikowski > <ma...@ma... <mailto:ma...@ma...>> wrote: > > > > On 4/14/2023 2:58 PM, Erez wrote: > > > > > > On Tue, 11 Apr 2023 at 10:28, Maciek Machnikowski > > <ma...@ma... <mailto:ma...@ma...> > <mailto:ma...@ma... <mailto:ma...@ma...>>> > wrote: > > > > > > On 4/8/2023 5:42 PM, Erez wrote: > > > > > > I think I have it: > > > > > > + /* Run the callback on signaling messages if > > configured */ > > > + if (res == 0 && node->signaling_cb_ena && > > > (msg_type(*msg) == SIGNALING)) { > > > + res=2; > > > + } > > > > > > > > > - management_tlv_id(*msg) != ds_id) { > > > + res == 1 && management_tlv_id(*msg) != > ds_id) { > > > > > > Erez :-) > > > > Thanks for the effort. > > I started with a similar solution, but after seeing how > unreadable it > > became - I decided to add a new variable to address the > readability of > > this part of the code. I don't like abusing res to get fewer > lines of > > code :) > > > > > > Reusing 'ret' is not abuse. > > As we change code we may broaden existing variables, nothing is wrong > > with it. > > Thought, you are free to rewrite the code in your way :-) > > I only show a simple way to achieve it, not necessarily the best. > > I think using boolean 'skip_cb' is not sufficient, as you should not > > call 'management_tlv_id' with a signalling message! > > Hey! > The management_tlv_id won't be called on signaling messages, because the > callback should always return 1 and cause the if to stop checking > > > You should not make this assumption, the user may want to end the agent > on signaling message as well :-) > You add a new feature, you should not assume how we use it! > > Erez OK Will fix in a new revision. And if we're at it - is there any use case that sends signaling and management messages over the same UDS socket? They are separate options inside the config file, so I'd assume they should be separated. Thanks, Maciek |
From: Maciek M. <ma...@ma...> - 2023-04-11 08:38:51
|
Add option to run callback that when the PMC agent receives signaling messages. Signed-off-by: Maciek Machnikowski <ma...@ma...> --- pmc_agent.c | 19 ++++++++++++++++++- pmc_agent.h | 7 +++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/pmc_agent.c b/pmc_agent.c index 62d1a86..7ffcec8 100644 --- a/pmc_agent.c +++ b/pmc_agent.c @@ -42,6 +42,7 @@ struct pmc_agent { bool dds_valid; int leap; int pmc_ds_requested; + bool signaling_cb_ena; bool stay_subscribed; int sync_offset; int utc_offset_traceable; @@ -127,6 +128,7 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, #define N_FD 1 struct pollfd pollfd[N_FD]; int cnt, res; + bool skip_cb; while (1) { pollfd[0].fd = pmc_get_transport_fd(node->pmc); @@ -178,7 +180,16 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, node->pmc_ds_requested = 0; return RUN_PMC_NODEV; } - if (res <= 0 || + + /* Skip callback if message is not management */ + skip_cb = (res <= 0) ? true : false; + + /* Run the callback on signaling messages if configured */ + if (node->signaling_cb_ena && (msg_type(*msg) == SIGNALING)) { + skip_cb = false; + } + + if (skip_cb || node->recv_subscribed(node->recv_context, *msg, ds_id) || management_tlv_id(*msg) != ds_id) { msg_put(*msg); @@ -430,3 +441,9 @@ bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent) { return agent->utc_offset_traceable; } + +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable) +{ + agent->signaling_cb_ena = enable; +} + diff --git a/pmc_agent.h b/pmc_agent.h index 2fb1cc8..9ae37f7 100644 --- a/pmc_agent.h +++ b/pmc_agent.h @@ -170,4 +170,11 @@ int pmc_agent_update(struct pmc_agent *agent); */ bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent); +/** + * Enables or disables callback on signaling messages + * @param agent Pointer to a PMC instance obtained via @ref pmc_agent_create(). + * @param enable - if set to true, callback will be called on signaling msgs + */ +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable); + #endif -- 2.30.2 |
From: Maciek M. <ma...@ma...> - 2023-04-11 08:38:52
|
Add PPS ToD source that extracts ToD from master timestamps from a slave event monitor of the running ptp4l instance. It enables scenarios where reliable ToD sources, such as nmea, may not be available, but a system is connected to different ptp masters. It can be used in the APTS deployment with the PPS from the local GNSS receiver that doesn't supply the ToD information, but PTP service is enabled in the backhaul. To use the servo ts2phc -s ptp -c eth0 -m --slave_event_monitor /var/run/ts2phc-mon And run the ptp4l with slave event monitor enabled ptp4l -i eth0 -m --free_running 1 --slave_event_monitor /var/run/ts2phc-mon Note: Do not enable ptp4l control of the clock on the same port. v2: changed pmc_agent implementation to not use a separate callback v3: updated poll thread to use less CPU Signed-off-by: Maciek Machnikowski <ma...@ma...> --- makefile | 3 +- ts2phc.8 | 6 ++ ts2phc.c | 2 + ts2phc_pps_source.c | 4 + ts2phc_pps_source.h | 1 + ts2phc_ptp_pps_source.c | 216 ++++++++++++++++++++++++++++++++++++++++ ts2phc_ptp_pps_source.h | 15 +++ 7 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 ts2phc_ptp_pps_source.c create mode 100644 ts2phc_ptp_pps_source.h diff --git a/makefile b/makefile index 3e3b8b3..e15c22d 100644 --- a/makefile +++ b/makefile @@ -27,7 +27,8 @@ FILTERS = filter.o mave.o mmedian.o SERVOS = linreg.o ntpshm.o nullf.o pi.o refclock_sock.o servo.o TRANSP = raw.o transport.o udp.o udp6.o uds.o TS2PHC = ts2phc.o lstab.o nmea.o serial.o sock.o ts2phc_generic_pps_source.o \ - ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o ts2phc_pps_source.o + ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o ts2phc_pps_source.o \ + ts2phc_ptp_pps_source.o OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o designated_fsm.o \ e2e_tc.o fault.o $(FILTERS) fsm.o hash.o interface.o monitor.o msg.o phc.o \ port.o port_signaling.o pqueue.o print.o ptp4l.o p2p_tc.o rtnl.o $(SERVOS) \ diff --git a/ts2phc.8 b/ts2phc.8 index 3c71d47..f3abb32 100644 --- a/ts2phc.8 +++ b/ts2phc.8 @@ -72,6 +72,8 @@ device (like /dev/ptp0) or its associated network interface (like eth0). Use the key word "nmea" for an external 1-PPS from a GPS providing ToD information via the RMC NMEA sentence. +Use the key word "ptp" for the ToD source from the running ptp4l instance pushing +timestamps through the slave event monitor. .TP .B \-v Prints the software version and exits. @@ -259,6 +261,10 @@ Some PHC devices feature programmable pins, and this option allows configuration of a particular pin for the external time stamping or periodic output function. The default is pin index 0. +.TP +.B slave_event_monitor +Used with ptp ToD source, specifies the UNIX domain socket to read +SLAVE_RX_SYNC_TIMING_DATA from. .SH WARNING diff --git a/ts2phc.c b/ts2phc.c index 4393059..b998c7d 100644 --- a/ts2phc.c +++ b/ts2phc.c @@ -733,6 +733,8 @@ int main(int argc, char *argv[]) pps_type = TS2PHC_PPS_SOURCE_GENERIC; } else if (!strcasecmp(tod_source, "nmea")) { pps_type = TS2PHC_PPS_SOURCE_NMEA; + } else if (!strcasecmp(tod_source, "ptp")) { + pps_type = TS2PHC_PPS_SOURCE_PTP; } else { pps_type = TS2PHC_PPS_SOURCE_PHC; } diff --git a/ts2phc_pps_source.c b/ts2phc_pps_source.c index c333f65..87b0ffa 100644 --- a/ts2phc_pps_source.c +++ b/ts2phc_pps_source.c @@ -8,6 +8,7 @@ #include "ts2phc_nmea_pps_source.h" #include "ts2phc_phc_pps_source.h" #include "ts2phc_pps_source_private.h" +#include "ts2phc_ptp_pps_source.h" struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv, const char *dev, @@ -25,6 +26,9 @@ struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv, case TS2PHC_PPS_SOURCE_PHC: src = ts2phc_phc_pps_source_create(priv, dev); break; + case TS2PHC_PPS_SOURCE_PTP: + src = ts2phc_ptp_pps_source_create(priv, dev); + break; } return src; } diff --git a/ts2phc_pps_source.h b/ts2phc_pps_source.h index 293c693..3a66219 100644 --- a/ts2phc_pps_source.h +++ b/ts2phc_pps_source.h @@ -23,6 +23,7 @@ enum ts2phc_pps_source_type { TS2PHC_PPS_SOURCE_GENERIC, TS2PHC_PPS_SOURCE_NMEA, TS2PHC_PPS_SOURCE_PHC, + TS2PHC_PPS_SOURCE_PTP, }; /** diff --git a/ts2phc_ptp_pps_source.c b/ts2phc_ptp_pps_source.c new file mode 100644 index 0000000..666129a --- /dev/null +++ b/ts2phc_ptp_pps_source.c @@ -0,0 +1,216 @@ +/** + * @file ts2phc_ptp_pps_source.c + * @note Copyright (C) 2023 Maciek Machnikowski <ma...@ma...> + * @note SPDX-License-Identifier: GPL-2.0+ + */ +#include <pthread.h> +#include <stdlib.h> +#include <time.h> + +#include "missing.h" +#include "pmc_agent.h" +#include "print.h" +#include "ts2phc_pps_source_private.h" +#include "ts2phc_ptp_pps_source.h" +#include "util.h" + + +#define TIMESTAMP_SEC(ts) (((uint64_t)ts.seconds_lsb) | (((uint64_t)ts.seconds_msb) << 32)) +#define MAX_PTP_AGE 5000000000ULL +#define TOD_UPDATE_INTERVAL 300000000ULL + + +struct ts2phc_ptp_pps_source { + struct ts2phc_pps_source pps_source; + struct ts2phc_private *priv; + struct pmc_agent *agent; + uint64_t last_update; + + pthread_t worker; + /* Protects anonymous struct fields, below, from concurrent access. */ + pthread_mutex_t mutex; + struct { + struct timespec local_monotime; + struct timespec ptp_time; + bool ptp_time_valid; + }; +}; + +static void ts2phc_ptp_pps_source_destroy(struct ts2phc_pps_source *src) +{ + struct ts2phc_ptp_pps_source *s = + container_of(src, struct ts2phc_ptp_pps_source, pps_source); + + pthread_join(s->worker, NULL); + pthread_mutex_destroy(&s->mutex); + + pmc_agent_destroy(s->agent); + + free(s); +} + +static int ts2phc_ptp_pps_source_getppstime(struct ts2phc_pps_source *src, + struct timespec *ts) +{ + struct ts2phc_ptp_pps_source *s = + container_of(src, struct ts2phc_ptp_pps_source, pps_source); + tmv_t duration_since_ptp, local_t1, local_t2, sync_time; + struct timespec now; + bool data_valid; + + clock_gettime(CLOCK_MONOTONIC, &now); + local_t2 = timespec_to_tmv(now); + + pthread_mutex_lock(&s->mutex); + + local_t1 = timespec_to_tmv(s->local_monotime); + sync_time = timespec_to_tmv(s->ptp_time); + data_valid = s->ptp_time_valid; + + pthread_mutex_unlock(&s->mutex); + + if (!data_valid) { + pr_debug("ptp: no valid PTP timestamp received"); + return -1; + } + + duration_since_ptp = tmv_sub(local_t2, local_t1); + if (tmv_to_nanoseconds(duration_since_ptp) > MAX_PTP_AGE) { + /* Invalidate the timestamp */ + pthread_mutex_lock(&s->mutex); + s->ptp_time_valid = false; + pthread_mutex_unlock(&s->mutex); + + pr_err("ptp: time stamp stale"); + return -1; + } + sync_time = tmv_add(sync_time, duration_since_ptp); + + *ts = tmv_to_timespec(sync_time); + + return 0; +} + +static void *monitor_ptp_signaling_msg(void *arg) +{ + struct ts2phc_ptp_pps_source *s = arg; + struct timespec tp; + uint64_t ts; + + while (is_running()) { + if (clock_gettime(CLOCK_MONOTONIC, &tp)) { + pr_err("failed to read clock: %m"); + return NULL; + } + ts = tp.tv_sec * NS_PER_SEC + tp.tv_nsec; + + if (ts - s->last_update >= TOD_UPDATE_INTERVAL) { + pmc_agent_update(s->agent); + s->last_update = ts; + } else { + sleep(0.01); + } + } + + return NULL; +} + +static int ts2phc_recv_signaling_subscribed(void *context, + struct ptp_message *msg, + int excluded) +{ + struct slave_rx_sync_timing_record *sync_record; + struct slave_rx_sync_timing_data_tlv *srstd; + struct ts2phc_ptp_pps_source *s = context; + struct tlv_extra *extra; + struct timespec rxtime; + int i, cnt; + + TAILQ_FOREACH(extra, &msg->tlv_list, list) { + if (extra->tlv->type != TLV_SLAVE_RX_SYNC_TIMING_DATA) { + continue; + } + + /* Read the time from the Sync/FollowUp message */ + srstd = (struct slave_rx_sync_timing_data_tlv *)extra->tlv; + cnt = (srstd->length - sizeof(srstd->sourcePortIdentity)) / + sizeof(*sync_record); + sync_record = srstd->record; + + for (i = 0; i < cnt; i++) { + clock_gettime(CLOCK_MONOTONIC, &rxtime); + + pthread_mutex_lock(&s->mutex); + + s->local_monotime = rxtime; + s->ptp_time.tv_sec = TIMESTAMP_SEC(sync_record->syncOriginTimestamp); + s->ptp_time.tv_nsec = sync_record->syncOriginTimestamp.nanoseconds; + s->ptp_time_valid = true; + + pthread_mutex_unlock(&s->mutex); + + pr_debug("%ld.%ld Seq: %d\n", s->ptp_time.tv_sec, + s->ptp_time.tv_nsec, sync_record->sequenceId); + + sync_record++; + } + } + + return 1; +} + +struct ts2phc_pps_source *ts2phc_ptp_pps_source_create(struct ts2phc_private *priv, + const char *dev) +{ + char uds_local[MAX_IFNAME_SIZE + 1]; + struct ts2phc_ptp_pps_source *s; + const char *path; + int err; + + s = calloc(1, sizeof(*s)); + if (!s) { + return NULL; + } + path = config_get_string(priv->cfg, NULL, "slave_event_monitor"); + if (!path) { + snprintf(uds_local, sizeof(uds_local), "/var/run/ts2phc-ptpmon"); + } else { + snprintf(uds_local, sizeof(uds_local), path); + } + + s->agent = pmc_agent_create(); + if (!s->agent) { + pr_err("failed to start pmc agent"); + goto err_agent; + } + + err = init_pmc_node(priv->cfg, s->agent, uds_local, + ts2phc_recv_signaling_subscribed, s); + if (err) { + pr_err("failed to create PMC agent: %s", strerror(err)); + goto err_pmc_init; + } + pmc_agent_enable_signaling_cb(s->agent, true); + + s->pps_source.destroy = ts2phc_ptp_pps_source_destroy; + s->pps_source.getppstime = ts2phc_ptp_pps_source_getppstime; + s->last_update = 0; + s->priv = priv; + + pthread_mutex_init(&s->mutex, NULL); + err = pthread_create(&s->worker, NULL, monitor_ptp_signaling_msg, s); + if (err) { + pr_err("failed to create worker thread: %s", strerror(err)); + goto err_thread; + } + + return &s->pps_source; + +err_thread: + pthread_mutex_destroy(&s->mutex); +err_pmc_init: + pmc_agent_destroy(s->agent); +err_agent: + free(s); + return NULL; +} diff --git a/ts2phc_ptp_pps_source.h b/ts2phc_ptp_pps_source.h new file mode 100644 index 0000000..45bf7ae --- /dev/null +++ b/ts2phc_ptp_pps_source.h @@ -0,0 +1,15 @@ +/** + * @file ts2phc_ptp_pps_source.h + * @note Copyright (C) 2023 Maciek Machnikowski <ma...@ma...> + * @note SPDX-License-Identifier: GPL-2.0+ + */ +#ifndef HAVE_TS2PHC_PTP_PPS_SOURCE_H +#define HAVE_TS2PHC_PTP_PPS_SOURCE_H + +#include "ts2phc.h" +#include "ts2phc_pps_source.h" + +struct ts2phc_pps_source *ts2phc_ptp_pps_source_create(struct ts2phc_private *priv, + const char *dev); + +#endif -- 2.30.2 |
From: Maciek M. <ma...@ma...> - 2023-04-11 13:59:07
|
On 4/11/2023 3:20 PM, Jakub Raczyński wrote: > Dear Maciek, > >> + if (ts - s->last_update >= TOD_UPDATE_INTERVAL) { >> + pmc_agent_update(s->agent); >> + s->last_update = ts; >> + } else { >> + sleep(0.01); >> + } > > Unless I am missing something, doesn't sleep accept unsigned int only? > https://man7.org/linux/man-pages/man3/sleep.3.html > Cannot compile this at the moment, but if this is new version of sleep, for old system libraries probably correct function would be usleep(). > > Best regards, > Jakub Raczyński Hmmm- you're right - GCC probably changed it to sleep 0 and that's why it was not complaining - will change to usleep and resubmit Thanks, Maciek |
From: Maciek M. <ma...@ma...> - 2023-04-11 14:16:09
|
Add option to run callback that when the PMC agent receives signaling messages. Signed-off-by: Maciek Machnikowski <ma...@ma...> --- pmc_agent.c | 19 ++++++++++++++++++- pmc_agent.h | 7 +++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/pmc_agent.c b/pmc_agent.c index 62d1a86..7ffcec8 100644 --- a/pmc_agent.c +++ b/pmc_agent.c @@ -42,6 +42,7 @@ struct pmc_agent { bool dds_valid; int leap; int pmc_ds_requested; + bool signaling_cb_ena; bool stay_subscribed; int sync_offset; int utc_offset_traceable; @@ -127,6 +128,7 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, #define N_FD 1 struct pollfd pollfd[N_FD]; int cnt, res; + bool skip_cb; while (1) { pollfd[0].fd = pmc_get_transport_fd(node->pmc); @@ -178,7 +180,16 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, node->pmc_ds_requested = 0; return RUN_PMC_NODEV; } - if (res <= 0 || + + /* Skip callback if message is not management */ + skip_cb = (res <= 0) ? true : false; + + /* Run the callback on signaling messages if configured */ + if (node->signaling_cb_ena && (msg_type(*msg) == SIGNALING)) { + skip_cb = false; + } + + if (skip_cb || node->recv_subscribed(node->recv_context, *msg, ds_id) || management_tlv_id(*msg) != ds_id) { msg_put(*msg); @@ -430,3 +441,9 @@ bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent) { return agent->utc_offset_traceable; } + +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable) +{ + agent->signaling_cb_ena = enable; +} + diff --git a/pmc_agent.h b/pmc_agent.h index 2fb1cc8..9ae37f7 100644 --- a/pmc_agent.h +++ b/pmc_agent.h @@ -170,4 +170,11 @@ int pmc_agent_update(struct pmc_agent *agent); */ bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent); +/** + * Enables or disables callback on signaling messages + * @param agent Pointer to a PMC instance obtained via @ref pmc_agent_create(). + * @param enable - if set to true, callback will be called on signaling msgs + */ +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable); + #endif -- 2.30.2 |
From: Maciek M. <ma...@ma...> - 2023-04-11 14:16:15
|
Add PPS ToD source that extracts ToD from master timestamps from a slave event monitor of the running ptp4l instance. It enables scenarios where reliable ToD sources, such as nmea, may not be available, but a system is connected to different ptp masters. It can be used in the APTS deployment with the PPS from the local GNSS receiver that doesn't supply the ToD information, but PTP service is enabled in the backhaul. To use the servo ts2phc -s ptp -c eth0 -m --slave_event_monitor /var/run/ts2phc-mon And run the ptp4l with slave event monitor enabled ptp4l -i eth0 -m --free_running 1 --slave_event_monitor /var/run/ts2phc-mon Note: Do not enable ptp4l control of the clock on the same port. v2: changed pmc_agent implementation to not use a separate callback v3: updated poll thread to use less CPU v4: changed sleep routine to usleep, simplified poll thread Signed-off-by: Maciek Machnikowski <ma...@ma...> --- makefile | 3 +- ts2phc.8 | 6 ++ ts2phc.c | 2 + ts2phc_pps_source.c | 4 + ts2phc_pps_source.h | 1 + ts2phc_ptp_pps_source.c | 199 ++++++++++++++++++++++++++++++++++++++++ ts2phc_ptp_pps_source.h | 15 +++ 7 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 ts2phc_ptp_pps_source.c create mode 100644 ts2phc_ptp_pps_source.h diff --git a/makefile b/makefile index 3e3b8b3..e15c22d 100644 --- a/makefile +++ b/makefile @@ -27,7 +27,8 @@ FILTERS = filter.o mave.o mmedian.o SERVOS = linreg.o ntpshm.o nullf.o pi.o refclock_sock.o servo.o TRANSP = raw.o transport.o udp.o udp6.o uds.o TS2PHC = ts2phc.o lstab.o nmea.o serial.o sock.o ts2phc_generic_pps_source.o \ - ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o ts2phc_pps_source.o + ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o ts2phc_pps_source.o \ + ts2phc_ptp_pps_source.o OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o designated_fsm.o \ e2e_tc.o fault.o $(FILTERS) fsm.o hash.o interface.o monitor.o msg.o phc.o \ port.o port_signaling.o pqueue.o print.o ptp4l.o p2p_tc.o rtnl.o $(SERVOS) \ diff --git a/ts2phc.8 b/ts2phc.8 index 3c71d47..f3abb32 100644 --- a/ts2phc.8 +++ b/ts2phc.8 @@ -72,6 +72,8 @@ device (like /dev/ptp0) or its associated network interface (like eth0). Use the key word "nmea" for an external 1-PPS from a GPS providing ToD information via the RMC NMEA sentence. +Use the key word "ptp" for the ToD source from the running ptp4l instance pushing +timestamps through the slave event monitor. .TP .B \-v Prints the software version and exits. @@ -259,6 +261,10 @@ Some PHC devices feature programmable pins, and this option allows configuration of a particular pin for the external time stamping or periodic output function. The default is pin index 0. +.TP +.B slave_event_monitor +Used with ptp ToD source, specifies the UNIX domain socket to read +SLAVE_RX_SYNC_TIMING_DATA from. .SH WARNING diff --git a/ts2phc.c b/ts2phc.c index 4393059..b998c7d 100644 --- a/ts2phc.c +++ b/ts2phc.c @@ -733,6 +733,8 @@ int main(int argc, char *argv[]) pps_type = TS2PHC_PPS_SOURCE_GENERIC; } else if (!strcasecmp(tod_source, "nmea")) { pps_type = TS2PHC_PPS_SOURCE_NMEA; + } else if (!strcasecmp(tod_source, "ptp")) { + pps_type = TS2PHC_PPS_SOURCE_PTP; } else { pps_type = TS2PHC_PPS_SOURCE_PHC; } diff --git a/ts2phc_pps_source.c b/ts2phc_pps_source.c index c333f65..87b0ffa 100644 --- a/ts2phc_pps_source.c +++ b/ts2phc_pps_source.c @@ -8,6 +8,7 @@ #include "ts2phc_nmea_pps_source.h" #include "ts2phc_phc_pps_source.h" #include "ts2phc_pps_source_private.h" +#include "ts2phc_ptp_pps_source.h" struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv, const char *dev, @@ -25,6 +26,9 @@ struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv, case TS2PHC_PPS_SOURCE_PHC: src = ts2phc_phc_pps_source_create(priv, dev); break; + case TS2PHC_PPS_SOURCE_PTP: + src = ts2phc_ptp_pps_source_create(priv, dev); + break; } return src; } diff --git a/ts2phc_pps_source.h b/ts2phc_pps_source.h index 293c693..3a66219 100644 --- a/ts2phc_pps_source.h +++ b/ts2phc_pps_source.h @@ -23,6 +23,7 @@ enum ts2phc_pps_source_type { TS2PHC_PPS_SOURCE_GENERIC, TS2PHC_PPS_SOURCE_NMEA, TS2PHC_PPS_SOURCE_PHC, + TS2PHC_PPS_SOURCE_PTP, }; /** diff --git a/ts2phc_ptp_pps_source.c b/ts2phc_ptp_pps_source.c new file mode 100644 index 0000000..5e160d0 --- /dev/null +++ b/ts2phc_ptp_pps_source.c @@ -0,0 +1,199 @@ +/** + * @file ts2phc_ptp_pps_source.c + * @note Copyright (C) 2023 Maciek Machnikowski <ma...@ma...> + * @note SPDX-License-Identifier: GPL-2.0+ + */ +#include <pthread.h> +#include <stdlib.h> +#include <time.h> + +#include "missing.h" +#include "pmc_agent.h" +#include "print.h" +#include "ts2phc_pps_source_private.h" +#include "ts2phc_ptp_pps_source.h" +#include "util.h" + +#define TIMESTAMP_SEC(ts) (((uint64_t)ts.seconds_lsb) | (((uint64_t)ts.seconds_msb) << 32)) +#define MAX_PTP_AGE 5000000000ULL + +struct ts2phc_ptp_pps_source { + struct ts2phc_pps_source pps_source; + struct ts2phc_private *priv; + struct pmc_agent *agent; + + pthread_t worker; + /* Protects anonymous struct fields, below, from concurrent access. */ + pthread_mutex_t mutex; + struct { + struct timespec local_monotime; + struct timespec ptp_time; + bool ptp_time_valid; + }; +}; + +static void ts2phc_ptp_pps_source_destroy(struct ts2phc_pps_source *src) +{ + struct ts2phc_ptp_pps_source *s = + container_of(src, struct ts2phc_ptp_pps_source, pps_source); + + pthread_join(s->worker, NULL); + pthread_mutex_destroy(&s->mutex); + + pmc_agent_destroy(s->agent); + + free(s); +} + +static int ts2phc_ptp_pps_source_getppstime(struct ts2phc_pps_source *src, + struct timespec *ts) +{ + struct ts2phc_ptp_pps_source *s = + container_of(src, struct ts2phc_ptp_pps_source, pps_source); + tmv_t duration_since_ptp, local_t1, local_t2, sync_time; + struct timespec now; + bool data_valid; + + clock_gettime(CLOCK_MONOTONIC, &now); + local_t2 = timespec_to_tmv(now); + + pthread_mutex_lock(&s->mutex); + + local_t1 = timespec_to_tmv(s->local_monotime); + sync_time = timespec_to_tmv(s->ptp_time); + data_valid = s->ptp_time_valid; + + pthread_mutex_unlock(&s->mutex); + + if (!data_valid) { + pr_debug("ptp: no valid PTP timestamp received"); + return -1; + } + + duration_since_ptp = tmv_sub(local_t2, local_t1); + if (tmv_to_nanoseconds(duration_since_ptp) > MAX_PTP_AGE) { + /* Invalidate the timestamp */ + pthread_mutex_lock(&s->mutex); + s->ptp_time_valid = false; + pthread_mutex_unlock(&s->mutex); + + pr_err("ptp: time stamp stale"); + return -1; + } + sync_time = tmv_add(sync_time, duration_since_ptp); + + *ts = tmv_to_timespec(sync_time); + + return 0; +} + +static void *monitor_ptp_signaling_msg(void *arg) +{ + struct ts2phc_ptp_pps_source *s = arg; + + while (is_running()) { + pmc_agent_update(s->agent); + usleep(10000); + } + + return NULL; +} + +static int ts2phc_recv_signaling_subscribed(void *context, + struct ptp_message *msg, + int excluded) +{ + struct slave_rx_sync_timing_record *sync_record; + struct slave_rx_sync_timing_data_tlv *srstd; + struct ts2phc_ptp_pps_source *s = context; + struct tlv_extra *extra; + struct timespec rxtime; + int i, cnt; + + TAILQ_FOREACH(extra, &msg->tlv_list, list) { + if (extra->tlv->type != TLV_SLAVE_RX_SYNC_TIMING_DATA) { + continue; + } + + /* Read the time from the Sync/FollowUp message */ + srstd = (struct slave_rx_sync_timing_data_tlv *)extra->tlv; + cnt = (srstd->length - sizeof(srstd->sourcePortIdentity)) / + sizeof(*sync_record); + sync_record = srstd->record; + + for (i = 0; i < cnt; i++) { + clock_gettime(CLOCK_MONOTONIC, &rxtime); + + pthread_mutex_lock(&s->mutex); + + s->local_monotime = rxtime; + s->ptp_time.tv_sec = TIMESTAMP_SEC(sync_record->syncOriginTimestamp); + s->ptp_time.tv_nsec = sync_record->syncOriginTimestamp.nanoseconds; + s->ptp_time_valid = true; + + pthread_mutex_unlock(&s->mutex); + + pr_debug("%ld.%ld Seq: %d\n", s->ptp_time.tv_sec, + s->ptp_time.tv_nsec, sync_record->sequenceId); + + sync_record++; + } + } + + return 1; +} + +struct ts2phc_pps_source *ts2phc_ptp_pps_source_create(struct ts2phc_private *priv, + const char *dev) +{ + char uds_local[MAX_IFNAME_SIZE + 1]; + struct ts2phc_ptp_pps_source *s; + const char *path; + int err; + + s = calloc(1, sizeof(*s)); + if (!s) { + return NULL; + } + path = config_get_string(priv->cfg, NULL, "slave_event_monitor"); + if (!path) { + snprintf(uds_local, sizeof(uds_local), "/var/run/ts2phc-ptpmon"); + } else { + snprintf(uds_local, sizeof(uds_local), path); + } + + s->agent = pmc_agent_create(); + if (!s->agent) { + pr_err("failed to start pmc agent"); + goto err_agent; + } + + err = init_pmc_node(priv->cfg, s->agent, uds_local, + ts2phc_recv_signaling_subscribed, s); + if (err) { + pr_err("failed to create PMC agent: %s", strerror(err)); + goto err_pmc_init; + } + pmc_agent_enable_signaling_cb(s->agent, true); + + s->pps_source.destroy = ts2phc_ptp_pps_source_destroy; + s->pps_source.getppstime = ts2phc_ptp_pps_source_getppstime; + s->priv = priv; + + pthread_mutex_init(&s->mutex, NULL); + err = pthread_create(&s->worker, NULL, monitor_ptp_signaling_msg, s); + if (err) { + pr_err("failed to create worker thread: %s", strerror(err)); + goto err_thread; + } + + return &s->pps_source; + +err_thread: + pthread_mutex_destroy(&s->mutex); +err_pmc_init: + pmc_agent_destroy(s->agent); +err_agent: + free(s); + return NULL; +} diff --git a/ts2phc_ptp_pps_source.h b/ts2phc_ptp_pps_source.h new file mode 100644 index 0000000..45bf7ae --- /dev/null +++ b/ts2phc_ptp_pps_source.h @@ -0,0 +1,15 @@ +/** + * @file ts2phc_ptp_pps_source.h + * @note Copyright (C) 2023 Maciek Machnikowski <ma...@ma...> + * @note SPDX-License-Identifier: GPL-2.0+ + */ +#ifndef HAVE_TS2PHC_PTP_PPS_SOURCE_H +#define HAVE_TS2PHC_PTP_PPS_SOURCE_H + +#include "ts2phc.h" +#include "ts2phc_pps_source.h" + +struct ts2phc_pps_source *ts2phc_ptp_pps_source_create(struct ts2phc_private *priv, + const char *dev); + +#endif -- 2.30.2 |
From: Maciek M. <ma...@ma...> - 2023-04-20 17:02:06
|
Add option to run callback that when the PMC agent receives signaling messages. Signed-off-by: Maciek Machnikowski <ma...@ma...> --- pmc_agent.c | 21 +++++++++++++++++++-- pmc_agent.h | 7 +++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/pmc_agent.c b/pmc_agent.c index 62d1a86..ef4e1bb 100644 --- a/pmc_agent.c +++ b/pmc_agent.c @@ -42,6 +42,7 @@ struct pmc_agent { bool dds_valid; int leap; int pmc_ds_requested; + bool signaling_cb_ena; bool stay_subscribed; int sync_offset; int utc_offset_traceable; @@ -127,6 +128,7 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, #define N_FD 1 struct pollfd pollfd[N_FD]; int cnt, res; + bool skip_cb; while (1) { pollfd[0].fd = pmc_get_transport_fd(node->pmc); @@ -178,9 +180,18 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, node->pmc_ds_requested = 0; return RUN_PMC_NODEV; } - if (res <= 0 || + + /* Skip callback if message is not management */ + skip_cb = (res <= 0) ? true : false; + + /* Run the callback on signaling messages if configured */ + if (node->signaling_cb_ena && (msg_type(*msg) == SIGNALING)) { + skip_cb = false; + } + + if (skip_cb || node->recv_subscribed(node->recv_context, *msg, ds_id) || - management_tlv_id(*msg) != ds_id) { + (res == 1 && management_tlv_id(*msg) != ds_id)) { msg_put(*msg); *msg = NULL; continue; @@ -430,3 +441,9 @@ bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent) { return agent->utc_offset_traceable; } + +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable) +{ + agent->signaling_cb_ena = enable; +} + diff --git a/pmc_agent.h b/pmc_agent.h index 2fb1cc8..9ae37f7 100644 --- a/pmc_agent.h +++ b/pmc_agent.h @@ -170,4 +170,11 @@ int pmc_agent_update(struct pmc_agent *agent); */ bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent); +/** + * Enables or disables callback on signaling messages + * @param agent Pointer to a PMC instance obtained via @ref pmc_agent_create(). + * @param enable - if set to true, callback will be called on signaling msgs + */ +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable); + #endif -- 2.30.2 |
From: Maciek M. <ma...@ma...> - 2023-04-20 17:02:13
|
Add PPS ToD source that extracts ToD from master timestamps from a slave event monitor of the running ptp4l instance. It enables scenarios where reliable ToD sources, such as nmea, may not be available, but a system is connected to different ptp masters. It can be used in the APTS deployment with the PPS from the local GNSS receiver that doesn't supply the ToD information, but PTP service is enabled in the backhaul. To use the servo ts2phc -s ptp -c eth0 -m --slave_event_monitor /var/run/ts2phc-mon And run the ptp4l with slave event monitor enabled ptp4l -i eth0 -m --free_running 1 --slave_event_monitor /var/run/ts2phc-mon Note: Do not enable ptp4l control of the clock on the same port. v2: changed pmc_agent implementation to not use a separate callback v3: updated poll thread to use less CPU v4: changed sleep routine to usleep, simplified poll thread v5: make sure management_tlv_id is not called on non-management messages Signed-off-by: Maciek Machnikowski <ma...@ma...> --- makefile | 3 +- ts2phc.8 | 6 ++ ts2phc.c | 2 + ts2phc_pps_source.c | 4 + ts2phc_pps_source.h | 1 + ts2phc_ptp_pps_source.c | 199 ++++++++++++++++++++++++++++++++++++++++ ts2phc_ptp_pps_source.h | 15 +++ 7 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 ts2phc_ptp_pps_source.c create mode 100644 ts2phc_ptp_pps_source.h diff --git a/makefile b/makefile index 3e3b8b3..e15c22d 100644 --- a/makefile +++ b/makefile @@ -27,7 +27,8 @@ FILTERS = filter.o mave.o mmedian.o SERVOS = linreg.o ntpshm.o nullf.o pi.o refclock_sock.o servo.o TRANSP = raw.o transport.o udp.o udp6.o uds.o TS2PHC = ts2phc.o lstab.o nmea.o serial.o sock.o ts2phc_generic_pps_source.o \ - ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o ts2phc_pps_source.o + ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o ts2phc_pps_source.o \ + ts2phc_ptp_pps_source.o OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o designated_fsm.o \ e2e_tc.o fault.o $(FILTERS) fsm.o hash.o interface.o monitor.o msg.o phc.o \ port.o port_signaling.o pqueue.o print.o ptp4l.o p2p_tc.o rtnl.o $(SERVOS) \ diff --git a/ts2phc.8 b/ts2phc.8 index 3c71d47..f3abb32 100644 --- a/ts2phc.8 +++ b/ts2phc.8 @@ -72,6 +72,8 @@ device (like /dev/ptp0) or its associated network interface (like eth0). Use the key word "nmea" for an external 1-PPS from a GPS providing ToD information via the RMC NMEA sentence. +Use the key word "ptp" for the ToD source from the running ptp4l instance pushing +timestamps through the slave event monitor. .TP .B \-v Prints the software version and exits. @@ -259,6 +261,10 @@ Some PHC devices feature programmable pins, and this option allows configuration of a particular pin for the external time stamping or periodic output function. The default is pin index 0. +.TP +.B slave_event_monitor +Used with ptp ToD source, specifies the UNIX domain socket to read +SLAVE_RX_SYNC_TIMING_DATA from. .SH WARNING diff --git a/ts2phc.c b/ts2phc.c index 4393059..b998c7d 100644 --- a/ts2phc.c +++ b/ts2phc.c @@ -733,6 +733,8 @@ int main(int argc, char *argv[]) pps_type = TS2PHC_PPS_SOURCE_GENERIC; } else if (!strcasecmp(tod_source, "nmea")) { pps_type = TS2PHC_PPS_SOURCE_NMEA; + } else if (!strcasecmp(tod_source, "ptp")) { + pps_type = TS2PHC_PPS_SOURCE_PTP; } else { pps_type = TS2PHC_PPS_SOURCE_PHC; } diff --git a/ts2phc_pps_source.c b/ts2phc_pps_source.c index c333f65..87b0ffa 100644 --- a/ts2phc_pps_source.c +++ b/ts2phc_pps_source.c @@ -8,6 +8,7 @@ #include "ts2phc_nmea_pps_source.h" #include "ts2phc_phc_pps_source.h" #include "ts2phc_pps_source_private.h" +#include "ts2phc_ptp_pps_source.h" struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv, const char *dev, @@ -25,6 +26,9 @@ struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv, case TS2PHC_PPS_SOURCE_PHC: src = ts2phc_phc_pps_source_create(priv, dev); break; + case TS2PHC_PPS_SOURCE_PTP: + src = ts2phc_ptp_pps_source_create(priv, dev); + break; } return src; } diff --git a/ts2phc_pps_source.h b/ts2phc_pps_source.h index 293c693..3a66219 100644 --- a/ts2phc_pps_source.h +++ b/ts2phc_pps_source.h @@ -23,6 +23,7 @@ enum ts2phc_pps_source_type { TS2PHC_PPS_SOURCE_GENERIC, TS2PHC_PPS_SOURCE_NMEA, TS2PHC_PPS_SOURCE_PHC, + TS2PHC_PPS_SOURCE_PTP, }; /** diff --git a/ts2phc_ptp_pps_source.c b/ts2phc_ptp_pps_source.c new file mode 100644 index 0000000..5e160d0 --- /dev/null +++ b/ts2phc_ptp_pps_source.c @@ -0,0 +1,199 @@ +/** + * @file ts2phc_ptp_pps_source.c + * @note Copyright (C) 2023 Maciek Machnikowski <ma...@ma...> + * @note SPDX-License-Identifier: GPL-2.0+ + */ +#include <pthread.h> +#include <stdlib.h> +#include <time.h> + +#include "missing.h" +#include "pmc_agent.h" +#include "print.h" +#include "ts2phc_pps_source_private.h" +#include "ts2phc_ptp_pps_source.h" +#include "util.h" + +#define TIMESTAMP_SEC(ts) (((uint64_t)ts.seconds_lsb) | (((uint64_t)ts.seconds_msb) << 32)) +#define MAX_PTP_AGE 5000000000ULL + +struct ts2phc_ptp_pps_source { + struct ts2phc_pps_source pps_source; + struct ts2phc_private *priv; + struct pmc_agent *agent; + + pthread_t worker; + /* Protects anonymous struct fields, below, from concurrent access. */ + pthread_mutex_t mutex; + struct { + struct timespec local_monotime; + struct timespec ptp_time; + bool ptp_time_valid; + }; +}; + +static void ts2phc_ptp_pps_source_destroy(struct ts2phc_pps_source *src) +{ + struct ts2phc_ptp_pps_source *s = + container_of(src, struct ts2phc_ptp_pps_source, pps_source); + + pthread_join(s->worker, NULL); + pthread_mutex_destroy(&s->mutex); + + pmc_agent_destroy(s->agent); + + free(s); +} + +static int ts2phc_ptp_pps_source_getppstime(struct ts2phc_pps_source *src, + struct timespec *ts) +{ + struct ts2phc_ptp_pps_source *s = + container_of(src, struct ts2phc_ptp_pps_source, pps_source); + tmv_t duration_since_ptp, local_t1, local_t2, sync_time; + struct timespec now; + bool data_valid; + + clock_gettime(CLOCK_MONOTONIC, &now); + local_t2 = timespec_to_tmv(now); + + pthread_mutex_lock(&s->mutex); + + local_t1 = timespec_to_tmv(s->local_monotime); + sync_time = timespec_to_tmv(s->ptp_time); + data_valid = s->ptp_time_valid; + + pthread_mutex_unlock(&s->mutex); + + if (!data_valid) { + pr_debug("ptp: no valid PTP timestamp received"); + return -1; + } + + duration_since_ptp = tmv_sub(local_t2, local_t1); + if (tmv_to_nanoseconds(duration_since_ptp) > MAX_PTP_AGE) { + /* Invalidate the timestamp */ + pthread_mutex_lock(&s->mutex); + s->ptp_time_valid = false; + pthread_mutex_unlock(&s->mutex); + + pr_err("ptp: time stamp stale"); + return -1; + } + sync_time = tmv_add(sync_time, duration_since_ptp); + + *ts = tmv_to_timespec(sync_time); + + return 0; +} + +static void *monitor_ptp_signaling_msg(void *arg) +{ + struct ts2phc_ptp_pps_source *s = arg; + + while (is_running()) { + pmc_agent_update(s->agent); + usleep(10000); + } + + return NULL; +} + +static int ts2phc_recv_signaling_subscribed(void *context, + struct ptp_message *msg, + int excluded) +{ + struct slave_rx_sync_timing_record *sync_record; + struct slave_rx_sync_timing_data_tlv *srstd; + struct ts2phc_ptp_pps_source *s = context; + struct tlv_extra *extra; + struct timespec rxtime; + int i, cnt; + + TAILQ_FOREACH(extra, &msg->tlv_list, list) { + if (extra->tlv->type != TLV_SLAVE_RX_SYNC_TIMING_DATA) { + continue; + } + + /* Read the time from the Sync/FollowUp message */ + srstd = (struct slave_rx_sync_timing_data_tlv *)extra->tlv; + cnt = (srstd->length - sizeof(srstd->sourcePortIdentity)) / + sizeof(*sync_record); + sync_record = srstd->record; + + for (i = 0; i < cnt; i++) { + clock_gettime(CLOCK_MONOTONIC, &rxtime); + + pthread_mutex_lock(&s->mutex); + + s->local_monotime = rxtime; + s->ptp_time.tv_sec = TIMESTAMP_SEC(sync_record->syncOriginTimestamp); + s->ptp_time.tv_nsec = sync_record->syncOriginTimestamp.nanoseconds; + s->ptp_time_valid = true; + + pthread_mutex_unlock(&s->mutex); + + pr_debug("%ld.%ld Seq: %d\n", s->ptp_time.tv_sec, + s->ptp_time.tv_nsec, sync_record->sequenceId); + + sync_record++; + } + } + + return 1; +} + +struct ts2phc_pps_source *ts2phc_ptp_pps_source_create(struct ts2phc_private *priv, + const char *dev) +{ + char uds_local[MAX_IFNAME_SIZE + 1]; + struct ts2phc_ptp_pps_source *s; + const char *path; + int err; + + s = calloc(1, sizeof(*s)); + if (!s) { + return NULL; + } + path = config_get_string(priv->cfg, NULL, "slave_event_monitor"); + if (!path) { + snprintf(uds_local, sizeof(uds_local), "/var/run/ts2phc-ptpmon"); + } else { + snprintf(uds_local, sizeof(uds_local), path); + } + + s->agent = pmc_agent_create(); + if (!s->agent) { + pr_err("failed to start pmc agent"); + goto err_agent; + } + + err = init_pmc_node(priv->cfg, s->agent, uds_local, + ts2phc_recv_signaling_subscribed, s); + if (err) { + pr_err("failed to create PMC agent: %s", strerror(err)); + goto err_pmc_init; + } + pmc_agent_enable_signaling_cb(s->agent, true); + + s->pps_source.destroy = ts2phc_ptp_pps_source_destroy; + s->pps_source.getppstime = ts2phc_ptp_pps_source_getppstime; + s->priv = priv; + + pthread_mutex_init(&s->mutex, NULL); + err = pthread_create(&s->worker, NULL, monitor_ptp_signaling_msg, s); + if (err) { + pr_err("failed to create worker thread: %s", strerror(err)); + goto err_thread; + } + + return &s->pps_source; + +err_thread: + pthread_mutex_destroy(&s->mutex); +err_pmc_init: + pmc_agent_destroy(s->agent); +err_agent: + free(s); + return NULL; +} diff --git a/ts2phc_ptp_pps_source.h b/ts2phc_ptp_pps_source.h new file mode 100644 index 0000000..45bf7ae --- /dev/null +++ b/ts2phc_ptp_pps_source.h @@ -0,0 +1,15 @@ +/** + * @file ts2phc_ptp_pps_source.h + * @note Copyright (C) 2023 Maciek Machnikowski <ma...@ma...> + * @note SPDX-License-Identifier: GPL-2.0+ + */ +#ifndef HAVE_TS2PHC_PTP_PPS_SOURCE_H +#define HAVE_TS2PHC_PTP_PPS_SOURCE_H + +#include "ts2phc.h" +#include "ts2phc_pps_source.h" + +struct ts2phc_pps_source *ts2phc_ptp_pps_source_create(struct ts2phc_private *priv, + const char *dev); + +#endif -- 2.30.2 |
From: Maciek M. <ma...@ma...> - 2023-04-26 09:56:10
|
On 4/21/2023 6:46 PM, Erez wrote: > > > On Fri, 21 Apr 2023 at 17:27, Maciek Machnikowski > <ma...@ma... <mailto:ma...@ma...>> wrote: > > On 4/21/2023 1:25 PM, Erez wrote: > > > > > > On Thu, 20 Apr 2023 at 19:01, Maciek Machnikowski > > <ma...@ma... <mailto:ma...@ma...> > <mailto:ma...@ma... <mailto:ma...@ma...>>> > wrote: > > > > Add option to run callback that when the PMC agent receives > > signaling messages. > > > > Signed-off-by: Maciek Machnikowski <ma...@ma... > <mailto:ma...@ma...> > > <mailto:ma...@ma... <mailto:ma...@ma...>>> > > --- > > pmc_agent.c | 21 +++++++++++++++++++-- > > pmc_agent.h | 7 +++++++ > > 2 files changed, 26 insertions(+), 2 deletions(-) > > > > diff --git a/pmc_agent.c b/pmc_agent.c > > index 62d1a86..ef4e1bb 100644 > > --- a/pmc_agent.c > > +++ b/pmc_agent.c > > @@ -42,6 +42,7 @@ struct pmc_agent { > > bool dds_valid; > > int leap; > > int pmc_ds_requested; > > + bool signaling_cb_ena; > > bool stay_subscribed; > > int sync_offset; > > int utc_offset_traceable; > > @@ -127,6 +128,7 @@ static int run_pmc(struct pmc_agent *node, int > > timeout, int ds_id, > > #define N_FD 1 > > struct pollfd pollfd[N_FD]; > > int cnt, res; > > + bool skip_cb; > > > > while (1) { > > pollfd[0].fd = pmc_get_transport_fd(node->pmc); > > @@ -178,9 +180,18 @@ static int run_pmc(struct pmc_agent > *node, int > > timeout, int ds_id, > > node->pmc_ds_requested = 0; > > return RUN_PMC_NODEV; > > } > > - if (res <= 0 || > > + > > + /* Skip callback if message is not management */ > > + skip_cb = (res <= 0) ? true : false; > > + > > + /* Run the callback on signaling messages if > > configured */ > > + if (node->signaling_cb_ena && (msg_type(*msg) == > > SIGNALING)) { > > > > > > I would add res == 0, to save time here. > > > > You can have > > if (res < 0) { > > skip_cb = false; > > } else if(res == 0) { > > if (node->signaling_cb_ena && (msg_type(*msg) == > > SIGNALING)) { > > skip_cb = true; > > } else { > > skip_cb = false; > > } > > } else { > > skip_cb = true; > > } > > > > This code saves you from checking 'node->signaling_cb_ena' if res > is not 0. > > 'res' is a local variable, 'node->signaling_cb_ena' is referred > through> a pointer, so its fetch time is much bigger. > > This is not true. It compiles to a single ASM line even without any > optimizations enabled: > > > This looks like arm64. There are other CPUs beside ARM. > And in assembler it is hard to "see" how long it takes to load > 'signaling_cb_ena' from node unless it is in the memory cache. It's similar on x86. /* Run the callback on signaling messages if configured */ if (res == 0 && node->signaling_cb_ena && 475: 83 7d f4 00 cmpl $0x0,-0xc(%rbp) 479: 75 24 jne 49f <run_pmc+0x1e0> >47b: 48 8b 45 d8 mov -0x28(%rbp),%rax >47f: 0f b6 40 30 movzbl 0x30(%rax),%eax >483: 84 c0 test %al,%al 485: 74 18 je 49f <run_pmc+0x1e0> msg_type(*msg) == SIGNALING) { 487: 48 8b 45 c8 mov -0x38(%rbp),%rax 48b: 48 8b 00 mov (%rax),%rax 48e: 48 89 c7 mov %rax,%rdi 491: e8 39 fc ff ff callq cf <msg_type> Do you know any architecture that's different than this? > > > /* Skip callback if message is not management */ > skip_cb = (res <= 0) ? true : false; > 54c: b94047e0 ldr w0, [sp, #68] > 550: 7100001f cmp w0, #0x0 > 554: 1a9fc7e0 cset w0, le > 558: 39013fe0 strb w0, [sp, #79] > /* Run the callback on signaling messages if configured */ > if (node->signaling_cb_ena && (msg_type(*msg) == > SIGNALING)) { > 55c: f94017e0 ldr x0, [sp, #40] > >560: 3940c000 ldrb w0, [x0, #48] > 564: 7100001f cmp w0, #0x0 > 568: 540000e0 b.eq 584 <run_pmc+0x210> // b.none > 56c: f9400fe0 ldr x0, [sp, #24] > 570: f9400000 ldr x0, [x0] > 574: 97fffeea bl 11c <msg_type> > 578: 7100301f cmp w0, #0xc > 57c: 54000041 b.ne <http://b.ne> 584 <run_pmc+0x210> > // b.any > skip_cb = false; > 580: 39013fff strb wzr, [sp, #79] > } > > > Adding a check for res adds 3 more ASM instructions: > > > But save loading `signaling_cb_ena` if you mainly use management messages! > > > > /* Run the callback on signaling messages if > configured */ > if (res == 0 && node->signaling_cb_ena && > >55c: b94047e0 ldr w0, [sp, #68] > >560: 7100001f cmp w0, #0x0 > >564: 54000161 b.ne <http://b.ne> 590 <run_pmc+0x21c> > // b.any > 568: f94017e0 ldr x0, [sp, #40] > 56c: 3940c000 ldrb w0, [x0, #48] > 570: 7100001f cmp w0, #0x0 > 574: 540000e0 b.eq 590 <run_pmc+0x21c> // b.none > msg_type(*msg) == SIGNALING) { > 578: f9400fe0 ldr x0, [sp, #24] > 57c: f9400000 ldr x0, [x0] > 580: 97fffee7 bl 11c <msg_type> > if (res == 0 && node->signaling_cb_ena && > 584: 7100301f cmp w0, #0xc > 588: 54000041 b.ne <http://b.ne> 590 <run_pmc+0x21c> > // b.any > skip_cb = false; > 58c: 39013fff strb wzr, [sp, #79] > } > > Thanks, > Maciek > > > Again you can simply reuse `res` and have a small code and also faster code. > I do not understand why you are afraid of using reuse `res`. > > I only point on optimization. I assume that most cases are > management messages, and testing the signaling flag for all packets does > not help. > > If you want a better solution, you replace in run_pmc() : > > type = msg_type(msg); > switch (type) { > case SIGNALING: > res = node->signaling_cb_ena ? 2 : 0; > break; > case MANAGEMENT: > res = is_msg_mgt(msg); > break; > default: > break > } > > And remove the 'msg_type(msg) != MANAGEMENT' from is_msg_mgt(). Optimizing run_pmc is beyond the scope of my patch. I'll add a check for res==0 before checking node->signaling_cb_ena, but I hardly believe it'll have any performance benefit. If you run the run_pmc infrequently, this check will not matter, as it takes only a couple of ns to read from memory. You'll spend much more time on function calls preceding this check. If you call run_pmc constantly, then this location will most likely be cached, as it holds a static value, and it will still not negatively impact the performance. To see any difference you'd need to hit a ton of messages/second - is there any use case that requires it? Thanks Maciek > > Assembler rarely helps to understand speed and size of code. > Especially code that runs on multiple CPUs as free software. > > > > > > Erez > > > > > > This code is bigger, but you do not want to reuse the 'res' variable. > > > > > > > > + skip_cb = false; > > + } > > + > > + if (skip_cb || > > node->recv_subscribed(node->recv_context, > *msg, > > ds_id) || > > - management_tlv_id(*msg) != ds_id) { > > + (res == 1 && management_tlv_id(*msg) != > ds_id)) { > > > > > > This part is OK. > > > > Erez > > > > > > > > msg_put(*msg); > > *msg = NULL; > > continue; > > @@ -430,3 +441,9 @@ bool pmc_agent_utc_offset_traceable(struct > > pmc_agent *agent) > > { > > return agent->utc_offset_traceable; > > } > > + > > +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool > > enable) > > +{ > > + agent->signaling_cb_ena = enable; > > +} > > + > > diff --git a/pmc_agent.h b/pmc_agent.h > > index 2fb1cc8..9ae37f7 100644 > > --- a/pmc_agent.h > > +++ b/pmc_agent.h > > @@ -170,4 +170,11 @@ int pmc_agent_update(struct pmc_agent > *agent); > > */ > > bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent); > > > > +/** > > + * Enables or disables callback on signaling messages > > + * @param agent Pointer to a PMC instance obtained via @ref > > pmc_agent_create(). > > + * @param enable - if set to true, callback will be called on > > signaling msgs > > + */ > > +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool > > enable); > > + > > #endif > > -- > > 2.30.2 > > > |
From: Erez <ere...@gm...> - 2023-04-26 11:36:22
|
On Wed, 26 Apr 2023 at 11:55, Maciek Machnikowski <ma...@ma...> wrote: > On 4/21/2023 6:46 PM, Erez wrote: > > > > > > On Fri, 21 Apr 2023 at 17:27, Maciek Machnikowski > > <ma...@ma... <mailto:ma...@ma...>> wrote: > > > > On 4/21/2023 1:25 PM, Erez wrote: > > > > > > > > > On Thu, 20 Apr 2023 at 19:01, Maciek Machnikowski > > > <ma...@ma... <mailto:ma...@ma...> > > <mailto:ma...@ma... <mailto:ma...@ma...>>> > > wrote: > > > > > > Add option to run callback that when the PMC agent receives > > > signaling messages. > > > > > > Signed-off-by: Maciek Machnikowski <ma...@ma... > > <mailto:ma...@ma...> > > > <mailto:ma...@ma... <mailto: > ma...@ma...>>> > > > --- > > > pmc_agent.c | 21 +++++++++++++++++++-- > > > pmc_agent.h | 7 +++++++ > > > 2 files changed, 26 insertions(+), 2 deletions(-) > > > > > > diff --git a/pmc_agent.c b/pmc_agent.c > > > index 62d1a86..ef4e1bb 100644 > > > --- a/pmc_agent.c > > > +++ b/pmc_agent.c > > > @@ -42,6 +42,7 @@ struct pmc_agent { > > > bool dds_valid; > > > int leap; > > > int pmc_ds_requested; > > > + bool signaling_cb_ena; > > > bool stay_subscribed; > > > int sync_offset; > > > int utc_offset_traceable; > > > @@ -127,6 +128,7 @@ static int run_pmc(struct pmc_agent *node, > int > > > timeout, int ds_id, > > > #define N_FD 1 > > > struct pollfd pollfd[N_FD]; > > > int cnt, res; > > > + bool skip_cb; > > > > > > while (1) { > > > pollfd[0].fd = pmc_get_transport_fd(node->pmc); > > > @@ -178,9 +180,18 @@ static int run_pmc(struct pmc_agent > > *node, int > > > timeout, int ds_id, > > > node->pmc_ds_requested = 0; > > > return RUN_PMC_NODEV; > > > } > > > - if (res <= 0 || > > > + > > > + /* Skip callback if message is not management > */ > > > + skip_cb = (res <= 0) ? true : false; > > > + > > > + /* Run the callback on signaling messages if > > > configured */ > > > + if (node->signaling_cb_ena && (msg_type(*msg) > == > > > SIGNALING)) { > > > > > > > > > I would add res == 0, to save time here. > > > > > > You can have > > > if (res < 0) { > > > skip_cb = false; > > > } else if(res == 0) { > > > if (node->signaling_cb_ena && (msg_type(*msg) == > > > SIGNALING)) { > > > skip_cb = true; > > > } else { > > > skip_cb = false; > > > } > > > } else { > > > skip_cb = true; > > > } > > > > > > This code saves you from checking 'node->signaling_cb_ena' if res > > is not 0. > > > 'res' is a local variable, 'node->signaling_cb_ena' is referred > > through> a pointer, so its fetch time is much bigger. > > > > This is not true. It compiles to a single ASM line even without any > > optimizations enabled: > > > > > > This looks like arm64. There are other CPUs beside ARM. > > And in assembler it is hard to "see" how long it takes to load > > 'signaling_cb_ena' from node unless it is in the memory cache. > > It's similar on x86. > /* Run the callback on signaling messages if configured */ > if (res == 0 && node->signaling_cb_ena && > 475: 83 7d f4 00 cmpl $0x0,-0xc(%rbp) > 479: 75 24 jne 49f <run_pmc+0x1e0> > >47b: 48 8b 45 d8 mov -0x28(%rbp),%rax > >47f: 0f b6 40 30 movzbl 0x30(%rax),%eax > >483: 84 c0 test %al,%al > 485: 74 18 je 49f <run_pmc+0x1e0> > msg_type(*msg) == SIGNALING) { > 487: 48 8b 45 c8 mov -0x38(%rbp),%rax > 48b: 48 8b 00 mov (%rax),%rax > 48e: 48 89 c7 mov %rax,%rdi > 491: e8 39 fc ff ff callq cf <msg_type> > > Do you know any architecture that's different than this? > There are about 12 different architectures, not to mention 32 and 64 bit and old 8 and 16 bits. But this group is not about machine code and assembler. I suggest we leave it out. This argument does not fly anywhere. > > > > > > > /* Skip callback if message is not management */ > > skip_cb = (res <= 0) ? true : false; > > 54c: b94047e0 ldr w0, [sp, #68] > > 550: 7100001f cmp w0, #0x0 > > 554: 1a9fc7e0 cset w0, le > > 558: 39013fe0 strb w0, [sp, #79] > > /* Run the callback on signaling messages if configured */ > > if (node->signaling_cb_ena && (msg_type(*msg) == > > SIGNALING)) { > > 55c: f94017e0 ldr x0, [sp, #40] > > >560: 3940c000 ldrb w0, [x0, #48] > > 564: 7100001f cmp w0, #0x0 > > 568: 540000e0 b.eq 584 <run_pmc+0x210> // b.none > > 56c: f9400fe0 ldr x0, [sp, #24] > > 570: f9400000 ldr x0, [x0] > > 574: 97fffeea bl 11c <msg_type> > > 578: 7100301f cmp w0, #0xc > > 57c: 54000041 b.ne <http://b.ne> 584 <run_pmc+0x210> > > // b.any > > skip_cb = false; > > 580: 39013fff strb wzr, [sp, #79] > > } > > > > > > Adding a check for res adds 3 more ASM instructions: > > > > > > But save loading `signaling_cb_ena` if you mainly use > management messages! > > > > > > > > /* Run the callback on signaling messages if > > configured */ > > if (res == 0 && node->signaling_cb_ena && > > >55c: b94047e0 ldr w0, [sp, #68] > > >560: 7100001f cmp w0, #0x0 > > >564: 54000161 b.ne <http://b.ne> 590 <run_pmc+0x21c> > > // b.any > > 568: f94017e0 ldr x0, [sp, #40] > > 56c: 3940c000 ldrb w0, [x0, #48] > > 570: 7100001f cmp w0, #0x0 > > 574: 540000e0 b.eq 590 <run_pmc+0x21c> // b.none > > msg_type(*msg) == SIGNALING) { > > 578: f9400fe0 ldr x0, [sp, #24] > > 57c: f9400000 ldr x0, [x0] > > 580: 97fffee7 bl 11c <msg_type> > > if (res == 0 && node->signaling_cb_ena && > > 584: 7100301f cmp w0, #0xc > > 588: 54000041 b.ne <http://b.ne> 590 <run_pmc+0x21c> > > // b.any > > skip_cb = false; > > 58c: 39013fff strb wzr, [sp, #79] > > } > > > > Thanks, > > Maciek > > > > > > Again you can simply reuse `res` and have a small code and also faster > code. > > I do not understand why you are afraid of using reuse `res`. > > > > I only point on optimization. I assume that most cases are > > management messages, and testing the signaling flag for all packets does > > not help. > > > > If you want a better solution, you replace in run_pmc() : > > > > type = msg_type(msg); > > switch (type) { > > case SIGNALING: > > res = node->signaling_cb_ena ? 2 : 0; > > break; > > case MANAGEMENT: > > res = is_msg_mgt(msg); > > break; > > default: > > break > > } > > > > And remove the 'msg_type(msg) != MANAGEMENT' from is_msg_mgt(). > > Optimizing run_pmc is beyond the scope of my patch. I'll add a check for > res==0 before checking node->signaling_cb_ena, but I hardly believe > it'll have any performance benefit. > It is your call. As you change the code, you can also optimize your change. Nothing is holy with the current implementation. I understand you want to do "minimum". But the current code is also optimized code. > > If you run the run_pmc infrequently, this check will not matter, as it > takes only a couple of ns to read from memory. You'll spend much more > time on function calls preceding this check. > Yes, this code does not run much. This is a more basic question. Do optimize only a hard real time code. And leave all the rest, heavy and badly optimized? Again, I understand why you think minimal changes are more pretty. It is your patch, you and Richard decide. I only share my opinion. > > If you call run_pmc constantly, then this location will most likely be > cached, as it holds a static value, and it will still not negatively > impact the performance. To see any difference you'd need to hit a ton of > messages/second - is there any use case that requires it? > Again, not all architectures are using catch memory. Please do not make assumptions. Same as machine code, this is not the group. And yes, this is not hard real time. I do not ask for a very complicated optimization. Just write good code, please. Erez > > Thanks > Maciek > > > > > Assembler rarely helps to understand speed and size of code. > > Especially code that runs on multiple CPUs as free software. > > > > > > > > > > > > Erez > > > > > > > > > > > This code is bigger, but you do not want to reuse the 'res' > variable. > > > > > > > > > > > > + skip_cb = false; > > > + } > > > + > > > + if (skip_cb || > > > node->recv_subscribed(node->recv_context, > > *msg, > > > ds_id) || > > > - management_tlv_id(*msg) != ds_id) { > > > + (res == 1 && management_tlv_id(*msg) != > > ds_id)) { > > > > > > > > > This part is OK. > > > > > > Erez > > > > > > > > > > > > msg_put(*msg); > > > *msg = NULL; > > > continue; > > > @@ -430,3 +441,9 @@ bool pmc_agent_utc_offset_traceable(struct > > > pmc_agent *agent) > > > { > > > return agent->utc_offset_traceable; > > > } > > > + > > > +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, > bool > > > enable) > > > +{ > > > + agent->signaling_cb_ena = enable; > > > +} > > > + > > > diff --git a/pmc_agent.h b/pmc_agent.h > > > index 2fb1cc8..9ae37f7 100644 > > > --- a/pmc_agent.h > > > +++ b/pmc_agent.h > > > @@ -170,4 +170,11 @@ int pmc_agent_update(struct pmc_agent > > *agent); > > > */ > > > bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent); > > > > > > +/** > > > + * Enables or disables callback on signaling messages > > > + * @param agent Pointer to a PMC instance obtained via @ref > > > pmc_agent_create(). > > > + * @param enable - if set to true, callback will be called on > > > signaling msgs > > > + */ > > > +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, > bool > > > enable); > > > + > > > #endif > > > -- > > > 2.30.2 > > > > > > |
From: Maciek M. <ma...@ma...> - 2023-04-26 10:03:04
|
Add option to run callback that when the PMC agent receives signaling messages. v2: changed pmc_agent implementation to not use a separate callback v3: updated poll thread to use less CPU v4: changed sleep routine to usleep, simplified poll thread v5: make sure management_tlv_id is not called on non-management messages v6: optimized code to not check signaling_cb_ena on management messages Signed-off-by: Maciek Machnikowski <ma...@ma...> --- pmc_agent.c | 22 ++++++++++++++++++++-- pmc_agent.h | 7 +++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/pmc_agent.c b/pmc_agent.c index 62d1a86..bea6b59 100644 --- a/pmc_agent.c +++ b/pmc_agent.c @@ -42,6 +42,7 @@ struct pmc_agent { bool dds_valid; int leap; int pmc_ds_requested; + bool signaling_cb_ena; bool stay_subscribed; int sync_offset; int utc_offset_traceable; @@ -127,6 +128,7 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, #define N_FD 1 struct pollfd pollfd[N_FD]; int cnt, res; + bool skip_cb; while (1) { pollfd[0].fd = pmc_get_transport_fd(node->pmc); @@ -178,9 +180,19 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, node->pmc_ds_requested = 0; return RUN_PMC_NODEV; } - if (res <= 0 || + + /* Skip callback if message is not management */ + skip_cb = (res <= 0) ? true : false; + + /* Run the callback on signaling messages if configured */ + if (res == 0 && node->signaling_cb_ena && + msg_type(*msg) == SIGNALING) { + skip_cb = false; + } + + if (skip_cb || node->recv_subscribed(node->recv_context, *msg, ds_id) || - management_tlv_id(*msg) != ds_id) { + (res == 1 && management_tlv_id(*msg) != ds_id)) { msg_put(*msg); *msg = NULL; continue; @@ -430,3 +442,9 @@ bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent) { return agent->utc_offset_traceable; } + +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable) +{ + agent->signaling_cb_ena = enable; +} + diff --git a/pmc_agent.h b/pmc_agent.h index 2fb1cc8..9ae37f7 100644 --- a/pmc_agent.h +++ b/pmc_agent.h @@ -170,4 +170,11 @@ int pmc_agent_update(struct pmc_agent *agent); */ bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent); +/** + * Enables or disables callback on signaling messages + * @param agent Pointer to a PMC instance obtained via @ref pmc_agent_create(). + * @param enable - if set to true, callback will be called on signaling msgs + */ +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable); + #endif -- 2.30.2 |
From: Maciek M. <ma...@ma...> - 2023-04-26 10:03:11
|
Add PPS ToD source that extracts ToD from master timestamps from a slave event monitor of the running ptp4l instance. It enables scenarios where reliable ToD sources, such as nmea, may not be available, but a system is connected to different ptp masters. It can be used in the APTS deployment with the PPS from the local GNSS receiver that doesn't supply the ToD information, but PTP service is enabled in the backhaul. To use the servo ts2phc -s ptp -c eth0 -m --slave_event_monitor /var/run/ts2phc-mon And run the ptp4l with slave event monitor enabled ptp4l -i eth0 -m --free_running 1 --slave_event_monitor /var/run/ts2phc-mon Note: Do not enable ptp4l control of the clock on the same port. v2: changed pmc_agent implementation to not use a separate callback v3: updated poll thread to use less CPU v4: changed sleep routine to usleep, simplified poll thread v5: make sure management_tlv_id is not called on non-management messages v6: optimized code to not check signaling_cb_ena on management messages Signed-off-by: Maciek Machnikowski <ma...@ma...> --- makefile | 3 +- ts2phc.8 | 6 ++ ts2phc.c | 2 + ts2phc_pps_source.c | 4 + ts2phc_pps_source.h | 1 + ts2phc_ptp_pps_source.c | 199 ++++++++++++++++++++++++++++++++++++++++ ts2phc_ptp_pps_source.h | 15 +++ 7 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 ts2phc_ptp_pps_source.c create mode 100644 ts2phc_ptp_pps_source.h diff --git a/makefile b/makefile index 3e3b8b3..e15c22d 100644 --- a/makefile +++ b/makefile @@ -27,7 +27,8 @@ FILTERS = filter.o mave.o mmedian.o SERVOS = linreg.o ntpshm.o nullf.o pi.o refclock_sock.o servo.o TRANSP = raw.o transport.o udp.o udp6.o uds.o TS2PHC = ts2phc.o lstab.o nmea.o serial.o sock.o ts2phc_generic_pps_source.o \ - ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o ts2phc_pps_source.o + ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o ts2phc_pps_source.o \ + ts2phc_ptp_pps_source.o OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o designated_fsm.o \ e2e_tc.o fault.o $(FILTERS) fsm.o hash.o interface.o monitor.o msg.o phc.o \ port.o port_signaling.o pqueue.o print.o ptp4l.o p2p_tc.o rtnl.o $(SERVOS) \ diff --git a/ts2phc.8 b/ts2phc.8 index 3c71d47..f3abb32 100644 --- a/ts2phc.8 +++ b/ts2phc.8 @@ -72,6 +72,8 @@ device (like /dev/ptp0) or its associated network interface (like eth0). Use the key word "nmea" for an external 1-PPS from a GPS providing ToD information via the RMC NMEA sentence. +Use the key word "ptp" for the ToD source from the running ptp4l instance pushing +timestamps through the slave event monitor. .TP .B \-v Prints the software version and exits. @@ -259,6 +261,10 @@ Some PHC devices feature programmable pins, and this option allows configuration of a particular pin for the external time stamping or periodic output function. The default is pin index 0. +.TP +.B slave_event_monitor +Used with ptp ToD source, specifies the UNIX domain socket to read +SLAVE_RX_SYNC_TIMING_DATA from. .SH WARNING diff --git a/ts2phc.c b/ts2phc.c index 4393059..b998c7d 100644 --- a/ts2phc.c +++ b/ts2phc.c @@ -733,6 +733,8 @@ int main(int argc, char *argv[]) pps_type = TS2PHC_PPS_SOURCE_GENERIC; } else if (!strcasecmp(tod_source, "nmea")) { pps_type = TS2PHC_PPS_SOURCE_NMEA; + } else if (!strcasecmp(tod_source, "ptp")) { + pps_type = TS2PHC_PPS_SOURCE_PTP; } else { pps_type = TS2PHC_PPS_SOURCE_PHC; } diff --git a/ts2phc_pps_source.c b/ts2phc_pps_source.c index c333f65..87b0ffa 100644 --- a/ts2phc_pps_source.c +++ b/ts2phc_pps_source.c @@ -8,6 +8,7 @@ #include "ts2phc_nmea_pps_source.h" #include "ts2phc_phc_pps_source.h" #include "ts2phc_pps_source_private.h" +#include "ts2phc_ptp_pps_source.h" struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv, const char *dev, @@ -25,6 +26,9 @@ struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv, case TS2PHC_PPS_SOURCE_PHC: src = ts2phc_phc_pps_source_create(priv, dev); break; + case TS2PHC_PPS_SOURCE_PTP: + src = ts2phc_ptp_pps_source_create(priv, dev); + break; } return src; } diff --git a/ts2phc_pps_source.h b/ts2phc_pps_source.h index 293c693..3a66219 100644 --- a/ts2phc_pps_source.h +++ b/ts2phc_pps_source.h @@ -23,6 +23,7 @@ enum ts2phc_pps_source_type { TS2PHC_PPS_SOURCE_GENERIC, TS2PHC_PPS_SOURCE_NMEA, TS2PHC_PPS_SOURCE_PHC, + TS2PHC_PPS_SOURCE_PTP, }; /** diff --git a/ts2phc_ptp_pps_source.c b/ts2phc_ptp_pps_source.c new file mode 100644 index 0000000..5e160d0 --- /dev/null +++ b/ts2phc_ptp_pps_source.c @@ -0,0 +1,199 @@ +/** + * @file ts2phc_ptp_pps_source.c + * @note Copyright (C) 2023 Maciek Machnikowski <ma...@ma...> + * @note SPDX-License-Identifier: GPL-2.0+ + */ +#include <pthread.h> +#include <stdlib.h> +#include <time.h> + +#include "missing.h" +#include "pmc_agent.h" +#include "print.h" +#include "ts2phc_pps_source_private.h" +#include "ts2phc_ptp_pps_source.h" +#include "util.h" + +#define TIMESTAMP_SEC(ts) (((uint64_t)ts.seconds_lsb) | (((uint64_t)ts.seconds_msb) << 32)) +#define MAX_PTP_AGE 5000000000ULL + +struct ts2phc_ptp_pps_source { + struct ts2phc_pps_source pps_source; + struct ts2phc_private *priv; + struct pmc_agent *agent; + + pthread_t worker; + /* Protects anonymous struct fields, below, from concurrent access. */ + pthread_mutex_t mutex; + struct { + struct timespec local_monotime; + struct timespec ptp_time; + bool ptp_time_valid; + }; +}; + +static void ts2phc_ptp_pps_source_destroy(struct ts2phc_pps_source *src) +{ + struct ts2phc_ptp_pps_source *s = + container_of(src, struct ts2phc_ptp_pps_source, pps_source); + + pthread_join(s->worker, NULL); + pthread_mutex_destroy(&s->mutex); + + pmc_agent_destroy(s->agent); + + free(s); +} + +static int ts2phc_ptp_pps_source_getppstime(struct ts2phc_pps_source *src, + struct timespec *ts) +{ + struct ts2phc_ptp_pps_source *s = + container_of(src, struct ts2phc_ptp_pps_source, pps_source); + tmv_t duration_since_ptp, local_t1, local_t2, sync_time; + struct timespec now; + bool data_valid; + + clock_gettime(CLOCK_MONOTONIC, &now); + local_t2 = timespec_to_tmv(now); + + pthread_mutex_lock(&s->mutex); + + local_t1 = timespec_to_tmv(s->local_monotime); + sync_time = timespec_to_tmv(s->ptp_time); + data_valid = s->ptp_time_valid; + + pthread_mutex_unlock(&s->mutex); + + if (!data_valid) { + pr_debug("ptp: no valid PTP timestamp received"); + return -1; + } + + duration_since_ptp = tmv_sub(local_t2, local_t1); + if (tmv_to_nanoseconds(duration_since_ptp) > MAX_PTP_AGE) { + /* Invalidate the timestamp */ + pthread_mutex_lock(&s->mutex); + s->ptp_time_valid = false; + pthread_mutex_unlock(&s->mutex); + + pr_err("ptp: time stamp stale"); + return -1; + } + sync_time = tmv_add(sync_time, duration_since_ptp); + + *ts = tmv_to_timespec(sync_time); + + return 0; +} + +static void *monitor_ptp_signaling_msg(void *arg) +{ + struct ts2phc_ptp_pps_source *s = arg; + + while (is_running()) { + pmc_agent_update(s->agent); + usleep(10000); + } + + return NULL; +} + +static int ts2phc_recv_signaling_subscribed(void *context, + struct ptp_message *msg, + int excluded) +{ + struct slave_rx_sync_timing_record *sync_record; + struct slave_rx_sync_timing_data_tlv *srstd; + struct ts2phc_ptp_pps_source *s = context; + struct tlv_extra *extra; + struct timespec rxtime; + int i, cnt; + + TAILQ_FOREACH(extra, &msg->tlv_list, list) { + if (extra->tlv->type != TLV_SLAVE_RX_SYNC_TIMING_DATA) { + continue; + } + + /* Read the time from the Sync/FollowUp message */ + srstd = (struct slave_rx_sync_timing_data_tlv *)extra->tlv; + cnt = (srstd->length - sizeof(srstd->sourcePortIdentity)) / + sizeof(*sync_record); + sync_record = srstd->record; + + for (i = 0; i < cnt; i++) { + clock_gettime(CLOCK_MONOTONIC, &rxtime); + + pthread_mutex_lock(&s->mutex); + + s->local_monotime = rxtime; + s->ptp_time.tv_sec = TIMESTAMP_SEC(sync_record->syncOriginTimestamp); + s->ptp_time.tv_nsec = sync_record->syncOriginTimestamp.nanoseconds; + s->ptp_time_valid = true; + + pthread_mutex_unlock(&s->mutex); + + pr_debug("%ld.%ld Seq: %d\n", s->ptp_time.tv_sec, + s->ptp_time.tv_nsec, sync_record->sequenceId); + + sync_record++; + } + } + + return 1; +} + +struct ts2phc_pps_source *ts2phc_ptp_pps_source_create(struct ts2phc_private *priv, + const char *dev) +{ + char uds_local[MAX_IFNAME_SIZE + 1]; + struct ts2phc_ptp_pps_source *s; + const char *path; + int err; + + s = calloc(1, sizeof(*s)); + if (!s) { + return NULL; + } + path = config_get_string(priv->cfg, NULL, "slave_event_monitor"); + if (!path) { + snprintf(uds_local, sizeof(uds_local), "/var/run/ts2phc-ptpmon"); + } else { + snprintf(uds_local, sizeof(uds_local), path); + } + + s->agent = pmc_agent_create(); + if (!s->agent) { + pr_err("failed to start pmc agent"); + goto err_agent; + } + + err = init_pmc_node(priv->cfg, s->agent, uds_local, + ts2phc_recv_signaling_subscribed, s); + if (err) { + pr_err("failed to create PMC agent: %s", strerror(err)); + goto err_pmc_init; + } + pmc_agent_enable_signaling_cb(s->agent, true); + + s->pps_source.destroy = ts2phc_ptp_pps_source_destroy; + s->pps_source.getppstime = ts2phc_ptp_pps_source_getppstime; + s->priv = priv; + + pthread_mutex_init(&s->mutex, NULL); + err = pthread_create(&s->worker, NULL, monitor_ptp_signaling_msg, s); + if (err) { + pr_err("failed to create worker thread: %s", strerror(err)); + goto err_thread; + } + + return &s->pps_source; + +err_thread: + pthread_mutex_destroy(&s->mutex); +err_pmc_init: + pmc_agent_destroy(s->agent); +err_agent: + free(s); + return NULL; +} diff --git a/ts2phc_ptp_pps_source.h b/ts2phc_ptp_pps_source.h new file mode 100644 index 0000000..45bf7ae --- /dev/null +++ b/ts2phc_ptp_pps_source.h @@ -0,0 +1,15 @@ +/** + * @file ts2phc_ptp_pps_source.h + * @note Copyright (C) 2023 Maciek Machnikowski <ma...@ma...> + * @note SPDX-License-Identifier: GPL-2.0+ + */ +#ifndef HAVE_TS2PHC_PTP_PPS_SOURCE_H +#define HAVE_TS2PHC_PTP_PPS_SOURCE_H + +#include "ts2phc.h" +#include "ts2phc_pps_source.h" + +struct ts2phc_pps_source *ts2phc_ptp_pps_source_create(struct ts2phc_private *priv, + const char *dev); + +#endif -- 2.30.2 |
From: Maciek M. <ma...@ma...> - 2023-08-08 09:37:18
|
Add option to run callback that when the PMC agent receives signaling messages. Signed-off-by: Maciek Machnikowski <ma...@ma...> --- pmc_agent.c | 19 ++++++++++++++++++- pmc_agent.h | 7 +++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/pmc_agent.c b/pmc_agent.c index 62d1a86..7ffcec8 100644 --- a/pmc_agent.c +++ b/pmc_agent.c @@ -42,6 +42,7 @@ struct pmc_agent { bool dds_valid; int leap; int pmc_ds_requested; + bool signaling_cb_ena; bool stay_subscribed; int sync_offset; int utc_offset_traceable; @@ -127,6 +128,7 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, #define N_FD 1 struct pollfd pollfd[N_FD]; int cnt, res; + bool skip_cb; while (1) { pollfd[0].fd = pmc_get_transport_fd(node->pmc); @@ -178,7 +180,16 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, node->pmc_ds_requested = 0; return RUN_PMC_NODEV; } - if (res <= 0 || + + /* Skip callback if message is not management */ + skip_cb = (res <= 0) ? true : false; + + /* Run the callback on signaling messages if configured */ + if (node->signaling_cb_ena && (msg_type(*msg) == SIGNALING)) { + skip_cb = false; + } + + if (skip_cb || node->recv_subscribed(node->recv_context, *msg, ds_id) || management_tlv_id(*msg) != ds_id) { msg_put(*msg); @@ -430,3 +441,9 @@ bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent) { return agent->utc_offset_traceable; } + +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable) +{ + agent->signaling_cb_ena = enable; +} + diff --git a/pmc_agent.h b/pmc_agent.h index 2fb1cc8..9ae37f7 100644 --- a/pmc_agent.h +++ b/pmc_agent.h @@ -170,4 +170,11 @@ int pmc_agent_update(struct pmc_agent *agent); */ bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent); +/** + * Enables or disables callback on signaling messages + * @param agent Pointer to a PMC instance obtained via @ref pmc_agent_create(). + * @param enable - if set to true, callback will be called on signaling msgs + */ +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable); + #endif -- 2.30.2 |
From: Maciek M. <ma...@ma...> - 2023-08-08 09:37:19
|
Add PPS ToD source that extracts ToD from master timestamps from a slave event monitor of the running ptp4l instance. It enables scenarios where reliable ToD sources, such as nmea, may not be available, but a system is connected to different ptp masters. It can be used in the APTS deployment with the PPS from the local GNSS receiver that doesn't supply the ToD information, but PTP service is enabled in the backhaul. To use the servo ts2phc -s ptp -c eth0 -m --slave_event_monitor /var/run/ts2phc-mon And run the ptp4l with slave event monitor enabled ptp4l -i eth0 -m --free_running 1 --slave_event_monitor /var/run/ts2phc-mon Note: Do not enable ptp4l control of the clock on the same port. v2: changed pmc_agent implementation to not use a separate callback v3: updated poll thread to use less CPU v4: changed sleep routine to usleep, simplified poll thread Signed-off-by: Maciek Machnikowski <ma...@ma...> --- makefile | 3 +- ts2phc.8 | 6 ++ ts2phc.c | 2 + ts2phc_pps_source.c | 4 + ts2phc_pps_source.h | 1 + ts2phc_ptp_pps_source.c | 199 ++++++++++++++++++++++++++++++++++++++++ ts2phc_ptp_pps_source.h | 15 +++ 7 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 ts2phc_ptp_pps_source.c create mode 100644 ts2phc_ptp_pps_source.h diff --git a/makefile b/makefile index 3e3b8b3..e15c22d 100644 --- a/makefile +++ b/makefile @@ -27,7 +27,8 @@ FILTERS = filter.o mave.o mmedian.o SERVOS = linreg.o ntpshm.o nullf.o pi.o refclock_sock.o servo.o TRANSP = raw.o transport.o udp.o udp6.o uds.o TS2PHC = ts2phc.o lstab.o nmea.o serial.o sock.o ts2phc_generic_pps_source.o \ - ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o ts2phc_pps_source.o + ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o ts2phc_pps_source.o \ + ts2phc_ptp_pps_source.o OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o designated_fsm.o \ e2e_tc.o fault.o $(FILTERS) fsm.o hash.o interface.o monitor.o msg.o phc.o \ port.o port_signaling.o pqueue.o print.o ptp4l.o p2p_tc.o rtnl.o $(SERVOS) \ diff --git a/ts2phc.8 b/ts2phc.8 index 3c71d47..f3abb32 100644 --- a/ts2phc.8 +++ b/ts2phc.8 @@ -72,6 +72,8 @@ device (like /dev/ptp0) or its associated network interface (like eth0). Use the key word "nmea" for an external 1-PPS from a GPS providing ToD information via the RMC NMEA sentence. +Use the key word "ptp" for the ToD source from the running ptp4l instance pushing +timestamps through the slave event monitor. .TP .B \-v Prints the software version and exits. @@ -259,6 +261,10 @@ Some PHC devices feature programmable pins, and this option allows configuration of a particular pin for the external time stamping or periodic output function. The default is pin index 0. +.TP +.B slave_event_monitor +Used with ptp ToD source, specifies the UNIX domain socket to read +SLAVE_RX_SYNC_TIMING_DATA from. .SH WARNING diff --git a/ts2phc.c b/ts2phc.c index 3bbbbd3..8fc260f 100644 --- a/ts2phc.c +++ b/ts2phc.c @@ -734,6 +734,8 @@ int main(int argc, char *argv[]) pps_type = TS2PHC_PPS_SOURCE_GENERIC; } else if (!strcasecmp(tod_source, "nmea")) { pps_type = TS2PHC_PPS_SOURCE_NMEA; + } else if (!strcasecmp(tod_source, "ptp")) { + pps_type = TS2PHC_PPS_SOURCE_PTP; } else { pps_type = TS2PHC_PPS_SOURCE_PHC; } diff --git a/ts2phc_pps_source.c b/ts2phc_pps_source.c index c333f65..87b0ffa 100644 --- a/ts2phc_pps_source.c +++ b/ts2phc_pps_source.c @@ -8,6 +8,7 @@ #include "ts2phc_nmea_pps_source.h" #include "ts2phc_phc_pps_source.h" #include "ts2phc_pps_source_private.h" +#include "ts2phc_ptp_pps_source.h" struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv, const char *dev, @@ -25,6 +26,9 @@ struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv, case TS2PHC_PPS_SOURCE_PHC: src = ts2phc_phc_pps_source_create(priv, dev); break; + case TS2PHC_PPS_SOURCE_PTP: + src = ts2phc_ptp_pps_source_create(priv, dev); + break; } return src; } diff --git a/ts2phc_pps_source.h b/ts2phc_pps_source.h index 293c693..3a66219 100644 --- a/ts2phc_pps_source.h +++ b/ts2phc_pps_source.h @@ -23,6 +23,7 @@ enum ts2phc_pps_source_type { TS2PHC_PPS_SOURCE_GENERIC, TS2PHC_PPS_SOURCE_NMEA, TS2PHC_PPS_SOURCE_PHC, + TS2PHC_PPS_SOURCE_PTP, }; /** diff --git a/ts2phc_ptp_pps_source.c b/ts2phc_ptp_pps_source.c new file mode 100644 index 0000000..5e160d0 --- /dev/null +++ b/ts2phc_ptp_pps_source.c @@ -0,0 +1,199 @@ +/** + * @file ts2phc_ptp_pps_source.c + * @note Copyright (C) 2023 Maciek Machnikowski <ma...@ma...> + * @note SPDX-License-Identifier: GPL-2.0+ + */ +#include <pthread.h> +#include <stdlib.h> +#include <time.h> + +#include "missing.h" +#include "pmc_agent.h" +#include "print.h" +#include "ts2phc_pps_source_private.h" +#include "ts2phc_ptp_pps_source.h" +#include "util.h" + +#define TIMESTAMP_SEC(ts) (((uint64_t)ts.seconds_lsb) | (((uint64_t)ts.seconds_msb) << 32)) +#define MAX_PTP_AGE 5000000000ULL + +struct ts2phc_ptp_pps_source { + struct ts2phc_pps_source pps_source; + struct ts2phc_private *priv; + struct pmc_agent *agent; + + pthread_t worker; + /* Protects anonymous struct fields, below, from concurrent access. */ + pthread_mutex_t mutex; + struct { + struct timespec local_monotime; + struct timespec ptp_time; + bool ptp_time_valid; + }; +}; + +static void ts2phc_ptp_pps_source_destroy(struct ts2phc_pps_source *src) +{ + struct ts2phc_ptp_pps_source *s = + container_of(src, struct ts2phc_ptp_pps_source, pps_source); + + pthread_join(s->worker, NULL); + pthread_mutex_destroy(&s->mutex); + + pmc_agent_destroy(s->agent); + + free(s); +} + +static int ts2phc_ptp_pps_source_getppstime(struct ts2phc_pps_source *src, + struct timespec *ts) +{ + struct ts2phc_ptp_pps_source *s = + container_of(src, struct ts2phc_ptp_pps_source, pps_source); + tmv_t duration_since_ptp, local_t1, local_t2, sync_time; + struct timespec now; + bool data_valid; + + clock_gettime(CLOCK_MONOTONIC, &now); + local_t2 = timespec_to_tmv(now); + + pthread_mutex_lock(&s->mutex); + + local_t1 = timespec_to_tmv(s->local_monotime); + sync_time = timespec_to_tmv(s->ptp_time); + data_valid = s->ptp_time_valid; + + pthread_mutex_unlock(&s->mutex); + + if (!data_valid) { + pr_debug("ptp: no valid PTP timestamp received"); + return -1; + } + + duration_since_ptp = tmv_sub(local_t2, local_t1); + if (tmv_to_nanoseconds(duration_since_ptp) > MAX_PTP_AGE) { + /* Invalidate the timestamp */ + pthread_mutex_lock(&s->mutex); + s->ptp_time_valid = false; + pthread_mutex_unlock(&s->mutex); + + pr_err("ptp: time stamp stale"); + return -1; + } + sync_time = tmv_add(sync_time, duration_since_ptp); + + *ts = tmv_to_timespec(sync_time); + + return 0; +} + +static void *monitor_ptp_signaling_msg(void *arg) +{ + struct ts2phc_ptp_pps_source *s = arg; + + while (is_running()) { + pmc_agent_update(s->agent); + usleep(10000); + } + + return NULL; +} + +static int ts2phc_recv_signaling_subscribed(void *context, + struct ptp_message *msg, + int excluded) +{ + struct slave_rx_sync_timing_record *sync_record; + struct slave_rx_sync_timing_data_tlv *srstd; + struct ts2phc_ptp_pps_source *s = context; + struct tlv_extra *extra; + struct timespec rxtime; + int i, cnt; + + TAILQ_FOREACH(extra, &msg->tlv_list, list) { + if (extra->tlv->type != TLV_SLAVE_RX_SYNC_TIMING_DATA) { + continue; + } + + /* Read the time from the Sync/FollowUp message */ + srstd = (struct slave_rx_sync_timing_data_tlv *)extra->tlv; + cnt = (srstd->length - sizeof(srstd->sourcePortIdentity)) / + sizeof(*sync_record); + sync_record = srstd->record; + + for (i = 0; i < cnt; i++) { + clock_gettime(CLOCK_MONOTONIC, &rxtime); + + pthread_mutex_lock(&s->mutex); + + s->local_monotime = rxtime; + s->ptp_time.tv_sec = TIMESTAMP_SEC(sync_record->syncOriginTimestamp); + s->ptp_time.tv_nsec = sync_record->syncOriginTimestamp.nanoseconds; + s->ptp_time_valid = true; + + pthread_mutex_unlock(&s->mutex); + + pr_debug("%ld.%ld Seq: %d\n", s->ptp_time.tv_sec, + s->ptp_time.tv_nsec, sync_record->sequenceId); + + sync_record++; + } + } + + return 1; +} + +struct ts2phc_pps_source *ts2phc_ptp_pps_source_create(struct ts2phc_private *priv, + const char *dev) +{ + char uds_local[MAX_IFNAME_SIZE + 1]; + struct ts2phc_ptp_pps_source *s; + const char *path; + int err; + + s = calloc(1, sizeof(*s)); + if (!s) { + return NULL; + } + path = config_get_string(priv->cfg, NULL, "slave_event_monitor"); + if (!path) { + snprintf(uds_local, sizeof(uds_local), "/var/run/ts2phc-ptpmon"); + } else { + snprintf(uds_local, sizeof(uds_local), path); + } + + s->agent = pmc_agent_create(); + if (!s->agent) { + pr_err("failed to start pmc agent"); + goto err_agent; + } + + err = init_pmc_node(priv->cfg, s->agent, uds_local, + ts2phc_recv_signaling_subscribed, s); + if (err) { + pr_err("failed to create PMC agent: %s", strerror(err)); + goto err_pmc_init; + } + pmc_agent_enable_signaling_cb(s->agent, true); + + s->pps_source.destroy = ts2phc_ptp_pps_source_destroy; + s->pps_source.getppstime = ts2phc_ptp_pps_source_getppstime; + s->priv = priv; + + pthread_mutex_init(&s->mutex, NULL); + err = pthread_create(&s->worker, NULL, monitor_ptp_signaling_msg, s); + if (err) { + pr_err("failed to create worker thread: %s", strerror(err)); + goto err_thread; + } + + return &s->pps_source; + +err_thread: + pthread_mutex_destroy(&s->mutex); +err_pmc_init: + pmc_agent_destroy(s->agent); +err_agent: + free(s); + return NULL; +} diff --git a/ts2phc_ptp_pps_source.h b/ts2phc_ptp_pps_source.h new file mode 100644 index 0000000..45bf7ae --- /dev/null +++ b/ts2phc_ptp_pps_source.h @@ -0,0 +1,15 @@ +/** + * @file ts2phc_ptp_pps_source.h + * @note Copyright (C) 2023 Maciek Machnikowski <ma...@ma...> + * @note SPDX-License-Identifier: GPL-2.0+ + */ +#ifndef HAVE_TS2PHC_PTP_PPS_SOURCE_H +#define HAVE_TS2PHC_PTP_PPS_SOURCE_H + +#include "ts2phc.h" +#include "ts2phc_pps_source.h" + +struct ts2phc_pps_source *ts2phc_ptp_pps_source_create(struct ts2phc_private *priv, + const char *dev); + +#endif -- 2.30.2 |
From: Richard C. <ric...@gm...> - 2023-09-03 23:01:14
|
On Tue, Aug 08, 2023 at 11:36:52AM +0200, Maciek Machnikowski wrote: > v2: changed pmc_agent implementation to not use a separate callback > v3: updated poll thread to use less CPU > v4: changed sleep routine to usleep, simplified poll thread You have resent v5, but I see v6 with changes: 26.Apr'23 Maciek Machniko [PATCH v6 1/2] pmc_agent: Add option to run callback for signaling messages 26.Apr'23 Maciek Machniko └─>[PATCH v6 2/2] ts2phc: Add PTP as a source of ToD Did you resend v5 by mistake? Thanks, Richard |
From: Maciek M. <ma...@ma...> - 2023-09-04 07:17:48
|
On 9/4/2023 1:01 AM, Richard Cochran wrote: > On Tue, Aug 08, 2023 at 11:36:52AM +0200, Maciek Machnikowski wrote: > >> v2: changed pmc_agent implementation to not use a separate callback >> v3: updated poll thread to use less CPU >> v4: changed sleep routine to usleep, simplified poll thread > > You have resent v5, but I see v6 with changes: > > 26.Apr'23 Maciek Machniko [PATCH v6 1/2] pmc_agent: Add option to run callback for signaling messages > 26.Apr'23 Maciek Machniko └─>[PATCH v6 2/2] ts2phc: Add PTP as a source of ToD > > Did you resend v5 by mistake? > > Thanks, > Richard > Yes - you're right. Please look at the v6. Sorry for the confusion. Thanks Maciek |
From: Maciek M. <ma...@ma...> - 2023-11-10 14:15:15
|
Add option to run callback that when the PMC agent receives signaling messages. v2: changed pmc_agent implementation to not use a separate callback v3: updated poll thread to use less CPU v4: changed sleep routine to usleep, simplified poll thread v5: make sure management_tlv_id is not called on non-management messages v6: optimized code to not check signaling_cb_ena on management messages v7: rebase Signed-off-by: Maciek Machnikowski <ma...@ma...> --- pmc_agent.c | 22 ++++++++++++++++++++-- pmc_agent.h | 7 +++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/pmc_agent.c b/pmc_agent.c index 62d1a86..bea6b59 100644 --- a/pmc_agent.c +++ b/pmc_agent.c @@ -42,6 +42,7 @@ struct pmc_agent { bool dds_valid; int leap; int pmc_ds_requested; + bool signaling_cb_ena; bool stay_subscribed; int sync_offset; int utc_offset_traceable; @@ -127,6 +128,7 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, #define N_FD 1 struct pollfd pollfd[N_FD]; int cnt, res; + bool skip_cb; while (1) { pollfd[0].fd = pmc_get_transport_fd(node->pmc); @@ -178,9 +180,19 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, node->pmc_ds_requested = 0; return RUN_PMC_NODEV; } - if (res <= 0 || + + /* Skip callback if message is not management */ + skip_cb = (res <= 0) ? true : false; + + /* Run the callback on signaling messages if configured */ + if (res == 0 && node->signaling_cb_ena && + msg_type(*msg) == SIGNALING) { + skip_cb = false; + } + + if (skip_cb || node->recv_subscribed(node->recv_context, *msg, ds_id) || - management_tlv_id(*msg) != ds_id) { + (res == 1 && management_tlv_id(*msg) != ds_id)) { msg_put(*msg); *msg = NULL; continue; @@ -430,3 +442,9 @@ bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent) { return agent->utc_offset_traceable; } + +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable) +{ + agent->signaling_cb_ena = enable; +} + diff --git a/pmc_agent.h b/pmc_agent.h index 2fb1cc8..9ae37f7 100644 --- a/pmc_agent.h +++ b/pmc_agent.h @@ -170,4 +170,11 @@ int pmc_agent_update(struct pmc_agent *agent); */ bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent); +/** + * Enables or disables callback on signaling messages + * @param agent Pointer to a PMC instance obtained via @ref pmc_agent_create(). + * @param enable - if set to true, callback will be called on signaling msgs + */ +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable); + #endif -- 2.30.2 |
From: Maciek M. <ma...@ma...> - 2023-11-10 14:15:30
|
Add PPS ToD source that extracts ToD from master timestamps from a slave event monitor of the running ptp4l instance. It enables scenarios where reliable ToD sources, such as nmea, may not be available, but a system is connected to different ptp masters. It can be used in the APTS deployment with the PPS from the local GNSS receiver that doesn't supply the ToD information, but PTP service is enabled in the backhaul. To use the servo ts2phc -s ptp -c eth0 -m --slave_event_monitor /var/run/ts2phc-mon And run the ptp4l with slave event monitor enabled ptp4l -i eth0 -m --free_running 1 --slave_event_monitor /var/run/ts2phc-mon Note: Do not enable ptp4l control of the clock on the same port. v2: changed pmc_agent implementation to not use a separate callback v3: updated poll thread to use less CPU v4: changed sleep routine to usleep, simplified poll thread v5: make sure management_tlv_id is not called on non-management messages v6: optimized code to not check signaling_cb_ena on management messages v7: rebase Signed-off-by: Maciek Machnikowski <ma...@ma...> --- makefile | 3 +- ts2phc.8 | 6 ++ ts2phc.c | 2 + ts2phc_pps_source.c | 4 + ts2phc_pps_source.h | 1 + ts2phc_ptp_pps_source.c | 199 ++++++++++++++++++++++++++++++++++++++++ ts2phc_ptp_pps_source.h | 15 +++ 7 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 ts2phc_ptp_pps_source.c create mode 100644 ts2phc_ptp_pps_source.h diff --git a/makefile b/makefile index 3e3b8b3..e15c22d 100644 --- a/makefile +++ b/makefile @@ -27,7 +27,8 @@ FILTERS = filter.o mave.o mmedian.o SERVOS = linreg.o ntpshm.o nullf.o pi.o refclock_sock.o servo.o TRANSP = raw.o transport.o udp.o udp6.o uds.o TS2PHC = ts2phc.o lstab.o nmea.o serial.o sock.o ts2phc_generic_pps_source.o \ - ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o ts2phc_pps_source.o + ts2phc_nmea_pps_source.o ts2phc_phc_pps_source.o ts2phc_pps_sink.o ts2phc_pps_source.o \ + ts2phc_ptp_pps_source.o OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o designated_fsm.o \ e2e_tc.o fault.o $(FILTERS) fsm.o hash.o interface.o monitor.o msg.o phc.o \ port.o port_signaling.o pqueue.o print.o ptp4l.o p2p_tc.o rtnl.o $(SERVOS) \ diff --git a/ts2phc.8 b/ts2phc.8 index 3c71d47..f3abb32 100644 --- a/ts2phc.8 +++ b/ts2phc.8 @@ -72,6 +72,8 @@ device (like /dev/ptp0) or its associated network interface (like eth0). Use the key word "nmea" for an external 1-PPS from a GPS providing ToD information via the RMC NMEA sentence. +Use the key word "ptp" for the ToD source from the running ptp4l instance pushing +timestamps through the slave event monitor. .TP .B \-v Prints the software version and exits. @@ -259,6 +261,10 @@ Some PHC devices feature programmable pins, and this option allows configuration of a particular pin for the external time stamping or periodic output function. The default is pin index 0. +.TP +.B slave_event_monitor +Used with ptp ToD source, specifies the UNIX domain socket to read +SLAVE_RX_SYNC_TIMING_DATA from. .SH WARNING diff --git a/ts2phc.c b/ts2phc.c index 3bbbbd3..8fc260f 100644 --- a/ts2phc.c +++ b/ts2phc.c @@ -734,6 +734,8 @@ int main(int argc, char *argv[]) pps_type = TS2PHC_PPS_SOURCE_GENERIC; } else if (!strcasecmp(tod_source, "nmea")) { pps_type = TS2PHC_PPS_SOURCE_NMEA; + } else if (!strcasecmp(tod_source, "ptp")) { + pps_type = TS2PHC_PPS_SOURCE_PTP; } else { pps_type = TS2PHC_PPS_SOURCE_PHC; } diff --git a/ts2phc_pps_source.c b/ts2phc_pps_source.c index c333f65..87b0ffa 100644 --- a/ts2phc_pps_source.c +++ b/ts2phc_pps_source.c @@ -8,6 +8,7 @@ #include "ts2phc_nmea_pps_source.h" #include "ts2phc_phc_pps_source.h" #include "ts2phc_pps_source_private.h" +#include "ts2phc_ptp_pps_source.h" struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv, const char *dev, @@ -25,6 +26,9 @@ struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv, case TS2PHC_PPS_SOURCE_PHC: src = ts2phc_phc_pps_source_create(priv, dev); break; + case TS2PHC_PPS_SOURCE_PTP: + src = ts2phc_ptp_pps_source_create(priv, dev); + break; } return src; } diff --git a/ts2phc_pps_source.h b/ts2phc_pps_source.h index 293c693..3a66219 100644 --- a/ts2phc_pps_source.h +++ b/ts2phc_pps_source.h @@ -23,6 +23,7 @@ enum ts2phc_pps_source_type { TS2PHC_PPS_SOURCE_GENERIC, TS2PHC_PPS_SOURCE_NMEA, TS2PHC_PPS_SOURCE_PHC, + TS2PHC_PPS_SOURCE_PTP, }; /** diff --git a/ts2phc_ptp_pps_source.c b/ts2phc_ptp_pps_source.c new file mode 100644 index 0000000..5e160d0 --- /dev/null +++ b/ts2phc_ptp_pps_source.c @@ -0,0 +1,199 @@ +/** + * @file ts2phc_ptp_pps_source.c + * @note Copyright (C) 2023 Maciek Machnikowski <ma...@ma...> + * @note SPDX-License-Identifier: GPL-2.0+ + */ +#include <pthread.h> +#include <stdlib.h> +#include <time.h> + +#include "missing.h" +#include "pmc_agent.h" +#include "print.h" +#include "ts2phc_pps_source_private.h" +#include "ts2phc_ptp_pps_source.h" +#include "util.h" + +#define TIMESTAMP_SEC(ts) (((uint64_t)ts.seconds_lsb) | (((uint64_t)ts.seconds_msb) << 32)) +#define MAX_PTP_AGE 5000000000ULL + +struct ts2phc_ptp_pps_source { + struct ts2phc_pps_source pps_source; + struct ts2phc_private *priv; + struct pmc_agent *agent; + + pthread_t worker; + /* Protects anonymous struct fields, below, from concurrent access. */ + pthread_mutex_t mutex; + struct { + struct timespec local_monotime; + struct timespec ptp_time; + bool ptp_time_valid; + }; +}; + +static void ts2phc_ptp_pps_source_destroy(struct ts2phc_pps_source *src) +{ + struct ts2phc_ptp_pps_source *s = + container_of(src, struct ts2phc_ptp_pps_source, pps_source); + + pthread_join(s->worker, NULL); + pthread_mutex_destroy(&s->mutex); + + pmc_agent_destroy(s->agent); + + free(s); +} + +static int ts2phc_ptp_pps_source_getppstime(struct ts2phc_pps_source *src, + struct timespec *ts) +{ + struct ts2phc_ptp_pps_source *s = + container_of(src, struct ts2phc_ptp_pps_source, pps_source); + tmv_t duration_since_ptp, local_t1, local_t2, sync_time; + struct timespec now; + bool data_valid; + + clock_gettime(CLOCK_MONOTONIC, &now); + local_t2 = timespec_to_tmv(now); + + pthread_mutex_lock(&s->mutex); + + local_t1 = timespec_to_tmv(s->local_monotime); + sync_time = timespec_to_tmv(s->ptp_time); + data_valid = s->ptp_time_valid; + + pthread_mutex_unlock(&s->mutex); + + if (!data_valid) { + pr_debug("ptp: no valid PTP timestamp received"); + return -1; + } + + duration_since_ptp = tmv_sub(local_t2, local_t1); + if (tmv_to_nanoseconds(duration_since_ptp) > MAX_PTP_AGE) { + /* Invalidate the timestamp */ + pthread_mutex_lock(&s->mutex); + s->ptp_time_valid = false; + pthread_mutex_unlock(&s->mutex); + + pr_err("ptp: time stamp stale"); + return -1; + } + sync_time = tmv_add(sync_time, duration_since_ptp); + + *ts = tmv_to_timespec(sync_time); + + return 0; +} + +static void *monitor_ptp_signaling_msg(void *arg) +{ + struct ts2phc_ptp_pps_source *s = arg; + + while (is_running()) { + pmc_agent_update(s->agent); + usleep(10000); + } + + return NULL; +} + +static int ts2phc_recv_signaling_subscribed(void *context, + struct ptp_message *msg, + int excluded) +{ + struct slave_rx_sync_timing_record *sync_record; + struct slave_rx_sync_timing_data_tlv *srstd; + struct ts2phc_ptp_pps_source *s = context; + struct tlv_extra *extra; + struct timespec rxtime; + int i, cnt; + + TAILQ_FOREACH(extra, &msg->tlv_list, list) { + if (extra->tlv->type != TLV_SLAVE_RX_SYNC_TIMING_DATA) { + continue; + } + + /* Read the time from the Sync/FollowUp message */ + srstd = (struct slave_rx_sync_timing_data_tlv *)extra->tlv; + cnt = (srstd->length - sizeof(srstd->sourcePortIdentity)) / + sizeof(*sync_record); + sync_record = srstd->record; + + for (i = 0; i < cnt; i++) { + clock_gettime(CLOCK_MONOTONIC, &rxtime); + + pthread_mutex_lock(&s->mutex); + + s->local_monotime = rxtime; + s->ptp_time.tv_sec = TIMESTAMP_SEC(sync_record->syncOriginTimestamp); + s->ptp_time.tv_nsec = sync_record->syncOriginTimestamp.nanoseconds; + s->ptp_time_valid = true; + + pthread_mutex_unlock(&s->mutex); + + pr_debug("%ld.%ld Seq: %d\n", s->ptp_time.tv_sec, + s->ptp_time.tv_nsec, sync_record->sequenceId); + + sync_record++; + } + } + + return 1; +} + +struct ts2phc_pps_source *ts2phc_ptp_pps_source_create(struct ts2phc_private *priv, + const char *dev) +{ + char uds_local[MAX_IFNAME_SIZE + 1]; + struct ts2phc_ptp_pps_source *s; + const char *path; + int err; + + s = calloc(1, sizeof(*s)); + if (!s) { + return NULL; + } + path = config_get_string(priv->cfg, NULL, "slave_event_monitor"); + if (!path) { + snprintf(uds_local, sizeof(uds_local), "/var/run/ts2phc-ptpmon"); + } else { + snprintf(uds_local, sizeof(uds_local), path); + } + + s->agent = pmc_agent_create(); + if (!s->agent) { + pr_err("failed to start pmc agent"); + goto err_agent; + } + + err = init_pmc_node(priv->cfg, s->agent, uds_local, + ts2phc_recv_signaling_subscribed, s); + if (err) { + pr_err("failed to create PMC agent: %s", strerror(err)); + goto err_pmc_init; + } + pmc_agent_enable_signaling_cb(s->agent, true); + + s->pps_source.destroy = ts2phc_ptp_pps_source_destroy; + s->pps_source.getppstime = ts2phc_ptp_pps_source_getppstime; + s->priv = priv; + + pthread_mutex_init(&s->mutex, NULL); + err = pthread_create(&s->worker, NULL, monitor_ptp_signaling_msg, s); + if (err) { + pr_err("failed to create worker thread: %s", strerror(err)); + goto err_thread; + } + + return &s->pps_source; + +err_thread: + pthread_mutex_destroy(&s->mutex); +err_pmc_init: + pmc_agent_destroy(s->agent); +err_agent: + free(s); + return NULL; +} diff --git a/ts2phc_ptp_pps_source.h b/ts2phc_ptp_pps_source.h new file mode 100644 index 0000000..45bf7ae --- /dev/null +++ b/ts2phc_ptp_pps_source.h @@ -0,0 +1,15 @@ +/** + * @file ts2phc_ptp_pps_source.h + * @note Copyright (C) 2023 Maciek Machnikowski <ma...@ma...> + * @note SPDX-License-Identifier: GPL-2.0+ + */ +#ifndef HAVE_TS2PHC_PTP_PPS_SOURCE_H +#define HAVE_TS2PHC_PTP_PPS_SOURCE_H + +#include "ts2phc.h" +#include "ts2phc_pps_source.h" + +struct ts2phc_pps_source *ts2phc_ptp_pps_source_create(struct ts2phc_private *priv, + const char *dev); + +#endif -- 2.30.2 |
From: Maciek M. <ma...@ma...> - 2023-12-04 07:40:14
|
Hey, please take a look at these patches and merge them if there are no bugs in this code. Thanks, Maciek On 10/11/2023 15:14, Maciek Machnikowski wrote: > Add option to run callback that when the PMC agent receives signaling messages. > > v2: changed pmc_agent implementation to not use a separate callback > v3: updated poll thread to use less CPU > v4: changed sleep routine to usleep, simplified poll thread > v5: make sure management_tlv_id is not called on non-management messages > v6: optimized code to not check signaling_cb_ena on management messages > v7: rebase > > Signed-off-by: Maciek Machnikowski <ma...@ma...> > --- > pmc_agent.c | 22 ++++++++++++++++++++-- > pmc_agent.h | 7 +++++++ > 2 files changed, 27 insertions(+), 2 deletions(-) > > diff --git a/pmc_agent.c b/pmc_agent.c > index 62d1a86..bea6b59 100644 > --- a/pmc_agent.c > +++ b/pmc_agent.c > @@ -42,6 +42,7 @@ struct pmc_agent { > bool dds_valid; > int leap; > int pmc_ds_requested; > + bool signaling_cb_ena; > bool stay_subscribed; > int sync_offset; > int utc_offset_traceable; > @@ -127,6 +128,7 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, > #define N_FD 1 > struct pollfd pollfd[N_FD]; > int cnt, res; > + bool skip_cb; > > while (1) { > pollfd[0].fd = pmc_get_transport_fd(node->pmc); > @@ -178,9 +180,19 @@ static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, > node->pmc_ds_requested = 0; > return RUN_PMC_NODEV; > } > - if (res <= 0 || > + > + /* Skip callback if message is not management */ > + skip_cb = (res <= 0) ? true : false; > + > + /* Run the callback on signaling messages if configured */ > + if (res == 0 && node->signaling_cb_ena && > + msg_type(*msg) == SIGNALING) { > + skip_cb = false; > + } > + > + if (skip_cb || > node->recv_subscribed(node->recv_context, *msg, ds_id) || > - management_tlv_id(*msg) != ds_id) { > + (res == 1 && management_tlv_id(*msg) != ds_id)) { > msg_put(*msg); > *msg = NULL; > continue; > @@ -430,3 +442,9 @@ bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent) > { > return agent->utc_offset_traceable; > } > + > +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable) > +{ > + agent->signaling_cb_ena = enable; > +} > + > diff --git a/pmc_agent.h b/pmc_agent.h > index 2fb1cc8..9ae37f7 100644 > --- a/pmc_agent.h > +++ b/pmc_agent.h > @@ -170,4 +170,11 @@ int pmc_agent_update(struct pmc_agent *agent); > */ > bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent); > > +/** > + * Enables or disables callback on signaling messages > + * @param agent Pointer to a PMC instance obtained via @ref pmc_agent_create(). > + * @param enable - if set to true, callback will be called on signaling msgs > + */ > +void pmc_agent_enable_signaling_cb(struct pmc_agent *agent, bool enable); > + > #endif |