dcavalca / rpms / linuxptp

Forked from rpms/linuxptp 2 years ago
Clone
ab4723
Patches backported from the upstream repository.
ab4723
ab4723
commit 6d2e07353d042b845da60dc6e3a20a71932678d0
ab4723
Author: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
Date:   Tue Mar 8 11:46:58 2022 +0100
ab4723
ab4723
    rtnl: Fix rtnl_rtattr_parse() to process max attribute.
ab4723
    
ab4723
    Initialize the whole array passed to rtnl_rtattr_parse() and don't
ab4723
    ignore the last attribute with the maximum value. This will be needed to
ab4723
    get the ETHTOOL_A_PHC_VCLOCKS_INDEX attribute.
ab4723
    
ab4723
    Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
    Acked-by: Hangbin Liu <liuhangbin@gmail.com>
ab4723
ab4723
diff --git a/rtnl.c b/rtnl.c
ab4723
index b7a2667..b02e07d 100644
ab4723
--- a/rtnl.c
ab4723
+++ b/rtnl.c
ab4723
@@ -178,10 +178,10 @@ static int rtnl_rtattr_parse(struct rtattr *tb[], int max, struct rtattr *rta, i
ab4723
 {
ab4723
 	unsigned short type;
ab4723
 
ab4723
-	memset(tb, 0, sizeof(struct rtattr *) * max);
ab4723
+	memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
ab4723
 	while (RTA_OK(rta, len)) {
ab4723
 		type = rta->rta_type;
ab4723
-		if ((type < max) && (!tb[type]))
ab4723
+		if ((type <= max) && (!tb[type]))
ab4723
 			tb[type] = rta;
ab4723
 		rta = RTA_NEXT(rta, len);
ab4723
 	}
ab4723
@@ -200,8 +200,8 @@ static inline int rtnl_nested_rtattr_parse(struct rtattr *tb[], int max, struct
ab4723
 
ab4723
 static int rtnl_linkinfo_parse(int master_index, struct rtattr *rta)
ab4723
 {
ab4723
-	struct rtattr *linkinfo[IFLA_INFO_MAX];
ab4723
-	struct rtattr *bond[IFLA_BOND_MAX];
ab4723
+	struct rtattr *linkinfo[IFLA_INFO_MAX+1];
ab4723
+	struct rtattr *bond[IFLA_BOND_MAX+1];
ab4723
 	int index = -1;
ab4723
 	char *kind;
ab4723
 
ab4723
commit 8c557a7c7e5eebc6f0d7e1de44c53791fba265c1
ab4723
Author: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
Date:   Tue Mar 8 11:46:59 2022 +0100
ab4723
ab4723
    rtnl: Add function to detect virtual clocks.
ab4723
    
ab4723
    Add a function using ethtool netlink to check whether a PHC is a virtual
ab4723
    clock of an interface.
ab4723
    
ab4723
    Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
    Acked-by: Hangbin Liu <liuhangbin@gmail.com>
ab4723
ab4723
diff --git a/incdefs.sh b/incdefs.sh
ab4723
index 19e620e..21333e5 100755
ab4723
--- a/incdefs.sh
ab4723
+++ b/incdefs.sh
ab4723
@@ -86,6 +86,10 @@ kernel_flags()
ab4723
 	if grep -q HWTSTAMP_TX_ONESTEP_P2P ${prefix}${tstamp}; then
ab4723
 		printf " -DHAVE_ONESTEP_P2P"
ab4723
 	fi
ab4723
+
ab4723
+	if grep -q SOF_TIMESTAMPING_BIND_PHC ${prefix}${tstamp}; then
ab4723
+		printf " -DHAVE_VCLOCKS"
ab4723
+	fi
ab4723
 }
ab4723
 
ab4723
 flags="$(user_flags)$(kernel_flags)"
ab4723
diff --git a/missing.h b/missing.h
ab4723
index 35eaf15..3df7bd1 100644
ab4723
--- a/missing.h
ab4723
+++ b/missing.h
ab4723
@@ -251,6 +251,107 @@ enum {
ab4723
 #define NLA_TYPE_MAX (__NLA_TYPE_MAX - 1)
ab4723
 #endif /*NLA_TYPE_MAX*/
ab4723
 
ab4723
+#ifndef ETHTOOL_GENL_NAME
ab4723
+#define ETHTOOL_GENL_NAME "ethtool"
ab4723
+#define ETHTOOL_GENL_VERSION 1
ab4723
+#endif
ab4723
+
ab4723
+#ifndef HAVE_VCLOCKS
ab4723
+enum {
ab4723
+	ETHTOOL_MSG_USER_NONE,
ab4723
+	ETHTOOL_MSG_STRSET_GET,
ab4723
+	ETHTOOL_MSG_LINKINFO_GET,
ab4723
+	ETHTOOL_MSG_LINKINFO_SET,
ab4723
+	ETHTOOL_MSG_LINKMODES_GET,
ab4723
+	ETHTOOL_MSG_LINKMODES_SET,
ab4723
+	ETHTOOL_MSG_LINKSTATE_GET,
ab4723
+	ETHTOOL_MSG_DEBUG_GET,
ab4723
+	ETHTOOL_MSG_DEBUG_SET,
ab4723
+	ETHTOOL_MSG_WOL_GET,
ab4723
+	ETHTOOL_MSG_WOL_SET,
ab4723
+	ETHTOOL_MSG_FEATURES_GET,
ab4723
+	ETHTOOL_MSG_FEATURES_SET,
ab4723
+	ETHTOOL_MSG_PRIVFLAGS_GET,
ab4723
+	ETHTOOL_MSG_PRIVFLAGS_SET,
ab4723
+	ETHTOOL_MSG_RINGS_GET,
ab4723
+	ETHTOOL_MSG_RINGS_SET,
ab4723
+	ETHTOOL_MSG_CHANNELS_GET,
ab4723
+	ETHTOOL_MSG_CHANNELS_SET,
ab4723
+	ETHTOOL_MSG_COALESCE_GET,
ab4723
+	ETHTOOL_MSG_COALESCE_SET,
ab4723
+	ETHTOOL_MSG_PAUSE_GET,
ab4723
+	ETHTOOL_MSG_PAUSE_SET,
ab4723
+	ETHTOOL_MSG_EEE_GET,
ab4723
+	ETHTOOL_MSG_EEE_SET,
ab4723
+	ETHTOOL_MSG_TSINFO_GET,
ab4723
+	ETHTOOL_MSG_CABLE_TEST_ACT,
ab4723
+	ETHTOOL_MSG_CABLE_TEST_TDR_ACT,
ab4723
+	ETHTOOL_MSG_TUNNEL_INFO_GET,
ab4723
+	ETHTOOL_MSG_FEC_GET,
ab4723
+	ETHTOOL_MSG_FEC_SET,
ab4723
+	ETHTOOL_MSG_MODULE_EEPROM_GET,
ab4723
+	ETHTOOL_MSG_STATS_GET,
ab4723
+	ETHTOOL_MSG_PHC_VCLOCKS_GET,
ab4723
+};
ab4723
+
ab4723
+enum {
ab4723
+	ETHTOOL_MSG_KERNEL_NONE,
ab4723
+	ETHTOOL_MSG_STRSET_GET_REPLY,
ab4723
+	ETHTOOL_MSG_LINKINFO_GET_REPLY,
ab4723
+	ETHTOOL_MSG_LINKINFO_NTF,
ab4723
+	ETHTOOL_MSG_LINKMODES_GET_REPLY,
ab4723
+	ETHTOOL_MSG_LINKMODES_NTF,
ab4723
+	ETHTOOL_MSG_LINKSTATE_GET_REPLY,
ab4723
+	ETHTOOL_MSG_DEBUG_GET_REPLY,
ab4723
+	ETHTOOL_MSG_DEBUG_NTF,
ab4723
+	ETHTOOL_MSG_WOL_GET_REPLY,
ab4723
+	ETHTOOL_MSG_WOL_NTF,
ab4723
+	ETHTOOL_MSG_FEATURES_GET_REPLY,
ab4723
+	ETHTOOL_MSG_FEATURES_SET_REPLY,
ab4723
+	ETHTOOL_MSG_FEATURES_NTF,
ab4723
+	ETHTOOL_MSG_PRIVFLAGS_GET_REPLY,
ab4723
+	ETHTOOL_MSG_PRIVFLAGS_NTF,
ab4723
+	ETHTOOL_MSG_RINGS_GET_REPLY,
ab4723
+	ETHTOOL_MSG_RINGS_NTF,
ab4723
+	ETHTOOL_MSG_CHANNELS_GET_REPLY,
ab4723
+	ETHTOOL_MSG_CHANNELS_NTF,
ab4723
+	ETHTOOL_MSG_COALESCE_GET_REPLY,
ab4723
+	ETHTOOL_MSG_COALESCE_NTF,
ab4723
+	ETHTOOL_MSG_PAUSE_GET_REPLY,
ab4723
+	ETHTOOL_MSG_PAUSE_NTF,
ab4723
+	ETHTOOL_MSG_EEE_GET_REPLY,
ab4723
+	ETHTOOL_MSG_EEE_NTF,
ab4723
+	ETHTOOL_MSG_TSINFO_GET_REPLY,
ab4723
+	ETHTOOL_MSG_CABLE_TEST_NTF,
ab4723
+	ETHTOOL_MSG_CABLE_TEST_TDR_NTF,
ab4723
+	ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY,
ab4723
+	ETHTOOL_MSG_FEC_GET_REPLY,
ab4723
+	ETHTOOL_MSG_FEC_NTF,
ab4723
+	ETHTOOL_MSG_MODULE_EEPROM_GET_REPLY,
ab4723
+	ETHTOOL_MSG_STATS_GET_REPLY,
ab4723
+	ETHTOOL_MSG_PHC_VCLOCKS_GET_REPLY,
ab4723
+};
ab4723
+
ab4723
+enum {
ab4723
+	ETHTOOL_A_HEADER_UNSPEC,
ab4723
+	ETHTOOL_A_HEADER_DEV_INDEX,		/* u32 */
ab4723
+	ETHTOOL_A_HEADER_DEV_NAME,		/* string */
ab4723
+	ETHTOOL_A_HEADER_FLAGS,			/* u32 - ETHTOOL_FLAG_* */
ab4723
+	__ETHTOOL_A_HEADER_CNT,
ab4723
+	ETHTOOL_A_HEADER_MAX = __ETHTOOL_A_HEADER_CNT - 1
ab4723
+};
ab4723
+
ab4723
+enum {
ab4723
+	ETHTOOL_A_PHC_VCLOCKS_UNSPEC,
ab4723
+	ETHTOOL_A_PHC_VCLOCKS_HEADER,			/* nest - _A_HEADER_* */
ab4723
+	ETHTOOL_A_PHC_VCLOCKS_NUM,			/* u32 */
ab4723
+	ETHTOOL_A_PHC_VCLOCKS_INDEX,			/* array, s32 */
ab4723
+	__ETHTOOL_A_PHC_VCLOCKS_CNT,
ab4723
+	ETHTOOL_A_PHC_VCLOCKS_MAX = (__ETHTOOL_A_PHC_VCLOCKS_CNT - 1)
ab4723
+};
ab4723
+
ab4723
+#endif /* HAVE_VCLOCKS */
ab4723
+
ab4723
 #ifdef __UCLIBC__
ab4723
 
ab4723
 #if (_XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L) && \
ab4723
diff --git a/rtnl.c b/rtnl.c
ab4723
index b02e07d..a8999b2 100644
ab4723
--- a/rtnl.c
ab4723
+++ b/rtnl.c
ab4723
@@ -19,6 +19,9 @@
ab4723
 #include <asm/types.h>
ab4723
 #include <sys/socket.h> /* Must come before linux/netlink.h on some systems. */
ab4723
 #include <linux/netlink.h>
ab4723
+#ifdef HAVE_VCLOCKS
ab4723
+#include <linux/ethtool_netlink.h>
ab4723
+#endif
ab4723
 #include <linux/rtnetlink.h>
ab4723
 #include <linux/genetlink.h>
ab4723
 #include <linux/if_team.h>
ab4723
@@ -465,3 +468,85 @@ no_info:
ab4723
 	nl_close(fd);
ab4723
 	return index;
ab4723
 }
ab4723
+
ab4723
+static int rtnl_search_vclocks(struct rtattr *attr, int phc_index)
ab4723
+{
ab4723
+	int i, len = RTA_PAYLOAD(attr);
ab4723
+
ab4723
+	for (i = 0; i < len / sizeof (__s32); i++) {
ab4723
+		if (((__s32 *)RTA_DATA(attr))[i] == phc_index)
ab4723
+			return 1;
ab4723
+	}
ab4723
+
ab4723
+	return 0;
ab4723
+}
ab4723
+
ab4723
+int rtnl_iface_has_vclock(const char *device, int phc_index)
ab4723
+{
ab4723
+	struct rtattr *tb[ETHTOOL_A_PHC_VCLOCKS_MAX + 1];
ab4723
+	int index, fd, gf_id, len, ret = 0;
ab4723
+	struct genlmsghdr *gnlh;
ab4723
+	struct nlmsghdr *nlh;
ab4723
+	char msg[BUF_SIZE];
ab4723
+	struct {
ab4723
+		struct nlattr attr;
ab4723
+		uint32_t index;
ab4723
+	} req;
ab4723
+
ab4723
+	index = if_nametoindex(device);
ab4723
+
ab4723
+	fd = nl_open(NETLINK_GENERIC);
ab4723
+	if (fd < 0)
ab4723
+		return 0;
ab4723
+
ab4723
+	gf_id = genl_get_family_id(fd, ETHTOOL_GENL_NAME);
ab4723
+	if (gf_id < 0) {
ab4723
+		pr_debug("ethtool netlink not supported");
ab4723
+		goto no_info;
ab4723
+	}
ab4723
+
ab4723
+	req.attr.nla_len = sizeof(req);
ab4723
+	req.attr.nla_type = ETHTOOL_A_HEADER_DEV_INDEX;
ab4723
+	req.index = index;
ab4723
+
ab4723
+	len = genl_send_msg(fd, gf_id, ETHTOOL_MSG_PHC_VCLOCKS_GET,
ab4723
+			    ETHTOOL_GENL_VERSION,
ab4723
+			    NLA_F_NESTED | ETHTOOL_A_PHC_VCLOCKS_HEADER, 
ab4723
+			    &req, sizeof(req));
ab4723
+
ab4723
+	if (len < 0) {
ab4723
+		pr_err("send vclock request failed: %m");
ab4723
+		goto no_info;
ab4723
+	}
ab4723
+
ab4723
+	len = recv(fd, msg, sizeof(msg), 0);
ab4723
+	if (len < 0) {
ab4723
+		pr_err("recv vclock failed: %m");
ab4723
+		goto no_info;
ab4723
+	}
ab4723
+
ab4723
+	for (nlh = (struct nlmsghdr *) msg; NLMSG_OK(nlh, len);
ab4723
+	     nlh = NLMSG_NEXT(nlh, len)) {
ab4723
+		if (nlh->nlmsg_type != gf_id)
ab4723
+			continue;
ab4723
+
ab4723
+		gnlh = (struct genlmsghdr *) NLMSG_DATA(nlh);
ab4723
+		if (gnlh->cmd != ETHTOOL_MSG_PHC_VCLOCKS_GET_REPLY)
ab4723
+			continue;
ab4723
+
ab4723
+		if (rtnl_rtattr_parse(tb, ETHTOOL_A_PHC_VCLOCKS_MAX,
ab4723
+				      (struct rtattr *) GENLMSG_DATA(msg),
ab4723
+				      NLMSG_PAYLOAD(nlh, GENL_HDRLEN)))
ab4723
+			continue;
ab4723
+
ab4723
+		if (tb[ETHTOOL_A_PHC_VCLOCKS_INDEX]) {
ab4723
+			ret = rtnl_search_vclocks(tb[ETHTOOL_A_PHC_VCLOCKS_INDEX],
ab4723
+						  phc_index);
ab4723
+			break;
ab4723
+		}
ab4723
+	}
ab4723
+
ab4723
+no_info:
ab4723
+	nl_close(fd);
ab4723
+	return ret;
ab4723
+}
ab4723
diff --git a/rtnl.h b/rtnl.h
ab4723
index 8fef4a9..96fee29 100644
ab4723
--- a/rtnl.h
ab4723
+++ b/rtnl.h
ab4723
@@ -59,6 +59,15 @@ int rtnl_link_query(int fd, const char *device);
ab4723
  */
ab4723
 int rtnl_link_status(int fd, const char *device, rtnl_callback cb, void *ctx);
ab4723
 
ab4723
+/**
ab4723
+ * Check if the PHC is a virtual clock of the interface (i.e. sockets bound to
ab4723
+ * the interface also need to be bound to the clock).
ab4723
+ * @param device    Name of the interface.
ab4723
+ * @param phc_index Index of the clock to check.
ab4723
+ * @return          1 if true, otherwise 0.
ab4723
+ */
ab4723
+int rtnl_iface_has_vclock(const char *device, int phc_index);
ab4723
+
ab4723
 /**
ab4723
  * Open a RT netlink socket for monitoring link state.
ab4723
  * @return    A valid socket, or -1 on error.
ab4723
commit 5477078bf5c9ef050c3bcb037f856b693f1247e7
ab4723
Author: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
Date:   Tue Mar 8 11:47:00 2022 +0100
ab4723
ab4723
    Add support for binding sockets to virtual clocks.
ab4723
    
ab4723
    With the latest kernels it is possible to create virtual PHCs on top of
ab4723
    a free-running physical PHC. In order for the application to get
ab4723
    timestamps captured by the clock which it is controlling, it needs to
ab4723
    bind its sockets to the clock using a new field in the SO_TIMESTAMPING
ab4723
    option.
ab4723
    
ab4723
    Extend the interface structure with the vclock index and modify the
ab4723
    transport code to pass it to sk_timestamping_init() to bind the sockets
ab4723
    to the clock.
ab4723
    
ab4723
    Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
ab4723
diff --git a/interface.c b/interface.c
ab4723
index 65bdff0..445a270 100644
ab4723
--- a/interface.c
ab4723
+++ b/interface.c
ab4723
@@ -12,6 +12,7 @@ struct interface {
ab4723
 	char name[MAX_IFNAME_SIZE + 1];
ab4723
 	char ts_label[MAX_IFNAME_SIZE + 1];
ab4723
 	struct sk_ts_info ts_info;
ab4723
+	int vclock;
ab4723
 };
ab4723
 
ab4723
 struct interface *interface_create(const char *name)
ab4723
@@ -23,6 +24,7 @@ struct interface *interface_create(const char *name)
ab4723
 		return NULL;
ab4723
 	}
ab4723
 	strncpy(iface->name, name, MAX_IFNAME_SIZE);
ab4723
+	iface->vclock = -1;
ab4723
 
ab4723
 	return iface;
ab4723
 }
ab4723
@@ -76,3 +78,13 @@ bool interface_tsmodes_supported(struct interface *iface, int modes)
ab4723
 	}
ab4723
 	return false;
ab4723
 }
ab4723
+
ab4723
+void interface_set_vclock(struct interface *iface, int vclock)
ab4723
+{
ab4723
+	iface->vclock = vclock;
ab4723
+}
ab4723
+
ab4723
+int interface_get_vclock(struct interface *iface)
ab4723
+{
ab4723
+	return iface->vclock;
ab4723
+}
ab4723
diff --git a/interface.h b/interface.h
ab4723
index 8bf2727..752f4f1 100644
ab4723
--- a/interface.h
ab4723
+++ b/interface.h
ab4723
@@ -91,4 +91,18 @@ bool interface_tsinfo_valid(struct interface *iface);
ab4723
  */
ab4723
 bool interface_tsmodes_supported(struct interface *iface, int modes);
ab4723
 
ab4723
+/**
ab4723
+ * Set the vclock (virtual PHC) to be used for timestamping on an interface.
ab4723
+ * @param iface  The interface of interest.
ab4723
+ * @param vclock The index of the vclock.
ab4723
+ */
ab4723
+void interface_set_vclock(struct interface *iface, int vclock);
ab4723
+
ab4723
+/**
ab4723
+ * Get the vclock index set for the interface.
ab4723
+ * @param iface  The interface of interest.
ab4723
+ * @return       The index of the vclock, or -1 if not set.
ab4723
+ */
ab4723
+int interface_get_vclock(struct interface *iface);
ab4723
+
ab4723
 #endif
ab4723
diff --git a/missing.h b/missing.h
ab4723
index 3df7bd1..c5194f4 100644
ab4723
--- a/missing.h
ab4723
+++ b/missing.h
ab4723
@@ -62,6 +62,17 @@ enum {
ab4723
 };
ab4723
 #endif
ab4723
 
ab4723
+#ifndef HAVE_VCLOCKS
ab4723
+enum {
ab4723
+	SOF_TIMESTAMPING_BIND_PHC = (1 << 15),
ab4723
+};
ab4723
+
ab4723
+struct so_timestamping {
ab4723
+	int flags;
ab4723
+	int bind_phc;
ab4723
+};
ab4723
+#endif
ab4723
+
ab4723
 #ifdef PTP_EXTTS_REQUEST2
ab4723
 #define PTP_EXTTS_REQUEST_FAILED "PTP_EXTTS_REQUEST2 failed: %m"
ab4723
 #else
ab4723
diff --git a/raw.c b/raw.c
ab4723
index 0bd15b0..ce64684 100644
ab4723
--- a/raw.c
ab4723
+++ b/raw.c
ab4723
@@ -243,7 +243,8 @@ static int raw_open(struct transport *t, struct interface *iface,
ab4723
 	if (gfd < 0)
ab4723
 		goto no_general;
ab4723
 
ab4723
-	if (sk_timestamping_init(efd, name, ts_type, TRANS_IEEE_802_3))
ab4723
+	if (sk_timestamping_init(efd, name, ts_type, TRANS_IEEE_802_3,
ab4723
+				 interface_get_vclock(iface)))
ab4723
 		goto no_timestamping;
ab4723
 
ab4723
 	if (sk_general_init(gfd))
ab4723
diff --git a/sk.c b/sk.c
ab4723
index 8be0708..b55d6b5 100644
ab4723
--- a/sk.c
ab4723
+++ b/sk.c
ab4723
@@ -447,9 +447,10 @@ int sk_set_priority(int fd, int family, uint8_t dscp)
ab4723
 }
ab4723
 
ab4723
 int sk_timestamping_init(int fd, const char *device, enum timestamp_type type,
ab4723
-			 enum transport_type transport)
ab4723
+			 enum transport_type transport, int vclock)
ab4723
 {
ab4723
 	int err, filter1, filter2 = 0, flags, tx_type = HWTSTAMP_TX_ON;
ab4723
+	struct so_timestamping timestamping;
ab4723
 
ab4723
 	switch (type) {
ab4723
 	case TS_SOFTWARE:
ab4723
@@ -509,8 +510,14 @@ int sk_timestamping_init(int fd, const char *device, enum timestamp_type type,
ab4723
 			return err;
ab4723
 	}
ab4723
 
ab4723
+	if (vclock >= 0)
ab4723
+		flags |= SOF_TIMESTAMPING_BIND_PHC;
ab4723
+
ab4723
+	timestamping.flags = flags;
ab4723
+	timestamping.bind_phc = vclock;
ab4723
+
ab4723
 	if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING,
ab4723
-		       &flags, sizeof(flags)) < 0) {
ab4723
+		       &timestamping, sizeof(timestamping)) < 0) {
ab4723
 		pr_err("ioctl SO_TIMESTAMPING failed: %m");
ab4723
 		return -1;
ab4723
 	}
ab4723
diff --git a/sk.h b/sk.h
ab4723
index 04d26ee..486dbc4 100644
ab4723
--- a/sk.h
ab4723
+++ b/sk.h
ab4723
@@ -124,10 +124,11 @@ int sk_set_priority(int fd, int family, uint8_t dscp);
ab4723
  * @param device      The name of the network interface to configure.
ab4723
  * @param type        The requested flavor of time stamping.
ab4723
  * @param transport   The type of transport used.
ab4723
+ * @param vclock      Index of the virtual PHC, or -1 for the physical clock.
ab4723
  * @return            Zero on success, non-zero otherwise.
ab4723
  */
ab4723
 int sk_timestamping_init(int fd, const char *device, enum timestamp_type type,
ab4723
-			 enum transport_type transport);
ab4723
+			 enum transport_type transport, int vclock);
ab4723
 
ab4723
 /**
ab4723
  * Limits the time that RECVMSG(2) will poll while waiting for the tx timestamp
ab4723
diff --git a/udp.c b/udp.c
ab4723
index 826bd12..7c9402e 100644
ab4723
--- a/udp.c
ab4723
+++ b/udp.c
ab4723
@@ -179,7 +179,8 @@ static int udp_open(struct transport *t, struct interface *iface,
ab4723
 	if (gfd < 0)
ab4723
 		goto no_general;
ab4723
 
ab4723
-	if (sk_timestamping_init(efd, interface_label(iface), ts_type, TRANS_UDP_IPV4))
ab4723
+	if (sk_timestamping_init(efd, interface_label(iface), ts_type, TRANS_UDP_IPV4,
ab4723
+				 interface_get_vclock(iface)))
ab4723
 		goto no_timestamping;
ab4723
 
ab4723
 	if (sk_general_init(gfd))
ab4723
diff --git a/udp6.c b/udp6.c
ab4723
index ba5482e..bde1710 100644
ab4723
--- a/udp6.c
ab4723
+++ b/udp6.c
ab4723
@@ -196,7 +196,8 @@ static int udp6_open(struct transport *t, struct interface *iface,
ab4723
 	if (gfd < 0)
ab4723
 		goto no_general;
ab4723
 
ab4723
-	if (sk_timestamping_init(efd, interface_label(iface), ts_type, TRANS_UDP_IPV6))
ab4723
+	if (sk_timestamping_init(efd, interface_label(iface), ts_type,
ab4723
+				 TRANS_UDP_IPV6, interface_get_vclock(iface)))
ab4723
 		goto no_timestamping;
ab4723
 
ab4723
 	if (sk_general_init(gfd))
ab4723
commit daaaff6b553290cf09284b0cc7756b9e24358ace
ab4723
Author: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
Date:   Tue Mar 8 11:47:01 2022 +0100
ab4723
ab4723
    config: Add port-specific phc_index option.
ab4723
    
ab4723
    Allow the PHC index to be configured for each port. The default value is
ab4723
    -1, which enables the original behavior using the PHC specified by -p or
ab4723
    the index from ETHTOOL_GET_TS_INFO.
ab4723
    
ab4723
    (Rebased to 3.1.1)
ab4723
    
ab4723
    Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
ab4723
diff --git a/clock.c b/clock.c
ab4723
index 49bd4a9..67b3372 100644
ab4723
--- a/clock.c
ab4723
+++ b/clock.c
ab4723
@@ -900,7 +900,7 @@ struct clock *clock_create(enum clock_type type, struct config *config,
ab4723
 	char ts_label[IF_NAMESIZE], phc[32], *tmp;
ab4723
 	enum timestamp_type timestamping;
ab4723
 	int fadj = 0, max_adj = 0, sw_ts;
ab4723
-	int phc_index, required_modes = 0;
ab4723
+	int phc_index, conf_phc_index, required_modes = 0;
ab4723
 	struct clock *c = &the_clock;
ab4723
 	const char *uds_ifname;
ab4723
 	struct port *p;
ab4723
@@ -1018,6 +1018,8 @@ struct clock *clock_create(enum clock_type type, struct config *config,
ab4723
 
ab4723
 	iface = STAILQ_FIRST(&config->interfaces);
ab4723
 
ab4723
+	conf_phc_index = config_get_int(config, interface_name(iface), "phc_index");
ab4723
+
ab4723
 	/* determine PHC Clock index */
ab4723
 	if (config_get_int(config, NULL, "free_running")) {
ab4723
 		phc_index = -1;
ab4723
@@ -1027,6 +1029,8 @@ struct clock *clock_create(enum clock_type type, struct config *config,
ab4723
 		if (1 != sscanf(phc_device, "/dev/ptp%d", &phc_index)) {
ab4723
 			phc_index = -1;
ab4723
 		}
ab4723
+	} else if (conf_phc_index >= 0) {
ab4723
+		phc_index = conf_phc_index;
ab4723
 	} else if (interface_tsinfo_valid(iface)) {
ab4723
 		phc_index = interface_phc_index(iface);
ab4723
 	} else {
ab4723
diff --git a/config.c b/config.c
ab4723
index ef5e833..0613eda 100644
ab4723
--- a/config.c
ab4723
+++ b/config.c
ab4723
@@ -282,6 +282,7 @@ struct config_item config_tab[] = {
ab4723
 	PORT_ITEM_INT("operLogPdelayReqInterval", 0, INT8_MIN, INT8_MAX),
ab4723
 	PORT_ITEM_INT("operLogSyncInterval", 0, INT8_MIN, INT8_MAX),
ab4723
 	PORT_ITEM_INT("path_trace_enabled", 0, 0, 1),
ab4723
+	PORT_ITEM_INT("phc_index", -1, -1, INT_MAX),
ab4723
 	GLOB_ITEM_DBL("pi_integral_const", 0.0, 0.0, DBL_MAX),
ab4723
 	GLOB_ITEM_DBL("pi_integral_exponent", 0.4, -DBL_MAX, DBL_MAX),
ab4723
 	GLOB_ITEM_DBL("pi_integral_norm_max", 0.3, DBL_MIN, 2.0),
ab4723
diff --git a/configs/default.cfg b/configs/default.cfg
ab4723
index 8c19129..45888d5 100644
ab4723
--- a/configs/default.cfg
ab4723
+++ b/configs/default.cfg
ab4723
@@ -103,6 +103,7 @@ delay_filter_length	10
ab4723
 egressLatency		0
ab4723
 ingressLatency		0
ab4723
 boundary_clock_jbod	0
ab4723
+phc_index		-1
ab4723
 #
ab4723
 # Clock description
ab4723
 #
ab4723
diff --git a/port.c b/port.c
ab4723
index d26b87f..7912ee6 100644
ab4723
--- a/port.c
ab4723
+++ b/port.c
ab4723
@@ -3057,7 +3057,9 @@ struct port *port_open(const char *phc_device,
ab4723
 		goto err_port;
ab4723
 	}
ab4723
 
ab4723
-	p->phc_index = phc_index;
ab4723
+	p->phc_index = config_get_int(cfg, interface_name(interface), "phc_index");
ab4723
+	if (p->phc_index < 0)
ab4723
+		p->phc_index = phc_index;
ab4723
 	p->jbod = config_get_int(cfg, interface_name(interface), "boundary_clock_jbod");
ab4723
 	transport = config_get_int(cfg, interface_name(interface), "network_transport");
ab4723
 	p->master_only = config_get_int(cfg, interface_name(interface), "masterOnly");
ab4723
@@ -3080,8 +3082,8 @@ struct port *port_open(const char *phc_device,
ab4723
 		; /* UDS cannot have a PHC. */
ab4723
 	} else if (!interface_tsinfo_valid(interface)) {
ab4723
 		pr_warning("port %d: get_ts_info not supported", number);
ab4723
-	} else if (phc_index >= 0 &&
ab4723
-		   phc_index != interface_phc_index(interface)) {
ab4723
+	} else if (p->phc_index >= 0 &&
ab4723
+		   p->phc_index != interface_phc_index(interface)) {
ab4723
 		if (p->jbod) {
ab4723
 			pr_warning("port %d: just a bunch of devices", number);
ab4723
 			p->phc_index = interface_phc_index(interface);
ab4723
diff --git a/ptp4l.8 b/ptp4l.8
ab4723
index b179b81..fc73e84 100644
ab4723
--- a/ptp4l.8
ab4723
+++ b/ptp4l.8
ab4723
@@ -365,6 +365,11 @@ collection of clocks must be synchronized by an external program, for
ab4723
 example phc2sys(8) in "automatic" mode.
ab4723
 The default is 0 (disabled).
ab4723
 .TP
ab4723
+.B phc_index
ab4723
+Specifies the index of the PHC to be used for synchronization with hardware
ab4723
+timestamping. The default is -1, which means the index will be set to the PHC
ab4723
+associated with the interface, or the device specified by the \fB-p\fP option.
ab4723
+.TP
ab4723
 .B udp_ttl
ab4723
 Specifies the Time to live (TTL) value for IPv4 multicast messages and the hop
ab4723
 limit for IPv6 multicast messages. This option is only relevant with the IPv4
ab4723
commit bb50991e8b9ecbcea53abbd0164a51e3e0bfe246
ab4723
Author: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
Date:   Tue Mar 8 11:47:02 2022 +0100
ab4723
ab4723
    port: Check for virtual clocks.
ab4723
    
ab4723
    If the PHC specified with the phc_index or -p option is a virtual clock
ab4723
    of the interface, bind sockets to the virtual clock instead of the real
ab4723
    clock to get correct timestamps.
ab4723
    
ab4723
    (Rebased to 3.1.1)
ab4723
    
ab4723
    Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
ab4723
diff --git a/port.c b/port.c
ab4723
index 7912ee6..b4dcd1b 100644
ab4723
--- a/port.c
ab4723
+++ b/port.c
ab4723
@@ -3084,7 +3084,12 @@ struct port *port_open(const char *phc_device,
ab4723
 		pr_warning("port %d: get_ts_info not supported", number);
ab4723
 	} else if (p->phc_index >= 0 &&
ab4723
 		   p->phc_index != interface_phc_index(interface)) {
ab4723
-		if (p->jbod) {
ab4723
+		if (rtnl_iface_has_vclock(interface_name(interface),
ab4723
+					  p->phc_index)) {
ab4723
+			pr_info("port %d: /dev/ptp%d is virtual clock",
ab4723
+				number, p->phc_index);
ab4723
+			interface_set_vclock(interface, p->phc_index);
ab4723
+		} else if (p->jbod) {
ab4723
 			pr_warning("port %d: just a bunch of devices", number);
ab4723
 			p->phc_index = interface_phc_index(interface);
ab4723
 		} else if (phc_device) {
ab4723
diff --git a/ptp4l.8 b/ptp4l.8
ab4723
index fc73e84..d0446d5 100644
ab4723
--- a/ptp4l.8
ab4723
+++ b/ptp4l.8
ab4723
@@ -367,8 +367,11 @@ The default is 0 (disabled).
ab4723
 .TP
ab4723
 .B phc_index
ab4723
 Specifies the index of the PHC to be used for synchronization with hardware
ab4723
-timestamping. The default is -1, which means the index will be set to the PHC
ab4723
-associated with the interface, or the device specified by the \fB-p\fP option.
ab4723
+timestamping. This option is useful with virtual clocks running on top of a
ab4723
+free-running physical clock (created by writing to
ab4723
+/sys/class/ptp/ptp*/n_vclocks).
ab4723
+The default is -1, which means the index will be set to the PHC associated with
ab4723
+the interface, or the device specified by the \fB-p\fP option.
ab4723
 .TP
ab4723
 .B udp_ttl
ab4723
 Specifies the Time to live (TTL) value for IPv4 multicast messages and the hop
ab4723
commit 2b1657a65c0f3c880a0b9982401d419108560a1f
ab4723
Author: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
Date:   Tue Mar 8 11:47:03 2022 +0100
ab4723
ab4723
    tlv: Add PORT_HWCLOCK_NP.
ab4723
    
ab4723
    Add a new command to get the PHC index associated with the port. This
ab4723
    will be needed for phc2sys -a to use the correct PHC for synchronization
ab4723
    if ptp4l is using a virtual clock.
ab4723
    
ab4723
    The TLV also contains a flag indicating a virtual clock.
ab4723
    
ab4723
    To follow the PORT_PROPERTIES_NP access policy, PORT_HWCLOCK_NP is
ab4723
    limited to the UDS-RW port.
ab4723
    
ab4723
    (Rebased to 3.1.1)
ab4723
    
ab4723
    Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
ab4723
diff --git a/clock.c b/clock.c
ab4723
index 67b3372..39df135 100644
ab4723
--- a/clock.c
ab4723
+++ b/clock.c
ab4723
@@ -1482,6 +1482,7 @@ int clock_manage(struct clock *c, struct port *p, struct ptp_message *msg)
ab4723
 
ab4723
 	switch (mgt->id) {
ab4723
 	case TLV_PORT_PROPERTIES_NP:
ab4723
+	case TLV_PORT_HWCLOCK_NP:
ab4723
 		if (p != c->uds_rw_port) {
ab4723
 			/* Only the UDS-RW port allowed. */
ab4723
 			clock_management_send_error(p, msg, TLV_NOT_SUPPORTED);
ab4723
diff --git a/pmc.c b/pmc.c
ab4723
index 65d1d61..3832f0d 100644
ab4723
--- a/pmc.c
ab4723
+++ b/pmc.c
ab4723
@@ -144,6 +144,7 @@ static void pmc_show(struct ptp_message *msg, FILE *fp)
ab4723
 	struct subscribe_events_np *sen;
ab4723
 	struct management_tlv_datum *mtd;
ab4723
 	struct port_properties_np *ppn;
ab4723
+	struct port_hwclock_np *phn;
ab4723
 	struct timePropertiesDS *tp;
ab4723
 	struct management_tlv *mgt;
ab4723
 	struct time_status_np *tsn;
ab4723
@@ -487,6 +488,16 @@ static void pmc_show(struct ptp_message *msg, FILE *fp)
ab4723
 			pcp->stats.txMsgType[SIGNALING],
ab4723
 			pcp->stats.txMsgType[MANAGEMENT]);
ab4723
 		break;
ab4723
+	case TLV_PORT_HWCLOCK_NP:
ab4723
+		phn = (struct port_hwclock_np *) mgt->data;
ab4723
+		fprintf(fp, "PORT_HWCLOCK_NP "
ab4723
+			IFMT "portIdentity            %s"
ab4723
+			IFMT "phcIndex                %d"
ab4723
+			IFMT "flags                   %hhu",
ab4723
+			pid2str(&phn->portIdentity),
ab4723
+			phn->phc_index,
ab4723
+			phn->flags);
ab4723
+		break;
ab4723
 	case TLV_LOG_ANNOUNCE_INTERVAL:
ab4723
 		mtd = (struct management_tlv_datum *) mgt->data;
ab4723
 		fprintf(fp, "LOG_ANNOUNCE_INTERVAL "
ab4723
diff --git a/pmc_common.c b/pmc_common.c
ab4723
index f07f6f6..756edf5 100644
ab4723
--- a/pmc_common.c
ab4723
+++ b/pmc_common.c
ab4723
@@ -132,6 +132,7 @@ struct management_id idtab[] = {
ab4723
 	{ "PORT_DATA_SET_NP", TLV_PORT_DATA_SET_NP, do_set_action },
ab4723
 	{ "PORT_STATS_NP", TLV_PORT_STATS_NP, do_get_action },
ab4723
 	{ "PORT_PROPERTIES_NP", TLV_PORT_PROPERTIES_NP, do_get_action },
ab4723
+	{ "PORT_HWCLOCK_NP", TLV_PORT_HWCLOCK_NP, do_get_action },
ab4723
 };
ab4723
 
ab4723
 static void do_get_action(struct pmc *pmc, int action, int index, char *str)
ab4723
diff --git a/port.c b/port.c
ab4723
index b4dcd1b..e309b98 100644
ab4723
--- a/port.c
ab4723
+++ b/port.c
ab4723
@@ -797,6 +797,7 @@ static int port_management_fill_response(struct port *target,
ab4723
 	struct management_tlv_datum *mtd;
ab4723
 	struct clock_description *desc;
ab4723
 	struct port_properties_np *ppn;
ab4723
+	struct port_hwclock_np *phn;
ab4723
 	struct port_stats_np *psn;
ab4723
 	struct management_tlv *tlv;
ab4723
 	struct port_ds_np *pdsnp;
ab4723
@@ -961,6 +962,14 @@ static int port_management_fill_response(struct port *target,
ab4723
 		psn->stats = target->stats;
ab4723
 		datalen = sizeof(*psn);
ab4723
 		break;
ab4723
+	case TLV_PORT_HWCLOCK_NP:
ab4723
+		phn = (struct port_hwclock_np *)tlv->data;
ab4723
+		phn->portIdentity = target->portIdentity;
ab4723
+		phn->phc_index = target->phc_index;
ab4723
+		phn->flags = interface_get_vclock(target->iface) >= 0 ?
ab4723
+			PORT_HWCLOCK_VCLOCK : 0;
ab4723
+		datalen = sizeof(*phn);
ab4723
+		break;
ab4723
 	default:
ab4723
 		/* The caller should *not* respond to this message. */
ab4723
 		tlv_extra_recycle(extra);
ab4723
diff --git a/tlv.c b/tlv.c
ab4723
index 738e404..38aeb80 100644
ab4723
--- a/tlv.c
ab4723
+++ b/tlv.c
ab4723
@@ -123,6 +123,7 @@ static int mgt_post_recv(struct management_tlv *m, uint16_t data_len,
ab4723
 	struct grandmaster_settings_np *gsn;
ab4723
 	struct subscribe_events_np *sen;
ab4723
 	struct port_properties_np *ppn;
ab4723
+	struct port_hwclock_np *phn;
ab4723
 	struct port_stats_np *psn;
ab4723
 	struct mgmt_clock_description *cd;
ab4723
 	int extra_len = 0, len;
ab4723
@@ -326,6 +327,14 @@ static int mgt_post_recv(struct management_tlv *m, uint16_t data_len,
ab4723
 			ntohs(psn->portIdentity.portNumber);
ab4723
 		extra_len = sizeof(struct port_stats_np);
ab4723
 		break;
ab4723
+	case TLV_PORT_HWCLOCK_NP:
ab4723
+		if (data_len < sizeof(struct port_hwclock_np))
ab4723
+			goto bad_length;
ab4723
+		phn = (struct port_hwclock_np *)m->data;
ab4723
+		phn->portIdentity.portNumber = ntohs(phn->portIdentity.portNumber);
ab4723
+		phn->phc_index = ntohl(phn->phc_index);
ab4723
+		extra_len = sizeof(struct port_hwclock_np);
ab4723
+		break;
ab4723
 	case TLV_SAVE_IN_NON_VOLATILE_STORAGE:
ab4723
 	case TLV_RESET_NON_VOLATILE_STORAGE:
ab4723
 	case TLV_INITIALIZE:
ab4723
@@ -352,6 +361,7 @@ static void mgt_pre_send(struct management_tlv *m, struct tlv_extra *extra)
ab4723
 	struct defaultDS *dds;
ab4723
 	struct currentDS *cds;
ab4723
 	struct parentDS *pds;
ab4723
+	struct port_hwclock_np *phn;
ab4723
 	struct timePropertiesDS *tp;
ab4723
 	struct portDS *p;
ab4723
 	struct port_ds_np *pdsnp;
ab4723
@@ -437,6 +447,11 @@ static void mgt_pre_send(struct management_tlv *m, struct tlv_extra *extra)
ab4723
 		psn->portIdentity.portNumber =
ab4723
 			htons(psn->portIdentity.portNumber);
ab4723
 		break;
ab4723
+	case TLV_PORT_HWCLOCK_NP:
ab4723
+		phn = (struct port_hwclock_np *)m->data;
ab4723
+		phn->portIdentity.portNumber = htons(phn->portIdentity.portNumber);
ab4723
+		phn->phc_index = htonl(phn->phc_index);
ab4723
+		break;
ab4723
 	}
ab4723
 }
ab4723
 
ab4723
diff --git a/tlv.h b/tlv.h
ab4723
index a205119..5ac3d7c 100644
ab4723
--- a/tlv.h
ab4723
+++ b/tlv.h
ab4723
@@ -125,6 +125,7 @@ enum management_action {
ab4723
 #define TLV_PORT_DATA_SET_NP				0xC002
ab4723
 #define TLV_PORT_PROPERTIES_NP				0xC004
ab4723
 #define TLV_PORT_STATS_NP				0xC005
ab4723
+#define TLV_PORT_HWCLOCK_NP				0xC009
ab4723
 
ab4723
 /* Management error ID values */
ab4723
 #define TLV_RESPONSE_TOO_BIG				0x0001
ab4723
@@ -144,6 +145,9 @@ enum management_action {
ab4723
 #define CANCEL_UNICAST_MAINTAIN_GRANT	(1 << 1)
ab4723
 #define GRANT_UNICAST_RENEWAL_INVITED	(1 << 0)
ab4723
 
ab4723
+/* Flags in PORT_HWCLOCK_NP */
ab4723
+#define PORT_HWCLOCK_VCLOCK		(1 << 0)
ab4723
+
ab4723
 struct ack_cancel_unicast_xmit_tlv {
ab4723
 	Enumeration16   type;
ab4723
 	UInteger16      length;
ab4723
@@ -344,6 +348,12 @@ struct port_properties_np {
ab4723
 	struct PTPText interface;
ab4723
 } PACKED;
ab4723
 
ab4723
+struct port_hwclock_np {
ab4723
+	struct PortIdentity portIdentity;
ab4723
+	Integer32 phc_index;
ab4723
+	UInteger8 flags;
ab4723
+} PACKED;
ab4723
+
ab4723
 struct port_stats_np {
ab4723
 	struct PortIdentity portIdentity;
ab4723
 	struct PortStats stats;
ab4723
commit a64a45a0eedec82376fd9dab4d960b6fa652513e
ab4723
Author: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
Date:   Tue Mar 8 11:47:04 2022 +0100
ab4723
ab4723
    phc2sys: Use PHC index from PORT_HWCLOCK_NP.
ab4723
    
ab4723
    When running in the automatic mode, get the PHC index of the port
ab4723
    from PORT_HWCLOCK_NP instead of calling sk_get_ts_info(). This allows
ab4723
    phc2sys -a to synchronize (to) a virtual clock.
ab4723
    
ab4723
    (Rebased to 3.1.1)
ab4723
    
ab4723
    Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
ab4723
diff --git a/phc2sys.c b/phc2sys.c
ab4723
index a36cbe0..adbe37d 100644
ab4723
--- a/phc2sys.c
ab4723
+++ b/phc2sys.c
ab4723
@@ -135,7 +135,8 @@ static void run_pmc_events(struct phc2sys_private *priv);
ab4723
 static int normalize_state(int state);
ab4723
 static int run_pmc_port_properties(struct phc2sys_private *priv,
ab4723
 				   int timeout, unsigned int port,
ab4723
-				   int *state, int *tstamping, char *iface);
ab4723
+				   int *state, int *tstamping, int *phc_index,
ab4723
+				   char *iface);
ab4723
 
ab4723
 static struct servo *servo_add(struct phc2sys_private *priv,
ab4723
 			       struct clock *clock)
ab4723
@@ -172,14 +173,21 @@ static struct servo *servo_add(struct phc2sys_private *priv,
ab4723
 	return servo;
ab4723
 }
ab4723
 
ab4723
-static struct clock *clock_add(struct phc2sys_private *priv, char *device)
ab4723
+static struct clock *clock_add(struct phc2sys_private *priv, char *device,
ab4723
+			       int phc_index)
ab4723
 {
ab4723
 	struct clock *c;
ab4723
 	clockid_t clkid = CLOCK_INVALID;
ab4723
-	int phc_index = -1;
ab4723
+	char phc_device[19];
ab4723
 
ab4723
 	if (device) {
ab4723
-		clkid = posix_clock_open(device, &phc_index);
ab4723
+		if (phc_index >= 0) {
ab4723
+			snprintf(phc_device, sizeof(phc_device), "/dev/ptp%d",
ab4723
+				 phc_index);
ab4723
+			clkid = posix_clock_open(phc_device, &phc_index);
ab4723
+		} else {
ab4723
+			clkid = posix_clock_open(device, &phc_index);
ab4723
+		}
ab4723
 		if (clkid == CLOCK_INVALID)
ab4723
 			return NULL;
ab4723
 	}
ab4723
@@ -279,7 +287,7 @@ static struct port *port_get(struct phc2sys_private *priv, unsigned int number)
ab4723
 }
ab4723
 
ab4723
 static struct port *port_add(struct phc2sys_private *priv, unsigned int number,
ab4723
-			     char *device)
ab4723
+			     char *device, int phc_index)
ab4723
 {
ab4723
 	struct port *p;
ab4723
 	struct clock *c = NULL, *tmp;
ab4723
@@ -296,7 +304,7 @@ static struct port *port_add(struct phc2sys_private *priv, unsigned int number,
ab4723
 		}
ab4723
 	}
ab4723
 	if (!c) {
ab4723
-		c = clock_add(priv, device);
ab4723
+		c = clock_add(priv, device, phc_index);
ab4723
 		if (!c)
ab4723
 			return NULL;
ab4723
 	}
ab4723
@@ -316,17 +324,16 @@ static void clock_reinit(struct phc2sys_private *priv, struct clock *clock,
ab4723
 {
ab4723
 	int phc_index = -1, phc_switched = 0;
ab4723
 	int state, timestamping, ret = -1;
ab4723
+	char iface[IFNAMSIZ], phc_device[19];
ab4723
 	struct port *p;
ab4723
 	struct servo *servo;
ab4723
-	struct sk_ts_info ts_info;
ab4723
-	char iface[IFNAMSIZ];
ab4723
 	clockid_t clkid = CLOCK_INVALID;
ab4723
 
ab4723
 	LIST_FOREACH(p, &priv->ports, list) {
ab4723
 		if (p->clock == clock) {
ab4723
 			ret = run_pmc_port_properties(priv, 1000, p->number,
ab4723
 					              &state, &timestamping,
ab4723
-						      iface);
ab4723
+						      &phc_index, iface);
ab4723
 			if (ret > 0)
ab4723
 				p->state = normalize_state(state);
ab4723
 		}
ab4723
@@ -339,9 +346,10 @@ static void clock_reinit(struct phc2sys_private *priv, struct clock *clock,
ab4723
 			clock->device = strdup(iface);
ab4723
 		}
ab4723
 		/* Check if phc index changed */
ab4723
-		if (!sk_get_ts_info(clock->device, &ts_info) &&
ab4723
-		    clock->phc_index != ts_info.phc_index) {
ab4723
-			clkid = posix_clock_open(clock->device, &phc_index);
ab4723
+		if (clock->phc_index != phc_index) {
ab4723
+			snprintf(phc_device, sizeof(phc_device), "/dev/ptp%d",
ab4723
+				 phc_index);
ab4723
+			clkid = posix_clock_open(phc_device, &phc_index);
ab4723
 			if (clkid == CLOCK_INVALID)
ab4723
 				return;
ab4723
 
ab4723
@@ -1099,11 +1107,13 @@ static void run_pmc_events(struct phc2sys_private *priv)
ab4723
 
ab4723
 static int run_pmc_port_properties(struct phc2sys_private *priv, int timeout,
ab4723
 				   unsigned int port,
ab4723
-				   int *state, int *tstamping, char *iface)
ab4723
+				   int *state, int *tstamping, int *phc_index,
ab4723
+				   char *iface)
ab4723
 {
ab4723
 	struct ptp_message *msg;
ab4723
 	int res, len;
ab4723
 	struct port_properties_np *ppn;
ab4723
+	struct port_hwclock_np *phn;
ab4723
 
ab4723
 	pmc_target_port(priv->pmc, port);
ab4723
 	while (1) {
ab4723
@@ -1125,6 +1135,21 @@ static int run_pmc_port_properties(struct phc2sys_private *priv, int timeout,
ab4723
 		memcpy(iface, ppn->interface.text, len);
ab4723
 		iface[len] = '\0';
ab4723
 
ab4723
+		msg_put(msg);
ab4723
+		break;
ab4723
+	}
ab4723
+	while (1) {
ab4723
+		res = run_pmc(priv, timeout, TLV_PORT_HWCLOCK_NP, &msg;;
ab4723
+		if (res <= 0)
ab4723
+			goto out;
ab4723
+
ab4723
+		phn = get_mgt_data(msg);
ab4723
+		if (ppn->portIdentity.portNumber != port) {
ab4723
+			msg_put(msg);
ab4723
+			continue;
ab4723
+		}
ab4723
+		*phc_index = phn->phc_index;
ab4723
+
ab4723
 		msg_put(msg);
ab4723
 		res = 1;
ab4723
 		break;
ab4723
@@ -1164,7 +1189,7 @@ static int auto_init_ports(struct phc2sys_private *priv, int add_rt)
ab4723
 	struct clock *clock;
ab4723
 	int number_ports, res;
ab4723
 	unsigned int i;
ab4723
-	int state, timestamping;
ab4723
+	int state, timestamping, phc_index;
ab4723
 	char iface[IFNAMSIZ];
ab4723
 
ab4723
 	while (1) {
ab4723
@@ -1193,7 +1218,7 @@ static int auto_init_ports(struct phc2sys_private *priv, int add_rt)
ab4723
 
ab4723
 	for (i = 1; i <= number_ports; i++) {
ab4723
 		res = run_pmc_port_properties(priv, 1000, i, &state,
ab4723
-					      &timestamping, iface);
ab4723
+					      &timestamping, &phc_index, iface);
ab4723
 		if (res == -1) {
ab4723
 			/* port does not exist, ignore the port */
ab4723
 			continue;
ab4723
@@ -1206,7 +1231,7 @@ static int auto_init_ports(struct phc2sys_private *priv, int add_rt)
ab4723
 			/* ignore ports with software time stamping */
ab4723
 			continue;
ab4723
 		}
ab4723
-		port = port_add(priv, i, iface);
ab4723
+		port = port_add(priv, i, iface, phc_index);
ab4723
 		if (!port)
ab4723
 			return -1;
ab4723
 		port->state = normalize_state(state);
ab4723
@@ -1221,7 +1246,7 @@ static int auto_init_ports(struct phc2sys_private *priv, int add_rt)
ab4723
 	priv->state_changed = 1;
ab4723
 
ab4723
 	if (add_rt) {
ab4723
-		clock = clock_add(priv, "CLOCK_REALTIME");
ab4723
+		clock = clock_add(priv, "CLOCK_REALTIME", -1);
ab4723
 		if (!clock)
ab4723
 			return -1;
ab4723
 		if (add_rt == 1)
ab4723
@@ -1598,7 +1623,7 @@ int main(int argc, char *argv[])
ab4723
 		goto end;
ab4723
 	}
ab4723
 
ab4723
-	src = clock_add(&priv, src_name);
ab4723
+	src = clock_add(&priv, src_name, -1);
ab4723
 	free(src_name);
ab4723
 	if (!src) {
ab4723
 		fprintf(stderr,
ab4723
@@ -1608,7 +1633,7 @@ int main(int argc, char *argv[])
ab4723
 	src->state = PS_SLAVE;
ab4723
 	priv.master = src;
ab4723
 
ab4723
-	dst = clock_add(&priv, dst_name ? dst_name : "CLOCK_REALTIME");
ab4723
+	dst = clock_add(&priv, dst_name ? dst_name : "CLOCK_REALTIME", -1);
ab4723
 	free(dst_name);
ab4723
 	if (!dst) {
ab4723
 		fprintf(stderr,
ab4723
commit 3238beafd5aca017a29f335e94b1ff05f4596fe3
ab4723
Author: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
Date:   Tue Mar 8 11:47:05 2022 +0100
ab4723
ab4723
    timemaster: Add support for virtual clocks.
ab4723
    
ab4723
    Add "use_vclocks" option to enable synchronization with virtual clocks.
ab4723
    This enables multiple ptp4l instances sharing an interface to use
ab4723
    hardware timestamping. By default, vclocks are enabled if running on
ab4723
    Linux 5.18 or later, which should have all features to make them work as
ab4723
    well as physical clocks.
ab4723
    
ab4723
    When preparing the script, count how many vclocks are needed for each
ab4723
    physical clock. Add a placeholder option in the form of "--phc_index
ab4723
    %PHC0-0%" to the generated ptp4l commands that need hardware clock. The
ab4723
    index of the virtual clock is unknown at this point.
ab4723
    
ab4723
    When running the script (not just printing), create the required number
ab4723
    of virtual clocks by writing to /sys/.../n_vclocks and fix the
ab4723
    --phc_index options to refer to the indices of the created vclocks. On
ab4723
    exit, remove the virtual clocks to restore the original state.
ab4723
    
ab4723
    Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
ab4723
diff --git a/timemaster.8 b/timemaster.8
ab4723
index 2f92976..102768c 100644
ab4723
--- a/timemaster.8
ab4723
+++ b/timemaster.8
ab4723
@@ -97,6 +97,15 @@ configuration error). If a process was terminated and is not started again,
ab4723
 \fBtimemaster\fR will kill the other processes and exit with a non-zero status.
ab4723
 The default value is 1 (enabled).
ab4723
 
ab4723
+.TP
ab4723
+.B use_vclocks
ab4723
+Enable or disable synchronization with virtual clocks. If enabled,
ab4723
+\fBtimemaster\fR will create virtual clocks running on top of physical clocks
ab4723
+needed by configured PTP domains. This enables hardware time stamping for
ab4723
+multiple \fBptp4l\fR instances using the same network interface. The default
ab4723
+value is -1, which enables the virtual clocks if running on Linux 5.18 or
ab4723
+later.
ab4723
+
ab4723
 .SS [ntp_server address]
ab4723
 
ab4723
 The \fBntp_server\fR section specifies an NTP server that should be used as a
ab4723
@@ -140,8 +149,8 @@ Specify which network interfaces should be used for this PTP domain. A separate
ab4723
 \fBptp4l\fR instance will be started for each group of interfaces sharing the
ab4723
 same PHC and for each interface that supports only SW time stamping. HW time
ab4723
 stamping is enabled automatically. If an interface with HW time stamping is
ab4723
-specified also in other PTP domains, only the \fBptp4l\fR instance from the
ab4723
-first PTP domain will be using HW time stamping.
ab4723
+specified also in other PTP domains and virtual clocks are disabled, only the
ab4723
+\fBptp4l\fR instance from the first PTP domain will be using HW time stamping.
ab4723
 
ab4723
 .TP
ab4723
 .B ntp_poll
ab4723
@@ -333,6 +342,7 @@ ntp_program chronyd
ab4723
 rundir /var/run/timemaster
ab4723
 first_shm_segment 1
ab4723
 restart_processes 0
ab4723
+use_vclocks 0
ab4723
 
ab4723
 [chronyd]
ab4723
 path /usr/sbin/chronyd
ab4723
diff --git a/timemaster.c b/timemaster.c
ab4723
index 02408d6..1fbadcb 100644
ab4723
--- a/timemaster.c
ab4723
+++ b/timemaster.c
ab4723
@@ -20,6 +20,7 @@
ab4723
 
ab4723
 #include <ctype.h>
ab4723
 #include <errno.h>
ab4723
+#include <glob.h>
ab4723
 #include <libgen.h>
ab4723
 #include <limits.h>
ab4723
 #include <time.h>
ab4723
@@ -33,6 +34,7 @@
ab4723
 #include <string.h>
ab4723
 #include <sys/stat.h>
ab4723
 #include <sys/types.h>
ab4723
+#include <sys/utsname.h>
ab4723
 #include <sys/wait.h>
ab4723
 #include <unistd.h>
ab4723
 
ab4723
@@ -46,6 +48,7 @@
ab4723
 
ab4723
 #define DEFAULT_FIRST_SHM_SEGMENT 0
ab4723
 #define DEFAULT_RESTART_PROCESSES 1
ab4723
+#define DEFAULT_USE_VCLOCKS -1
ab4723
 
ab4723
 #define DEFAULT_NTP_PROGRAM CHRONYD
ab4723
 #define DEFAULT_NTP_MINPOLL 6
ab4723
@@ -111,6 +114,7 @@ struct timemaster_config {
ab4723
 	char *rundir;
ab4723
 	int first_shm_segment;
ab4723
 	int restart_processes;
ab4723
+	int use_vclocks;
ab4723
 	struct program_config chronyd;
ab4723
 	struct program_config ntpd;
ab4723
 	struct program_config phc2sys;
ab4723
@@ -122,7 +126,13 @@ struct config_file {
ab4723
 	char *content;
ab4723
 };
ab4723
 
ab4723
+struct phc_vclocks {
ab4723
+	int pclock_index;
ab4723
+	int vclocks;
ab4723
+};
ab4723
+
ab4723
 struct script {
ab4723
+	struct phc_vclocks **vclocks;
ab4723
 	struct config_file **configs;
ab4723
 	char ***commands;
ab4723
 	int **command_groups;
ab4723
@@ -393,6 +403,8 @@ static int parse_timemaster_settings(char **settings,
ab4723
 			r = parse_int(value, &config->first_shm_segment);
ab4723
 		} else if (!strcasecmp(name, "restart_processes")) {
ab4723
 			r = parse_int(value, &config->restart_processes);
ab4723
+		} else if (!strcasecmp(name, "use_vclocks")) {
ab4723
+			r = parse_int(value, &config->use_vclocks);
ab4723
 		} else {
ab4723
 			pr_err("unknown timemaster setting %s", name);
ab4723
 			return 1;
ab4723
@@ -520,6 +532,20 @@ static void config_destroy(struct timemaster_config *config)
ab4723
 	free(config);
ab4723
 }
ab4723
 
ab4723
+static int check_kernel_version(int version, int patch)
ab4723
+{
ab4723
+	struct utsname uts;
ab4723
+	int v, p;
ab4723
+
ab4723
+	if (uname(&uts) < 0)
ab4723
+		return 1;
ab4723
+	if (sscanf(uts.release, "%d.%d", &v, &p) < 2)
ab4723
+		return 1;
ab4723
+	if (version > v || (version == v && patch > p))
ab4723
+		return 1;
ab4723
+	return 0;
ab4723
+}
ab4723
+
ab4723
 static struct timemaster_config *config_parse(char *path)
ab4723
 {
ab4723
 	struct timemaster_config *config = xcalloc(1, sizeof(*config));
ab4723
@@ -533,6 +559,7 @@ static struct timemaster_config *config_parse(char *path)
ab4723
 	config->rundir = xstrdup(DEFAULT_RUNDIR);
ab4723
 	config->first_shm_segment = DEFAULT_FIRST_SHM_SEGMENT;
ab4723
 	config->restart_processes = DEFAULT_RESTART_PROCESSES;
ab4723
+	config->use_vclocks = DEFAULT_USE_VCLOCKS;
ab4723
 
ab4723
 	init_program_config(&config->chronyd, "chronyd",
ab4723
 			    NULL, DEFAULT_CHRONYD_SETTINGS, NULL);
ab4723
@@ -593,6 +620,9 @@ static struct timemaster_config *config_parse(char *path)
ab4723
 
ab4723
 	fclose(f);
ab4723
 
ab4723
+	if (config->use_vclocks < 0)
ab4723
+		config->use_vclocks = !check_kernel_version(5, 18);
ab4723
+
ab4723
 	if (section_name)
ab4723
 		free(section_name);
ab4723
 	if (section_lines)
ab4723
@@ -608,7 +638,7 @@ static struct timemaster_config *config_parse(char *path)
ab4723
 
ab4723
 static char **get_ptp4l_command(struct program_config *config,
ab4723
 				struct config_file *file, char **interfaces,
ab4723
-				int hw_ts)
ab4723
+				char *phc_index, int hw_ts)
ab4723
 {
ab4723
 	char **command = (char **)parray_new();
ab4723
 
ab4723
@@ -617,6 +647,9 @@ static char **get_ptp4l_command(struct program_config *config,
ab4723
 	parray_extend((void ***)&command,
ab4723
 		      xstrdup("-f"), xstrdup(file->path),
ab4723
 		      xstrdup(hw_ts ? "-H" : "-S"), NULL);
ab4723
+	if (phc_index && phc_index[0])
ab4723
+		parray_extend((void ***)&command,
ab4723
+			      xstrdup("--phc_index"), xstrdup(phc_index), NULL);
ab4723
 
ab4723
 	for (; *interfaces; interfaces++)
ab4723
 		parray_extend((void ***)&command,
ab4723
@@ -706,6 +739,24 @@ static int add_ntp_source(struct ntp_server *source, char **ntp_config)
ab4723
 	return 0;
ab4723
 }
ab4723
 
ab4723
+static int add_vclock(struct script *script, int pclock_index)
ab4723
+{
ab4723
+	struct phc_vclocks **vclocks, *v;
ab4723
+
ab4723
+	for (vclocks = script->vclocks; *vclocks; vclocks++) {
ab4723
+		if ((*vclocks)->pclock_index != pclock_index)
ab4723
+			continue;
ab4723
+		return (*vclocks)->vclocks++;
ab4723
+	}
ab4723
+
ab4723
+	v = xmalloc(sizeof(*v));
ab4723
+	v->pclock_index = pclock_index;
ab4723
+	v->vclocks = 1;
ab4723
+	parray_append((void ***)&script->vclocks, v);
ab4723
+
ab4723
+	return 0;
ab4723
+}
ab4723
+
ab4723
 static int add_ptp_source(struct ptp_domain *source,
ab4723
 			  struct timemaster_config *config, int *shm_segment,
ab4723
 			  int *command_group, int ***allocated_phcs,
ab4723
@@ -713,7 +764,7 @@ static int add_ptp_source(struct ptp_domain *source,
ab4723
 {
ab4723
 	struct config_file *config_file;
ab4723
 	char **command, *uds_path, *uds_path2, **interfaces, *message_tag;
ab4723
-	char ts_interface[IF_NAMESIZE];
ab4723
+	char ts_interface[IF_NAMESIZE], vclock_index[20];
ab4723
 	int i, j, num_interfaces, *phc, *phcs, hw_ts, sw_ts;
ab4723
 	struct sk_ts_info ts_info;
ab4723
 
ab4723
@@ -801,10 +852,18 @@ static int add_ptp_source(struct ptp_domain *source,
ab4723
 				}
ab4723
 			}
ab4723
 
ab4723
-			/* don't use this PHC in other sources */
ab4723
-			phc = xmalloc(sizeof(int));
ab4723
-			*phc = phcs[i];
ab4723
-			parray_append((void ***)allocated_phcs, phc);
ab4723
+			if (config->use_vclocks) {
ab4723
+				/* request new vclock for the PHC */
ab4723
+				int vclock = add_vclock(script, phcs[i]);
ab4723
+				snprintf(vclock_index, sizeof(vclock_index),
ab4723
+					 "%%PHC%d-%d%%", phcs[i], vclock);
ab4723
+			} else {
ab4723
+				/* don't use this PHC in other sources */
ab4723
+				phc = xmalloc(sizeof(int));
ab4723
+				*phc = phcs[i];
ab4723
+				parray_append((void ***)allocated_phcs, phc);
ab4723
+				vclock_index[0] = '\0';
ab4723
+			}
ab4723
 		}
ab4723
 
ab4723
 		uds_path = string_newf("%s/ptp4l.%d.socket",
ab4723
@@ -842,7 +901,8 @@ static int add_ptp_source(struct ptp_domain *source,
ab4723
 		if (phcs[i] >= 0) {
ab4723
 			/* HW time stamping */
ab4723
 			command = get_ptp4l_command(&config->ptp4l, config_file,
ab4723
-						    interfaces, 1);
ab4723
+						    interfaces,
ab4723
+						    vclock_index, 1);
ab4723
 			add_command(command, *command_group, script);
ab4723
 
ab4723
 			command = get_phc2sys_command(&config->phc2sys,
ab4723
@@ -854,7 +914,7 @@ static int add_ptp_source(struct ptp_domain *source,
ab4723
 		} else {
ab4723
 			/* SW time stamping */
ab4723
 			command = get_ptp4l_command(&config->ptp4l, config_file,
ab4723
-						    interfaces, 0);
ab4723
+						    interfaces, NULL, 0);
ab4723
 			add_command(command, (*command_group)++, script);
ab4723
 
ab4723
 			string_appendf(&config_file->content,
ab4723
@@ -943,6 +1003,11 @@ static void script_destroy(struct script *script)
ab4723
 	char ***commands, **command;
ab4723
 	int **groups;
ab4723
 	struct config_file *config, **configs;
ab4723
+	struct phc_vclocks **vclocks;
ab4723
+
ab4723
+	for (vclocks = script->vclocks; *vclocks; vclocks++)
ab4723
+		free(*vclocks);
ab4723
+	free(script->vclocks);
ab4723
 
ab4723
 	for (configs = script->configs; *configs; configs++) {
ab4723
 		config = *configs;
ab4723
@@ -974,6 +1039,7 @@ static struct script *script_create(struct timemaster_config *config)
ab4723
 	int **allocated_phcs = (int **)parray_new();
ab4723
 	int ret = 0, shm_segment, command_group = 0;
ab4723
 
ab4723
+	script->vclocks = (struct phc_vclocks **)parray_new();
ab4723
 	script->configs = (struct config_file **)parray_new();
ab4723
 	script->commands = (char ***)parray_new();
ab4723
 	script->command_groups = (int **)parray_new();
ab4723
@@ -1116,6 +1182,102 @@ static int remove_config_files(struct config_file **configs)
ab4723
 	return 0;
ab4723
 }
ab4723
 
ab4723
+static int set_phc_n_vclocks(int phc_index, int n_vclocks)
ab4723
+{
ab4723
+	char path[PATH_MAX];
ab4723
+	FILE *f;
ab4723
+
ab4723
+	snprintf(path, sizeof(path), "/sys/class/ptp/ptp%d/n_vclocks",
ab4723
+		 phc_index);
ab4723
+	f = fopen(path, "w");
ab4723
+	if (!f) {
ab4723
+		pr_err("failed to open %s: %m", path);
ab4723
+		return 1;
ab4723
+	}
ab4723
+	fprintf(f, "%d\n", n_vclocks);
ab4723
+	fclose(f);
ab4723
+
ab4723
+	return 0;
ab4723
+}
ab4723
+
ab4723
+static int create_vclocks(struct phc_vclocks **phc_vclocks)
ab4723
+{
ab4723
+	struct phc_vclocks **vclocks;
ab4723
+
ab4723
+	for (vclocks = phc_vclocks; *vclocks; vclocks++) {
ab4723
+		if (set_phc_n_vclocks((*vclocks)->pclock_index,
ab4723
+				      (*vclocks)->vclocks))
ab4723
+			return 1;
ab4723
+	}
ab4723
+
ab4723
+	return 0;
ab4723
+}
ab4723
+
ab4723
+static int remove_vclocks(struct phc_vclocks **phc_vclocks)
ab4723
+{
ab4723
+	struct phc_vclocks **vclocks;
ab4723
+
ab4723
+	for (vclocks = phc_vclocks; *vclocks; vclocks++) {
ab4723
+		if (set_phc_n_vclocks((*vclocks)->pclock_index, 0))
ab4723
+			return 1;
ab4723
+	}
ab4723
+
ab4723
+	return 0;
ab4723
+}
ab4723
+
ab4723
+static int get_vclock_index(int pindex, int vclock)
ab4723
+{
ab4723
+	char pattern[PATH_MAX], *s;
ab4723
+	int n, vindex;
ab4723
+	glob_t gl;
ab4723
+
ab4723
+	snprintf(pattern, sizeof(pattern), "/sys/class/ptp/ptp%d/ptp[0-9]*",
ab4723
+		 pindex);
ab4723
+
ab4723
+	if (glob(pattern, 0, NULL, &gl)) {
ab4723
+		pr_err("glob(%s) failed", pattern);
ab4723
+		return -1;
ab4723
+	}
ab4723
+
ab4723
+	if (vclock >= gl.gl_pathc ||
ab4723
+	    !(s = strrchr(gl.gl_pathv[vclock], '/')) ||
ab4723
+	    sscanf(s + 1, "ptp%d%n", &vindex, &n) != 1 ||
ab4723
+	    n != strlen(s + 1)) {
ab4723
+		pr_err("missing vclock %d:%d", pindex, vclock);
ab4723
+		globfree(&gl);
ab4723
+		return -1;
ab4723
+	}
ab4723
+
ab4723
+	globfree(&gl);
ab4723
+
ab4723
+	return vindex;
ab4723
+}
ab4723
+
ab4723
+static int translate_vclock_options(char ***commands)
ab4723
+{
ab4723
+	int n, pindex, vclock, vindex, blen;
ab4723
+	char **command;
ab4723
+
ab4723
+	for (; *commands; commands++) {
ab4723
+		for (command = *commands; *command; command++) {
ab4723
+			if (sscanf(*command, "%%PHC%d-%d%%%n",
ab4723
+				   &pindex, &vclock, &n) != 2 ||
ab4723
+			    n != strlen(*command))
ab4723
+				continue;
ab4723
+			vindex = get_vclock_index(pindex, vclock);
ab4723
+			if (vindex < 0)
ab4723
+				return 1;
ab4723
+
ab4723
+			/* overwrite the string with the vclock PHC index */
ab4723
+			blen = strlen(*command) + 1;
ab4723
+			if (snprintf(*command, blen, "%d", vindex) >= blen)
ab4723
+				return 1;
ab4723
+		}
ab4723
+	}
ab4723
+
ab4723
+	return 0;
ab4723
+}
ab4723
+
ab4723
 static int script_run(struct script *script)
ab4723
 {
ab4723
 	struct timespec ts_start, ts_now;
ab4723
@@ -1135,6 +1297,12 @@ static int script_run(struct script *script)
ab4723
 	if (create_config_files(script->configs))
ab4723
 		return 1;
ab4723
 
ab4723
+	if (create_vclocks(script->vclocks))
ab4723
+		return 1;
ab4723
+
ab4723
+	if (translate_vclock_options(script->commands))
ab4723
+		return 1;
ab4723
+
ab4723
 	sigemptyset(&mask);
ab4723
 	sigaddset(&mask, SIGCHLD);
ab4723
 	sigaddset(&mask, SIGTERM);
ab4723
@@ -1278,6 +1446,9 @@ static int script_run(struct script *script)
ab4723
 
ab4723
 	free(pids);
ab4723
 
ab4723
+	if (remove_vclocks(script->vclocks))
ab4723
+		return 1;
ab4723
+
ab4723
 	if (remove_config_files(script->configs))
ab4723
 		return 1;
ab4723
 
ab4723
@@ -1289,13 +1460,20 @@ static void script_print(struct script *script)
ab4723
 	char ***commands, **command;
ab4723
 	int **groups;
ab4723
 	struct config_file *config, **configs;
ab4723
+	struct phc_vclocks **vclocks;
ab4723
 
ab4723
 	for (configs = script->configs; *configs; configs++) {
ab4723
 		config = *configs;
ab4723
 		fprintf(stderr, "%s:\n\n%s\n", config->path, config->content);
ab4723
 	}
ab4723
 
ab4723
-	fprintf(stderr, "commands:\n\n");
ab4723
+	fprintf(stderr, "virtual clocks:\n\n");
ab4723
+	for (vclocks = script->vclocks; *vclocks; vclocks++) {
ab4723
+		fprintf(stderr, "PHC%d: %d\n",
ab4723
+			(*vclocks)->pclock_index, (*vclocks)->vclocks);
ab4723
+	}
ab4723
+
ab4723
+	fprintf(stderr, "\ncommands:\n\n");
ab4723
 	for (commands = script->commands, groups = script->command_groups;
ab4723
 	     *commands; commands++, groups++) {
ab4723
 		fprintf(stderr, "[%d] ", **groups);
ab4723
commit e09b9fda7435799afad45c96b56ac020e7f7b3d3
ab4723
Author: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
Date:   Thu Apr 28 14:23:57 2022 +0200
ab4723
ab4723
    timemaster: Check for RH-specific kernel with vclock support.
ab4723
ab4723
diff --git a/timemaster.8 b/timemaster.8
ab4723
index 102768c..edf5818 100644
ab4723
--- a/timemaster.8
ab4723
+++ b/timemaster.8
ab4723
@@ -104,7 +104,7 @@ Enable or disable synchronization with virtual clocks. If enabled,
ab4723
 needed by configured PTP domains. This enables hardware time stamping for
ab4723
 multiple \fBptp4l\fR instances using the same network interface. The default
ab4723
 value is -1, which enables the virtual clocks if running on Linux 5.18 or
ab4723
-later.
ab4723
+later, or the EL9-specific kernel-5.14.0-106 or later release.
ab4723
 
ab4723
 .SS [ntp_server address]
ab4723
 
ab4723
diff --git a/timemaster.c b/timemaster.c
ab4723
index 1fbadcb..287d77c 100644
ab4723
--- a/timemaster.c
ab4723
+++ b/timemaster.c
ab4723
@@ -546,6 +546,23 @@ static int check_kernel_version(int version, int patch)
ab4723
 	return 0;
ab4723
 }
ab4723
 
ab4723
+static int check_rh_kernel_version(const char *el, int version, int patch,
ab4723
+				   int sub, int release)
ab4723
+{
ab4723
+	struct utsname uts;
ab4723
+	int v, p, sp, r;
ab4723
+
ab4723
+	if (uname(&uts) < 0)
ab4723
+		return 1;
ab4723
+	if (!strstr(uts.release, el))
ab4723
+		return 1;
ab4723
+	if (sscanf(uts.release, "%d.%d.%d-%d", &v, &p, &sp, &r) < 4)
ab4723
+		return 1;
ab4723
+	if (version != v || patch != p || sub != sp || release > r)
ab4723
+		return 1;
ab4723
+	return 0;
ab4723
+}
ab4723
+
ab4723
 static struct timemaster_config *config_parse(char *path)
ab4723
 {
ab4723
 	struct timemaster_config *config = xcalloc(1, sizeof(*config));
ab4723
@@ -621,7 +638,8 @@ static struct timemaster_config *config_parse(char *path)
ab4723
 	fclose(f);
ab4723
 
ab4723
 	if (config->use_vclocks < 0)
ab4723
-		config->use_vclocks = !check_kernel_version(5, 18);
ab4723
+		config->use_vclocks = !check_kernel_version(5, 18) ||
ab4723
+			!check_rh_kernel_version(".el9.", 5, 14, 0, 106);
ab4723
 
ab4723
 	if (section_name)
ab4723
 		free(section_name);
ab4723
commit 5f402a959959edc7248415a98581f3eaab3c9735
ab4723
Author: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
Date:   Thu Jul 14 17:06:15 2022 +0200
ab4723
ab4723
    port: Disable PHC switch with vclocks.
ab4723
    
ab4723
    With a virtual PHC, don't try to switch to the physical PHC after a
ab4723
    link-state change. JBOD and other multi-PHC configurations are not
ab4723
    supported with vclocks yet.
ab4723
    
ab4723
    Fixes: 9b9c2c58e6ed ("port: Check for virtual clocks.")
ab4723
    
ab4723
    Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
ab4723
ab4723
diff --git a/port.c b/port.c
ab4723
index e309b98..70b6e60 100644
ab4723
--- a/port.c
ab4723
+++ b/port.c
ab4723
@@ -2591,8 +2591,9 @@ void port_link_status(void *ctx, int linkup, int ts_index)
ab4723
 	    (p->link_status & LINK_STATE_CHANGED || p->link_status & TS_LABEL_CHANGED)) {
ab4723
 		interface_get_tsinfo(p->iface);
ab4723
 
ab4723
-		/* Only switch phc with HW time stamping mode */
ab4723
+		/* Only switch a non-vclock PHC with HW time stamping. */
ab4723
 		if (interface_tsinfo_valid(p->iface) &&
ab4723
+		    interface_get_vclock(p->iface) < 0 &&
ab4723
 		    interface_phc_index(p->iface) >= 0) {
ab4723
 			required_modes = clock_required_modes(p->clock);
ab4723
 			if (!interface_tsmodes_supported(p->iface, required_modes)) {