dcavalca / rpms / linuxptp

Forked from rpms/linuxptp 2 years ago
Clone

Blame SOURCES/linuxptp-vclock.patch

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