linuxptp/linuxptp-profinet.patch
Miroslav Lichvar be6e1a1b0d add experimental PROFINET transport (RHEL-212949)
Resolves: RHEL-212949
2026-08-19 14:20:04 +02:00

1881 lines
54 KiB
Diff

commit dc03af081e36dd2d0aa90f55e6ee6ecf6c5a31e7
Author: Luke Howard (via linuxptp-devel Mailing List) <linuxptp-devel@lists.nwtime.org>
Date: Mon Sep 16 06:45:20 2024 +0000
makefile: use $(TRANSP) in ts2phc
ts2phc explicitly included the transport object files, rather than using the
$(TRANSP) variable defined earlier in the makefile.
Signed-off-by: Luke Howard <lukeh@padl.com>
diff --git a/makefile b/makefile
index 3c2406b..a12f061 100644
--- a/makefile
+++ b/makefile
@@ -89,7 +89,7 @@ timemaster: phc.o print.o rtnl.o sk.o timemaster.o util.o version.o
ts2phc: config.o clockadj.o hash.o interface.o msg.o phc.o pmc_agent.o \
pmc_common.o print.o $(SECURITY) $(SERVOS) sk.o $(TS2PHC) tlv.o transport.o \
- raw.o udp.o udp6.o uds.o util.o version.o
+ $(TRANSP) util.o version.o
tz2alt: config.o hash.o interface.o lstab.o msg.o phc.o pmc_common.o print.o \
$(SECURITY) sk.o tlv.o $(TRANSP) tz2alt.o util.o version.o
commit fd952e62cb0ed4d5bdb6bf4bb86e558ba95b3546
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Mon Apr 13 14:52:14 2026 +0200
clock: Add function to read clock.
Add a function to directly read a timestamp from the controlled clock.
This will be needed to provide an estimate of the transmit timestamp in
two-step sync messages.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/clock.c b/clock.c
index 7c30066..d0ff9a7 100644
--- a/clock.c
+++ b/clock.c
@@ -1967,6 +1967,20 @@ struct tsproc *clock_get_tsproc(struct clock *c)
return c->tsproc;
}
+int clock_read_time(struct clock *c, tmv_t *ts)
+{
+ struct timespec tspec;
+
+ if (clock_gettime(c->clkid, &tspec) < 0) {
+ pr_err("failed to read clock: %m");
+ return -errno;
+ }
+
+ *ts = timespec_to_tmv(tspec);
+
+ return 0;
+}
+
int clock_switch_phc(struct clock *c, int phc_index)
{
struct servo *servo;
diff --git a/clock.h b/clock.h
index ce9ae91..fed4d5a 100644
--- a/clock.h
+++ b/clock.h
@@ -318,6 +318,15 @@ UInteger16 clock_steps_removed(struct clock *c);
*/
struct tsproc *clock_get_tsproc(struct clock *c);
+/**
+ * Read the clock and return the timestamp as a tmv_t.
+ *
+ * @param c The clock instance.
+ * @param ts Pointer where to save the timestamp.
+ * @return Zero on success, negative error code otherwise.
+ */
+int clock_read_time(struct clock *c, tmv_t *ts);
+
/**
* Switch to a new PTP Hardware Clock, for use with the "jbod" mode.
* @param c The clock instance.
commit bbb16dbac6d03a1477422a459e0df26a11298fbd
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Mon Apr 13 12:00:39 2026 +0200
port: Add option to estimate two-step sync timestamp.
The PTP specification says that the origin timestamp in two-step sync
messages should either be zero or a valid timestamp accurate to +/- 1
second. ptp4l always set it to zero.
Add an option to provide an estimated timestamp by directly reading the
clock when the message is formed. This will be needed by the PROFINET
transport, where sync and follow-up messages are expected to contain the
same timestamp (to which the provided correction is applied).
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/config.c b/config.c
index fedc3a0..d7f433e 100644
--- a/config.c
+++ b/config.c
@@ -283,6 +283,7 @@ struct config_item config_tab[] = {
GLOB_ITEM_INT("dscp_general", 0, 0, 63),
GLOB_ITEM_INT("domainNumber", 0, 0, 255),
PORT_ITEM_INT("egressLatency", 0, INT_MIN, INT_MAX),
+ PORT_ITEM_INT("estimate_sync_timestamp", 0, 0, 1),
PORT_ITEM_INT("fault_badpeernet_interval", 16, INT32_MIN, INT32_MAX),
PORT_ITEM_INT("fault_reset_interval", 4, INT8_MIN, INT8_MAX),
GLOB_ITEM_DBL("first_step_threshold", 0.00002, 0.0, DBL_MAX),
diff --git a/configs/default.cfg b/configs/default.cfg
index 768eef8..c0e885c 100644
--- a/configs/default.cfg
+++ b/configs/default.cfg
@@ -39,6 +39,7 @@ G.8275.portDS.localPriority 128
allowedLostResponses 3
asCapable auto
BMCA ptp
+estimate_sync_timestamp 0
inhibit_announce 0
inhibit_delay_req 0
ignore_source_id 0
diff --git a/msg.c b/msg.c
index 7c236c3..4caabd5 100644
--- a/msg.c
+++ b/msg.c
@@ -450,6 +450,7 @@ int msg_pre_send(struct ptp_message *m)
switch (type) {
case SYNC:
+ timestamp_pre_send(&m->sync.originTimestamp);
break;
case DELAY_REQ:
clock_gettime(CLOCK_MONOTONIC, &m->ts.host);
diff --git a/port.c b/port.c
index 05aabea..4f1dd40 100644
--- a/port.c
+++ b/port.c
@@ -1830,6 +1830,17 @@ int port_tx_sync(struct port *p, struct address *dst, uint16_t sequence_id)
if (p->timestamping != TS_ONESTEP && p->timestamping != TS_P2P1STEP) {
msg->header.flagField[0] |= TWO_STEP;
+ if (p->estimate_sync_timestamp) {
+ tmv_t estxtm;
+
+ /* Estimate the transmit time. */
+ if (clock_read_time(p->clock, &estxtm)) {
+ err = -1;
+ goto out;
+ }
+
+ msg->sync.originTimestamp = tmv_to_Timestamp(estxtm);
+ }
}
if (dst) {
@@ -3582,6 +3593,8 @@ struct port *port_open(const char *phc_device,
if (p->phc_index < 0)
p->phc_index = phc_index;
p->jbod = config_get_int(cfg, interface_name(interface), "boundary_clock_jbod");
+ p->estimate_sync_timestamp =
+ config_get_int(cfg, interface_name(interface), "estimate_sync_timestamp");
p->master_only = config_get_int(cfg, interface_name(interface), "serverOnly");
p->bmca = config_get_int(cfg, interface_name(interface), "BMCA");
p->trp = transport_create(cfg, config_get_int(cfg,
diff --git a/port_private.h b/port_private.h
index e3c6679..e0417af 100644
--- a/port_private.h
+++ b/port_private.h
@@ -77,6 +77,7 @@ struct port {
enum fsm_event (*event)(struct port *p, int fd_index);
int jbod;
+ int estimate_sync_timestamp;
struct foreign_clock *best;
enum syfu_state syfu;
struct ptp_message *last_syncfup;
diff --git a/ptp4l.8 b/ptp4l.8
index 28cc4c9..06e3943 100644
--- a/ptp4l.8
+++ b/ptp4l.8
@@ -248,6 +248,12 @@ time at the reference plane and the reported transmit time stamp. This
value will be added to egress time stamps obtained from the hardware.
The default is 0.
+.TP
+.B estimate_sync_timestamp
+Enables ptp4l to provide in two-step sync messages an estimated transmit time
+stamp instead of zero.
+The default is 0 (disabled).
+
.TP
.B fault_badpeernet_interval
The time in seconds between the detection of a peer network misconfiguration
commit eee58e0c8116c1e713c09b208dd1f2918274922f
Author: Richard Cochran <richardcochran@gmail.com>
Date: Tue Jan 10 10:51:36 2017 +0100
Add PROFINET transport.
Add support for PTP over PROFINET (IEC 61158 Type 10), described in
IEEE 1588-2019 annex H, IEC 61158-6-10 and other standards. The protocol
is called Precision Transparent Clock Protocol (PTCP) and has its own
message format.
The layer converts between the PTP and PTCP packet formats. In addition
it also maintains a fair amount of state in order to translate between the
two protocols. In general PTCP provides less information than does PTP,
and it distributes the various fields differently among the message types.
A new option is added to configure the PTCP-specific subdomainUUID.
Co-authored-by: Miroslav Lichvar <mlichvar@redhat.com>
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/config.c b/config.c
index d7f433e..ecd612b 100644
--- a/config.c
+++ b/config.c
@@ -219,6 +219,7 @@ static struct config_enum nw_trans_enu[] = {
{ "L2", TRANS_IEEE_802_3 },
{ "UDPv4", TRANS_UDP_IPV4 },
{ "UDPv6", TRANS_UDP_IPV6 },
+ { "PN", TRANS_PROFINET },
{ NULL, 0 },
};
@@ -362,6 +363,8 @@ struct config_item config_tab[] = {
PORT_ITEM_INT("spp", -1, -1, UINT8_MAX),
GLOB_ITEM_DBL("step_threshold", 0.0, 0.0, DBL_MAX),
GLOB_ITEM_INT("step_window", 0, 0, INT_MAX),
+ PORT_ITEM_STR("subdomainUUID",
+ "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"),
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/configs/default.cfg b/configs/default.cfg
index c0e885c..8bce8e6 100644
--- a/configs/default.cfg
+++ b/configs/default.cfg
@@ -109,6 +109,7 @@ 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
+subdomainUUID 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
udp_ttl 1
udp6_scope 0x0E
uds_address /var/run/ptp4l
diff --git a/makefile b/makefile
index a12f061..6f04160 100644
--- a/makefile
+++ b/makefile
@@ -26,7 +26,7 @@ PRG = ptp4l hwstamp_ctl nsm phc2sys phc_ctl pmc timemaster ts2phc tz2alt
SECURITY = sad.o
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
+TRANSP = pn.o pn-ptcp.o 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
OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o designated_fsm.o \
diff --git a/missing.h b/missing.h
index c6be8fd..83e163d 100644
--- a/missing.h
+++ b/missing.h
@@ -54,6 +54,9 @@
/* ETH_P_PRP is defined in if_ether.h from kernel 3.13 */
#define ETH_P_PRP 0x88FB /* IEC 62439-3 PRP/HSRv0 */
#endif
+#ifndef ETH_P_PROFINET
+#define ETH_P_PROFINET 0x8892
+#endif
#ifndef HAVE_ONESTEP_SYNC
enum _missing_hwtstamp_tx_types {
diff --git a/pn-ptcp.c b/pn-ptcp.c
new file mode 100644
index 0000000..30943d2
--- /dev/null
+++ b/pn-ptcp.c
@@ -0,0 +1,635 @@
+/**
+ * @file pn-ptcp.c
+ * @brief Implements PTCP message types.
+ * @note Copyright (C) 2016 linutronix GmbH
+ * @note The PTP message construction was based on code from port.c.
+ * Copyright (C) 2011 Richard Cochran <richardcochran@gmail.com>
+ * @note SPDX-License-Identifier: GPL-2.0+
+ */
+#include <errno.h>
+#include <linux/if_ether.h>
+
+#include "missing.h"
+#include "pn-ptcp.h"
+#include "print.h"
+#include "util.h"
+
+#define FID_SYFU_CLOCK 0x0020 /* Two-step Sync */
+#define FID_SYFU_TIME 0x0021
+#define FID_SYNC_CLOCK 0x0080 /* One-step Sync */
+#define FID_SYNC_TIME 0x0081
+#define FID_ANNO_CLOCK 0xFF00 /* Announce */
+#define FID_ANNO_TIME 0xFF01
+#define FID_FUP_CLOCK 0xFF20 /* FollowUp */
+#define FID_FUP_TIME 0xFF21
+#define FID_DELAY_REQ 0xFF40 /* Peer Delay Request */
+#define FID_DELAY_RESP 0xFF41 /* Two-step Peer Delay Response */
+#define FID_DELAY_RESP_FU 0xFF42 /* Peer Delay Response FollowUp */
+#define FID_DELAY_RESP_1 0xFF43 /* One-step Peer Delay Response */
+
+#define MAC_SYFU_CLOCK 0x20
+#define MAC_SYFU_TIME 0x21
+#define MAC_ANNO_CLOCK 0x00
+#define MAC_ANNO_TIME 0x01
+#define MAC_FUP_CLOCK 0x40
+#define MAC_FUP_TIME 0x41
+#define MAC_PEER 0x0E
+
+#define PRIMARY_MASTER (1 << 0)
+#define MASTER_ACTIVE (1 << 7)
+#define MASTER_PRIO_1 (PRIMARY_MASTER | MASTER_ACTIVE)
+
+enum controlField {
+ CTL_SYNC,
+ CTL_DELAY_REQ,
+ CTL_FOLLOW_UP,
+ CTL_DELAY_RESP,
+ CTL_MANAGEMENT,
+ CTL_OTHER,
+};
+
+static void tlv_set(void *buf, int tag, int len)
+{
+ uint16_t word;
+
+ word = htons((tag << 9) | len);
+ memcpy(buf, &word, sizeof(word));
+}
+
+#define TLV_SET(x, type) \
+ tlv_set(&(x).TLVHeader, type, sizeof(x) - 2)
+
+static const unsigned char pn_ptcp_mac[MAC_LEN] = {
+ 0x01, 0x0E, 0xCF, 0x00, 0x04, 0x00
+};
+
+static const unsigned char pn_peer_mac[MAC_LEN] = {
+ 0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E
+};
+
+static void mac_to_pid(eth_addr mac, struct PortIdentity *pid)
+{
+ pid->clockIdentity.id[0] = mac[0];
+ pid->clockIdentity.id[1] = mac[1];
+ pid->clockIdentity.id[2] = mac[2];
+ pid->clockIdentity.id[3] = 0xFF;
+ pid->clockIdentity.id[4] = 0xFE;
+ pid->clockIdentity.id[5] = mac[3];
+ pid->clockIdentity.id[6] = mac[4];
+ pid->clockIdentity.id[7] = mac[5];
+}
+
+static void ns_to_Timestamp(uint64_t ns, struct Timestamp *ts)
+{
+ *ts = tmv_to_Timestamp(nanoseconds_to_tmv(ns));
+}
+
+static void pid_to_mac(struct PortIdentity *pid, eth_addr mac)
+{
+ mac[0] = pid->clockIdentity.id[0];
+ mac[1] = pid->clockIdentity.id[1];
+ mac[2] = pid->clockIdentity.id[2];
+ mac[3] = pid->clockIdentity.id[5];
+ mac[4] = pid->clockIdentity.id[6];
+ mac[5] = pid->clockIdentity.id[7];
+}
+
+static void ptp_to_ptcp_subdomain(struct pn_info *info,
+ struct PortIdentity *pid,
+ struct PTCPSubdomain *s)
+{
+ TLV_SET(*s, PTCP_SUBDOMAIN);
+ pid_to_mac(pid, s->MasterSourceAddress);
+ memcpy(s->SubdomainUUID, info->subdomain, SUBD_LEN);
+}
+
+static void ptp_to_ptcp_master(struct pn_info *info, struct PTCPMaster *m)
+{
+ TLV_SET(*m, PTCP_MASTER);
+ m->MasterPriority1 = MASTER_PRIO_1;
+ m->MasterPriority2 = info->priority2;
+ m->ClockClass = info->quality.clockClass;
+ m->ClockAccuracy = info->quality.clockAccuracy;
+ m->ClockVariance = info->quality.offsetScaledLogVariance;
+}
+
+static unsigned char *setup_ethernet_header(unsigned char *ptr,
+ const unsigned char *dst_mac,
+ unsigned char last_mac_byte)
+{
+ struct eth_hdr *hdr;
+
+ ptr -= sizeof(*hdr);
+ hdr = (struct eth_hdr *) ptr;
+ memcpy(&hdr->dst, dst_mac, MAC_LEN);
+ hdr->dst[MAC_LEN - 1] = last_mac_byte;
+ hdr->type = htons(ETH_P_PROFINET);
+
+ return ptr;
+}
+
+static uint64_t Timestamp_to_ns(struct Timestamp *ts)
+{
+ uint64_t sec, nsec, result;
+ uint32_t lsb = ntohl(ts->seconds_lsb);
+ uint16_t msb = ntohs(ts->seconds_msb);
+
+ sec = ((uint64_t)lsb) | (((uint64_t)msb) << 32);
+ nsec = ntohl(ts->nanoseconds);
+ result = sec * NS_PER_SEC + nsec;
+ return result;
+}
+
+static tmv_t ptcp_time_to_tmv(struct PTCPTime *t)
+{
+ struct Timestamp ts;
+
+ ts.seconds_msb = t->EpochNumber;
+ ts.seconds_lsb = t->Seconds;
+ ts.nanoseconds = t->NanoSeconds;
+
+ return nanoseconds_to_tmv(Timestamp_to_ns(&ts));
+}
+
+static unsigned char *ptp_sync_to_ptcp(struct ptp_message *msg,
+ struct PTCP_PDU *pdu,
+ struct pn_info *info, int *len)
+{
+ unsigned char *ptr = (unsigned char *) pdu;
+ struct PTCP_SYNC_PDU *sync = &pdu->sync;
+
+ /* Only two-step sync is supported. */
+ if (!(msg->header.flagField[0] & TWO_STEP))
+ return NULL;
+
+ *len = sizeof(struct eth_hdr) + sizeof(pdu->frame_id) + sizeof(*sync);
+
+ ptr = setup_ethernet_header(ptr, pn_ptcp_mac, MAC_SYFU_TIME);
+ pdu->frame_id = htons(FID_SYFU_TIME);
+
+ sync->hdr.SequenceID = msg->header.sequenceId;
+
+ ptp_to_ptcp_subdomain(info, &msg->header.sourcePortIdentity,
+ &sync->pdu.subdomain);
+
+ TLV_SET(sync->pdu.time, PTCP_TIME);
+ sync->pdu.time.EpochNumber =
+ msg->sync.originTimestamp.seconds_msb;
+ sync->pdu.time.Seconds =
+ msg->sync.originTimestamp.seconds_lsb;
+ sync->pdu.time.NanoSeconds =
+ msg->sync.originTimestamp.nanoseconds;
+
+ info->time = sync->pdu.time;
+
+ TLV_SET(sync->pdu.tmext, PTCP_TIME_EXTENSION);
+ sync->pdu.tmext.Flags = info->tmext_flags;
+ sync->pdu.tmext.CurrentUTCOffset = info->utc_offset;
+
+ ptp_to_ptcp_master(info, &sync->pdu.master);
+
+ return ptr;
+}
+
+static unsigned char *ptp_fup_to_ptcp(struct ptp_message *msg,
+ struct PTCP_PDU *pdu,
+ struct pn_info *info, int *len)
+{
+ unsigned char *ptr = (unsigned char *) pdu;
+ struct PTCP_FUP_PDU *fup = &pdu->fup;
+ tmv_t actx, estx;
+ int32_t corr;
+
+ *len = sizeof(struct eth_hdr) + sizeof(pdu->frame_id) + sizeof(*fup);
+
+ ptr = setup_ethernet_header(ptr, pn_ptcp_mac, MAC_FUP_TIME);
+ pdu->frame_id = htons(FID_FUP_TIME);
+
+ fup->hdr.SequenceID = msg->header.sequenceId;
+
+ ptp_to_ptcp_subdomain(info, &msg->header.sourcePortIdentity,
+ &fup->pdu.subdomain);
+
+ fup->pdu.time = info->time;
+
+ estx = ptcp_time_to_tmv(&fup->pdu.time);
+ actx = nanoseconds_to_tmv(Timestamp_to_ns(&msg->follow_up.preciseOriginTimestamp));
+ corr = tmv_to_nanoseconds(tmv_sub(actx, estx));
+
+ if (corr > INT32_MAX) {
+ corr = INT32_MAX;
+ } else if (corr < INT32_MIN) {
+ corr = INT32_MIN;
+ }
+
+ fup->hdr.Delay1ns_FUP = htonl((uint32_t) corr);
+
+ return ptr;
+}
+
+static unsigned char *ptp_anno_to_ptcp(struct ptp_message *msg,
+ struct PTCP_PDU *pdu,
+ struct pn_info *info, int *len)
+{
+ unsigned char *ptr = (unsigned char *) pdu;
+ struct PTCP_ANNOUNCE_PDU *anno = &pdu->anno;
+
+ *len = sizeof(struct eth_hdr) + sizeof(pdu->frame_id) + sizeof(*anno);
+
+ ptr = setup_ethernet_header(ptr, pn_ptcp_mac, MAC_ANNO_TIME);
+ pdu->frame_id = htons(FID_ANNO_TIME);
+
+ anno->hdr.SequenceID = msg->header.sequenceId;
+
+ ptp_to_ptcp_subdomain(info, &msg->header.sourcePortIdentity,
+ &anno->pdu.subdomain);
+
+ /*
+ * We will need quality, priority2, and friends in the PTCP
+ * Sync message, so we remember the latest values here.
+ */
+ info->quality = msg->announce.grandmasterClockQuality;
+ info->priority2 = msg->announce.grandmasterPriority2;
+ info->utc_offset = msg->announce.currentUtcOffset;
+
+ if (field_is_set(msg, 1, LEAP_61)) {
+ info->tmext_flags = htons(1 << 8);
+ } else if (field_is_set(msg, 1, LEAP_59)) {
+ info->tmext_flags = htons(2 << 8);
+ } else {
+ info->tmext_flags = 0;
+ }
+
+ ptp_to_ptcp_master(info, &anno->pdu.master);
+
+ return ptr;
+}
+
+static unsigned char *ptp_req_to_ptcp(struct ptp_message *msg,
+ struct PTCP_PDU *pdu,
+ struct pn_info *info, int *len)
+{
+ unsigned char *ptr = (unsigned char *) pdu;
+ struct PTCP_DELAY_REQ_PDU *req = &pdu->req;
+
+ *len = sizeof(struct eth_hdr) + sizeof(pdu->frame_id) + sizeof(*req);
+
+ ptr = setup_ethernet_header(ptr, pn_peer_mac, MAC_PEER);
+ pdu->frame_id = htons(FID_DELAY_REQ);
+
+ req->hdr.SequenceID = msg->header.sequenceId;
+
+ TLV_SET(req->pdu.param, PTCP_DELAY_PARAMETER);
+ pid_to_mac(&msg->header.sourcePortIdentity,
+ req->pdu.param.PortMACAddress);
+ /*
+ * The requesting port ID in the PTCP responses does not
+ * include the port number. Remember the local port number
+ * so that we can fudge it back into the responses.
+ */
+ info->portNumber = ntohs(msg->header.sourcePortIdentity.portNumber);
+
+ return ptr;
+}
+
+static unsigned char *ptp_resp_to_ptcp(struct ptp_message *msg,
+ struct PTCP_PDU *pdu,
+ struct pn_info *info, int *len)
+{
+ unsigned char *ptr = (unsigned char *) pdu;
+ struct PTCP_DELAY_RSP_PDU *resp = &pdu->resp;
+
+ *len = sizeof(struct eth_hdr) + sizeof(pdu->frame_id) + sizeof(*resp);
+
+ ptr = setup_ethernet_header(ptr, pn_peer_mac, MAC_PEER);
+ pdu->frame_id = htons(FID_DELAY_RESP);
+
+ resp->hdr.SequenceID = msg->header.sequenceId;
+
+ TLV_SET(resp->pdu.delay_param, PTCP_DELAY_PARAMETER);
+ pid_to_mac(&msg->pdelay_resp.requestingPortIdentity,
+ resp->pdu.delay_param.PortMACAddress);
+
+ TLV_SET(resp->pdu.port_param, PTCP_PORT_PARAMETER);
+ /*
+ * We leave T2PortRxDelay and T3PortTxDelay as zero, since
+ * the time stamps are already corrected for ingress/egress
+ * latency at the port layer.
+ */
+
+ TLV_SET(resp->pdu.time, PTCP_PORT_TIME);
+ /*
+ * The port layer always sends the pdelay_resp_fup immediately
+ * after the pdelay_resp. Remember timestamp T2 so that we can
+ * calculate the local residence time for the PTCP follow up.
+ */
+ info->tx_t2 = Timestamp_to_ns(&msg->pdelay_resp.requestReceiptTimestamp);
+ resp->pdu.time.T2TimeStamp = htonl(info->tx_t2);
+
+ return ptr;
+}
+
+static unsigned char *ptp_resfup_to_ptcp(struct ptp_message *msg,
+ struct PTCP_PDU *pdu,
+ struct pn_info *info, int *len)
+{
+ unsigned char *ptr = (unsigned char *) pdu;
+ struct PTCP_DELAY_FU_RSP_PDU *rfp = &pdu->resfup;
+ uint64_t diff;
+
+ *len = sizeof(struct eth_hdr) + sizeof(pdu->frame_id) + sizeof(*rfp);
+
+ ptr = setup_ethernet_header(ptr, pn_peer_mac, MAC_PEER);
+ pdu->frame_id = htons(FID_DELAY_RESP_FU);
+
+ rfp->hdr.SequenceID = msg->header.sequenceId;
+
+ diff = Timestamp_to_ns(&msg->pdelay_resp_fup.responseOriginTimestamp);
+ diff -= info->tx_t2;
+ rfp->hdr.Delay1ns = htonl(diff);
+
+ TLV_SET(rfp->pdu.param, PTCP_DELAY_PARAMETER);
+ pid_to_mac(&msg->pdelay_resp.requestingPortIdentity,
+ rfp->pdu.param.PortMACAddress);
+
+ return ptr;
+}
+
+unsigned char *ptp_to_ptcp(struct ptp_message *msg, struct PTCP_PDU *pdu,
+ struct pn_info *info, int *len)
+{
+ memset(pdu, 0, sizeof(*pdu));
+
+ switch (msg_type(msg)) {
+ case SYNC:
+ return ptp_sync_to_ptcp(msg, pdu, info, len);
+ case FOLLOW_UP:
+ return ptp_fup_to_ptcp(msg, pdu, info, len);
+ case PDELAY_REQ:
+ return ptp_req_to_ptcp(msg, pdu, info, len);
+ case PDELAY_RESP:
+ return ptp_resp_to_ptcp(msg, pdu, info, len);
+ case PDELAY_RESP_FOLLOW_UP:
+ return ptp_resfup_to_ptcp(msg, pdu, info, len);
+ case ANNOUNCE:
+ return ptp_anno_to_ptcp(msg, pdu, info, len);
+ }
+ return NULL;
+}
+
+static int ptcp_sync_to_ptp(struct PTCP_SYNC_PDU *pdu, int len,
+ struct ptp_message *msg, struct pn_info *info,
+ int two_step)
+{
+ int pdulen = sizeof(struct sync_msg);
+
+ if (len < sizeof(*pdu))
+ return -EBADMSG;
+
+ msg->hwts.type = info->timestamping;
+
+ msg->header.tsmt = SYNC | info->transportSpecific;
+ msg->header.ver = PTP_VERSION;
+ msg->header.messageLength = pdulen;
+ msg->header.domainNumber = info->domain;
+
+ mac_to_pid(pdu->pdu.subdomain.MasterSourceAddress,
+ &msg->header.sourcePortIdentity);
+
+ msg->header.sequenceId = ntohs(pdu->hdr.SequenceID);
+ msg->header.control = CTL_SYNC;
+ msg->header.logMessageInterval = info->logSyncInterval;
+
+ if (two_step)
+ msg->header.flagField[0] |= TWO_STEP;
+
+ if (msg_pre_send(msg)) {
+ return -1;
+ }
+
+ /* Remember fields to be reported in the announce message. */
+ info->tmext_flags = pdu->pdu.tmext.Flags;
+ info->utc_offset = pdu->pdu.tmext.CurrentUTCOffset;
+ info->priority2 = pdu->pdu.master.MasterPriority2;
+ info->quality.clockClass = pdu->pdu.master.ClockClass;
+ info->quality.clockAccuracy = pdu->pdu.master.ClockAccuracy;
+ info->quality.offsetScaledLogVariance = pdu->pdu.master.ClockVariance;
+
+ return pdulen;
+}
+
+static int ptcp_anno_to_ptp(struct PTCP_ANNOUNCE_PDU *pdu, int len,
+ struct ptp_message *msg, struct pn_info *info)
+{
+ int pdulen = sizeof(struct announce_msg);
+ uint16_t flags;
+
+ if (len < sizeof(*pdu))
+ return -EBADMSG;
+
+ msg->hwts.type = info->timestamping;
+
+ msg->header.tsmt = ANNOUNCE | info->transportSpecific;
+ msg->header.ver = PTP_VERSION;
+ msg->header.messageLength = pdulen;
+ msg->header.domainNumber = info->domain;
+
+ mac_to_pid(pdu->pdu.subdomain.MasterSourceAddress,
+ &msg->header.sourcePortIdentity);
+
+ msg->header.sequenceId = ntohs(pdu->hdr.SequenceID);
+ msg->header.control = CTL_OTHER;
+ msg->header.logMessageInterval = info->logAnnounceInterval;
+
+ flags = ntohs(info->tmext_flags);
+ if (flags & (1 << 8)) {
+ msg->header.flagField[1] |= LEAP_61;
+ } else if (flags & (2 << 8)) {
+ msg->header.flagField[1] |= LEAP_59;
+ }
+
+ msg->announce.currentUtcOffset = ntohs(info->utc_offset);
+ msg->announce.grandmasterPriority1 = 128;
+ msg->announce.grandmasterClockQuality = info->quality;
+ msg->announce.grandmasterClockQuality.offsetScaledLogVariance =
+ ntohs(info->quality.offsetScaledLogVariance);
+ msg->announce.grandmasterPriority2 = info->priority2;
+ msg->announce.grandmasterIdentity =
+ msg->header.sourcePortIdentity.clockIdentity;
+ msg->announce.stepsRemoved = 1;
+ msg->announce.timeSource = INTERNAL_OSCILLATOR;
+
+ return msg_pre_send(msg) ? -1 : pdulen;
+}
+
+static int ptcp_fup_to_ptp(struct PTCP_FUP_PDU *pdu, int len,
+ struct ptp_message *msg, struct pn_info *info)
+{
+ int pdulen = sizeof(struct follow_up_msg);
+ tmv_t actx, corr, estx;
+
+ if (len < sizeof(*pdu))
+ return -EBADMSG;
+
+ msg->hwts.type = info->timestamping;
+
+ msg->header.tsmt = FOLLOW_UP | info->transportSpecific;
+ msg->header.ver = PTP_VERSION;
+ msg->header.messageLength = pdulen;
+ msg->header.domainNumber = info->domain;
+
+ mac_to_pid(pdu->pdu.subdomain.MasterSourceAddress,
+ &msg->header.sourcePortIdentity);
+
+ msg->header.sequenceId = ntohs(pdu->hdr.SequenceID);
+ msg->header.control = CTL_FOLLOW_UP;
+ msg->header.logMessageInterval = info->logSyncInterval;
+
+ estx = ptcp_time_to_tmv(&pdu->pdu.time);
+ corr = nanoseconds_to_tmv(ntohl(pdu->hdr.Delay1ns_FUP));
+ actx = tmv_add(estx, corr);
+
+ msg->follow_up.preciseOriginTimestamp = tmv_to_Timestamp(actx);
+
+ return msg_pre_send(msg) ? -1 : pdulen;
+}
+
+static int ptcp_req_to_ptp(struct PTCP_DELAY_REQ_PDU *pdu, int len,
+ struct ptp_message *msg, struct pn_info *info)
+{
+ int pdulen = sizeof(struct pdelay_req_msg);
+
+ if (len < sizeof(*pdu))
+ return -EBADMSG;
+
+ msg->hwts.type = info->timestamping;
+
+ msg->header.tsmt = PDELAY_REQ | info->transportSpecific;
+ msg->header.ver = PTP_VERSION;
+ msg->header.messageLength = pdulen;
+ msg->header.domainNumber = info->domain;
+
+ mac_to_pid(pdu->pdu.param.PortMACAddress,
+ &msg->header.sourcePortIdentity);
+
+ msg->header.sourcePortIdentity.portNumber = 1;
+
+ msg->header.sequenceId = ntohs(pdu->hdr.SequenceID);
+ msg->header.control = CTL_OTHER;
+ msg->header.logMessageInterval = 0x7f;
+
+ /* Assume this peer is sending responses to our requests as well. */
+ info->peer_portid = msg->header.sourcePortIdentity;
+
+ return msg_pre_send(msg) ? -1 : pdulen;
+}
+
+static int ptcp_resp_to_ptp(struct PTCP_DELAY_RSP_PDU *pdu, int len,
+ struct ptp_message *msg, struct pn_info *info,
+ int two_step)
+{
+ int pdulen = sizeof(struct pdelay_resp_msg);
+
+ if (len < sizeof(*pdu))
+ return -EBADMSG;
+
+ msg->hwts.type = info->timestamping;
+
+ msg->header.tsmt = PDELAY_RESP | info->transportSpecific;
+ msg->header.ver = PTP_VERSION;
+ msg->header.messageLength = pdulen;
+ msg->header.domainNumber = info->domain;
+ /*
+ * PTCP doesn't tell us the peer's port ID. Assume this
+ * message came from the sender of the last received request.
+ */
+ msg->header.sourcePortIdentity = info->peer_portid;
+ msg->header.sequenceId = ntohs(pdu->hdr.SequenceID);
+ msg->header.control = CTL_OTHER;
+ msg->header.logMessageInterval = 0x7f;
+
+ if (two_step)
+ msg->header.flagField[0] |= TWO_STEP;
+
+ mac_to_pid(pdu->pdu.delay_param.PortMACAddress,
+ &msg->pdelay_resp.requestingPortIdentity);
+
+ msg->pdelay_resp.requestingPortIdentity.portNumber = info->portNumber;
+
+ /*
+ * Remember T2 from the payload. The follow-up will deliver
+ * (T3-T2) instead of T3. In addition, the payload values are
+ * only 32 bits wide. We will just construct phony T2 and T3
+ * values without the missing upper bits.
+ */
+ info->rx_t2 = ntohl(pdu->pdu.time.T2TimeStamp);
+ ns_to_Timestamp(info->rx_t2, &msg->pdelay_resp.requestReceiptTimestamp);
+
+ return msg_pre_send(msg) ? -1 : pdulen;
+}
+
+static int ptcp_resfup_to_ptp(struct PTCP_DELAY_FU_RSP_PDU *pdu, int len,
+ struct ptp_message *msg, struct pn_info *info)
+{
+ int pdulen = sizeof(struct pdelay_resp_fup_msg);
+ uint64_t diff;
+
+ if (len < sizeof(*pdu))
+ return -EBADMSG;
+
+ msg->hwts.type = info->timestamping;
+
+ msg->header.tsmt = PDELAY_RESP_FOLLOW_UP | info->transportSpecific;
+ msg->header.ver = PTP_VERSION;
+ msg->header.messageLength = pdulen;
+ msg->header.domainNumber = info->domain;
+ msg->header.sourcePortIdentity = info->peer_portid;
+ msg->header.sequenceId = ntohs(pdu->hdr.SequenceID);
+ msg->header.control = CTL_OTHER;
+ msg->header.logMessageInterval = 0x7f;
+
+ mac_to_pid(pdu->pdu.param.PortMACAddress,
+ &msg->pdelay_resp_fup.requestingPortIdentity);
+
+ msg->pdelay_resp_fup.requestingPortIdentity.portNumber = info->portNumber;
+
+ diff = ntohl(pdu->hdr.Delay1ns);
+ diff += info->rx_t2;
+ ns_to_Timestamp(diff, &msg->pdelay_resp_fup.responseOriginTimestamp);
+
+ return msg_pre_send(msg) ? -1 : pdulen;
+}
+
+int ptcp_to_ptp(struct PTCP_PDU *pdu, int len, struct ptp_message *msg,
+ struct pn_info *info)
+{
+ if (len < sizeof(pdu->frame_id))
+ return -EBADMSG;
+ len -= sizeof(pdu->frame_id);
+
+ switch (ntohs(pdu->frame_id)) {
+ case FID_SYFU_TIME:
+ return ptcp_sync_to_ptp(&pdu->sync, len, msg, info, 1);
+ case FID_SYNC_TIME:
+ return ptcp_sync_to_ptp(&pdu->sync, len, msg, info, 0);
+ case FID_ANNO_TIME:
+ return ptcp_anno_to_ptp(&pdu->anno, len, msg, info);
+ case FID_FUP_TIME:
+ return ptcp_fup_to_ptp(&pdu->fup, len, msg, info);
+ case FID_DELAY_REQ:
+ return ptcp_req_to_ptp(&pdu->req, len, msg, info);
+ case FID_DELAY_RESP:
+ return ptcp_resp_to_ptp(&pdu->resp, len, msg, info, 1);
+ case FID_DELAY_RESP_FU:
+ return ptcp_resfup_to_ptp(&pdu->resfup, len, msg, info);
+ case FID_DELAY_RESP_1:
+ return ptcp_resp_to_ptp(&pdu->resp, len, msg, info, 0);
+ case FID_SYFU_CLOCK:
+ case FID_SYNC_CLOCK:
+ case FID_ANNO_CLOCK:
+ case FID_FUP_CLOCK:
+ pr_debug("ignoring PTCP message %x", ntohs(pdu->frame_id));
+ return 0;
+ }
+ return -1;
+}
diff --git a/pn-ptcp.h b/pn-ptcp.h
new file mode 100644
index 0000000..f2a5cd4
--- /dev/null
+++ b/pn-ptcp.h
@@ -0,0 +1,252 @@
+/**
+ * @file pn-ptcp.h
+ * @brief Implements PTCP message types.
+ * @note Copyright (C) 2016 linutronix GmbH
+ * @note SPDX-License-Identifier: GPL-2.0+
+ */
+#ifndef HAVE_PN_PTCP_H
+#define HAVE_PN_PTCP_H
+
+#include <stdint.h>
+
+#include "address.h"
+#include "ddt.h"
+#include "ether.h"
+#include "msg.h"
+#include "transport_private.h"
+
+#define SUBD_LEN 16
+
+struct PTCP_Header_Sync {
+ uint32_t reserved_1;
+ uint32_t reserved_2;
+ uint32_t Delay10ns;
+ uint16_t SequenceID;
+ uint8_t Delay1ns_Byte;
+ /* Ensure 32 bit alignment. */
+ uint8_t Padding;
+ uint32_t Delay1ns;
+} PACKED;
+
+struct PTCP_Header_FUP {
+ uint8_t Padding[12];
+ uint16_t SequenceID;
+ uint8_t Padding2[2];
+ int32_t Delay1ns_FUP;
+} PACKED;
+
+struct PTCP_Header_Announce {
+ uint8_t Padding[12];
+ uint16_t SequenceID;
+ uint8_t Padding2[6];
+} PACKED;
+
+struct PTCP_Header_Delay {
+ uint8_t Padding[12];
+ uint16_t SequenceID;
+ uint8_t Padding2[2];
+ uint32_t Delay1ns;
+} PACKED;
+
+enum ptcp_tlv_type {
+ PTCP_END,
+ PTCP_SUBDOMAIN,
+ PTCP_TIME,
+ PTCP_TIME_EXTENSION,
+ PTCP_MASTER,
+ PTCP_PORT_PARAMETER,
+ PTCP_DELAY_PARAMETER,
+ PTCP_PORT_TIME,
+};
+
+struct PTCPSubdomain {
+ uint16_t TLVHeader;
+ eth_addr MasterSourceAddress;
+ uint8_t SubdomainUUID[SUBD_LEN];
+} PACKED;
+
+struct PTCPTime {
+ uint16_t TLVHeader;
+ uint16_t EpochNumber;
+ uint32_t Seconds;
+ uint32_t NanoSeconds;
+} PACKED;
+
+struct PTCPTimeExtension {
+ uint16_t TLVHeader;
+ uint16_t Flags;
+ int16_t CurrentUTCOffset;
+ /* Ensure 32 bit alignment. */
+ uint16_t Padding;
+} PACKED;
+
+struct PTCPMaster {
+ uint16_t TLVHeader;
+ uint8_t MasterPriority1;
+ uint8_t MasterPriority2;
+ uint8_t ClockClass;
+ uint8_t ClockAccuracy;
+ uint16_t ClockVariance;
+ /* Ensure 32 bit alignment. */
+ /* Padding */
+} PACKED;
+
+struct PTCPDelayParameter {
+ uint16_t TLVHeader;
+ uint8_t PortMACAddress[6];
+} PACKED;
+
+struct PTCPPortParameter {
+ uint16_t TLVHeader;
+ uint8_t Padding[2];
+ uint32_t T2PortRxDelay;
+ uint32_t T3PortTxDelay;
+} PACKED;
+
+struct PTCPPortTime {
+ uint16_t TLVHeader;
+ uint8_t Padding[2];
+ uint32_t T2TimeStamp;
+} PACKED;
+
+struct PTCP_RTSyncPDU {
+ struct PTCPSubdomain subdomain;
+ struct PTCPTime time;
+ struct PTCPTimeExtension tmext;
+ struct PTCPMaster master;
+ /* PTCPOption */
+ uint16_t End;
+ /*
+ * Shall be existent, if initially transmitted in the RED
+ * period and may be omitted, if initially transmitted in the
+ * ORANGE or GREEN period:
+ */
+ /* APDU_Status */
+} PACKED;
+
+struct PTCP_FollowUpPDU {
+ struct PTCPSubdomain subdomain;
+ struct PTCPTime time;
+ /* PTCPOption */
+ uint16_t End;
+} PACKED;
+
+struct PTCP_AnnouncePDU {
+ struct PTCPSubdomain subdomain;
+ struct PTCPMaster master;
+ /* PTCPOption */
+ uint16_t End;
+} PACKED;
+
+struct PTCP_DelayReqPDU {
+ struct PTCPDelayParameter param;
+ /* PTCPOption */
+ uint16_t End;
+} PACKED;
+
+struct PTCP_DelayResPDU {
+ struct PTCPDelayParameter delay_param;
+ struct PTCPPortParameter port_param;
+ struct PTCPPortTime time;
+ /* PTCPOption */
+ uint16_t End;
+} PACKED;
+
+struct PTCP_DelayFuResPDU {
+ struct PTCPDelayParameter param;
+ /* PTCPOption */
+ uint16_t End;
+} PACKED;
+
+struct PTCP_SYNC_PDU {
+ struct PTCP_Header_Sync hdr;
+ struct PTCP_RTSyncPDU pdu;
+} PACKED;
+
+struct PTCP_FUP_PDU {
+ struct PTCP_Header_FUP hdr;
+ struct PTCP_FollowUpPDU pdu;
+} PACKED;
+
+struct PTCP_ANNOUNCE_PDU {
+ struct PTCP_Header_Announce hdr;
+ struct PTCP_AnnouncePDU pdu;
+} PACKED;
+
+struct PTCP_DELAY_REQ_PDU {
+ struct PTCP_Header_Delay hdr;
+ struct PTCP_DelayReqPDU pdu;
+} PACKED;
+
+struct PTCP_DELAY_RSP_PDU {
+ struct PTCP_Header_Delay hdr;
+ struct PTCP_DelayResPDU pdu;
+} PACKED;
+
+struct PTCP_DELAY_FU_RSP_PDU {
+ struct PTCP_Header_Delay hdr;
+ struct PTCP_DelayFuResPDU pdu;
+} PACKED;
+
+struct PTCP_PDU {
+ uint16_t frame_id;
+ union {
+ struct PTCP_SYNC_PDU sync;
+ struct PTCP_FUP_PDU fup;
+ struct PTCP_ANNOUNCE_PDU anno;
+ struct PTCP_DELAY_REQ_PDU req;
+ struct PTCP_DELAY_RSP_PDU resp;
+ struct PTCP_DELAY_FU_RSP_PDU resfup;
+ } PACKED;
+} PACKED;
+
+/*
+ * Head room fits a VLAN Ethernet header, and 'pdu.sync' is 64 bit aligned.
+ */
+#define PN_HEADROOM 22
+
+struct pn_storage {
+ unsigned char reserved[PN_HEADROOM];
+ struct PTCP_PDU pdu;
+} PACKED;
+
+struct pn_info {
+ struct transport t;
+ struct address src_addr;
+ int vlan;
+
+ /* Peer port ID is not provided by PTCP response or follow-up! */
+ struct PortIdentity peer_portid;
+
+ /* Transmit conversion helpers */
+ uint64_t tx_t2;
+
+ /* Receive conversion helpers */
+ enum timestamp_type timestamping;
+ UInteger16 portNumber;
+ uint64_t rx_t2;
+
+ /* The following fields are in network byte order. */
+
+ /* Transmit conversion helpers */
+ unsigned char subdomain[SUBD_LEN];
+ struct ClockQuality quality;
+ struct PTCPTime time;
+ uint8_t priority2;
+ int16_t utc_offset;
+ uint16_t tmext_flags;
+
+ /* Receive conversion helpers */
+ uint8_t domain;
+ UInteger8 transportSpecific;
+ Integer8 logSyncInterval;
+ Integer8 logAnnounceInterval;
+};
+
+unsigned char *ptp_to_ptcp(struct ptp_message *src, struct PTCP_PDU *dst,
+ struct pn_info *info, int *len);
+
+int ptcp_to_ptp(struct PTCP_PDU *src, int len, struct ptp_message *dst,
+ struct pn_info *info);
+
+#endif
diff --git a/pn.c b/pn.c
new file mode 100644
index 0000000..e5de0b3
--- /dev/null
+++ b/pn.c
@@ -0,0 +1,413 @@
+/**
+ * @file pn.c
+ * @note Derived from raw.c.
+ * @note Copyright (C) 2012 Richard Cochran <richardcochran@gmail.com>
+ * @note SPDX-License-Identifier: GPL-2.0+
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/filter.h>
+#include <linux/if_ether.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netpacket/packet.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <linux/errqueue.h>
+#include <linux/net_tstamp.h>
+#include <linux/sockios.h>
+
+#include "address.h"
+#include "config.h"
+#include "contain.h"
+#include "ether.h"
+#include "missing.h"
+#include "pn.h"
+#include "pn-ptcp.h"
+#include "print.h"
+#include "sk.h"
+#include "transport_private.h"
+#include "util.h"
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+/*
+ * tcpdump -d \
+ * '(ether[12:2]=0x8100 and ether[12+4:2]=0x8892 and '\
+ * ' (ether[14+4:2]=0x0020 or ether[14+4:2]=0x0021 or '\
+ * ' ether[14+4:2]=0x0080 or ether[14+4:2]=0x0081 or '\
+ * ' ether[14+4:2]=0xFF40 or ether[14+4:2]=0xFF41 or ether[14+4:2]=0xFF43)) or '\
+ * '(ether[12:2]=0x8892 and '\
+ * ' (ether[14:2]=0x0020 or ether[14:2]=0x0021 or '\
+ * ' ether[14:2]=0x0080 or ether[14:2]=0x0081 or '\
+ * ' ether[14:2]=0xFF40 or ether[14:2]=0xFF41 or ether[14:2]=0xFF43))'
+ *
+ * (000) ldh [12]
+ * (001) jeq #0x8100 jt 2 jf 6
+ * (002) ldh [16]
+ * (003) jeq #0x8892 jt 4 jf 16
+ * (004) ldh [18]
+ * (005) jeq #0x20 jt 15 jf 9
+ * (006) jeq #0x8892 jt 7 jf 16
+ * (007) ldh [14]
+ * (008) jeq #0x20 jt 15 jf 9
+ * (009) jeq #0x21 jt 15 jf 10
+ * (010) jeq #0x80 jt 15 jf 11
+ * (011) jeq #0x81 jt 15 jf 12
+ * (012) jeq #0xff40 jt 15 jf 13
+ * (013) jeq #0xff41 jt 15 jf 14
+ * (014) jeq #0xff43 jt 15 jf 16
+ * (015) ret #262144
+ * (016) ret #0
+ */
+static struct sock_filter pn_event_filter[] = {
+ { 0x28, 0, 0, 0x0000000c },
+ { 0x15, 0, 4, 0x00008100 },
+ { 0x28, 0, 0, 0x00000010 },
+ { 0x15, 0, 12, 0x00008892 },
+ { 0x28, 0, 0, 0x00000012 },
+ { 0x15, 9, 3, 0x00000020 },
+ { 0x15, 0, 9, 0x00008892 },
+ { 0x28, 0, 0, 0x0000000e },
+ { 0x15, 6, 0, 0x00000020 },
+ { 0x15, 5, 0, 0x00000021 },
+ { 0x15, 4, 0, 0x00000080 },
+ { 0x15, 3, 0, 0x00000081 },
+ { 0x15, 2, 0, 0x0000ff40 },
+ { 0x15, 1, 0, 0x0000ff41 },
+ { 0x15, 0, 1, 0x0000ff43 },
+ { 0x6, 0, 0, 0x00040000 },
+ { 0x6, 0, 0, 0x00000000 },
+};
+
+/*
+ * tcpdump -d \
+ * '(ether[12:2]=0x8100 and ether[12+4:2]=0x8892 and '\
+ * ' (ether[14+4:2]=0xFF00 or ether[14+4:2]=0xFF01 or ether[14+4:2]=0xFF20 or '\
+ * ' ether[14+4:2]=0xFF21 or ether[14+4:2]=0xFF42)) or '\
+ * '(ether[12:2]=0x8892 and '\
+ * ' (ether[14:2]=0xFF00 or ether[14:2]=0xFF01 or ether[14:2]=0xFF20 or '\
+ * ' ether[14:2]=0xFF21 or ether[14:2]=0xFF42))'
+ *
+ * (000) ldh [12]
+ * (001) jeq #0x8100 jt 2 jf 6
+ * (002) ldh [16]
+ * (003) jeq #0x8892 jt 4 jf 14
+ * (004) ldh [18]
+ * (005) jeq #0xff00 jt 13 jf 9
+ * (006) jeq #0x8892 jt 7 jf 14
+ * (007) ldh [14]
+ * (008) jeq #0xff00 jt 13 jf 9
+ * (009) jeq #0xff01 jt 13 jf 10
+ * (010) jeq #0xff20 jt 13 jf 11
+ * (011) jeq #0xff21 jt 13 jf 12
+ * (012) jeq #0xff42 jt 13 jf 14
+ * (013) ret #262144
+ * (014) ret #0
+ */
+static struct sock_filter pn_general_filter[] = {
+ { 0x28, 0, 0, 0x0000000c },
+ { 0x15, 0, 4, 0x00008100 },
+ { 0x28, 0, 0, 0x00000010 },
+ { 0x15, 0, 10, 0x00008892 },
+ { 0x28, 0, 0, 0x00000012 },
+ { 0x15, 7, 3, 0x0000ff00 },
+ { 0x15, 0, 7, 0x00008892 },
+ { 0x28, 0, 0, 0x0000000e },
+ { 0x15, 4, 0, 0x0000ff00 },
+ { 0x15, 3, 0, 0x0000ff01 },
+ { 0x15, 2, 0, 0x0000ff20 },
+ { 0x15, 1, 0, 0x0000ff21 },
+ { 0x15, 0, 1, 0x0000ff42 },
+ { 0x6, 0, 0, 0x00040000 },
+ { 0x6, 0, 0, 0x00000000 },
+};
+
+static int pn_configure(int fd, int event, int index, int enable)
+{
+ struct packet_mreq mreq;
+ struct sock_fprog prg;
+ int option;
+
+ if (event) {
+ prg.len = ARRAY_SIZE(pn_event_filter);
+ prg.filter = pn_event_filter;
+ } else {
+ prg.len = ARRAY_SIZE(pn_general_filter);
+ prg.filter = pn_general_filter;
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &prg, sizeof(prg))) {
+ pr_err("setsockopt SO_ATTACH_FILTER failed: %m");
+ return -1;
+ }
+
+ option = enable ? PACKET_ADD_MEMBERSHIP : PACKET_DROP_MEMBERSHIP;
+
+ memset(&mreq, 0, sizeof (mreq));
+ mreq.mr_ifindex = index;
+ mreq.mr_type = PACKET_MR_ALLMULTI;
+ mreq.mr_alen = 0;
+ if (!setsockopt(fd, SOL_PACKET, option, &mreq, sizeof(mreq))) {
+ return 0;
+ }
+ pr_warning("setsockopt PACKET_MR_ALLMULTI failed: %m");
+
+ mreq.mr_ifindex = index;
+ mreq.mr_type = PACKET_MR_PROMISC;
+ mreq.mr_alen = 0;
+ memset(&mreq.mr_address, 0, sizeof (mreq.mr_address));
+ if (!setsockopt(fd, SOL_PACKET, option, &mreq, sizeof(mreq))) {
+ return 0;
+ }
+ pr_warning("setsockopt PACKET_MR_PROMISC failed: %m");
+
+ pr_err("all socket options failed");
+ return -1;
+}
+
+static int pn_close(struct transport *t, struct fdarray *fda)
+{
+ close(fda->fd[0]);
+ close(fda->fd[1]);
+ return 0;
+}
+
+static int open_socket(struct interface *iface, int event,
+ enum timestamp_type ts_type)
+{
+ const char *name = interface_label(iface);
+ struct sockaddr_ll addr;
+ int fd, index;
+
+ fd = socket(PF_PACKET, SOCK_RAW, 0);
+ if (fd < 0) {
+ pr_err("socket failed: %m");
+ goto no_socket;
+ }
+ index = sk_interface_index(fd, name);
+ if (index < 0)
+ goto no_option;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, name, strlen(name))) {
+ pr_err("setsockopt SO_BINDTODEVICE failed: %m");
+ goto no_option;
+ }
+
+ if (event) {
+ if (sk_timestamping_init(fd, name, ts_type, TRANS_IEEE_802_3,
+ interface_get_vclock(iface)))
+ goto no_option;
+ } else {
+ if (sk_general_init(fd))
+ goto no_option;
+ }
+
+ if (pn_configure(fd, event, index, 1))
+ goto no_option;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sll_ifindex = index;
+ addr.sll_family = AF_PACKET;
+ addr.sll_protocol = htons(ETH_P_ALL);
+ if (bind(fd, (struct sockaddr *) &addr, sizeof(addr))) {
+ pr_err("bind failed: %m");
+ goto no_option;
+ }
+
+ return fd;
+no_option:
+ close(fd);
+no_socket:
+ return -1;
+}
+
+static void addr_to_mac(void *mac, struct address *addr)
+{
+ memcpy(mac, &addr->sll.sll_addr, MAC_LEN);
+}
+
+static int parse_subdomain(const char *str, unsigned char subdomain[SUBD_LEN])
+{
+ unsigned char buf[SUBD_LEN];
+ int c = sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:"
+ "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+ &buf[0], &buf[1], &buf[2], &buf[3],
+ &buf[4], &buf[5], &buf[6], &buf[7],
+ &buf[8], &buf[9], &buf[10], &buf[11],
+ &buf[12], &buf[13], &buf[14], &buf[15]);
+ if (c != SUBD_LEN) {
+ return -1;
+ }
+ memcpy(subdomain, buf, SUBD_LEN);
+ return 0;
+}
+
+static int pn_open(struct transport *t, struct interface *iface,
+ struct fdarray *fda, enum timestamp_type ts_type)
+{
+ struct pn_info *pn = container_of(t, struct pn_info, t);
+ uint16_t log_variance;
+ const char *name;
+ int efd, gfd;
+ char *str;
+
+ name = interface_label(iface);
+ str = config_get_string(t->cfg, name, "subdomainUUID");
+ if (parse_subdomain(str, pn->subdomain)) {
+ pr_err("invalid subdomainUUID %s", str);
+ return -1;
+ }
+ pn->quality.clockClass = config_get_int(t->cfg, NULL, "clockClass");
+ pn->quality.clockAccuracy = config_get_int(t->cfg, NULL, "clockAccuracy");
+ log_variance = config_get_int(t->cfg, NULL, "offsetScaledLogVariance");
+ pn->quality.offsetScaledLogVariance = htons(log_variance);
+ pn->priority2 = config_get_int(t->cfg, NULL, "priority2");
+ pn->utc_offset = htons(CURRENT_UTC_OFFSET);
+
+ pn->timestamping = config_get_int(t->cfg, NULL, "time_stamping");
+ pn->domain = config_get_int(t->cfg, NULL, "domainNumber");
+ pn->transportSpecific = config_get_int(t->cfg, name, "transportSpecific");
+ pn->logSyncInterval = config_get_int(t->cfg, name, "logSyncInterval");
+ pn->logAnnounceInterval = config_get_int(t->cfg, name, "logAnnounceInterval");
+
+ if (sk_interface_macaddr(name, &pn->src_addr))
+ goto no_mac;
+
+ efd = open_socket(iface, 1, ts_type);
+ if (efd < 0)
+ goto no_event;
+
+ gfd = open_socket(iface, 0, ts_type);
+ if (gfd < 0)
+ goto no_general;
+
+ fda->fd[FD_EVENT] = efd;
+ fda->fd[FD_GENERAL] = gfd;
+ return 0;
+
+no_general:
+ close(efd);
+no_event:
+no_mac:
+ return -1;
+}
+
+static int pn_recv(struct transport *t, int fd, void *buf, int buflen,
+ struct address *addr, struct hw_timestamp *hwts)
+{
+ struct pn_info *pn = container_of(t, struct pn_info, t);
+ struct pn_storage rx_data;
+ struct PTCP_PDU *src = &rx_data.pdu;
+ unsigned char *ptr = (unsigned char *) src;
+ int cnt, hlen, length = sizeof(*src);
+ struct ptp_message *dst = buf;
+ struct eth_hdr *hdr;
+
+ if (pn->vlan) {
+ hlen = sizeof(struct vlan_hdr);
+ } else {
+ hlen = sizeof(struct eth_hdr);
+ }
+ ptr -= hlen;
+ length += hlen;
+ hdr = (struct eth_hdr *) ptr;
+
+ cnt = sk_receive(fd, ptr, length, addr, hwts, MSG_DONTWAIT);
+ if (cnt >= 0) {
+ cnt -= hlen;
+ }
+ if (cnt < 0) {
+ return cnt;
+ }
+
+ if (pn->vlan) {
+ if (ETH_P_PROFINET == ntohs(hdr->type)) {
+ pr_notice("pn: disabling VLAN mode");
+ pn->vlan = 0;
+ }
+ } else {
+ if (ETH_P_8021Q == ntohs(hdr->type)) {
+ pr_notice("pn: switching to VLAN mode");
+ pn->vlan = 1;
+ }
+ }
+ cnt = ptcp_to_ptp(src, cnt, dst, pn);
+ return cnt;
+}
+
+static int pn_send(struct transport *t, struct fdarray *fda,
+ enum transport_event event, int peer, void *buf, int buflen,
+ struct address *addr, struct hw_timestamp *hwts)
+{
+ struct pn_info *pn = container_of(t, struct pn_info, t);
+ struct pn_storage tx_data;
+ struct PTCP_PDU *dst = &tx_data.pdu;
+ struct ptp_message *src = buf;
+ unsigned char pkt[1600], *ptr;
+ struct eth_hdr *hdr;
+ int fd, length;
+ ssize_t cnt;
+
+ fd = event ? fda->fd[FD_EVENT] : fda->fd[FD_GENERAL];
+ ptr = ptp_to_ptcp(src, dst, pn, &length);
+ if (!ptr) {
+ return -EINVAL;
+ }
+
+ hdr = (struct eth_hdr *) ptr;
+ addr_to_mac(&hdr->src, &pn->src_addr);
+ cnt = send(fd, ptr, length, 0);
+ if (cnt < 1) {
+ pr_err("send failed: %d %m", errno);
+ return cnt;
+ }
+ /*
+ * Get the time stamp right away.
+ */
+ return event == TRANS_EVENT ?
+ sk_receive(fd, pkt, sizeof(pkt), NULL, hwts, MSG_ERRQUEUE) : cnt;
+}
+
+static void pn_release(struct transport *t)
+{
+ struct pn_info *pn = container_of(t, struct pn_info, t);
+ free(pn);
+}
+
+static int pn_physical_addr(struct transport *t, uint8_t *addr)
+{
+ struct pn_info *pn = container_of(t, struct pn_info, t);
+ addr_to_mac(addr, &pn->src_addr);
+ return MAC_LEN;
+}
+
+static int pn_protocol_addr(struct transport *t, uint8_t *addr)
+{
+ struct pn_info *pn = container_of(t, struct pn_info, t);
+ addr_to_mac(addr, &pn->src_addr);
+ return MAC_LEN;
+}
+
+struct transport *pn_transport_create(void)
+{
+ struct pn_info *pn;
+ pn = calloc(1, sizeof(*pn));
+ if (!pn)
+ return NULL;
+ pn->t.close = pn_close;
+ pn->t.open = pn_open;
+ pn->t.recv = pn_recv;
+ pn->t.send = pn_send;
+ pn->t.release = pn_release;
+ pn->t.physical_addr = pn_physical_addr;
+ pn->t.protocol_addr = pn_protocol_addr;
+ return &pn->t;
+}
diff --git a/pn.h b/pn.h
new file mode 100644
index 0000000..d913f31
--- /dev/null
+++ b/pn.h
@@ -0,0 +1,19 @@
+/**
+ * @file pn.h
+ * @brief Implements transport over PN.
+ * @note Derived from raw.h.
+ * @note Copyright (C) 2012 Richard Cochran <richardcochran@gmail.com>
+ * @note SPDX-License-Identifier: GPL-2.0+
+ */
+#ifndef HAVE_PN_H
+#define HAVE_PN_H
+
+#include "transport.h"
+
+/**
+ * Allocate an instance of a PN Ethernet transport.
+ * @return Pointer to a new transport instance on success, NULL otherwise.
+ */
+struct transport *pn_transport_create(void);
+
+#endif
diff --git a/ptp4l.8 b/ptp4l.8
index 06e3943..d581614 100644
--- a/ptp4l.8
+++ b/ptp4l.8
@@ -251,7 +251,7 @@ The default is 0.
.TP
.B estimate_sync_timestamp
Enables ptp4l to provide in two-step sync messages an estimated transmit time
-stamp instead of zero.
+stamp instead of zero. The PN transport needs this option to be enabled.
The default is 0 (disabled).
.TP
@@ -373,7 +373,8 @@ greater than this value the port is marked as not 802.1AS capable.
.TP
.B network_transport
-Select the network transport. Possible values are UDPv4, UDPv6 and L2.
+Select the network transport. Possible values are UDPv4, UDPv6, L2 (Ethernet),
+and PN (PROFINET).
The default is UDPv4.
.TP
@@ -530,6 +531,12 @@ delay messages (logMinDelayReqInterval or logMinPdelayReqInterval). Weighting
is useful with larger network jitters (e.g. software time stamping).
The default is filter.
+.TP
+.B subdomainUUID
+Specifies the UUID of the PTCP subdomain used with the PN transport.
+The default is 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 (which indicates
+an unknown subdomain).
+
.TP
.B udp_ttl
Specifies the Time to live (TTL) value for IPv4 multicast messages and the hop
diff --git a/transport.c b/transport.c
index 9366fbf..ae5c06c 100644
--- a/transport.c
+++ b/transport.c
@@ -21,6 +21,7 @@
#include "transport.h"
#include "transport_private.h"
+#include "pn.h"
#include "raw.h"
#include "udp.h"
#include "udp6.h"
@@ -117,7 +118,9 @@ struct transport *transport_create(struct config *cfg,
break;
case TRANS_DEVICENET:
case TRANS_CONTROLNET:
+ break;
case TRANS_PROFINET:
+ t = pn_transport_create();
break;
}
if (t) {
commit 20b0c2d27da5c7689cf4d9d8510d885b0582c500
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Wed Aug 19 11:35:49 2026 +0200
SQUASH: pn-ptcp: ignore out-of-order DelayFuRes
diff --git a/pn-ptcp.c b/pn-ptcp.c
index 30943d2..ff22838 100644
--- a/pn-ptcp.c
+++ b/pn-ptcp.c
@@ -562,6 +562,7 @@ static int ptcp_resp_to_ptp(struct PTCP_DELAY_RSP_PDU *pdu, int len,
* only 32 bits wide. We will just construct phony T2 and T3
* values without the missing upper bits.
*/
+ info->sequenceId = msg->header.sequenceId;
info->rx_t2 = ntohl(pdu->pdu.time.T2TimeStamp);
ns_to_Timestamp(info->rx_t2, &msg->pdelay_resp.requestReceiptTimestamp);
@@ -577,6 +578,11 @@ static int ptcp_resfup_to_ptp(struct PTCP_DELAY_FU_RSP_PDU *pdu, int len,
if (len < sizeof(*pdu))
return -EBADMSG;
+ if (info->sequenceId != ntohs(pdu->hdr.SequenceID)) {
+ pr_warning("ignoring out-of-order PTCP DelayFuRes message");
+ return 0;
+ }
+
msg->hwts.type = info->timestamping;
msg->header.tsmt = PDELAY_RESP_FOLLOW_UP | info->transportSpecific;
diff --git a/pn-ptcp.h b/pn-ptcp.h
index f2a5cd4..a5f657c 100644
--- a/pn-ptcp.h
+++ b/pn-ptcp.h
@@ -224,6 +224,7 @@ struct pn_info {
/* Receive conversion helpers */
enum timestamp_type timestamping;
UInteger16 portNumber;
+ UInteger16 sequenceId;
uint64_t rx_t2;
/* The following fields are in network byte order. */
commit 7b782a4d133c7f827cacf2c5cf67e028b00906ea
Author: Richard Cochran <richardcochran@gmail.com>
Date: Tue Jan 10 10:51:55 2017 +0100
port: Ensure that PN transport is configured correctly.
Make sure that ports using the PROFINET transport have the delay
mechanism set to P2P and enabled the estimated timestamp in sync
messages.
Co-authored-by: Miroslav Lichvar <mlichvar@redhat.com>
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/port.c b/port.c
index 4f1dd40..b24268e 100644
--- a/port.c
+++ b/port.c
@@ -3723,6 +3723,18 @@ struct port *port_open(const char *phc_device,
pr_err("%s: spp needs at least PTPv2.1", p->log_name);
goto err_uc_service;
}
+ if (transport_type(p->trp) == TRANS_PROFINET) {
+ if (p->delayMechanism != DM_P2P) {
+ pr_err("%s: PN transport requires P2P mechanism",
+ p->log_name);
+ goto err_uc_service;
+ }
+ if (!p->estimate_sync_timestamp) {
+ pr_err("%s: PN transport requires estimate_sync_timestamp",
+ p->log_name);
+ goto err_uc_service;
+ }
+ }
/* Set fault timeouts to a default value */
for (i = 0; i < FT_CNT; i++) {
commit 86dff249d3a93a6f42294e6f3ad41c1f1d316ef6
Author: Miroslav Lichvar <mlichvar@redhat.com>
Date: Tue Apr 14 13:20:39 2026 +0200
config: Add rh_ prefix to new options.
Add rh_ prefix to the new PN-specific options and describe them as
experimental to make it clear they are downstream-only (at least for
now) and subject to change.
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
diff --git a/config.c b/config.c
index ecd612b..687288f 100644
--- a/config.c
+++ b/config.c
@@ -284,7 +284,7 @@ struct config_item config_tab[] = {
GLOB_ITEM_INT("dscp_general", 0, 0, 63),
GLOB_ITEM_INT("domainNumber", 0, 0, 255),
PORT_ITEM_INT("egressLatency", 0, INT_MIN, INT_MAX),
- PORT_ITEM_INT("estimate_sync_timestamp", 0, 0, 1),
+ PORT_ITEM_INT("rh_estimate_sync_timestamp", 0, 0, 1),
PORT_ITEM_INT("fault_badpeernet_interval", 16, INT32_MIN, INT32_MAX),
PORT_ITEM_INT("fault_reset_interval", 4, INT8_MIN, INT8_MAX),
GLOB_ITEM_DBL("first_step_threshold", 0.00002, 0.0, DBL_MAX),
@@ -363,7 +363,7 @@ struct config_item config_tab[] = {
PORT_ITEM_INT("spp", -1, -1, UINT8_MAX),
GLOB_ITEM_DBL("step_threshold", 0.0, 0.0, DBL_MAX),
GLOB_ITEM_INT("step_window", 0, 0, INT_MAX),
- PORT_ITEM_STR("subdomainUUID",
+ PORT_ITEM_STR("rh_subdomainUUID",
"00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"),
GLOB_ITEM_INT("summary_interval", 0, INT_MIN, INT_MAX),
PORT_ITEM_INT("syncReceiptTimeout", 0, 0, UINT8_MAX),
diff --git a/configs/default.cfg b/configs/default.cfg
index 8bce8e6..495ca43 100644
--- a/configs/default.cfg
+++ b/configs/default.cfg
@@ -39,7 +39,7 @@ G.8275.portDS.localPriority 128
allowedLostResponses 3
asCapable auto
BMCA ptp
-estimate_sync_timestamp 0
+rh_estimate_sync_timestamp 0
inhibit_announce 0
inhibit_delay_req 0
ignore_source_id 0
@@ -109,7 +109,7 @@ 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
-subdomainUUID 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
+rh_subdomainUUID 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
udp_ttl 1
udp6_scope 0x0E
uds_address /var/run/ptp4l
diff --git a/pn.c b/pn.c
index e5de0b3..cb064cb 100644
--- a/pn.c
+++ b/pn.c
@@ -260,7 +260,7 @@ static int pn_open(struct transport *t, struct interface *iface,
char *str;
name = interface_label(iface);
- str = config_get_string(t->cfg, name, "subdomainUUID");
+ str = config_get_string(t->cfg, name, "rh_subdomainUUID");
if (parse_subdomain(str, pn->subdomain)) {
pr_err("invalid subdomainUUID %s", str);
return -1;
diff --git a/port.c b/port.c
index b24268e..148af1b 100644
--- a/port.c
+++ b/port.c
@@ -3594,7 +3594,7 @@ struct port *port_open(const char *phc_device,
p->phc_index = phc_index;
p->jbod = config_get_int(cfg, interface_name(interface), "boundary_clock_jbod");
p->estimate_sync_timestamp =
- config_get_int(cfg, interface_name(interface), "estimate_sync_timestamp");
+ config_get_int(cfg, interface_name(interface), "rh_estimate_sync_timestamp");
p->master_only = config_get_int(cfg, interface_name(interface), "serverOnly");
p->bmca = config_get_int(cfg, interface_name(interface), "BMCA");
p->trp = transport_create(cfg, config_get_int(cfg,
diff --git a/ptp4l.8 b/ptp4l.8
index d581614..cf6b364 100644
--- a/ptp4l.8
+++ b/ptp4l.8
@@ -249,7 +249,10 @@ value will be added to egress time stamps obtained from the hardware.
The default is 0.
.TP
-.B estimate_sync_timestamp
+.B rh_estimate_sync_timestamp
+This is a RHEL-specific experimental option which can be renamed,
+removed, or the functionality changed, in a future minor update.
+
Enables ptp4l to provide in two-step sync messages an estimated transmit time
stamp instead of zero. The PN transport needs this option to be enabled.
The default is 0 (disabled).
@@ -374,7 +377,8 @@ greater than this value the port is marked as not 802.1AS capable.
.TP
.B network_transport
Select the network transport. Possible values are UDPv4, UDPv6, L2 (Ethernet),
-and PN (PROFINET).
+and PN (PROFINET). The PN transport is experimental in RHEL and it can be
+removed or changed in a future minor update.
The default is UDPv4.
.TP
@@ -532,7 +536,10 @@ is useful with larger network jitters (e.g. software time stamping).
The default is filter.
.TP
-.B subdomainUUID
+.B rh_subdomainUUID
+This is a RHEL-specific experimental option which can be renamed,
+removed, or the functionality changed, in a future minor update.
+
Specifies the UUID of the PTCP subdomain used with the PN transport.
The default is 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 (which indicates
an unknown subdomain).