Please ignore/delete my previous email with identical subject, line wrapping ruined the formatting.
Purpose of this patch: Locking to an external 1pps pulse with ts2phc is typically done to assign traceability to a PHC clock, such that then this clock can be used to time stamp other events with a high precision and known accuracy. This requires that the user only uses the PHC, when it is stably locked to the external pulse. This patch adds the --systemd_pidfile=/run/ts2phc.pid config option, allowing to use ts2phc as an forking-type deamon in a systemd.service. When specified together with --servo_offset_threshold=x, ts2phc will run until the servo enters its SERVO_LOCKED_STABLE (s3) state. It then forks, detaches the child and writes the pid of the child into the pidfile and quits the main process. This enables us to have other systemd.service to only start after the PHC lock is valid, and by using BindsTo inother services, they even can be linked to stop (and later automatically restart) when the PHC is not locked stably. A typical application would be a traceable NTP server or grandmaster clock. Signed-off-by: Jürgen Appel <j...@dfm.dk> --- 1pps2phc.service | 42 ++++++++++++++++++++++++++++++++++++++++++ config.c | 1 + servo.c | 4 ++++ ts2phc.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- ts2phc.h | 2 ++ 5 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 1pps2phc.service diff --git a/1pps2phc.service b/1pps2phc.service new file mode 100644 index 0000000..00e5fda --- /dev/null +++ b/1pps2phc.service @@ -0,0 +1,42 @@ +[Unit] + +# This example unit file shows how to configure a systemd-service that keeps +# ptp3 locked to an external 100ms-hardware-1PPS pulse connected to ptp3.SDP0 + +Description=Lock phc3 to 1pps + +After=network.target + +# This service roughly sets the PHC clock to within 1 s of the intended offset +After=initializePHC.service + +# Optional: As soon as we are locked, we want to use chrony to use /dev/ptp3 as time source +# Add the line "BindsTo=1pps2phc.service" to the [Unit] section in +# chrony's chrony.service configuration, and you are guaranteed that +# chrony is only running as long as your PHC is actually locked and is restarted when lock is lost. +#Wants=chrony.service + +[Service] +Type=forking +PIDFile=/run/ts2phc_1pps%i.pid + +# We start up ts2phc and give the process up to 3 minutes to lock to within 100 ns +TimeoutStartSec=180 +ExecStart=/usr/local/sbin/ts2phc -l 5 -c eth3 -s extpps \ + --ts2phc.extts_correction=-35 \ + --ts2phc.pin_index=0 \ + --ts2phc.channel=0 \ + --ts2phc.extts_polarity=both \ + --ts2phc.pulsewidth=100000000 \ + --message_tag=1pps \ + --servo_offset_threshold=100 \ + --systemd_pidfile=/run/ts2phc_1pps%i.pid + +Restart=on-failure + +# Optional: Enable an external Hardware-1PPS-output on ptp3.SDP1 as long as the lock is stable +ExecStartPost=/usr/local/sbin/testptp -d /dev/ptp3 -i 0 -L 1,2 -p 1000000000 +ExecStopPost=/usr/local/sbin/testptp -d /dev/ptp3 -i 0 -L 1,0 + +[Install] +WantedBy=multi-user.target diff --git a/config.c b/config.c index cb4421f..76a1056 100644 --- a/config.c +++ b/config.c @@ -329,6 +329,7 @@ struct config_item config_tab[] = { GLOB_ITEM_INT("socket_priority", 0, 0, 15), GLOB_ITEM_DBL("step_threshold", 0.0, 0.0, DBL_MAX), GLOB_ITEM_INT("step_window", 0, 0, INT_MAX), + GLOB_ITEM_STR("systemd_pidfile", NULL), GLOB_ITEM_INT("summary_interval", 0, INT_MIN, INT_MAX), PORT_ITEM_INT("syncReceiptTimeout", 0, 0, UINT8_MAX), GLOB_ITEM_INT("tc_spanning_tree", 0, 0, 1), diff --git a/servo.c b/servo.c index ea171cd..85fe665 100644 --- a/servo.c +++ b/servo.c @@ -107,6 +107,10 @@ static int check_offset_threshold(struct servo *s, int64_t offset) if (s->curr_offset_values) s->curr_offset_values--; } else { + if (! s->curr_offset_values) { + // lock became unstable after having been stable + pr_info("servo got unlocked"); + } s->curr_offset_values = s->num_offset_values; } return s->curr_offset_values ? 0 : 1; diff --git a/ts2phc.c b/ts2phc.c index 6a8cad9..13bf474 100644 --- a/ts2phc.c +++ b/ts2phc.c @@ -436,6 +436,9 @@ static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg) struct ts2phc_clock *c; int valid, err; + pid_t pid; + FILE *pid_file; + if (autocfg) { if (!priv->ref_clock) { pr_debug("no reference clock, skipping"); @@ -499,8 +502,45 @@ static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg) goto servo_unlock; } break; - case SERVO_LOCKED: case SERVO_LOCKED_STABLE: + if (priv->pidfile && !priv->is_forked) { + priv->is_forked = true; + pid = fork(); + switch (pid) { + case -1: + pr_crit("fork failed"); + exit(EXIT_FAILURE); + case 0: /* child */ + chdir("/"); + close(0); + fopen("/dev/null", "r+"); + dup2(0,1); + dup2(0,2); + setsid(); + break; + default: /* parent */ + pr_info("forked & detached (pid=%d)", + pid); + pid_file = fopen(priv->pidfile, "w"); + if (!pid_file) { + pr_err("cannot create pidfile"); + _exit(EXIT_FAILURE); + } else { + fprintf(pid_file,"%d\n", pid); + fclose(pid_file); + _exit(EXIT_SUCCESS); + } + } + } + if (clockadj_set_freq(c->clkid, -adj)) { + goto servo_unlock; + } + break; + case SERVO_LOCKED: + if (priv->is_forked) { + // servo has relocked + exit(EXIT_FAILURE); + } if (clockadj_set_freq(c->clkid, -adj)) { goto servo_unlock; } @@ -670,6 +710,9 @@ int main(int argc, char *argv[]) print_set_syslog(config_get_int(cfg, NULL, "use_syslog")); print_set_level(config_get_int(cfg, NULL, "logging_level")); + priv.pidfile = config_get_string(cfg, NULL, "systemd_pidfile"); + priv.is_forked = false; + STAILQ_INIT(&priv.sinks); priv.cfg = cfg; diff --git a/ts2phc.h b/ts2phc.h index 4833ded..6529f34 100644 --- a/ts2phc.h +++ b/ts2phc.h @@ -55,6 +55,8 @@ struct ts2phc_private { bool state_changed; LIST_HEAD(port_head, ts2phc_port) ports; LIST_HEAD(clock_head, ts2phc_clock) clocks; + char *pidfile; + bool is_forked; }; struct ts2phc_clock *ts2phc_clock_add(struct ts2phc_private *priv, -- 2.34.1 _______________________________________________ Linuxptp-devel mailing list Linuxptp-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/linuxptp-devel