From e3c74a41a6c82ce51ce5f864b58c0aa033eff719 Mon Sep 17 00:00:00 2001 From: Miroslav Lichvar Date: Thu, 9 Jun 2022 15:04:02 +0200 Subject: [PATCH] add support for virtual clocks (#2066452) Resolves: #2066452 --- .gitignore | 2 +- linuxptp-vclock.patch | 1507 +++++++++++++++++++++++++++++++++++++++++ linuxptp.spec | 5 +- sources | 2 +- 4 files changed, 1513 insertions(+), 3 deletions(-) create mode 100644 linuxptp-vclock.patch 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 +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 + Acked-by: Hangbin Liu + +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 +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 + Acked-by: Hangbin Liu + +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 + #include /* Must come before linux/netlink.h on some systems. */ + #include ++#ifdef HAVE_VCLOCKS ++#include ++#endif + #include + #include + #include +@@ -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 +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 + +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) { ++ ×tamping, 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 +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 + +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 +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 + +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 +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 + +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 +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 + +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, ×tamping, +- 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, +- ×tamping, iface); ++ ×tamping, &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 +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 + +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 + #include ++#include + #include + #include + #include +@@ -33,6 +34,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -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 +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