c9a9833432
Resolves: #2066452
1539 lines
47 KiB
Diff
1539 lines
47 KiB
Diff
Patches backported from the upstream repository.
|
|
|
|
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) {
|
|
+ ×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 <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, ×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 <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);
|
|
commit 5f402a959959edc7248415a98581f3eaab3c9735
|
|
Author: Miroslav Lichvar <mlichvar@redhat.com>
|
|
Date: Thu Jul 14 17:06:15 2022 +0200
|
|
|
|
port: Disable PHC switch with vclocks.
|
|
|
|
With a virtual PHC, don't try to switch to the physical PHC after a
|
|
link-state change. JBOD and other multi-PHC configurations are not
|
|
supported with vclocks yet.
|
|
|
|
Fixes: 9b9c2c58e6ed ("port: Check for virtual clocks.")
|
|
|
|
Signed-off-by: Miroslav Lichvar <mlichvar@redhat.com>
|
|
|
|
diff --git a/port.c b/port.c
|
|
index e309b98..70b6e60 100644
|
|
--- a/port.c
|
|
+++ b/port.c
|
|
@@ -2591,8 +2591,9 @@ void port_link_status(void *ctx, int linkup, int ts_index)
|
|
(p->link_status & LINK_STATE_CHANGED || p->link_status & TS_LABEL_CHANGED)) {
|
|
interface_get_tsinfo(p->iface);
|
|
|
|
- /* Only switch phc with HW time stamping mode */
|
|
+ /* Only switch a non-vclock PHC with HW time stamping. */
|
|
if (interface_tsinfo_valid(p->iface) &&
|
|
+ interface_get_vclock(p->iface) < 0 &&
|
|
interface_phc_index(p->iface) >= 0) {
|
|
required_modes = clock_required_modes(p->clock);
|
|
if (!interface_tsmodes_supported(p->iface, required_modes)) {
|