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