import OL linuxptp-4.4-1.el9_6.2

This commit is contained in:
eabdullin 2025-06-25 08:03:53 +00:00
parent 4dd6d94ed7
commit 615b7e880d
17 changed files with 979 additions and 1244 deletions

6
.gitignore vendored
View File

@ -1,3 +1,3 @@
SOURCES/clknetsim-5d1dc0.tar.gz
SOURCES/linuxptp-4.2.tgz
SOURCES/linuxptp-testsuite-bf8ead.tar.gz
SOURCES/clknetsim-64df92.tar.gz
SOURCES/linuxptp-4.4.tgz
SOURCES/linuxptp-testsuite-d27dbd.tar.gz

View File

@ -1,3 +1,3 @@
3ae72eb0ddafd8d8aeea7eac382d6e4a958717ef SOURCES/clknetsim-5d1dc0.tar.gz
309e6ab1fa3f61b3deb1735c3082dc2070870be1 SOURCES/linuxptp-4.2.tgz
7594d0705a1a648d5f7380d476bb3afebff21f6c SOURCES/linuxptp-testsuite-bf8ead.tar.gz
acb5ea319dfe96edd43033b35f025b3557b5e8a7 SOURCES/clknetsim-64df92.tar.gz
f35d56a2471359856843f7e703e5e8c681a66f0d SOURCES/linuxptp-4.4.tgz
8db14fbc7bdde436f2e957e77319569614ccf625 SOURCES/linuxptp-testsuite-d27dbd.tar.gz

View File

@ -1,176 +0,0 @@
commit 073faba77e8a82c54210941cee6023dc9e719329
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu Apr 25 14:25:57 2024 +0200
udp+udp6: Make IP addresses configurable.
Allow configuration of the multicast IPv4/IPv6 addresses, which can be
useful for testing. This complements the L2-specific ptp_dst_mac and
p2p_dst_mac options.
[ RPC: removed unused #defines PTP_PRIMARY_MCAST_IPADDR and PTP_PDELAY_MCAST_IPADDR ]
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
Signed-off-by: Richard Cochran <richardcochran@gmail.com>
diff --git a/config.c b/config.c
index d7775c0..c220a3e 100644
--- a/config.c
+++ b/config.c
@@ -299,6 +299,9 @@ struct config_item config_tab[] = {
GLOB_ITEM_INT("offsetScaledLogVariance", 0xffff, 0, UINT16_MAX),
PORT_ITEM_INT("operLogPdelayReqInterval", 0, INT8_MIN, INT8_MAX),
PORT_ITEM_INT("operLogSyncInterval", 0, INT8_MIN, INT8_MAX),
+ PORT_ITEM_STR("p2p_dst_ipv4", "224.0.0.107"),
+ PORT_ITEM_STR("p2p_dst_ipv6", "FF02:0:0:0:0:0:0:6B"),
+ PORT_ITEM_STR("p2p_dst_mac", "01:80:C2:00:00:0E"),
PORT_ITEM_INT("path_trace_enabled", 0, 0, 1),
PORT_ITEM_INT("phc_index", -1, -1, INT_MAX),
GLOB_ITEM_DBL("pi_integral_const", 0.0, 0.0, DBL_MAX),
@@ -317,8 +320,9 @@ struct config_item config_tab[] = {
GLOB_ITEM_INT("priority1", 128, 0, UINT8_MAX),
GLOB_ITEM_INT("priority2", 128, 0, UINT8_MAX),
GLOB_ITEM_STR("productDescription", ";;"),
+ PORT_ITEM_STR("ptp_dst_ipv4", "224.0.1.129"),
+ PORT_ITEM_STR("ptp_dst_ipv6", "FF0E:0:0:0:0:0:0:181"),
PORT_ITEM_STR("ptp_dst_mac", "01:1B:19:00:00:00"),
- PORT_ITEM_STR("p2p_dst_mac", "01:80:C2:00:00:0E"),
GLOB_ITEM_INT("ptp_minor_version", 0, 0, 1),
GLOB_ITEM_STR("refclock_sock_address", "/var/run/refclock.ptp.sock"),
GLOB_ITEM_STR("revisionData", ";;"),
diff --git a/configs/default.cfg b/configs/default.cfg
index 8f94c16..54ce62a 100644
--- a/configs/default.cfg
+++ b/configs/default.cfg
@@ -94,6 +94,10 @@ write_phase_mode 0
# Transport options
#
transportSpecific 0x0
+ptp_dst_ipv4 224.0.1.129
+p2p_dst_ipv4 224.0.0.107
+ptp_dst_ipv6 FF0E:0:0:0:0:0:0:181
+p2p_dst_ipv6 FF02:0:0:0:0:0:0:6B
ptp_dst_mac 01:1B:19:00:00:00
p2p_dst_mac 01:80:C2:00:00:0E
udp_ttl 1
diff --git a/ptp4l.8 b/ptp4l.8
index c59b0b4..a4eb603 100644
--- a/ptp4l.8
+++ b/ptp4l.8
@@ -397,6 +397,27 @@ This value may be changed dynamically using the
POWER_PROFILE_SETTINGS_NP management message.
The default is "none".
+.TP
+.B ptp_dst_ipv4
+The IPv4 address to which PTP messages should be sent.
+Relevant only with UDPv4 transport. The default is 224.0.1.129.
+
+.TP
+.B p2p_dst_ipv4
+The IPv4 address to which peer delay messages should be sent.
+Relevant only with UDPv4 transport. The default is 224.0.0.107.
+
+.TP
+.B ptp_dst_ipv6
+The IPv6 address to which PTP messages should be sent.
+The second byte of the address is substituted with udp6_scope.
+Relevant only with UDPv6 transport. The default is FF0E:0:0:0:0:0:0:181.
+
+.TP
+.B p2p_dst_ipv6
+The IPv6 address to which peer delay messages should be sent.
+Relevant only with UDPv6 transport. The default is FF02:0:0:0:0:0:0:6B.
+
.TP
.B ptp_dst_mac
The MAC address to which PTP messages should be sent.
diff --git a/udp.c b/udp.c
index 7c9402e..38d0ec4 100644
--- a/udp.c
+++ b/udp.c
@@ -39,8 +39,6 @@
#define EVENT_PORT 319
#define GENERAL_PORT 320
-#define PTP_PRIMARY_MCAST_IPADDR "224.0.1.129"
-#define PTP_PDELAY_MCAST_IPADDR "224.0.0.107"
struct udp {
struct transport t;
@@ -157,6 +155,7 @@ static int udp_open(struct transport *t, struct interface *iface,
const char *name = interface_name(iface);
uint8_t event_dscp, general_dscp;
int efd, gfd, ttl;
+ char *str;
ttl = config_get_int(t->cfg, name, "udp_ttl");
udp->mac.len = 0;
@@ -165,11 +164,17 @@ static int udp_open(struct transport *t, struct interface *iface,
udp->ip.len = 0;
sk_interface_addr(name, AF_INET, &udp->ip);
- if (!inet_aton(PTP_PRIMARY_MCAST_IPADDR, &mcast_addr[MC_PRIMARY]))
+ str = config_get_string(t->cfg, name, "ptp_dst_ipv4");
+ if (!inet_aton(str, &mcast_addr[MC_PRIMARY])) {
+ pr_err("invalid ptp_dst_ipv4 %s", str);
return -1;
+ }
- if (!inet_aton(PTP_PDELAY_MCAST_IPADDR, &mcast_addr[MC_PDELAY]))
+ str = config_get_string(t->cfg, name, "p2p_dst_ipv4");
+ if (!inet_aton(str, &mcast_addr[MC_PDELAY])) {
+ pr_err("invalid p2p_dst_ipv4 %s", str);
return -1;
+ }
efd = open_socket(name, mcast_addr, EVENT_PORT, ttl);
if (efd < 0)
diff --git a/udp6.c b/udp6.c
index bde1710..188d20e 100644
--- a/udp6.c
+++ b/udp6.c
@@ -40,10 +40,6 @@
#define EVENT_PORT 319
#define GENERAL_PORT 320
-/* The 0x0e in second byte is substituted with udp6_scope at runtime. */
-#define PTP_PRIMARY_MCAST_IP6ADDR "FF0E:0:0:0:0:0:0:181"
-#define PTP_PDELAY_MCAST_IP6ADDR "FF02:0:0:0:0:0:0:6B"
-
enum { MC_PRIMARY, MC_PDELAY };
struct udp6 {
@@ -167,6 +163,7 @@ static int udp6_open(struct transport *t, struct interface *iface,
const char *name = interface_name(iface);
uint8_t event_dscp, general_dscp;
int efd, gfd, hop_limit;
+ char *str;
hop_limit = config_get_int(t->cfg, name, "udp_ttl");
udp6->mac.len = 0;
@@ -175,16 +172,20 @@ static int udp6_open(struct transport *t, struct interface *iface,
udp6->ip.len = 0;
sk_interface_addr(name, AF_INET6, &udp6->ip);
- if (1 != inet_pton(AF_INET6, PTP_PRIMARY_MCAST_IP6ADDR,
- &udp6->mc6_addr[MC_PRIMARY]))
+ str = config_get_string(t->cfg, name, "ptp_dst_ipv6");
+ if (1 != inet_pton(AF_INET6, str, &udp6->mc6_addr[MC_PRIMARY])) {
+ pr_err("invalid ptp_dst_ipv6 %s", str);
return -1;
+ }
udp6->mc6_addr[MC_PRIMARY].s6_addr[1] = config_get_int(t->cfg, name,
"udp6_scope");
- if (1 != inet_pton(AF_INET6, PTP_PDELAY_MCAST_IP6ADDR,
- &udp6->mc6_addr[MC_PDELAY]))
+ str = config_get_string(t->cfg, name, "p2p_dst_ipv6");
+ if (1 != inet_pton(AF_INET6, str, &udp6->mc6_addr[MC_PDELAY])) {
+ pr_err("invalid p2p_dst_ipv6 %s", str);
return -1;
+ }
efd = open_socket_ipv6(name, udp6->mc6_addr, EVENT_PORT, &udp6->index,
hop_limit);

View File

@ -0,0 +1,275 @@
commit a0fb624d9a2bb804d85f4597b1249123c7978fed
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu Jan 16 15:04:04 2025 +0100
clock: Add variables for external grandmaster.
When ptp4l is configured to run as the server part of a bridge between
different domains, or a boundary clock split into multiple instances
(using one or two clocks), it announces its own clock identity as
grandmasterIdentity and stepsRemoved of 0. The clients of the server
don't see the true identity and distance, which can impact the result of
BMCA if there are multiple bridges/paths to the grandmaster. In the
worst case it could cause synchronization loops.
Add new variables to the clock that will override the grandmaster
identity and stepsRemoved values included in announce messages to make
it possible to pass them from the source domain as if the bridge/BC was
a single PTP instance. The variables will be used only when in the
grandmaster state and when not zero.
Runtime configuration over the UDS port will follow.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/clock.c b/clock.c
index 863e9d8..fb33ac0 100644
--- a/clock.c
+++ b/clock.c
@@ -148,6 +148,8 @@ struct clock {
int step_window_counter;
int step_window;
struct time_zone tz[MAX_TIME_ZONES];
+ struct ClockIdentity ext_gm_identity;
+ int ext_gm_steps_removed;
};
struct clock the_clock;
@@ -830,13 +832,22 @@ static int clock_compare_pds(struct parentDS *pds1, struct parentDS *pds2)
static void clock_update_grandmaster(struct clock *c)
{
struct parentDS *pds = &c->dad.pds, old_pds = *pds;
+ struct ClockIdentity gm_identity, nul_identity;
+
+ memset(&nul_identity, 0, sizeof(nul_identity));
memset(&c->cur, 0, sizeof(c->cur));
memset(c->ptl, 0, sizeof(c->ptl));
+ if (!cid_eq(&nul_identity, &c->ext_gm_identity))
+ gm_identity = c->ext_gm_identity;
+ else
+ gm_identity = c->dds.clockIdentity;
+
+ c->cur.stepsRemoved = c->ext_gm_steps_removed;
pds->parentPortIdentity.clockIdentity = c->dds.clockIdentity;
/* Follow IEEE 1588 Table 30: Updates for state decision code M1 and M2 */
pds->parentPortIdentity.portNumber = 0;
- pds->grandmasterIdentity = c->dds.clockIdentity;
+ pds->grandmasterIdentity = gm_identity;
pds->grandmasterClockQuality = c->dds.clockQuality;
pds->grandmasterPriority1 = c->dds.priority1;
pds->grandmasterPriority2 = c->dds.priority2;
commit d76e19e4ed4a4c1892b98124ffb66dba354f76ac
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu Jan 16 15:04:05 2025 +0100
Add management message for external grandmaster properties.
Add EXTERNAL_GRANDMASTER_PROPERTIES_NP management message to get and set
the new external grandmasterIdentity and stepsRemoved variables of the
clock. Together with the GRANDMASTER_SETTINGS_NP message all
grandmaster-specific values in announce messages can be now set.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/clock.c b/clock.c
index fb33ac0..7c30066 100644
--- a/clock.c
+++ b/clock.c
@@ -422,6 +422,7 @@ static int clock_management_fill_response(struct clock *c, struct port *p,
struct ptp_message *req,
struct ptp_message *rsp, int id)
{
+ struct external_grandmaster_properties_np *egpn;
struct alternate_time_offset_properties *atop;
struct alternate_time_offset_name *aton;
struct grandmaster_settings_np *gsn;
@@ -582,6 +583,12 @@ static int clock_management_fill_response(struct clock *c, struct port *p,
mtd->val = c->local_sync_uncertain;
datalen = sizeof(*mtd);
break;
+ case MID_EXTERNAL_GRANDMASTER_PROPERTIES_NP:
+ egpn = (struct external_grandmaster_properties_np *) tlv->data;
+ egpn->gmIdentity = c->ext_gm_identity;
+ egpn->stepsRemoved = c->ext_gm_steps_removed;
+ datalen = sizeof(*egpn);
+ break;
default:
/* The caller should *not* respond to this message. */
tlv_extra_recycle(extra);
@@ -620,6 +627,7 @@ static int clock_management_get_response(struct clock *c, struct port *p,
static int clock_management_set(struct clock *c, struct port *p,
int id, struct ptp_message *req, int *changed)
{
+ struct external_grandmaster_properties_np *egpn;
struct alternate_time_offset_properties *atop;
struct alternate_time_offset_name *aton;
struct grandmaster_settings_np *gsn;
@@ -710,6 +718,13 @@ static int clock_management_set(struct clock *c, struct port *p,
break;
}
break;
+ case MID_EXTERNAL_GRANDMASTER_PROPERTIES_NP:
+ egpn = (struct external_grandmaster_properties_np *) tlv->data;
+ c->ext_gm_identity = egpn->gmIdentity;
+ c->ext_gm_steps_removed = egpn->stepsRemoved;
+ *changed = 1;
+ respond = 1;
+ break;
}
if (respond && !clock_management_get_response(c, p, id, req))
pr_err("failed to send management set response");
diff --git a/pmc.c b/pmc.c
index 0b61da4..6a6a94e 100644
--- a/pmc.c
+++ b/pmc.c
@@ -158,6 +158,7 @@ static void pmc_show_signaling(struct ptp_message *msg, FILE *fp)
static void pmc_show(struct ptp_message *msg, FILE *fp)
{
+ struct external_grandmaster_properties_np *egpn;
struct alternate_time_offset_properties *atop;
struct alternate_time_offset_name *aton;
struct ieee_c37_238_settings_np *pwr;
@@ -475,6 +476,14 @@ static void pmc_show(struct ptp_message *msg, FILE *fp)
fprintf(fp, "SYNCHRONIZATION_UNCERTAIN_NP "
IFMT "uncertain %hhu", mtd->val);
break;
+ case MID_EXTERNAL_GRANDMASTER_PROPERTIES_NP:
+ egpn = (struct external_grandmaster_properties_np *) mgt->data;
+ fprintf(fp, "EXTERNAL_GRANDMASTER_PROPERTIES_NP "
+ IFMT "gmIdentity %s"
+ IFMT "stepsRemoved %hu",
+ cid2str(&egpn->gmIdentity),
+ egpn->stepsRemoved);
+ break;
case MID_PORT_DATA_SET:
p = (struct portDS *) mgt->data;
if (p->portState > PS_SLAVE) {
diff --git a/pmc_common.c b/pmc_common.c
index 7c77a10..b55e7ec 100644
--- a/pmc_common.c
+++ b/pmc_common.c
@@ -132,6 +132,7 @@ struct management_id idtab[] = {
{ "GRANDMASTER_SETTINGS_NP", MID_GRANDMASTER_SETTINGS_NP, do_set_action },
{ "SUBSCRIBE_EVENTS_NP", MID_SUBSCRIBE_EVENTS_NP, do_set_action },
{ "SYNCHRONIZATION_UNCERTAIN_NP", MID_SYNCHRONIZATION_UNCERTAIN_NP, do_set_action },
+ { "EXTERNAL_GRANDMASTER_PROPERTIES_NP", MID_EXTERNAL_GRANDMASTER_PROPERTIES_NP, do_set_action },
/* Port management ID values */
{ "NULL_MANAGEMENT", MID_NULL_MANAGEMENT, null_management },
{ "CLOCK_DESCRIPTION", MID_CLOCK_DESCRIPTION, do_get_action },
@@ -172,6 +173,7 @@ static void do_set_action(struct pmc *pmc, int action, int index, char *str)
{
int cnt, code = idtab[index].code, freq_traceable, leap_59, leap_61,
ptp_timescale, time_traceable, utc_off_valid;
+ struct external_grandmaster_properties_np egpn;
struct alternate_time_offset_properties atop;
struct ieee_c37_238_settings_np pwr;
struct grandmaster_settings_np gsn;
@@ -183,6 +185,7 @@ static void do_set_action(struct pmc *pmc, int action, int index, char *str)
char onoff_parent_data_set[4] = "off";
char onoff_cmlds[4] = "off";
char display_name[11] = {0};
+ char identity[19];
uint64_t jump;
uint8_t key;
int enable;
@@ -400,6 +403,19 @@ static void do_set_action(struct pmc *pmc, int action, int index, char *str)
IEEE_C37_238_VERSION_2017);
}
break;
+ case MID_EXTERNAL_GRANDMASTER_PROPERTIES_NP:
+ cnt = sscanf(str, " %*s %*s "
+ "gmIdentity %18s "
+ "stepsRemoved %hu ",
+ identity,
+ &egpn.stepsRemoved);
+ if (cnt != 2 || str2cid(identity, &egpn.gmIdentity)) {
+ fprintf(stderr, "%s SET needs 2 values\n",
+ idtab[index].name);
+ break;
+ }
+ pmc_send_set_action(pmc, code, &egpn, sizeof(egpn));
+ break;
}
}
@@ -704,6 +720,9 @@ static int pmc_tlv_datalen(struct pmc *pmc, int id)
case MID_POWER_PROFILE_SETTINGS_NP:
len += sizeof(struct ieee_c37_238_settings_np);
break;
+ case MID_EXTERNAL_GRANDMASTER_PROPERTIES_NP:
+ len += sizeof(struct external_grandmaster_properties_np);
+ break;
case MID_LOG_ANNOUNCE_INTERVAL:
case MID_ANNOUNCE_RECEIPT_TIMEOUT:
case MID_LOG_SYNC_INTERVAL:
diff --git a/tlv.c b/tlv.c
index b22277c..0639a71 100644
--- a/tlv.c
+++ b/tlv.c
@@ -164,6 +164,7 @@ static void alttime_offset_pre_send(struct tlv_extra *extra)
static int mgt_post_recv(struct management_tlv *m, uint16_t data_len,
struct tlv_extra *extra)
{
+ struct external_grandmaster_properties_np *egpn;
struct alternate_time_offset_properties *atop;
struct alternate_time_offset_name *aton;
struct ieee_c37_238_settings_np *pwr;
@@ -490,6 +491,12 @@ static int mgt_post_recv(struct management_tlv *m, uint16_t data_len,
NTOHL(cmlds->scaledNeighborRateRatio);
NTOHL(cmlds->as_capable);
break;
+ case MID_EXTERNAL_GRANDMASTER_PROPERTIES_NP:
+ if (data_len != sizeof(struct external_grandmaster_properties_np))
+ goto bad_length;
+ egpn = (struct external_grandmaster_properties_np *) m->data;
+ NTOHS(egpn->stepsRemoved);
+ break;
case MID_SAVE_IN_NON_VOLATILE_STORAGE:
case MID_RESET_NON_VOLATILE_STORAGE:
case MID_INITIALIZE:
@@ -513,6 +520,7 @@ bad_length:
static void mgt_pre_send(struct management_tlv *m, struct tlv_extra *extra)
{
+ struct external_grandmaster_properties_np *egpn;
struct alternate_time_offset_properties *atop;
struct ieee_c37_238_settings_np *pwr;
struct unicast_master_table_np *umtn;
@@ -688,6 +696,10 @@ static void mgt_pre_send(struct management_tlv *m, struct tlv_extra *extra)
HTONL(cmlds->scaledNeighborRateRatio);
HTONL(cmlds->as_capable);
break;
+ case MID_EXTERNAL_GRANDMASTER_PROPERTIES_NP:
+ egpn = (struct external_grandmaster_properties_np *)m->data;
+ HTONS(egpn->stepsRemoved);
+ break;
}
}
diff --git a/tlv.h b/tlv.h
index 1b464f3..f0c47ce 100644
--- a/tlv.h
+++ b/tlv.h
@@ -100,6 +100,7 @@ enum management_action {
#define MID_GRANDMASTER_SETTINGS_NP 0xC001
#define MID_SUBSCRIBE_EVENTS_NP 0xC003
#define MID_SYNCHRONIZATION_UNCERTAIN_NP 0xC006
+#define MID_EXTERNAL_GRANDMASTER_PROPERTIES_NP 0xC00D
/* Port management ID values */
#define MID_NULL_MANAGEMENT 0x0000
@@ -489,6 +490,11 @@ struct msg_interface_rate_tlv {
UInteger16 numberOfBitsAfterTimestamp;
} PACKED;
+struct external_grandmaster_properties_np {
+ struct ClockIdentity gmIdentity;
+ UInteger16 stepsRemoved;
+} PACKED;
+
/**
* Allocates a new tlv_extra structure.
* @return Pointer to a new structure on success or NULL otherwise.

View File

@ -0,0 +1,149 @@
commit f20e568cb2bf3b0ea6105e5624409f02fb9aa2bc
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu Jan 9 15:46:05 2025 +0100
ts2phc: Add option for external reference in automatic mode.
ts2phc running in the automatic mode assumes that the PPS source is one
of the PHCs used by ptp4l. That doesn't always have to be the case. Add
"external_pps" option to use the non-automatic-mode reading of the
source timestamp and don't mark sink clocks as targets if they are
synchronized by ptp4l.
The use case is holdover with an externally controlled stabilized clock.
The clock is synchronized by an external process to the PHC when it's
synchronized by ptp4l and the PHC is synchronized to the external clock
by ts2phc when not synchronized by ptp4l. Multiple PHCs can be connected
to the external clock to make a JBOD boundary clock.
Don't require the PHC that is synchronized by ptp4l to be receiving the
PPS signal (providing timestamps) to make it possible to switch the PPS
direction to synchronize the external clock if there is only one
usable channel.
(This is a RHEL-specific downstream patch using "ts2phc.rh_external_pps"
name for the option to make it clear it's considered experimental.)
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/config.c b/config.c
index cbff976..fedc3a0 100644
--- a/config.c
+++ b/config.c
@@ -368,6 +368,7 @@ struct config_item config_tab[] = {
GLOB_ITEM_ENU("time_stamping", TS_HARDWARE, timestamping_enu),
PORT_ITEM_INT("transportSpecific", 0, 0, 0x0F),
PORT_ITEM_INT("ts2phc.channel", 0, 0, INT_MAX),
+ GLOB_ITEM_INT("ts2phc.rh_external_pps", 0, 0, 1),
PORT_ITEM_INT("ts2phc.extts_correction", 0, INT_MIN, INT_MAX),
PORT_ITEM_ENU("ts2phc.extts_polarity", PTP_RISING_EDGE, extts_polarity_enu),
PORT_ITEM_INT("ts2phc.holdover", 0, 0, INT_MAX),
diff --git a/ts2phc.8 b/ts2phc.8
index f17ed71..d57402c 100644
--- a/ts2phc.8
+++ b/ts2phc.8
@@ -287,6 +287,20 @@ the SERVO_LOCKED_STABLE state. The servo state needs be enabled by the
\fB-a\fP option and when \fBts2phc.extts_polarity\fP is set to \fIboth\fP.
The default is 0 (disabled).
+.TP
+.B ts2phc.rh_external_pps
+This is a RHEL-specific experimental option which can be renamed,
+removed, or the functionality changed, in a future minor update.
+
+It enables an external reference with the \fB-a\fP option. If set to 1, ts2phc
+will assume the source of the PPS signal is a different clock from the PHCs
+used by ptp4l (configured with the \fBboundary_clock_jbod\fP option). The use
+case is a holdover using an externally controlled stabilized clock, which is
+expected to be synchronized to the PHC that is synchronized by ptp4l, and
+running free when ptp4l is not synchronizing any of the PHCs. Note that it is a
+different holdover than the one enabled by the \fBts2phc.holdover\fP option
+below. The default is 0 (disabled).
+
.TP
.B ts2phc.nmea_delay
Specifies the minimum expected delay of NMEA RMC messages in nanoseconds.
diff --git a/ts2phc.c b/ts2phc.c
index 39d31b6..5448496 100644
--- a/ts2phc.c
+++ b/ts2phc.c
@@ -348,18 +348,26 @@ static void ts2phc_reconfigure(struct ts2phc_private *priv)
}
num_target_clocks++;
break;
- case PS_UNCALIBRATED:
- num_ref_clocks++;
- break;
case PS_SLAVE:
ref_clk = c;
+ /* Fall through */
+ case PS_UNCALIBRATED:
num_ref_clocks++;
+ if (priv->external_pps && c->is_target) {
+ pr_info("unselecting %s for synchronization",
+ c->name);
+ c->is_target = false;
+ }
break;
default:
break;
}
last = c;
}
+ if (priv->external_pps) {
+ pr_info("assuming external reference clock");
+ return;
+ }
if (num_target_clocks >= 1 && !ref_clk) {
priv->ref_clock = last;
priv->ref_clock->is_target = false;
@@ -447,7 +455,7 @@ static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg)
struct ts2phc_clock *c;
int holdover, valid;
- if (autocfg) {
+ if (autocfg && !priv->external_pps) {
if (!priv->ref_clock) {
pr_debug("no reference clock, skipping");
return;
@@ -787,6 +795,8 @@ int main(int argc, char *argv[])
return -1;
}
+ priv.external_pps = config_get_int(cfg, NULL, "ts2phc.rh_external_pps");
+
priv.holdover_length = config_get_int(cfg, NULL, "ts2phc.holdover");
priv.holdover_start = 0;
diff --git a/ts2phc.h b/ts2phc.h
index 5dbde9b..63e6122 100644
--- a/ts2phc.h
+++ b/ts2phc.h
@@ -52,6 +52,7 @@ struct ts2phc_private {
struct config *cfg;
struct pmc_agent *agent;
struct ts2phc_clock *ref_clock;
+ bool external_pps;
bool state_changed;
LIST_HEAD(port_head, ts2phc_port) ports;
LIST_HEAD(clock_head, ts2phc_clock) clocks;
diff --git a/ts2phc_pps_sink.c b/ts2phc_pps_sink.c
index af34e39..9076de9 100644
--- a/ts2phc_pps_sink.c
+++ b/ts2phc_pps_sink.c
@@ -441,6 +441,15 @@ int ts2phc_pps_sink_poll(struct ts2phc_private *priv)
all_sinks_have_events = true;
for (i = 0; i < priv->n_sinks; i++) {
+ /*
+ * In the external PPS mode don't require non-target
+ * clocks to be receiving PPS to allow switching the
+ * PPS direction to synchronize the external clock.
+ */
+ if (priv->external_pps &&
+ !polling_array->sink[i]->clock->is_target)
+ continue;
+
if (!polling_array->collected_events[i]) {
all_sinks_have_events = false;
break;

View File

@ -1,360 +0,0 @@
commit c7acd4b5bdb95e45d93833ffaac9cac51dfe934b
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu May 16 15:52:48 2024 +0200
ts2phc: Avoid unnecessary call of getppstime().
Don't get the ToD timestamp for the pulse polarity detection if it won't
be needed (i.e. extts_polarity is not "both"). This allows PPS
timestamps to be saved even when the ToD source fails.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/ts2phc_pps_sink.c b/ts2phc_pps_sink.c
index 0d399b8..76912a2 100644
--- a/ts2phc_pps_sink.c
+++ b/ts2phc_pps_sink.c
@@ -277,21 +277,22 @@ static enum extts_result ts2phc_pps_sink_event(struct ts2phc_private *priv,
goto out;
}
- err = ts2phc_pps_source_getppstime(priv->src, &source_ts);
- if (err < 0) {
- pr_debug("source ts not valid");
- return 0;
- }
-
- if (sink->polarity == (PTP_RISING_EDGE | PTP_FALLING_EDGE) &&
- ts2phc_pps_sink_ignore(priv, sink, source_ts)) {
+ if (sink->polarity == (PTP_RISING_EDGE | PTP_FALLING_EDGE)) {
+ err = ts2phc_pps_source_getppstime(priv->src, &source_ts);
+ if (err < 0) {
+ pr_debug("source ts not valid");
+ return 0;
+ }
- pr_debug("%s SKIP extts index %u at %lld.%09u src %" PRIi64 ".%ld",
- sink->name, event.index, event.t.sec, event.t.nsec,
- (int64_t) source_ts.tv_sec, source_ts.tv_nsec);
+ if (ts2phc_pps_sink_ignore(priv, sink, source_ts)) {
+ pr_debug("%s SKIP extts index %u at %lld.%09u src %" PRIi64 ".%ld",
+ sink->name, event.index, event.t.sec,
+ event.t.nsec, (int64_t)source_ts.tv_sec,
+ source_ts.tv_nsec);
- result = EXTTS_IGNORE;
- goto out;
+ result = EXTTS_IGNORE;
+ goto out;
+ }
}
out:
commit 9880cccc928351a42a3fa9e018949442aca7ddae
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu May 16 15:52:49 2024 +0200
ts2phc: Add holdover support.
If the external PPS signal is generated by a clock with better long-term
stability than the clock synchronizing to it, it can work as a good time
source for some period of time after losing its own time source (e.g.
GPS receiver losing its signal).
Add an option to specify a holdover interval where ts2phc can continue
synchronizing the clock without any ToD information. Allow that only in
the LOCKED_STABLE servo state, which needs to be enabled by the
servo_num_offset_values option.
This is supported only in the non-automatic mode and when the pulse
polarity detection is disabled.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/config.c b/config.c
index c220a3e..58481db 100644
--- a/config.c
+++ b/config.c
@@ -344,6 +344,7 @@ struct config_item config_tab[] = {
PORT_ITEM_INT("ts2phc.channel", 0, 0, INT_MAX),
PORT_ITEM_INT("ts2phc.extts_correction", 0, INT_MIN, INT_MAX),
PORT_ITEM_ENU("ts2phc.extts_polarity", PTP_RISING_EDGE, extts_polarity_enu),
+ PORT_ITEM_INT("ts2phc.holdover", 0, 0, INT_MAX),
PORT_ITEM_INT("ts2phc.master", 0, 0, 1),
PORT_ITEM_INT("ts2phc.nmea_baudrate", 9600, 300, INT_MAX),
GLOB_ITEM_STR("ts2phc.nmea_remote_host", ""),
diff --git a/ts2phc.8 b/ts2phc.8
index 852a527..bcc6f61 100644
--- a/ts2phc.8
+++ b/ts2phc.8
@@ -168,6 +168,16 @@ by changing the clock frequency instead of stepping the clock. When
set to 0.0, the servo will never step the clock except on start.
The default is 0.0.
+.TP
+.B ts2phc.holdover
+The holdover interval, specified in seconds. When the ToD information stops
+working (e.g. GNSS receiver lost its fix), ts2phc is allowed for the specified
+interval to continue synchronizing the target clock as long as the servo is in
+the SERVO_LOCKED_STABLE state. The servo state needs be enabled by the
+\fBservo_num_offset_values\fP option. The holdover is not supported with the
+\fB-a\fP option and when \fBts2phc.extts_polarity\fP is set to \fIboth\fP.
+The default is 0 (disabled).
+
.TP
.B ts2phc.nmea_remote_host, ts2phc.nmea_remote_port
Specifies the remote host providing ToD information when using the
diff --git a/ts2phc.c b/ts2phc.c
index 4817c85..d552e0f 100644
--- a/ts2phc.c
+++ b/ts2phc.c
@@ -440,9 +440,10 @@ static int ts2phc_pps_source_implicit_tstamp(struct ts2phc_private *priv,
static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg)
{
+ struct timespec source_ts, now;
tmv_t source_tmv;
struct ts2phc_clock *c;
- int valid, err;
+ int holdover, valid;
if (autocfg) {
if (!priv->ref_clock) {
@@ -456,9 +457,20 @@ static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg)
return;
}
} else {
- err = ts2phc_pps_source_implicit_tstamp(priv, &source_tmv);
- if (err < 0)
+ valid = !ts2phc_pps_source_implicit_tstamp(priv, &source_tmv);
+ }
+
+ if (valid) {
+ priv->holdover_start = 0;
+ holdover = 0;
+ } else {
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ if (!priv->holdover_start)
+ priv->holdover_start = now.tv_sec;
+ if (now.tv_sec >= priv->holdover_start + priv->holdover_length)
return;
+ holdover = 1;
}
LIST_FOREACH(c, &priv->clocks, list) {
@@ -475,6 +487,16 @@ static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg)
continue;
}
+ if (holdover) {
+ if (c->servo_state != SERVO_LOCKED_STABLE)
+ continue;
+ source_ts = tmv_to_timespec(ts);
+ if (source_ts.tv_nsec > NS_PER_SEC / 2)
+ source_ts.tv_sec++;
+ source_ts.tv_nsec = 0;
+ source_tmv = timespec_to_tmv(source_ts);
+ }
+
offset = tmv_to_nanoseconds(tmv_sub(ts, source_tmv));
if (c->no_adj) {
@@ -486,8 +508,15 @@ static void ts2phc_synchronize_clocks(struct ts2phc_private *priv, int autocfg)
adj = servo_sample(c->servo, offset, tmv_to_nanoseconds(ts),
SAMPLE_WEIGHT, &c->servo_state);
- pr_info("%s offset %10" PRId64 " s%d freq %+7.0f",
- c->name, offset, c->servo_state, adj);
+ if (holdover && c->servo_state != SERVO_LOCKED_STABLE) {
+ pr_info("%s lost holdover lock (offset %10" PRId64 ")",
+ c->name, offset);
+ continue;
+ }
+
+ pr_info("%s offset %10" PRId64 " s%d freq %+7.0f%s",
+ c->name, offset, c->servo_state, adj,
+ holdover ? " holdover" : "");
switch (c->servo_state) {
case SERVO_UNLOCKED:
@@ -751,6 +780,9 @@ int main(int argc, char *argv[])
return -1;
}
+ priv.holdover_length = config_get_int(cfg, NULL, "ts2phc.holdover");
+ priv.holdover_start = 0;
+
while (is_running()) {
struct ts2phc_clock *c;
diff --git a/ts2phc.h b/ts2phc.h
index 4833ded..5dbde9b 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;
+ int holdover_length;
+ time_t holdover_start;
};
struct ts2phc_clock *ts2phc_clock_add(struct ts2phc_private *priv,
commit bf237fd55d4983a42d9344890dd861f18bea70ca
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu Jul 25 12:43:59 2024 +0200
ts2phc: Describe servo options in man page.
Copy and adapt missing servo options used by ts2phc from the ptp4l man
page.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/ts2phc.8 b/ts2phc.8
index bcc6f61..4c54576 100644
--- a/ts2phc.8
+++ b/ts2phc.8
@@ -117,6 +117,16 @@ command line option.
.SH GLOBAL OPTIONS
+.TP
+.B clock_servo
+The servo which is used to synchronize the local clock. Valid values
+are "pi" for a PI controller, "linreg" for an adaptive controller
+using linear regression, "ntpshm" and "refclock_sock" for the NTP SHM and
+chrony SOCK reference clocks respectively to allow another process to
+synchronize the local clock, and "nullf" for a servo that always dials
+frequency offset zero (for use in SyncE nodes).
+The default is "pi".
+
.TP
.B first_step_threshold
The maximum offset, specified in seconds, that the servo will correct by
@@ -162,12 +172,64 @@ with the log level of the message as a number. The default is an empty string
argument).
.TP
-.B step_threshold
-The maximum offset, specified in seconds, that the servo will correct
-by changing the clock frequency instead of stepping the clock. When
-set to 0.0, the servo will never step the clock except on start.
+.B ntpshm_segment
+The number of the SHM segment used by ntpshm servo.
+The default is 0.
+
+.TP
+.B pi_integral_const
+The integral constant of the PI controller. When set to 0.0, the
+integral constant will be set by the following formula from the current
+sync interval.
+The default is 0.0.
+
+ki = min(ki_scale * sync^ki_exponent, ki_norm_max / sync)
+
+.TP
+.B pi_integral_exponent
+The ki_exponent constant in the formula used to set the integral constant of
+the PI controller from the sync interval.
+The default is 0.4.
+
+.TP
+.B pi_integral_norm_max
+The ki_norm_max constant in the formula used to set the integral constant of
+the PI controller from the sync interval.
+The default is 0.3.
+
+.TP
+.B pi_integral_scale
+The ki_scale constant in the formula used to set the integral constant of
+the PI controller from the sync interval.
+The default is 0.3.
+
+.TP
+.B pi_proportional_const
+The proportional constant of the PI controller. When set to 0.0, the
+proportional constant will be set by the following formula from the current
+sync interval.
The default is 0.0.
+kp = min(kp_scale * sync^kp_exponent, kp_norm_max / sync)
+
+.TP
+.B pi_proportional_exponent
+The kp_exponent constant in the formula used to set the proportional constant of
+the PI controller from the sync interval.
+The default is \-0.3.
+
+.TP
+.B pi_proportional_norm_max
+The kp_norm_max constant in the formula used to set the proportional constant of
+the PI controller from the sync interval.
+The default is 0.7
+
+.TP
+.B pi_proportional_scale
+The kp_scale constant in the formula used to set the proportional constant of
+the PI controller from the sync interval.
+The default is 0.7.
+
.TP
.B ts2phc.holdover
The holdover interval, specified in seconds. When the ToD information stops
@@ -178,6 +240,28 @@ the SERVO_LOCKED_STABLE state. The servo state needs be enabled by the
\fB-a\fP option and when \fBts2phc.extts_polarity\fP is set to \fIboth\fP.
The default is 0 (disabled).
+.TP
+.B servo_num_offset_values
+The number of offset values considered in order to transition from the
+SERVO_LOCKED to the SERVO_LOCKED_STABLE state.
+The transition occurs once the last 'servo_num_offset_values' offsets
+are all below the 'servo_offset_threshold' value.
+The default value is 10.
+
+.TP
+.B servo_offset_threshold
+The offset threshold used in order to transition from the SERVO_LOCKED
+to the SERVO_LOCKED_STABLE state. The transition occurs once the
+last 'servo_num_offset_values' offsets are all below the threshold value.
+The default value of offset_threshold is 0 (disabled).
+
+.TP
+.B step_threshold
+The maximum offset, specified in seconds, that the servo will correct
+by changing the clock frequency instead of stepping the clock. When
+set to 0.0, the servo will never step the clock except on start.
+The default is 0.0.
+
.TP
.B ts2phc.nmea_remote_host, ts2phc.nmea_remote_port
Specifies the remote host providing ToD information when using the
commit 7734d2fe8ac52afaa233262548615f79021ae6ee
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu Jul 25 12:46:43 2024 +0200
ts2phc: Fix description of holdover option in man page.
Fix the man page to explain that the LOCKED_STABLE servo state is
enabled by setting the servo_offset_threshold, not
servo_num_offset_values, which is already enabled by default.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/ts2phc.8 b/ts2phc.8
index 4c54576..c0b718b 100644
--- a/ts2phc.8
+++ b/ts2phc.8
@@ -236,7 +236,7 @@ The holdover interval, specified in seconds. When the ToD information stops
working (e.g. GNSS receiver lost its fix), ts2phc is allowed for the specified
interval to continue synchronizing the target clock as long as the servo is in
the SERVO_LOCKED_STABLE state. The servo state needs be enabled by the
-\fBservo_num_offset_values\fP option. The holdover is not supported with the
+\fBservo_offset_threshold\fP option. The holdover is not supported with the
\fB-a\fP option and when \fBts2phc.extts_polarity\fP is set to \fIboth\fP.
The default is 0 (disabled).

View File

@ -1,75 +0,0 @@
commit bbfaa1e253b889aeea97702bbbc87e731e0caf87
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu Feb 22 13:51:59 2024 +0100
lstab: Limit number of parsed leap seconds.
The lstab structure has a fixed-size array for leap seconds
(currently 28 + 200). Don't read more leap seconds from the leapfile to
avoid corrupting memory.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/lstab.c b/lstab.c
index 24add26..8e35504 100644
--- a/lstab.c
+++ b/lstab.c
@@ -137,7 +137,7 @@ static int lstab_read(struct lstab *lstab, const char *name)
fprintf(stderr, "failed to open '%s' for reading: %m\n", name);
return -1;
}
- while (1) {
+ while (index < N_LEAPS) {
if (!fgets(buf, sizeof(buf), fp)) {
break;
}
commit 90ad2efc74b0f348fb6b417565b3ada7d161641b
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu Feb 22 13:56:53 2024 +0100
lstab: Don't free lstab on update.
The modification timestamp of the leapfile is checked with every
call of lstab_utc2tai(). If the file is modified, the provided lstab
structure is freed and a new one is allocated from the updated leapfile.
But the new lstab is not returned to the caller as the function doesn't
accept a pointer to the pointer to lstab. This causes reading from the
freed memory and leak of the newly allocated memory.
Modify update_leapsecond_table() to read the updated leapfile into the
existing lstab structure instead of the reallocation.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/lstab.c b/lstab.c
index 8e35504..357ed27 100644
--- a/lstab.c
+++ b/lstab.c
@@ -195,7 +195,6 @@ struct lstab *lstab_create(const char *filename)
int update_leapsecond_table(struct lstab *lstab)
{
- const char* leapfile;
struct stat statbuf;
int err;
@@ -212,14 +211,14 @@ int update_leapsecond_table(struct lstab *lstab)
return 0;
}
printf("updating leap seconds file\n");
- leapfile = lstab->leapfile;
- lstab_destroy(lstab);
- lstab = lstab_create(leapfile);
- if (!lstab) {
+ if (lstab_read(lstab, lstab->leapfile)) {
+ lstab->length = 0;
return -1;
}
+ lstab->lsfile_mtime = statbuf.st_mtim.tv_sec;
+
return 0;
}

View File

@ -1,365 +0,0 @@
commit 0c406008b530140ed6d992915a6c8a1e5abf15d5
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu Jan 25 11:26:15 2024 +0100
ts2phc: Don't switch system clock to nanosecond mode.
ts2phc is not synchronizing the system clock and should not switch the
clock to the nanosecond mode with adjtimex(modes=ADJ_NANO) or make any
other modifications to it. The process that is controlling the clock
(e.g. an NTP client) might not be using the nanosecond mode.
There are two instances of the adjtimex() call in the code. One is used
only to read the clock and can be replaced with faster clock_gettime().
The other instance is also reading the TAI offset. Instead of switching
to the nanosecond mode, change the timestamp conversion to handle both
microsecond and nanosecond modes according to the current clock status.
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/ts2phc_generic_pps_source.c b/ts2phc_generic_pps_source.c
index d503aac..e6b8145 100644
--- a/ts2phc_generic_pps_source.c
+++ b/ts2phc_generic_pps_source.c
@@ -38,7 +38,6 @@ static int get_ntx(struct timex *ntx)
return -1;
memset(ntx, 0, sizeof(*ntx));
- ntx->modes = ADJ_NANO;
code = adjtimex(ntx);
if (code == -1) {
pr_err("adjtimex failed: %m");
@@ -93,7 +92,10 @@ static int ts2phc_generic_pps_source_getppstime(struct ts2phc_pps_source *src,
}
ts->tv_sec = ntx.time.tv_sec + tai_offset;
- ts->tv_nsec = ntx.time.tv_usec;
+ if (ntx.status & STA_NANO)
+ ts->tv_nsec = ntx.time.tv_usec;
+ else
+ ts->tv_nsec = ntx.time.tv_usec * 1000;
return 0;
}
diff --git a/ts2phc_nmea_pps_source.c b/ts2phc_nmea_pps_source.c
index 3a4267d..5b2e06b 100644
--- a/ts2phc_nmea_pps_source.c
+++ b/ts2phc_nmea_pps_source.c
@@ -62,14 +62,13 @@ static int open_nmea_connection(const char *host, const char *port,
static void *monitor_nmea_status(void *arg)
{
+ struct timespec rxtime, rxtime_rt, tmo = { 2, 0 };
struct nmea_parser *np = nmea_parser_create();
struct pollfd pfd = { -1, POLLIN | POLLPRI };
char *host, input[256], *port, *ptr, *uart;
struct ts2phc_nmea_pps_source *s = arg;
- struct timespec rxtime, tmo = { 2, 0 };
int cnt, num, parsed, baud;
struct nmea_rmc rmc;
- struct timex ntx;
if (!np) {
pr_err("failed to create NMEA parser");
@@ -79,8 +78,6 @@ static void *monitor_nmea_status(void *arg)
port = config_get_string(s->config, NULL, "ts2phc.nmea_remote_port");
uart = config_get_string(s->config, NULL, "ts2phc.nmea_serialport");
baud = config_get_int(s->config, NULL, "ts2phc.nmea_baudrate");
- memset(&ntx, 0, sizeof(ntx));
- ntx.modes = ADJ_NANO;
while (is_running()) {
if (pfd.fd == -1) {
@@ -92,7 +89,7 @@ static void *monitor_nmea_status(void *arg)
}
num = poll(&pfd, 1, NMEA_TMO);
clock_gettime(CLOCK_MONOTONIC, &rxtime);
- adjtimex(&ntx);
+ clock_gettime(CLOCK_REALTIME, &rxtime_rt);
if (num < 0) {
pr_err("poll failed");
break;
@@ -124,8 +121,7 @@ static void *monitor_nmea_status(void *arg)
if (!nmea_parse(np, ptr, cnt, &rmc, &parsed)) {
pthread_mutex_lock(&s->mutex);
s->local_monotime = rxtime;
- s->local_utctime.tv_sec = ntx.time.tv_sec;
- s->local_utctime.tv_nsec = ntx.time.tv_usec;
+ s->local_utctime = rxtime_rt;
s->rmc_utctime = rmc.ts;
s->rmc_fix_valid = rmc.fix_valid;
pthread_mutex_unlock(&s->mutex);
commit 72b44bc885e519667a12c89d5b640484807e4946
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Tue Jun 4 08:57:15 2024 +0200
ts2phc: Use CLOCK_MONOTONIC_RAW for NMEA PPS timestamp.
In the calculation of the NMEA PPS timestamp is used an interval
measured by the CLOCK_MONOTONIC system clock. This clock may have a
large frequency error when another process (e.g. phc2sys or an NTP
client) is correcting a large time error by slewing.
This frequency error may cause the timestamp to overflow into the next
second and cause a one-second error in the measured offset, or the wrong
edge of the pulse to be rejected.
Switch from CLOCK_MONOTONIC to CLOCK_MONOTONIC_RAW to avoid the
impact of the system clock adjustments.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/ts2phc_nmea_pps_source.c b/ts2phc_nmea_pps_source.c
index 5b2e06b..7a28433 100644
--- a/ts2phc_nmea_pps_source.c
+++ b/ts2phc_nmea_pps_source.c
@@ -88,7 +88,7 @@ static void *monitor_nmea_status(void *arg)
}
}
num = poll(&pfd, 1, NMEA_TMO);
- clock_gettime(CLOCK_MONOTONIC, &rxtime);
+ clock_gettime(CLOCK_MONOTONIC_RAW, &rxtime);
clock_gettime(CLOCK_REALTIME, &rxtime_rt);
if (num < 0) {
pr_err("poll failed");
@@ -160,7 +160,7 @@ static int ts2phc_nmea_pps_source_getppstime(struct ts2phc_pps_source *src,
int64_t utc_time;
bool fix_valid;
- clock_gettime(CLOCK_MONOTONIC, &now);
+ clock_gettime(CLOCK_MONOTONIC_RAW, &now);
local_t2 = timespec_to_tmv(now);
pthread_mutex_lock(&m->mutex);
commit 30e6c4dba892236d8cfe08dc6c55238e11504c71
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Mon Jun 3 10:32:05 2024 +0200
ts2phc: Provide source type.
Save the PPS source type in the instance and add a function to retrieve
it. This will be needed for NMEA-specific rounding of timestamps.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/ts2phc_pps_source.c b/ts2phc_pps_source.c
index c333f65..ae2ad46 100644
--- a/ts2phc_pps_source.c
+++ b/ts2phc_pps_source.c
@@ -26,6 +26,8 @@ struct ts2phc_pps_source *ts2phc_pps_source_create(struct ts2phc_private *priv,
src = ts2phc_phc_pps_source_create(priv, dev);
break;
}
+ if (src)
+ src->type = type;
return src;
}
@@ -46,3 +48,8 @@ struct ts2phc_clock *ts2phc_pps_source_get_clock(struct ts2phc_pps_source *src)
return NULL;
}
+
+enum ts2phc_pps_source_type ts2phc_pps_source_get_type(struct ts2phc_pps_source *src)
+{
+ return src->type;
+}
diff --git a/ts2phc_pps_source.h b/ts2phc_pps_source.h
index 293c693..c87e3af 100644
--- a/ts2phc_pps_source.h
+++ b/ts2phc_pps_source.h
@@ -53,4 +53,11 @@ int ts2phc_pps_source_getppstime(struct ts2phc_pps_source *src, struct timespec
struct ts2phc_clock *ts2phc_pps_source_get_clock(struct ts2phc_pps_source *src);
+/**
+ * Returns the type of the PPS source
+ * @param src Pointer to a source obtained via @ref ts2phc_pps_source_create().
+ * @return The type of the clock.
+ */
+enum ts2phc_pps_source_type ts2phc_pps_source_get_type(struct ts2phc_pps_source *src);
+
#endif
diff --git a/ts2phc_pps_source_private.h b/ts2phc_pps_source_private.h
index 99e6a78..ea6a8ad 100644
--- a/ts2phc_pps_source_private.h
+++ b/ts2phc_pps_source_private.h
@@ -13,6 +13,7 @@
#include "ts2phc_pps_source.h"
struct ts2phc_pps_source {
+ enum ts2phc_pps_source_type type;
void (*destroy)(struct ts2phc_pps_source *src);
int (*getppstime)(struct ts2phc_pps_source *src, struct timespec *ts);
struct ts2phc_clock *(*get_clock)(struct ts2phc_pps_source *src);
commit bebd15ae90dbfcb74a33e5b428f24c733abf1134
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Mon Jun 3 11:06:16 2024 +0200
ts2phc: Fix edge rejection for pulse widths over 0.5s.
If the configured pulse width is longer than 0.5 seconds, the calculated
range of ignored offsets is too small to cover the wrong edge. Fix the
calculation of the limits to use the minimum of pulsewidth and
(1.0s - pulsewidth). A pulsewidth of 0.5s should give the shortest interval.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/ts2phc_pps_sink.c b/ts2phc_pps_sink.c
index 05ac225..ca9f721 100644
--- a/ts2phc_pps_sink.c
+++ b/ts2phc_pps_sink.c
@@ -175,6 +175,8 @@ static struct ts2phc_pps_sink *ts2phc_pps_sink_create(struct ts2phc_private *pri
sink->correction = nanoseconds_to_tmv(correction);
pulsewidth = config_get_int(cfg, device, "ts2phc.pulsewidth");
+ if (pulsewidth > 500000000)
+ pulsewidth = 1000000000 - pulsewidth;
pulsewidth /= 2;
sink->ignore_upper = 1000000000 - pulsewidth;
sink->ignore_lower = pulsewidth;
commit 435e9fc6ebec8daa8ab9f88c2d590e35ace9b2f6
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Mon Jun 3 13:57:23 2024 +0200
ts2phc: Move upper/lower rejection limit calculation.
The ignore_upper and ignore_lower fields of the ts2phc_pps_sink struct
are calculated when the PPS source is not known yet. Replace the fields
with the configured pulsewidth and calculate the limits locally later
when needed in ts2phc_pps_sink_ignore(). This will allow an
NMEA-specific calculation of the limits.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/ts2phc_pps_sink.c b/ts2phc_pps_sink.c
index ca9f721..8121afb 100644
--- a/ts2phc_pps_sink.c
+++ b/ts2phc_pps_sink.c
@@ -30,8 +30,7 @@ struct ts2phc_pps_sink {
struct ptp_pin_desc pin_desc;
unsigned int polarity;
tmv_t correction;
- uint32_t ignore_lower;
- uint32_t ignore_upper;
+ uint32_t pulsewidth;
struct ts2phc_clock *clock;
};
@@ -153,8 +152,8 @@ static struct ts2phc_pps_sink *ts2phc_pps_sink_create(struct ts2phc_private *pri
struct config *cfg = priv->cfg;
struct ptp_extts_request extts;
struct ts2phc_pps_sink *sink;
- int err, pulsewidth;
int32_t correction;
+ int err;
sink = calloc(1, sizeof(*sink));
if (!sink) {
@@ -174,12 +173,9 @@ static struct ts2phc_pps_sink *ts2phc_pps_sink_create(struct ts2phc_private *pri
correction = config_get_int(cfg, device, "ts2phc.extts_correction");
sink->correction = nanoseconds_to_tmv(correction);
- pulsewidth = config_get_int(cfg, device, "ts2phc.pulsewidth");
- if (pulsewidth > 500000000)
- pulsewidth = 1000000000 - pulsewidth;
- pulsewidth /= 2;
- sink->ignore_upper = 1000000000 - pulsewidth;
- sink->ignore_lower = pulsewidth;
+ sink->pulsewidth = config_get_int(cfg, device, "ts2phc.pulsewidth");
+ if (sink->pulsewidth > 500000000)
+ sink->pulsewidth = 1000000000 - sink->pulsewidth;
sink->clock = ts2phc_clock_add(priv, device);
if (!sink->clock) {
@@ -243,12 +239,16 @@ static bool ts2phc_pps_sink_ignore(struct ts2phc_private *priv,
struct timespec source_ts)
{
tmv_t source_tmv = timespec_to_tmv(source_ts);
+ uint32_t ignore_lower, ignore_upper;
source_tmv = tmv_sub(source_tmv, priv->perout_phase);
source_ts = tmv_to_timespec(source_tmv);
- return source_ts.tv_nsec > sink->ignore_lower &&
- source_ts.tv_nsec < sink->ignore_upper;
+ ignore_upper = 1000000000 - sink->pulsewidth / 2;
+ ignore_lower = sink->pulsewidth / 2;
+
+ return source_ts.tv_nsec > ignore_lower &&
+ source_ts.tv_nsec < ignore_upper;
}
static enum extts_result ts2phc_pps_sink_event(struct ts2phc_private *priv,
commit 0257b245df1a32869f356c0cfbeacfe5f0a522f5
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Mon Jun 3 11:31:45 2024 +0200
ts2phc: Allow longer NMEA delays.
Timestamps from PPS sources are rounded to the nearest pulse, i.e. for
correct synchronization the offset between the source timestamp and
external PPS event timestamp needs to be between -0.5 and +0.5 seconds.
For the pulse edge rejection to work correctly it needs to be even
smaller, between -pulsewidth/2 and +pulsewidth/2.
With the NMEA PPS source the offset is the delay in the reception of the
RMC message. This message is not expected to come exactly on time with
the corresponding pulse. A positive delay is expected, typically at
least few tens of milliseconds, but some receivers have delays longer
than 0.5 seconds.
Add NMEA-specific rounding of the offset to not expect negative delays
and allow positive delays of up to 1 second, or whole pulsewidth if the
edge rejection is enabled.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/ts2phc.c b/ts2phc.c
index 03c88b1..4817c85 100644
--- a/ts2phc.c
+++ b/ts2phc.c
@@ -418,9 +418,16 @@ static int ts2phc_pps_source_implicit_tstamp(struct ts2phc_private *priv,
* deduce the timestamp (actually only seconds part, nanoseconds are by
* construction zero) of this edge at the emitter based on the
* emitter's current time.
+ *
+ * With an NMEA source assume its messages always follow the pulse, i.e.
+ * assign the timestamp to the previous pulse instead of nearest pulse.
*/
- if (source_ts.tv_nsec > NS_PER_SEC / 2)
+ if (ts2phc_pps_source_get_type(priv->src) == TS2PHC_PPS_SOURCE_NMEA) {
source_ts.tv_sec++;
+ } else {
+ if (source_ts.tv_nsec > NS_PER_SEC / 2)
+ source_ts.tv_sec++;
+ }
source_ts.tv_nsec = 0;
tmv = timespec_to_tmv(source_ts);
diff --git a/ts2phc_pps_sink.c b/ts2phc_pps_sink.c
index 8121afb..0d399b8 100644
--- a/ts2phc_pps_sink.c
+++ b/ts2phc_pps_sink.c
@@ -244,8 +244,13 @@ static bool ts2phc_pps_sink_ignore(struct ts2phc_private *priv,
source_tmv = tmv_sub(source_tmv, priv->perout_phase);
source_ts = tmv_to_timespec(source_tmv);
- ignore_upper = 1000000000 - sink->pulsewidth / 2;
- ignore_lower = sink->pulsewidth / 2;
+ if (ts2phc_pps_source_get_type(priv->src) == TS2PHC_PPS_SOURCE_NMEA) {
+ ignore_upper = sink->pulsewidth;
+ ignore_lower = 0;
+ } else {
+ ignore_upper = 1000000000 - sink->pulsewidth / 2;
+ ignore_lower = sink->pulsewidth / 2;
+ }
return source_ts.tv_nsec > ignore_lower &&
source_ts.tv_nsec < ignore_upper;

View File

@ -1,83 +0,0 @@
commit 0d508d8cfb96cc32d3516f1d0d3e80db49f00468
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Mon Jun 3 16:43:54 2024 +0200
ts2phc: Add option to correct for NMEA delay.
Add an option to specify the minimum expected delay of NMEA RMC messages
to correct timestamps returned by the NMEA PPS time source.
This enables operation with receivers that have delays outside of the
expected 0-1.0s interval, or 0-pulsewidth if the PPS edge rejection is
enabled.
[ RPC: Preserve alphabetical ordering in the ts2phc man page. ]
(Rebased to 4.2)
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
Signed-off-by: Richard Cochran <richardcochran@gmail.com>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
diff --git a/config.c b/config.c
index 58481db..d441d1e 100644
--- a/config.c
+++ b/config.c
@@ -347,6 +347,7 @@ struct config_item config_tab[] = {
PORT_ITEM_INT("ts2phc.holdover", 0, 0, INT_MAX),
PORT_ITEM_INT("ts2phc.master", 0, 0, 1),
PORT_ITEM_INT("ts2phc.nmea_baudrate", 9600, 300, INT_MAX),
+ PORT_ITEM_INT("ts2phc.nmea_delay", 0, INT_MIN, INT_MAX),
GLOB_ITEM_STR("ts2phc.nmea_remote_host", ""),
GLOB_ITEM_STR("ts2phc.nmea_remote_port", ""),
GLOB_ITEM_STR("ts2phc.nmea_serialport", "/dev/ttyS0"),
diff --git a/ts2phc.8 b/ts2phc.8
index c0b718b..b8383ff 100644
--- a/ts2phc.8
+++ b/ts2phc.8
@@ -262,6 +262,16 @@ by changing the clock frequency instead of stepping the clock. When
set to 0.0, the servo will never step the clock except on start.
The default is 0.0.
+.TP
+.B ts2phc.nmea_delay
+Specifies the minimum expected delay of NMEA RMC messages in nanoseconds.
+If the maximum delay is longer than 1 second, or 'ts2phc.pulsewidth'
+if 'ts2phc.extts_polarity' is set to "both", this option needs to be set
+accordingly to allow the timestamps from NMEA messages to be correctly
+assigned to pulses from the PPS signal and wrong PPS edges to be rejected if
+the edge rejection is enabled.
+The default is 0 nanoseconds.
+
.TP
.B ts2phc.nmea_remote_host, ts2phc.nmea_remote_port
Specifies the remote host providing ToD information when using the
diff --git a/ts2phc_nmea_pps_source.c b/ts2phc_nmea_pps_source.c
index bdfaf19..e345969 100644
--- a/ts2phc_nmea_pps_source.c
+++ b/ts2phc_nmea_pps_source.c
@@ -33,6 +33,7 @@ struct ts2phc_nmea_pps_source {
pthread_t worker;
/* Protects anonymous struct fields, below, from concurrent access. */
pthread_mutex_t mutex;
+ tmv_t delay_correction;
struct {
struct timespec local_monotime;
struct timespec local_utctime;
@@ -207,6 +208,7 @@ static int ts2phc_nmea_pps_source_getppstime(struct ts2phc_pps_source *src,
}
rmc = tmv_add(rmc, duration_since_rmc);
+ rmc = tmv_add(rmc, m->delay_correction);
*ts = tmv_to_timespec(rmc);
ts->tv_sec += tai_offset;
@@ -234,6 +236,8 @@ struct ts2phc_pps_source *ts2phc_nmea_pps_source_create(struct ts2phc_private *p
s->pps_source.destroy = ts2phc_nmea_pps_source_destroy;
s->pps_source.getppstime = ts2phc_nmea_pps_source_getppstime;
s->config = priv->cfg;
+ s->delay_correction = nanoseconds_to_tmv(
+ config_get_int(priv->cfg, NULL, "ts2phc.nmea_delay"));
pthread_mutex_init(&s->mutex, NULL);
err = pthread_create(&s->worker, NULL, monitor_nmea_status, s);
if (err) {

View File

@ -1,76 +0,0 @@
commit f3c742e24a40cf75272ec39789a2cba35389230d
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Tue Jun 11 15:25:54 2024 +0200
nmea: Fix conversion of leap second.
When a leap second is inserted, the RMC message reports time of
23:59:60, which overflows in mktime() to 0:00:00 after the leap second
with an incremented TAI-UTC offset. This causes a one-second error in
the offset meaured by ts2phc.
Check the seconds field of the RMC message and convert 60 to 59 to make
the timestamp ambiguous per is_utc_ambiguous() and ignored by ts2phc to
avoid updating the clock with the one-second error.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/nmea.c b/nmea.c
index 7f1d9a2..b84b42e 100644
--- a/nmea.c
+++ b/nmea.c
@@ -155,6 +155,9 @@ static int nmea_scan_rmc(struct nmea_parser *np, struct nmea_rmc *result)
if (cnt != 3) {
return -1;
}
+ /* Convert an inserted leap second to ambiguous 23:59:59 */
+ if (tm.tm_sec == 60)
+ tm.tm_sec = 59;
tm.tm_year += 100;
tm.tm_mon--;
tm.tm_isdst = 0;
commit b396d361b0d290ce83395851860c2dcd074e0f3b
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Tue Jun 11 15:32:55 2024 +0200
ts2phc: Fix timestamp conversion for leap seconds.
The UTC timestamp parsed from the RMC message needs to be converted to
TAI in order to calculate the PHC offset. This conversion was done after
adjusting the timestamp for the measured delay between the reception of
the message and the following pulse, which caused the offset measured by
ts2phc to have a one-second error if the message expected during or
after a leap second was missed.
Apply the TAI-UTC offset to the timestamp parsed from the RMC message
before any adjustments are made to avoid the error.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/ts2phc_nmea_pps_source.c b/ts2phc_nmea_pps_source.c
index 7a28433..bdfaf19 100644
--- a/ts2phc_nmea_pps_source.c
+++ b/ts2phc_nmea_pps_source.c
@@ -186,10 +186,9 @@ static int ts2phc_nmea_pps_source_getppstime(struct ts2phc_pps_source *src,
pr_err("nmea: rmc time stamp stale");
return -1;
}
- rmc = tmv_add(rmc, duration_since_rmc);
+
utc_time = tmv_to_nanoseconds(rmc);
utc_time /= (int64_t) 1000000000;
- *ts = tmv_to_timespec(rmc);
result = lstab_utc2tai(m->lstab, utc_time, &tai_offset);
switch (result) {
@@ -206,6 +205,9 @@ static int ts2phc_nmea_pps_source_getppstime(struct ts2phc_pps_source *src,
pr_err("nmea: utc time stamp is ambiguous");
break;
}
+
+ rmc = tmv_add(rmc, duration_since_rmc);
+ *ts = tmv_to_timespec(rmc);
ts->tv_sec += tai_offset;
return lstab_error;

View File

@ -1,25 +0,0 @@
commit b4ad9dccf23cef2d79621cd36987428a65f2bcc9
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Tue Jun 11 12:35:10 2024 +0200
ts2phc: Reset parser after RMC message.
When the receiver was configured to generate only RMC messages, every
other message was skipped due to the parser not being in the right
state to process another message. Reset the parser to correctly parse
every valid message.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/nmea.c b/nmea.c
index 44c7c01..7f1d9a2 100644
--- a/nmea.c
+++ b/nmea.c
@@ -171,6 +171,7 @@ int nmea_parse(struct nmea_parser *np, const char *ptr, int buflen,
while (buflen) {
if (!nmea_parse_symbol(np, *ptr)) {
if (!nmea_scan_rmc(np, result)) {
+ nmea_reset(np);
*parsed = count + 1;
return 0;
}

View File

@ -1,4 +1,81 @@
commit e3867eb1ce2282f9ac4cc729a3b1beb74bb017d6
commit a3420abab9eec2dc7b35e0e1e9f37ebeffc87655
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Tue Nov 26 15:10:32 2024 +0100
pmc_agent: Use longer update interval when not subscribed.
When phc2sys is started with the -w option, the pmc agent is not
subscribed to events by the pmc_agent_subscribe() function, which also
sets the update interval. The update interval in this case is zero,
which means the pmc agent is trying to update the currentUtcOffset value
on every call of pmc_agent_update(), i.e. on every clock update in
phc2sys.
Set a default update interval of 60 seconds to reduce the rate of
pmc requests.
Fixes: e3ca7ea90a9e ("pmc_agent: Make update interval configurable.")
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
diff --git a/pmc_agent.c b/pmc_agent.c
index d1a3367..663adc0 100644
--- a/pmc_agent.c
+++ b/pmc_agent.c
@@ -33,6 +33,9 @@
#define UPDATES_PER_SUBSCRIPTION 3
#define MIN_UPDATE_INTERVAL 10
+/* Update interval if the agent not subscribed, just polling the UTC offset */
+#define DEFAULT_UPDATE_INTERVAL 60
+
struct pmc_agent {
struct pmc *pmc;
uint64_t pmc_last_update;
@@ -253,6 +256,7 @@ int init_pmc_node(struct config *cfg, struct pmc_agent *node, const char *uds,
}
node->recv_subscribed = recv_subscribed;
node->recv_context = context;
+ node->update_interval = DEFAULT_UPDATE_INTERVAL * NS_PER_SEC;
return 0;
}
commit 03ab6793abf665039b25319f0f4ad7ce03452874
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Tue Nov 26 15:10:33 2024 +0100
phc2sys: Don't disable pmc agent with -s -d -w options.
When phc2sys is started with -s and -d options to combine a PPS device
and PHC device as a time source, but without an offset specified by
the -O option, the pmc agent is disabled after waiting for ptp4l to have
a port in a synchronized state. This prevents phc2sys from following
changes in the currentUtcOffset value.
Disable the pmc agent only if no PHC device is specified by the -s
option, i.e. there are no PHC readings to which the UTC offset could be
applied.
Fixes: 5f1b419c4102 ("phc2sys: Replace magical test with a proper test.")
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
diff --git a/phc2sys.c b/phc2sys.c
index d09cb53..c98ecec 100644
--- a/phc2sys.c
+++ b/phc2sys.c
@@ -1594,7 +1594,7 @@ int main(int argc, char *argv[])
if (domains[0].forced_sync_offset ||
!phc2sys_using_systemclock(&domains[0]) ||
- hardpps_configured(pps_fd)) {
+ (hardpps_configured(pps_fd) && !src_name)) {
pmc_agent_disable(domains[0].agent);
}
}
commit 1e25a62e65e07f2cc9e1c390895f0a940d307a25
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Tue Nov 26 15:10:34 2024 +0100
@ -21,10 +98,10 @@ Date: Tue Nov 26 15:10:34 2024 +0100
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/phc2sys.8 b/phc2sys.8
index df0608a..5ca467d 100644
index 762a1b1..3246c8f 100644
--- a/phc2sys.8
+++ b/phc2sys.8
@@ -161,7 +161,10 @@ minimize the error caused by random delays in scheduling and bus utilization.
@@ -164,7 +164,10 @@ minimize the error caused by random delays in scheduling and bus utilization.
The default is 5.
.TP
.BI \-O " offset"
@ -37,10 +114,10 @@ index df0608a..5ca467d 100644
.B \-a
option. See
diff --git a/phc2sys.c b/phc2sys.c
index 1745558..d8b0991 100644
index c98ecec..ddd0a6a 100644
--- a/phc2sys.c
+++ b/phc2sys.c
@@ -1406,12 +1406,6 @@ int main(int argc, char *argv[])
@@ -1452,12 +1452,6 @@ int main(int argc, char *argv[])
goto bad_usage;
}
@ -53,7 +130,7 @@ index 1745558..d8b0991 100644
if (hardpps_configured(pps_fd) && (dst_cnt != 1 ||
strcmp(dst_names[0], "CLOCK_REALTIME"))) {
fprintf(stderr,
@@ -1501,7 +1495,7 @@ int main(int argc, char *argv[])
@@ -1559,7 +1553,7 @@ int main(int argc, char *argv[])
r = -1;
@ -62,7 +139,7 @@ index 1745558..d8b0991 100644
snprintf(uds_local, sizeof(uds_local),
"/var/run/phc2sys.%d", getpid());
@@ -1516,7 +1510,7 @@ int main(int argc, char *argv[])
@@ -1574,7 +1568,7 @@ int main(int argc, char *argv[])
phc2sys_recv_subscribed, &domains[0]))
goto end;

View File

@ -1,4 +1,4 @@
commit 6e480c9572925a4ea8aac45a10a306e0c4e509a9
commit daa82cda8318edd6a8e75ef46d0ed065445c8922
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu Jan 11 11:33:54 2024 +0100
@ -7,33 +7,33 @@ Date: Thu Jan 11 11:33:54 2024 +0100
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/config.c b/config.c
index 398b420..d7775c0 100644
index 4a28efb..cbff976 100644
--- a/config.c
+++ b/config.c
@@ -319,7 +319,7 @@ struct config_item config_tab[] = {
GLOB_ITEM_STR("productDescription", ";;"),
@@ -347,7 +347,7 @@ struct config_item config_tab[] = {
PORT_ITEM_STR("ptp_dst_ipv4", "224.0.1.129"),
PORT_ITEM_STR("ptp_dst_ipv6", "FF0E:0:0:0:0:0:0:181"),
PORT_ITEM_STR("ptp_dst_mac", "01:1B:19:00:00:00"),
PORT_ITEM_STR("p2p_dst_mac", "01:80:C2:00:00:0E"),
- GLOB_ITEM_INT("ptp_minor_version", 1, 0, 1),
+ GLOB_ITEM_INT("ptp_minor_version", 0, 0, 1),
GLOB_ITEM_STR("refclock_sock_address", "/var/run/refclock.ptp.sock"),
GLOB_ITEM_STR("revisionData", ";;"),
GLOB_ITEM_INT("sanity_freq_limit", 200000000, 0, INT_MAX),
GLOB_ITEM_STR("sa_file", NULL),
diff --git a/configs/default.cfg b/configs/default.cfg
index 0c7661c..8f94c16 100644
index c3ad618..768eef8 100644
--- a/configs/default.cfg
+++ b/configs/default.cfg
@@ -46,7 +46,7 @@ power_profile.2011.networkTimeInaccuracy -1
@@ -47,7 +47,7 @@ power_profile.2011.networkTimeInaccuracy -1
power_profile.2017.totalTimeInaccuracy -1
power_profile.grandmasterID 0
power_profile.version none
-ptp_minor_version 1
+ptp_minor_version 0
#
# Run time options
-ptp_minor_version 1
+ptp_minor_version 0
spp -1
active_key_id 0
#
diff --git a/msg.h b/msg.h
index 9c80f45..786ddc7 100644
index 58c2287..d2658c9 100644
--- a/msg.h
+++ b/msg.h
@@ -32,7 +32,7 @@
@ -45,16 +45,44 @@ index 9c80f45..786ddc7 100644
#define PTP_VERSION (PTP_MINOR_VERSION << 4 | PTP_MAJOR_VERSION)
#define MAJOR_VERSION_MASK 0x0f
diff --git a/port.c b/port.c
index db35a44..7f945ac 100644
--- a/port.c
+++ b/port.c
@@ -3696,7 +3696,7 @@ struct port *port_open(const char *phc_device,
pr_err("%s: spp not supported on one-step ports", p->log_name);
goto err_uc_service;
}
- if (port_has_security(p) && (config_get_int(cfg, NULL, "ptp_minor_version") < 1)) {
+ if (port_has_security(p) && ptp_hdr_ver >> 4 < 1) {
pr_err("%s: spp needs at least PTPv2.1", p->log_name);
goto err_uc_service;
}
diff --git a/ptp4l.8 b/ptp4l.8
index 4cb9adb..c59b0b4 100644
index 87900e3..28cc4c9 100644
--- a/ptp4l.8
+++ b/ptp4l.8
@@ -813,7 +813,7 @@ The default is 128.
@@ -900,7 +900,8 @@ The default is 128.
.TP
.B ptp_minor_version
This option sets the minorVersionPTP in the common PTP message header.
-The default is 1.
+The default (specific to the installed linuxptp package) is 0.
+The default (specific to the installed linuxptp package) is 0, but setting
+\fBsa_file\fR forces the version to 1.
.TP
.B refclock_sock_address
diff --git a/ptp4l.c b/ptp4l.c
index ac2ef96..0d54d6c 100644
--- a/ptp4l.c
+++ b/ptp4l.c
@@ -194,6 +194,9 @@ int main(int argc, char *argv[])
sk_hwts_filter_mode = config_get_int(cfg, NULL, "hwts_filter");
ptp_hdr_ver = config_get_int(cfg, NULL, "ptp_minor_version");
+ /* Override patched default for spp, which requires PTPv2.1 */
+ if (ptp_hdr_ver < 1 && config_get_string(cfg, NULL, "sa_file"))
+ ptp_hdr_ver = 1;
ptp_hdr_ver = (ptp_hdr_ver << 4) | PTP_MAJOR_VERSION;
if (sad_create(cfg)) {

View File

@ -0,0 +1,302 @@
commit f0ea5436b60494a8c5dac8d39e2b62dd8cab6f53
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu Oct 17 15:05:21 2024 +0200
pmc: Avoid race conditions in agent update.
The pmc_agent_update() function updates the subscription to
notifications and also the current UTC offset. It uses a timeout of 0
to avoid blocking. When the pmc client sends the first request, the
response from ptp4l may not come quickly enough to be received in the
same run_pmc() call. It then sends the other request and checks for the
response. If it is the response to the first request, it will be ignored.
The update works correctly only if both responses are quick enough to be
received in the same call, or are both slow enough that they are
received in the next call of the pmc_agent_update() function.
The function needs to be called a random number of times in order to
finish one update. If the mismatch between requests and responses
happened consistently, the agent would never reach the up-to-date state
and phc2sys would not enter the main synchronization loop.
Split the update into two phases, where only one thing is updated at a
time. The function now needs to be called at most 3 times to update both
the subscription and UTC offset, assuming it is not interrupted by
another request outside of the agent's update.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
diff --git a/pmc_agent.c b/pmc_agent.c
index 86b6ee6..d1a3367 100644
--- a/pmc_agent.c
+++ b/pmc_agent.c
@@ -37,6 +37,7 @@ struct pmc_agent {
struct pmc *pmc;
uint64_t pmc_last_update;
uint64_t update_interval;
+ int update_phase;
struct defaultDS dds;
bool dds_valid;
@@ -427,16 +428,27 @@ int pmc_agent_update(struct pmc_agent *node)
ts = tp.tv_sec * NS_PER_SEC + tp.tv_nsec;
if (ts - node->pmc_last_update >= node->update_interval) {
- if (node->stay_subscribed) {
- renew_subscription(node, 0);
- }
- if (!pmc_agent_query_utc_offset(node, 0)) {
+ switch (node->update_phase) {
+ case 0:
+ if (node->stay_subscribed &&
+ renew_subscription(node, 0))
+ break;
+ node->update_phase++;
+ /* Fall through */
+ case 1:
+ if (pmc_agent_query_utc_offset(node, 0))
+ break;
+ node->update_phase++;
+ /* Fall through */
+ default:
node->pmc_last_update = ts;
+ node->update_phase = 0;
+ break;
}
+ } else {
+ run_pmc(node, 0, -1, &msg);
}
- run_pmc(node, 0, -1, &msg);
-
return 0;
}
commit 2ceb8289b26e98f9f2179f021aa153db28c31dcf
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu Oct 17 15:05:22 2024 +0200
phc2sys: Wait until pmc agent is subscribed.
When phc2sys is configured with multiple domains, different domains may
have their pmc agent subscribed after different number of calls of the
pmc_agent_update() function depending on how quickly responses from
ptp4l are received. If one domain triggers reconfiguration and the other
domain does not have its agent subscribed yet, it will not have any of
its clocks synchronized until a port changes state and triggers another
reconfiguration of the domain.
To avoid this problem, wait for each domain to have its agent subscribed
before entering the main synchronization loop. Use a 10ms update
interval to speed up the start of phc2sys.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
diff --git a/phc2sys.c b/phc2sys.c
index 6113539..47e896e 100644
--- a/phc2sys.c
+++ b/phc2sys.c
@@ -962,6 +962,12 @@ static int auto_init_ports(struct domain *domain)
return -1;
}
+ while (!pmc_agent_is_subscribed(domain->agent)) {
+ usleep(10000);
+ if (pmc_agent_update(domain->agent) < 0)
+ return -1;
+ }
+
for (i = 1; i <= number_ports; i++) {
err = pmc_agent_query_port_properties(domain->agent, 1000, i,
&state, &timestamping,
commit 5f37c1609d4a21daf7f7c12ae6f3fee327c03c3f
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu Oct 24 15:24:07 2024 +0200
phc2sys: Keep clocks in command-line or ptp4l order.
When adding a new clock to the domain, add it to the end of the list to
keep them in the same order as specified on the command line or the port
order of ptp4l.
This will be needed by new code in the domain reconfiguration expecting
the order of reinitialized clocks to be the same as in which they were
added to the domain.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
diff --git a/phc2sys.c b/phc2sys.c
index 47e896e..bf36c38 100644
--- a/phc2sys.c
+++ b/phc2sys.c
@@ -161,7 +161,7 @@ static struct servo *servo_add(struct domain *domain,
static struct clock *clock_add(struct domain *domain, const char *device,
int phc_index)
{
- struct clock *c;
+ struct clock *c, *c2;
clockid_t clkid = CLOCK_INVALID;
char phc_device[19];
@@ -217,7 +217,19 @@ static struct clock *clock_add(struct domain *domain, const char *device,
c->sysoff_method = sysoff_probe(CLOCKID_TO_FD(clkid),
domain->phc_readings);
- LIST_INSERT_HEAD(&domain->clocks, c, list);
+ /* Add the clock to the end of the list to keep them in the
+ command-line or ptp4l order */
+ if (LIST_EMPTY(&domain->clocks)) {
+ LIST_INSERT_HEAD(&domain->clocks, c, list);
+ } else {
+ LIST_FOREACH(c2, &domain->clocks, list) {
+ if (LIST_NEXT(c2, list))
+ continue;
+ LIST_INSERT_AFTER(c2, c, list);
+ break;
+ }
+ }
+
return c;
}
commit 11f06a29e6cb34104785435274e3673dd72970e0
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Thu Oct 24 15:24:08 2024 +0200
phc2sys: Allow static sink clocks in automatic mode.
Allow the -c option to be used together with the -a option. Add the
specified clocks to the first domain, where they will stay in the master
state and be synchronized when a source is available in the same domain
or other domains.
Mark the clocks added by -c as static and skip them in the domain
reconfiguration if they duplicate a clock following a ptp4l port.
A use case is synchronization of clocks of backup interfaces in an
active-backup bond to minimize the observed offset when the active
interface is switched. ptp4l tracks the active interface, which is
followed by the automatic mode of phc2sys. All interfaces included in
the bond are specified by the -c option to keep them all synchronized to
the same source. The interface which duplicates the current active
interface provided by ptp4l is skipped.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/phc2sys.8 b/phc2sys.8
index dd97a70..762a1b1 100644
--- a/phc2sys.8
+++ b/phc2sys.8
@@ -117,10 +117,13 @@ should no longer be used.
.TP
.BI \-c " device"
Specify the time sink by device (e.g. /dev/ptp1) or interface (e.g. eth1) or
-by name. The default is CLOCK_REALTIME (the system clock). Not compatible
-with the
+by name. If used together with the
.B \-a
-option. This option may be given up to 128 times.
+option, it is an additional sink clock added to the clocks provided by the
+first ptp4l instance (if
+.B \-z
+is specified multiple times). Duplicated clocks are allowed. The default is
+CLOCK_REALTIME (the system clock). This option may be given up to 128 times.
.TP
.BI \-E " servo"
Specify which clock servo should be used. Valid values are pi for a PI
diff --git a/phc2sys.c b/phc2sys.c
index bf36c38..d09cb53 100644
--- a/phc2sys.c
+++ b/phc2sys.c
@@ -79,6 +79,7 @@ struct clock {
int dest_only;
int state;
int new_state;
+ int static_state;
int sync_offset;
int leap_set;
int utc_offset_set;
@@ -391,6 +392,18 @@ static struct clock *find_dst_clock(struct domain *domain,
return c;
}
+static struct clock *find_nonstatic_clock(struct domain *domain,
+ int phc_index)
+{
+ struct clock *c = NULL;
+ LIST_FOREACH(c, &domain->clocks, list) {
+ if (!c->static_state && c->phc_index == phc_index) {
+ break;
+ }
+ }
+ return c;
+}
+
static int reconfigure_domain(struct domain *domain)
{
struct clock *c, *src = NULL, *dup = NULL;
@@ -422,6 +435,17 @@ static int reconfigure_domain(struct domain *domain)
c->new_state = 0;
}
+ /* Ignore the clock if its state is not following ptp4l and has
+ the same PHC index as a clock that is following ptp4l */
+ if (c->static_state) {
+ dup = find_nonstatic_clock(domain, c->phc_index);
+ if (dup) {
+ pr_info("skipping static %s: %s has the same clock",
+ c->device, dup->device);
+ continue;
+ }
+ }
+
switch (c->state) {
case PS_FAULTY:
case PS_DISABLED:
@@ -436,6 +460,8 @@ static int reconfigure_domain(struct domain *domain)
dst_cnt++;
LIST_INSERT_HEAD(&domain->dst_clocks,
c, dst_list);
+ if (c->sanity_check)
+ clockcheck_reset(c->sanity_check);
} else {
pr_info("skipping %s: %s has the same clock "
"and is already selected",
@@ -1128,6 +1154,7 @@ static int phc2sys_static_dst_configuration(struct domain *domain,
return -1;
}
dst->state = PS_MASTER;
+ dst->static_state = 1;
LIST_INSERT_HEAD(&domain->dst_clocks, dst, dst_list);
return 0;
@@ -1407,7 +1434,7 @@ int main(int argc, char *argv[])
dst_names[dst_cnt++] = "CLOCK_REALTIME";
}
- if (autocfg && (src_name || dst_cnt > 0 || hardpps_configured(pps_fd) ||
+ if (autocfg && (src_name || hardpps_configured(pps_fd) ||
wait_sync || settings.forced_sync_offset)) {
fprintf(stderr,
"autoconfiguration cannot be mixed with manual config options.\n");
@@ -1506,6 +1533,14 @@ int main(int argc, char *argv[])
if (auto_init_ports(&domains[i]) < 0)
goto end;
}
+
+ for (i = 0; i < dst_cnt; i++) {
+ r = phc2sys_static_dst_configuration(&domains[0],
+ dst_names[i]);
+ if (r)
+ goto end;
+ }
+
r = do_loop(domains, n_domains);
goto end;
}

View File

@ -1,26 +0,0 @@
commit b421a4c66ce636adff150dd1aa8eafa981c2693d
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Wed Jan 3 14:45:51 2024 +0100
pmc: Allow missing values in SUBSCRIBE_EVENTS_NP command.
Don't require all supported notifications to be specified in the command
for compatibility with older scripts.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/pmc_common.c b/pmc_common.c
index 62e34a6..b88cfc2 100644
--- a/pmc_common.c
+++ b/pmc_common.c
@@ -310,8 +310,8 @@ static void do_set_action(struct pmc *pmc, int action, int index, char *str)
onoff_port_state,
onoff_time_status,
onoff_parent_data_set);
- if (cnt != 4) {
- fprintf(stderr, "%s SET needs 4 values\n",
+ if (cnt < 2) {
+ fprintf(stderr, "%s SET needs at least 2 values\n",
idtab[index].name);
break;
}

View File

@ -0,0 +1,85 @@
commit 77504ea63484dbc0c78b1ef58b29f06ced517223
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Wed Sep 25 14:16:16 2024 +0200
udp: Fix port-specific ptp/p2p_dst_ipv4 configuration.
If different ports are configured with a different ptp_dst_ipv4 or
p2p_dst_ipv4 address, only the last port in the configuration works
correctly. This is caused by a global variable holding the
destination address for all ports using the udp transport.
Move the address to the udp structure to avoid the conflict between
different ports, same as when port-specific scope in udp6 was fixed
in commit a48666bee3dd ("udp6: Make mc6_addr transport-local").
Fixes: 8a26c94cc88e ("udp+udp6: Make IP addresses configurable.")
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/udp.c b/udp.c
index 38d0ec4..c9b5f39 100644
--- a/udp.c
+++ b/udp.c
@@ -44,6 +44,7 @@ struct udp {
struct transport t;
struct address ip;
struct address mac;
+ struct in_addr mcast_addr[2];
};
static int mcast_bind(int fd, int index)
@@ -146,8 +147,6 @@ no_socket:
enum { MC_PRIMARY, MC_PDELAY };
-static struct in_addr mcast_addr[2];
-
static int udp_open(struct transport *t, struct interface *iface,
struct fdarray *fda, enum timestamp_type ts_type)
{
@@ -165,22 +164,22 @@ static int udp_open(struct transport *t, struct interface *iface,
sk_interface_addr(name, AF_INET, &udp->ip);
str = config_get_string(t->cfg, name, "ptp_dst_ipv4");
- if (!inet_aton(str, &mcast_addr[MC_PRIMARY])) {
+ if (!inet_aton(str, &udp->mcast_addr[MC_PRIMARY])) {
pr_err("invalid ptp_dst_ipv4 %s", str);
return -1;
}
str = config_get_string(t->cfg, name, "p2p_dst_ipv4");
- if (!inet_aton(str, &mcast_addr[MC_PDELAY])) {
+ if (!inet_aton(str, &udp->mcast_addr[MC_PDELAY])) {
pr_err("invalid p2p_dst_ipv4 %s", str);
return -1;
}
- efd = open_socket(name, mcast_addr, EVENT_PORT, ttl);
+ efd = open_socket(name, udp->mcast_addr, EVENT_PORT, ttl);
if (efd < 0)
goto no_event;
- gfd = open_socket(name, mcast_addr, GENERAL_PORT, ttl);
+ gfd = open_socket(name, udp->mcast_addr, GENERAL_PORT, ttl);
if (gfd < 0)
goto no_general;
@@ -223,6 +222,7 @@ static int udp_send(struct transport *t, struct fdarray *fda,
enum transport_event event, int peer, void *buf, int len,
struct address *addr, struct hw_timestamp *hwts)
{
+ struct udp *udp = container_of(t, struct udp, t);
struct address addr_buf;
unsigned char junk[1600];
ssize_t cnt;
@@ -243,8 +243,8 @@ static int udp_send(struct transport *t, struct fdarray *fda,
if (!addr) {
memset(&addr_buf, 0, sizeof(addr_buf));
addr_buf.sin.sin_family = AF_INET;
- addr_buf.sin.sin_addr = peer ? mcast_addr[MC_PDELAY] :
- mcast_addr[MC_PRIMARY];
+ addr_buf.sin.sin_addr = peer ? udp->mcast_addr[MC_PDELAY] :
+ udp->mcast_addr[MC_PRIMARY];
addr_buf.len = sizeof(addr_buf.sin);
addr = &addr_buf;
}

View File

@ -1,16 +1,16 @@
%global _hardened_build 1
%global testsuite_ver bf8ead
%global clknetsim_ver 5d1dc0
%global testsuite_ver d27dbd
%global clknetsim_ver 64df92
Name: linuxptp
Version: 4.2
Release: 3%{?dist}.1
Version: 4.4
Release: 1%{?dist}.2
Summary: PTP implementation for Linux
License: GPLv2+
URL: http://linuxptp.sourceforge.net/
License: GPL-2.0-or-later
URL: https://www.linuxptp.org/
Source0: https://downloads.sourceforge.net/%{name}/%{name}-%{version}.tgz
Source0: https://downloads.nwtime.org/%{name}/%{name}-%{version}.tgz
Source1: phc2sys.service
Source2: ptp4l.service
Source3: timemaster.service
@ -21,34 +21,26 @@ Source10: https://github.com/mlichvar/linuxptp-testsuite/archive/%{testsuite_ver
# simulator for test suite
Source11: https://github.com/mlichvar/clknetsim/archive/%{clknetsim_ver}/clknetsim-%{clknetsim_ver}.tar.gz
# allow old syntax of SET SUBSCRIBE_EVENTS_NP command
Patch1: linuxptp-subscribe.patch
# disable warning messages about deprecated options
Patch2: linuxptp-deprecated.patch
# revert default PTP version to 2.0 for better compatibility
Patch3: linuxptp-ptpver.patch
# limit unicast message rate per address and grant duration
Patch4: linuxptp-ucastrate.patch
# fix ts2phc to handle large NMEA delay
Patch5: linuxptp-nmeadelay.patch
# fix loading and reloading of leapfile
Patch6: linuxptp-lstab.patch
# fix ts2phc to correctly handle leap seconds
Patch7: linuxptp-nmealeap.patch
# fix ts2phc to reset NMEA parser after RMC message
Patch8: linuxptp-nmeareset.patch
# add options to configure multicast IP addresses
Patch9: linuxptp-addropts.patch
# add holdover support to ts2phc
Patch10: linuxptp-holdover.patch
# add option to ts2phc to specify minimum expected NMEA delay
Patch11: linuxptp-nmeadelay2.patch
# fix port-specific ptp/p2p_dst_ipv4 configuration
Patch5: linuxptp-udpaddr.patch
# support static sink clocks in phc2sys automatic mode
Patch6: linuxptp-staticauto.patch
# don't require -O option without -a and -w in phc2sys
Patch7: linuxptp-nowait.patch
# add experimental option for external PPS in ts2phc automatic mode
Patch8: linuxptp-externalpps.patch
# add command to set external grandmaster properties
Patch9: linuxptp-externalgm.patch
# check for EL-specific kernels with vclock support
Patch12: linuxptp-vclock.patch
# don't require -O option without -a and -w in phc2sys
Patch13: linuxptp-nowait.patch
BuildRequires: gcc gcc-c++ make systemd
BuildRequires: gcc gcc-c++ gnutls-devel make systemd
%{?systemd_requires}
@ -62,6 +54,10 @@ Supporting legacy APIs and other platforms is not a goal.
%prep
%setup -q -a 10 -a 11 -n %{name}-%{!?gitfullver:%{version}}%{?gitfullver}
%autopatch -p1
# disable nettle support in favor of gnutls
sed -i 's|find .*"nettle"|true|' incdefs.sh
mv linuxptp-testsuite-%{testsuite_ver}* testsuite
mv clknetsim-%{clknetsim_ver}* testsuite/clknetsim
@ -125,9 +121,18 @@ PATH=..:$PATH ./run
%{_mandir}/man8/*.8*
%changelog
* Thu Jan 02 2025 Miroslav Lichvar <mlichvar@redhat.com> 4.2-3.el9_5.1
- add option to ts2phc to specify minimum expected NMEA delay (RHEL-70168)
- don't require -O option without -a and -w in phc2sys (RHEL-70678)
* Wed May 14 2025 Miroslav Lichvar <mlichvar@redhat.com> 4.4-1.el9_6.2
- add command to set external grandmaster properties (RHEL-91297)
* Tue May 06 2025 Miroslav Lichvar <mlichvar@redhat.com> 4.4-1.el9_6.1
- add experimental option for external PPS in ts2phc automatic mode
(RHEL-89604)
* Tue Dec 03 2024 Miroslav Lichvar <mlichvar@redhat.com> 4.4-1
- update to 4.4 (RHEL-58213 RHEL-57040)
- fix port-specific ptp/p2p_dst_ipv4 configuration (RHEL-60027)
- support static sink clocks in phc2sys automatic mode (RHEL-62864)
- don't require -O option without -a and -w in phc2sys (RHEL-69138)
* Thu Jul 25 2024 Miroslav Lichvar <mlichvar@redhat.com> 4.2-3
- rework NMEA delay patch to fix PPS edge rejection (RHEL-39387)