Thread: [Linuxptp-devel] [PATCHv2 0/8] Support for virtual clocks (Page 2)
PTP IEEE 1588 stack for Linux
Brought to you by:
rcochran
From: Miroslav L. <mli...@re...> - 2022-02-15 15:05:05
|
v2->v1: - rebased on current HEAD - fixed order of phc_index option in config_tab array - renamed search_vclocks() to rtnl_search_vclocks() rfc->v1: - fixed rtnl_linkinfo_parse() to make space for the _MAX attrs - added new TLV for PHC index and flags - added support to timemaster - updated defaults.cfg With recent kernels it is possible to create virtual PHCs running on top of a free-running PHC by writing to /sys/class/ptp/ptp?/n_vclocks. If supported in linuxptp, it would allow users to run multiple ptp4l instances with hardware timestamping on one interface, e.g. in different domains for better resiliency, or separate instances using different unicast servers. The kernel support was added in 5.14, but there were some bugs fixed recently. 5.17-rc1 or later is recommended for testing. The 1st patch is a bug fix required by the second patch. The 2nd patch adds detection of virtual clocks via ethtool netlink. The 3rd-5th patch add support to ptp4l. The PHC index of the virtual clock needs to be specified in the configuration. The 6th and 7th patch add support to phc2sys. A new PORT_HWCLOCK_NP TLV is added to get the PHC index of the virtual clock in the automatic mode. The 8th patch adds support to timemaster. If enabled (by default on Linux >=5.18), a virtual clock is created for each HW-timestamping ptp4l instance. Miroslav Lichvar (8): rtnl: Fix rtnl_rtattr_parse() to process max attribute. rtnl: Add function to detect virtual clocks. Add support for binding sockets to virtual clocks. config: Add port-specific phc_index option. port: Check for virtual clocks. tlv: Add PORT_HWCLOCK_NP. phc2sys: Use PHC index from PORT_HWCLOCK_NP. timemaster: Add support for virtual clocks. clock.c | 7 +- config.c | 1 + configs/default.cfg | 1 + incdefs.sh | 4 + interface.c | 12 +++ interface.h | 14 ++++ missing.h | 11 +++ phc2sys.c | 42 ++++++---- pmc.c | 11 +++ pmc_agent.c | 18 +++- pmc_agent.h | 4 +- pmc_common.c | 1 + port.c | 24 +++++- ptp4l.8 | 8 ++ raw.c | 3 +- rtnl.c | 91 +++++++++++++++++++- rtnl.h | 9 ++ sk.c | 11 ++- sk.h | 3 +- timemaster.8 | 14 +++- timemaster.c | 196 ++++++++++++++++++++++++++++++++++++++++++-- tlv.c | 15 ++++ tlv.h | 10 +++ udp.c | 3 +- udp6.c | 3 +- 25 files changed, 471 insertions(+), 45 deletions(-) -- 2.34.1 |
From: Miroslav L. <mli...@re...> - 2022-02-15 15:05:03
|
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 <mli...@re...> Acked-by: Hangbin Liu <liu...@gm...> --- rtnl.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rtnl.c b/rtnl.c index fe625d6..f8bdbe6 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; -- 2.34.1 |
From: Miroslav L. <mli...@re...> - 2022-02-15 15:05:03
|
Add a function using ethtool netlink to check whether a PHC is a virtual clock of an interface. Signed-off-by: Miroslav Lichvar <mli...@re...> Acked-by: Hangbin Liu <liu...@gm...> --- rtnl.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ rtnl.h | 9 +++++++ 2 files changed, 92 insertions(+) diff --git a/rtnl.c b/rtnl.c index f8bdbe6..0039d07 100644 --- a/rtnl.c +++ b/rtnl.c @@ -19,6 +19,7 @@ #include <asm/types.h> #include <sys/socket.h> /* Must come before linux/netlink.h on some systems. */ #include <linux/netlink.h> +#include <linux/ethtool_netlink.h> #include <linux/rtnetlink.h> #include <linux/genetlink.h> #include <linux/if_team.h> @@ -465,3 +466,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 fd; + + gf_id = genl_get_family_id(fd, ETHTOOL_GENL_NAME); + if (gf_id < 0) { + pr_err("get genl family failed"); + 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. -- 2.34.1 |
From: Miroslav L. <mli...@re...> - 2022-02-15 15:05:05
|
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. Signed-off-by: Miroslav Lichvar <mli...@re...> --- clock.c | 6 +++++- config.c | 1 + configs/default.cfg | 1 + port.c | 8 +++++--- ptp4l.8 | 5 +++++ 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/clock.c b/clock.c index bbfd1a8..36ce4d8 100644 --- a/clock.c +++ b/clock.c @@ -898,7 +898,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; @@ -1016,6 +1016,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; @@ -1025,6 +1027,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 f3c52ba..4f3ceb8 100644 --- a/config.c +++ b/config.c @@ -284,6 +284,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 cd383b5..26817de 100644 --- a/configs/default.cfg +++ b/configs/default.cfg @@ -106,6 +106,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 c1d0ac8..fbd5abf 100644 --- a/port.c +++ b/port.c @@ -3125,7 +3125,9 @@ struct port *port_open(const char *phc_device, goto err_log_name; } - 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"); p->master_only = config_get_int(cfg, interface_name(interface), "serverOnly"); p->bmca = config_get_int(cfg, interface_name(interface), "BMCA"); @@ -3152,8 +3154,8 @@ struct port *port_open(const char *phc_device, ; /* UDS cannot have a PHC. */ } else if (!interface_tsinfo_valid(interface)) { pr_warning("%s: get_ts_info not supported", p->log_name); - } 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("%s: just a bunch of devices", p->log_name); p->phc_index = interface_phc_index(interface); diff --git a/ptp4l.8 b/ptp4l.8 index 8c2969f..5ef471b 100644 --- a/ptp4l.8 +++ b/ptp4l.8 @@ -375,6 +375,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 -- 2.34.1 |
From: Miroslav L. <mli...@re...> - 2022-02-15 15:05:07
|
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. Signed-off-by: Miroslav Lichvar <mli...@re...> --- phc2sys.c | 42 +++++++++++++++++++++++++----------------- pmc_agent.c | 18 +++++++++++++++++- pmc_agent.h | 4 +++- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/phc2sys.c b/phc2sys.c index 6815c3d..8fc241d 100644 --- a/phc2sys.c +++ b/phc2sys.c @@ -153,14 +153,21 @@ static struct servo *servo_add(struct phc2sys_private *priv, return servo; } -static struct clock *clock_add(struct phc2sys_private *priv, const char *device) +static struct clock *clock_add(struct phc2sys_private *priv, const 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; } @@ -257,7 +264,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; @@ -274,7 +281,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; } @@ -293,9 +300,8 @@ static void clock_reinit(struct phc2sys_private *priv, struct clock *clock, int new_state) { int err = -1, phc_index = -1, phc_switched = 0, state, timestamping; + char iface[IFNAMSIZ], phc_device[19]; struct port *p; - struct sk_ts_info ts_info; - char iface[IFNAMSIZ]; clockid_t clkid = CLOCK_INVALID; LIST_FOREACH(p, &priv->ports, list) { @@ -304,7 +310,8 @@ static void clock_reinit(struct phc2sys_private *priv, struct clock *clock, } err = pmc_agent_query_port_properties(priv->agent, 1000, p->number, &state, - ×tamping, iface); + ×tamping, &phc_index, + iface); if (!err) { p->state = normalize_state(state); } @@ -318,9 +325,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; @@ -844,7 +852,7 @@ static int phc2sys_recv_subscribed(void *context, struct ptp_message *msg, static int auto_init_ports(struct phc2sys_private *priv, int add_rt) { - int err, number_ports, state, timestamping; + int err, number_ports, phc_index, state, timestamping; char iface[IFNAMSIZ]; struct clock *clock; struct port *port; @@ -880,7 +888,7 @@ static int auto_init_ports(struct phc2sys_private *priv, int add_rt) for (i = 1; i <= number_ports; i++) { err = pmc_agent_query_port_properties(priv->agent, 1000, i, &state, ×tamping, - iface); + &phc_index, iface); if (err == -ENODEV) { /* port does not exist, ignore the port */ continue; @@ -893,7 +901,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); @@ -908,7 +916,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) @@ -991,7 +999,7 @@ static int phc2sys_static_configuration(struct phc2sys_private *priv, { struct clock *src, *dst; - src = clock_add(priv, src_name); + src = clock_add(priv, src_name, -1); if (!src) { fprintf(stderr, "valid source clock must be selected.\n"); return -1; @@ -999,7 +1007,7 @@ static int phc2sys_static_configuration(struct phc2sys_private *priv, src->state = PS_SLAVE; priv->master = src; - dst = clock_add(priv, dst_name); + dst = clock_add(priv, dst_name, -1); if (!dst) { fprintf(stderr, "valid destination clock must be selected.\n"); return -1; diff --git a/pmc_agent.c b/pmc_agent.c index 86350d8..79971e2 100644 --- a/pmc_agent.c +++ b/pmc_agent.c @@ -304,9 +304,10 @@ int pmc_agent_query_dds(struct pmc_agent *node, int timeout) int pmc_agent_query_port_properties(struct pmc_agent *node, int timeout, unsigned int port, int *state, - int *tstamping, char *iface) + int *tstamping, int *phc_index, char *iface) { struct port_properties_np *ppn; + struct port_hwclock_np *phn; struct ptp_message *msg; int res, len; @@ -330,6 +331,21 @@ int pmc_agent_query_port_properties(struct pmc_agent *node, int timeout, memcpy(iface, ppn->interface.text, len); iface[len] = '\0'; + msg_put(msg); + break; + } + while (1) { + res = run_pmc(node, timeout, MID_PORT_HWCLOCK_NP, &msg); + if (is_run_pmc_error(res)) { + goto out; + } + phn = management_tlv_data(msg); + if (phn->portIdentity.portNumber != port) { + msg_put(msg); + continue; + } + *phc_index = phn->phc_index; + msg_put(msg); res = RUN_PMC_OKAY; break; diff --git a/pmc_agent.h b/pmc_agent.h index 11b9347..ad4b771 100644 --- a/pmc_agent.h +++ b/pmc_agent.h @@ -103,12 +103,14 @@ int pmc_agent_query_dds(struct pmc_agent *agent, int timeout); * @param port The port index of interest. * @param state Buffer to hold the returned port state. * @param tstamping Buffer to hold the returned time stamping flavor. + * @param phc_index Buffer to hold the returned PHC index. * @param iface Buffer to hold the returned interface name. * @return Zero on success, negative error code otherwise. */ int pmc_agent_query_port_properties(struct pmc_agent *agent, int timeout, unsigned int port, int *state, - int *tstamping, char *iface); + int *tstamping, int *phc_index, + char *iface); /** * Queries the TAI-UTC offset and the current leap adjustment from the -- 2.34.1 |
From: Miroslav L. <mli...@re...> - 2022-02-15 15:05:07
|
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. Signed-off-by: Miroslav Lichvar <mli...@re...> --- clock.c | 1 + pmc.c | 11 +++++++++++ pmc_common.c | 1 + port.c | 9 +++++++++ tlv.c | 15 +++++++++++++++ tlv.h | 10 ++++++++++ 6 files changed, 47 insertions(+) diff --git a/clock.c b/clock.c index 36ce4d8..f808b35 100644 --- a/clock.c +++ b/clock.c @@ -1486,6 +1486,7 @@ int clock_manage(struct clock *c, struct port *p, struct ptp_message *msg) switch (mgt->id) { case MID_PORT_PROPERTIES_NP: + case MID_PORT_HWCLOCK_NP: if (p != c->uds_rw_port) { /* Only the UDS-RW port allowed. */ clock_management_send_error(p, msg, MID_NOT_SUPPORTED); diff --git a/pmc.c b/pmc.c index 2e5e93e..9337a2e 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; @@ -522,6 +523,16 @@ static void pmc_show(struct ptp_message *msg, FILE *fp) pssp->stats.sync_mismatch, pssp->stats.followup_mismatch); break; + case MID_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 MID_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 b691bbb..be78914 100644 --- a/pmc_common.c +++ b/pmc_common.c @@ -133,6 +133,7 @@ struct management_id idtab[] = { { "PORT_STATS_NP", MID_PORT_STATS_NP, do_get_action }, { "PORT_SERVICE_STATS_NP", MID_PORT_SERVICE_STATS_NP, do_get_action }, { "PORT_PROPERTIES_NP", MID_PORT_PROPERTIES_NP, do_get_action }, + { "PORT_HWCLOCK_NP", MID_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 3152dee..38dc840 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 port_service_stats_np *pssn; struct management_tlv *tlv; @@ -973,6 +974,14 @@ static int port_management_fill_response(struct port *target, pssn->stats = target->service_stats; datalen = sizeof(*pssn); break; + case MID_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 df516be..1762d15 100644 --- a/tlv.c +++ b/tlv.c @@ -117,6 +117,7 @@ static int mgt_post_recv(struct management_tlv *m, uint16_t data_len, struct mgmt_clock_description *cd; struct subscribe_events_np *sen; struct port_properties_np *ppn; + struct port_hwclock_np *phn; struct timePropertiesDS *tp; struct time_status_np *tsn; struct port_stats_np *psn; @@ -360,6 +361,14 @@ static int mgt_post_recv(struct management_tlv *m, uint16_t data_len, __le64_to_cpu(pssn->stats.followup_mismatch); extra_len = sizeof(struct port_service_stats_np); break; + case MID_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 MID_SAVE_IN_NON_VOLATILE_STORAGE: case MID_RESET_NON_VOLATILE_STORAGE: case MID_INITIALIZE: @@ -387,6 +396,7 @@ static void mgt_pre_send(struct management_tlv *m, struct tlv_extra *extra) struct mgmt_clock_description *cd; struct subscribe_events_np *sen; struct port_properties_np *ppn; + struct port_hwclock_np *phn; struct timePropertiesDS *tp; struct time_status_np *tsn; struct port_stats_np *psn; @@ -503,6 +513,11 @@ static void mgt_pre_send(struct management_tlv *m, struct tlv_extra *extra) pssn->stats.followup_mismatch = __cpu_to_le64(pssn->stats.followup_mismatch); break; + case MID_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 408c22c..8ff9b0f 100644 --- a/tlv.h +++ b/tlv.h @@ -126,6 +126,7 @@ enum management_action { #define MID_PORT_PROPERTIES_NP 0xC004 #define MID_PORT_STATS_NP 0xC005 #define MID_PORT_SERVICE_STATS_NP 0xC007 +#define MID_PORT_HWCLOCK_NP 0xC008 /* Management error ID values */ #define MID_RESPONSE_TOO_BIG 0x0001 @@ -145,6 +146,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; @@ -345,6 +349,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; -- 2.34.1 |
From: Miroslav L. <mli...@re...> - 2022-02-15 15:05:06
|
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. Signed-off-by: Miroslav Lichvar <mli...@re...> --- port.c | 7 ++++++- ptp4l.8 | 7 +++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/port.c b/port.c index fbd5abf..3152dee 100644 --- a/port.c +++ b/port.c @@ -3156,7 +3156,12 @@ struct port *port_open(const char *phc_device, pr_warning("%s: get_ts_info not supported", p->log_name); } 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("%s: /dev/ptp%d is virtual clock", + p->log_name, p->phc_index); + interface_set_vclock(interface, phc_index); + } else if (p->jbod) { pr_warning("%s: just a bunch of devices", p->log_name); p->phc_index = interface_phc_index(interface); } else if (phc_device) { diff --git a/ptp4l.8 b/ptp4l.8 index 5ef471b..71a414c 100644 --- a/ptp4l.8 +++ b/ptp4l.8 @@ -377,8 +377,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 -- 2.34.1 |
From: Miroslav L. <mli...@re...> - 2022-02-15 15:05:07
|
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 <mli...@re...> --- incdefs.sh | 4 ++++ interface.c | 12 ++++++++++++ interface.h | 14 ++++++++++++++ missing.h | 11 +++++++++++ raw.c | 3 ++- sk.c | 11 +++++++++-- sk.h | 3 ++- udp.c | 3 ++- udp6.c | 3 ++- 9 files changed, 58 insertions(+), 6 deletions(-) diff --git a/incdefs.sh b/incdefs.sh index 19e620e..d4c97be 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_BIND_PHC" + fi } flags="$(user_flags)$(kernel_flags)" 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 89cb513..64a856d 100644 --- a/missing.h +++ b/missing.h @@ -62,6 +62,17 @@ enum { }; #endif +#ifndef HAVE_BIND_PHC +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)) -- 2.34.1 |
From: Miroslav L. <mli...@re...> - 2022-02-15 15:05:12
|
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 <mli...@re...> --- timemaster.8 | 14 +++- timemaster.c | 196 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 199 insertions(+), 11 deletions(-) diff --git a/timemaster.8 b/timemaster.8 index a11f4dd..2891a97 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 616a99a..cafd3a3 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); -- 2.34.1 |
From: Richard C. <ric...@gm...> - 2022-03-07 02:30:58
|
On Tue, Feb 15, 2022 at 04:04:43PM +0100, Miroslav Lichvar wrote: > v2->v1: > - rebased on current HEAD > - fixed order of phc_index option in config_tab array > - renamed search_vclocks() to rtnl_search_vclocks() > > rfc->v1: > - fixed rtnl_linkinfo_parse() to make space for the _MAX attrs > - added new TLV for PHC index and flags > - added support to timemaster > - updated defaults.cfg I rebased this onto master and fixed up the trival conflict due to adding new _NP management TLVs. https://github.com/richardcochran/linuxptp/tree/vclock-support However, it won't compile with older toolchains: /opt/x-tools/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc -Wall -DVER=3.1-00132-g6837ba4 -D_GNU_SOURCE -DHAVE_CLOCK_ADJTIME -DHAVE_POSIX_SPAWN -DHAVE_ONESTEP_SYNC -O2 -Werror -std=gnu99 -g -c -o rtnl.o /home/richard/git/linuxptp/rtnl.c /home/richard/git/linuxptp/rtnl.c:22:10: fatal error: linux/ethtool_netlink.h: No such file or directory #include <linux/ethtool_netlink.h> ^~~~~~~~~~~~~~~~~~~~~~~~~ compilation terminated. make: *** [<builtin>: rtnl.o] Error 1 Can you take a look at that? Thanks, Richard |
From: Miroslav L. <mli...@re...> - 2022-03-08 10:47:26
|
v3: - rebased on current HEAD - added required ethtool netlink declarations to missing.h to build with old kernel headers - fixed return value when netlink socket cannot be opened - fixed vclock setting to use the port-specific phc_index - downgraded error message for missing ethtool netlink to debug message v2: - rebased on current HEAD - fixed order of phc_index option in config_tab array - renamed search_vclocks() to rtnl_search_vclocks() v1: - fixed rtnl_linkinfo_parse() to make space for the _MAX attrs - added new TLV for PHC index and flags - added support to timemaster - updated defaults.cfg With recent kernels it is possible to create virtual PHCs running on top of a free-running PHC by writing to /sys/class/ptp/ptp?/n_vclocks. If supported in linuxptp, it would allow users to run multiple ptp4l instances with hardware timestamping on one interface, e.g. in different domains for better resiliency, or separate instances using different unicast servers. The kernel support was added in 5.14, but there were some bugs fixed recently. 5.17-rc1 or later is recommended for testing. The 1st patch is a bug fix required by the second patch. The 2nd patch adds detection of virtual clocks via ethtool netlink. The 3rd-5th patch add support to ptp4l. The PHC index of the virtual clock needs to be specified in the configuration. The 6th and 7th patch add support to phc2sys. A new PORT_HWCLOCK_NP TLV is added to get the PHC index of the virtual clock in the automatic mode. The 8th patch adds support to timemaster. If enabled (by default on Linux >=5.18), a virtual clock is created for each HW-timestamping ptp4l instance. Miroslav Lichvar (8): rtnl: Fix rtnl_rtattr_parse() to process max attribute. rtnl: Add function to detect virtual clocks. Add support for binding sockets to virtual clocks. config: Add port-specific phc_index option. port: Check for virtual clocks. tlv: Add PORT_HWCLOCK_NP. phc2sys: Use PHC index from PORT_HWCLOCK_NP. timemaster: Add support for virtual clocks. clock.c | 7 +- config.c | 1 + configs/default.cfg | 1 + incdefs.sh | 4 + interface.c | 12 +++ interface.h | 14 ++++ missing.h | 112 +++++++++++++++++++++++++ phc2sys.c | 42 ++++++---- pmc.c | 11 +++ pmc_agent.c | 18 +++- pmc_agent.h | 4 +- pmc_common.c | 1 + port.c | 24 +++++- ptp4l.8 | 8 ++ raw.c | 3 +- rtnl.c | 93 ++++++++++++++++++++- rtnl.h | 9 ++ sk.c | 11 ++- sk.h | 3 +- timemaster.8 | 14 +++- timemaster.c | 196 ++++++++++++++++++++++++++++++++++++++++++-- tlv.c | 16 +++- tlv.h | 10 +++ udp.c | 3 +- udp6.c | 3 +- 25 files changed, 574 insertions(+), 46 deletions(-) -- 2.35.1 |
From: Miroslav L. <mli...@re...> - 2022-03-08 10:47:24
|
Add a function using ethtool netlink to check whether a PHC is a virtual clock of an interface. Signed-off-by: Miroslav Lichvar <mli...@re...> Acked-by: Hangbin Liu <liu...@gm...> --- incdefs.sh | 4 +++ missing.h | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++ rtnl.c | 85 ++++++++++++++++++++++++++++++++++++++++++++ rtnl.h | 9 +++++ 4 files changed, 199 insertions(+) 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 89cb513..14aa1e6 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 f8bdbe6..fa02388 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. -- 2.35.1 |
From: Miroslav L. <mli...@re...> - 2022-03-08 10:47:26
|
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. Signed-off-by: Miroslav Lichvar <mli...@re...> --- port.c | 7 ++++++- ptp4l.8 | 7 +++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/port.c b/port.c index cfa57b8..ced09ae 100644 --- a/port.c +++ b/port.c @@ -3204,7 +3204,12 @@ struct port *port_open(const char *phc_device, pr_warning("%s: get_ts_info not supported", p->log_name); } 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("%s: /dev/ptp%d is virtual clock", + p->log_name, p->phc_index); + interface_set_vclock(interface, p->phc_index); + } else if (p->jbod) { pr_warning("%s: just a bunch of devices", p->log_name); p->phc_index = interface_phc_index(interface); } else if (phc_device) { diff --git a/ptp4l.8 b/ptp4l.8 index 5ef471b..71a414c 100644 --- a/ptp4l.8 +++ b/ptp4l.8 @@ -377,8 +377,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 -- 2.35.1 |
From: Miroslav L. <mli...@re...> - 2022-03-08 10:47:24
|
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. Signed-off-by: Miroslav Lichvar <mli...@re...> --- clock.c | 6 +++++- config.c | 1 + configs/default.cfg | 1 + port.c | 8 +++++--- ptp4l.8 | 5 +++++ 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/clock.c b/clock.c index bbfd1a8..36ce4d8 100644 --- a/clock.c +++ b/clock.c @@ -898,7 +898,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; @@ -1016,6 +1016,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; @@ -1025,6 +1027,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 f3c52ba..4f3ceb8 100644 --- a/config.c +++ b/config.c @@ -284,6 +284,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 cd383b5..26817de 100644 --- a/configs/default.cfg +++ b/configs/default.cfg @@ -106,6 +106,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 85b6575..cfa57b8 100644 --- a/port.c +++ b/port.c @@ -3173,7 +3173,9 @@ struct port *port_open(const char *phc_device, goto err_log_name; } - 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"); p->master_only = config_get_int(cfg, interface_name(interface), "serverOnly"); p->bmca = config_get_int(cfg, interface_name(interface), "BMCA"); @@ -3200,8 +3202,8 @@ struct port *port_open(const char *phc_device, ; /* UDS cannot have a PHC. */ } else if (!interface_tsinfo_valid(interface)) { pr_warning("%s: get_ts_info not supported", p->log_name); - } 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("%s: just a bunch of devices", p->log_name); p->phc_index = interface_phc_index(interface); diff --git a/ptp4l.8 b/ptp4l.8 index 8c2969f..5ef471b 100644 --- a/ptp4l.8 +++ b/ptp4l.8 @@ -375,6 +375,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 -- 2.35.1 |
From: Miroslav L. <mli...@re...> - 2022-03-08 10:47:21
|
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 <mli...@re...> --- interface.c | 12 ++++++++++++ interface.h | 14 ++++++++++++++ missing.h | 11 +++++++++++ raw.c | 3 ++- sk.c | 11 +++++++++-- sk.h | 3 ++- udp.c | 3 ++- udp6.c | 3 ++- 8 files changed, 54 insertions(+), 6 deletions(-) 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 14aa1e6..20f7193 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)) -- 2.35.1 |
From: Miroslav L. <mli...@re...> - 2022-03-08 10:47:21
|
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 <mli...@re...> Acked-by: Hangbin Liu <liu...@gm...> --- rtnl.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rtnl.c b/rtnl.c index fe625d6..f8bdbe6 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; -- 2.35.1 |
From: Miroslav L. <mli...@re...> - 2022-03-08 10:47:25
|
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 <mli...@re...> --- timemaster.8 | 14 +++- timemaster.c | 196 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 199 insertions(+), 11 deletions(-) diff --git a/timemaster.8 b/timemaster.8 index a11f4dd..2891a97 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 616a99a..cafd3a3 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); -- 2.35.1 |
From: Miroslav L. <mli...@re...> - 2022-03-08 10:47:41
|
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. Signed-off-by: Miroslav Lichvar <mli...@re...> --- clock.c | 1 + pmc.c | 11 +++++++++++ pmc_common.c | 1 + port.c | 9 +++++++++ tlv.c | 16 +++++++++++++++- tlv.h | 10 ++++++++++ 6 files changed, 47 insertions(+), 1 deletion(-) diff --git a/clock.c b/clock.c index 36ce4d8..f808b35 100644 --- a/clock.c +++ b/clock.c @@ -1486,6 +1486,7 @@ int clock_manage(struct clock *c, struct port *p, struct ptp_message *msg) switch (mgt->id) { case MID_PORT_PROPERTIES_NP: + case MID_PORT_HWCLOCK_NP: if (p != c->uds_rw_port) { /* Only the UDS-RW port allowed. */ clock_management_send_error(p, msg, MID_NOT_SUPPORTED); diff --git a/pmc.c b/pmc.c index 7916153..0591dd1 100644 --- a/pmc.c +++ b/pmc.c @@ -165,6 +165,7 @@ static void pmc_show(struct ptp_message *msg, FILE *fp) struct unicast_master_entry *ume; struct subscribe_events_np *sen; struct port_properties_np *ppn; + struct port_hwclock_np *phn; struct timePropertiesDS *tp; struct management_tlv *mgt; struct time_status_np *tsn; @@ -561,6 +562,16 @@ static void pmc_show(struct ptp_message *msg, FILE *fp) buf += sizeof(*ume) + ume->address.addressLength; } break; + case MID_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 MID_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 017a4f7..1141957 100644 --- a/pmc_common.c +++ b/pmc_common.c @@ -134,6 +134,7 @@ struct management_id idtab[] = { { "PORT_STATS_NP", MID_PORT_STATS_NP, do_get_action }, { "PORT_SERVICE_STATS_NP", MID_PORT_SERVICE_STATS_NP, do_get_action }, { "UNICAST_MASTER_TABLE_NP", MID_UNICAST_MASTER_TABLE_NP, do_get_action }, + { "PORT_HWCLOCK_NP", MID_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 ced09ae..f2b666c 100644 --- a/port.c +++ b/port.c @@ -802,6 +802,7 @@ static int port_management_fill_response(struct port *target, struct unicast_master_entry *ume; struct clock_description *desc; struct port_properties_np *ppn; + struct port_hwclock_np *phn; struct management_tlv *tlv; struct port_stats_np *psn; struct foreign_clock *fc; @@ -1021,6 +1022,14 @@ static int port_management_fill_response(struct port *target, } datalen = buf - tlv->data; break; + case MID_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 4b74fe7..1c13460 100644 --- a/tlv.c +++ b/tlv.c @@ -120,6 +120,7 @@ static int mgt_post_recv(struct management_tlv *m, uint16_t data_len, struct unicast_master_entry *ume; struct subscribe_events_np *sen; struct port_properties_np *ppn; + struct port_hwclock_np *phn; struct timePropertiesDS *tp; struct time_status_np *tsn; struct port_stats_np *psn; @@ -388,7 +389,14 @@ static int mgt_post_recv(struct management_tlv *m, uint16_t data_len, goto bad_length; buf += sizeof(*ume) + ume->address.addressLength; } - + break; + case MID_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 MID_SAVE_IN_NON_VOLATILE_STORAGE: case MID_RESET_NON_VOLATILE_STORAGE: @@ -420,6 +428,7 @@ static void mgt_pre_send(struct management_tlv *m, struct tlv_extra *extra) struct unicast_master_entry *ume; struct subscribe_events_np *sen; struct port_properties_np *ppn; + struct port_hwclock_np *phn; struct timePropertiesDS *tp; struct time_status_np *tsn; struct port_stats_np *psn; @@ -555,6 +564,11 @@ static void mgt_pre_send(struct management_tlv *m, struct tlv_extra *extra) umtn->actual_table_size = htons(umtn->actual_table_size); break; + case MID_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 915b9fc..60a9edb 100644 --- a/tlv.h +++ b/tlv.h @@ -127,6 +127,7 @@ enum management_action { #define MID_PORT_STATS_NP 0xC005 #define MID_PORT_SERVICE_STATS_NP 0xC007 #define MID_UNICAST_MASTER_TABLE_NP 0xC008 +#define MID_PORT_HWCLOCK_NP 0xC009 /* Management error ID values */ #define MID_RESPONSE_TOO_BIG 0x0001 @@ -146,6 +147,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; @@ -346,6 +350,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; -- 2.35.1 |
From: Miroslav L. <mli...@re...> - 2022-03-08 10:47:46
|
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. Signed-off-by: Miroslav Lichvar <mli...@re...> --- phc2sys.c | 42 +++++++++++++++++++++++++----------------- pmc_agent.c | 18 +++++++++++++++++- pmc_agent.h | 4 +++- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/phc2sys.c b/phc2sys.c index 489e15b..15dd689 100644 --- a/phc2sys.c +++ b/phc2sys.c @@ -153,14 +153,21 @@ static struct servo *servo_add(struct phc2sys_private *priv, return servo; } -static struct clock *clock_add(struct phc2sys_private *priv, const char *device) +static struct clock *clock_add(struct phc2sys_private *priv, const 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; } @@ -257,7 +264,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; @@ -274,7 +281,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; } @@ -293,9 +300,8 @@ static void clock_reinit(struct phc2sys_private *priv, struct clock *clock, int new_state) { int err = -1, phc_index = -1, phc_switched = 0, state, timestamping; + char iface[IFNAMSIZ], phc_device[19]; struct port *p; - struct sk_ts_info ts_info; - char iface[IFNAMSIZ]; clockid_t clkid = CLOCK_INVALID; LIST_FOREACH(p, &priv->ports, list) { @@ -304,7 +310,8 @@ static void clock_reinit(struct phc2sys_private *priv, struct clock *clock, } err = pmc_agent_query_port_properties(priv->agent, 1000, p->number, &state, - ×tamping, iface); + ×tamping, &phc_index, + iface); if (!err) { p->state = normalize_state(state); } @@ -318,9 +325,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; @@ -816,7 +824,7 @@ static int phc2sys_recv_subscribed(void *context, struct ptp_message *msg, static int auto_init_ports(struct phc2sys_private *priv, int add_rt) { - int err, number_ports, state, timestamping; + int err, number_ports, phc_index, state, timestamping; char iface[IFNAMSIZ]; struct clock *clock; struct port *port; @@ -852,7 +860,7 @@ static int auto_init_ports(struct phc2sys_private *priv, int add_rt) for (i = 1; i <= number_ports; i++) { err = pmc_agent_query_port_properties(priv->agent, 1000, i, &state, ×tamping, - iface); + &phc_index, iface); if (err == -ENODEV) { /* port does not exist, ignore the port */ continue; @@ -865,7 +873,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); @@ -880,7 +888,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) @@ -963,7 +971,7 @@ static int phc2sys_static_configuration(struct phc2sys_private *priv, { struct clock *src, *dst; - src = clock_add(priv, src_name); + src = clock_add(priv, src_name, -1); if (!src) { fprintf(stderr, "valid source clock must be selected.\n"); return -1; @@ -971,7 +979,7 @@ static int phc2sys_static_configuration(struct phc2sys_private *priv, src->state = PS_SLAVE; priv->master = src; - dst = clock_add(priv, dst_name); + dst = clock_add(priv, dst_name, -1); if (!dst) { fprintf(stderr, "valid destination clock must be selected.\n"); return -1; diff --git a/pmc_agent.c b/pmc_agent.c index 86350d8..79971e2 100644 --- a/pmc_agent.c +++ b/pmc_agent.c @@ -304,9 +304,10 @@ int pmc_agent_query_dds(struct pmc_agent *node, int timeout) int pmc_agent_query_port_properties(struct pmc_agent *node, int timeout, unsigned int port, int *state, - int *tstamping, char *iface) + int *tstamping, int *phc_index, char *iface) { struct port_properties_np *ppn; + struct port_hwclock_np *phn; struct ptp_message *msg; int res, len; @@ -330,6 +331,21 @@ int pmc_agent_query_port_properties(struct pmc_agent *node, int timeout, memcpy(iface, ppn->interface.text, len); iface[len] = '\0'; + msg_put(msg); + break; + } + while (1) { + res = run_pmc(node, timeout, MID_PORT_HWCLOCK_NP, &msg); + if (is_run_pmc_error(res)) { + goto out; + } + phn = management_tlv_data(msg); + if (phn->portIdentity.portNumber != port) { + msg_put(msg); + continue; + } + *phc_index = phn->phc_index; + msg_put(msg); res = RUN_PMC_OKAY; break; diff --git a/pmc_agent.h b/pmc_agent.h index 11b9347..ad4b771 100644 --- a/pmc_agent.h +++ b/pmc_agent.h @@ -103,12 +103,14 @@ int pmc_agent_query_dds(struct pmc_agent *agent, int timeout); * @param port The port index of interest. * @param state Buffer to hold the returned port state. * @param tstamping Buffer to hold the returned time stamping flavor. + * @param phc_index Buffer to hold the returned PHC index. * @param iface Buffer to hold the returned interface name. * @return Zero on success, negative error code otherwise. */ int pmc_agent_query_port_properties(struct pmc_agent *agent, int timeout, unsigned int port, int *state, - int *tstamping, char *iface); + int *tstamping, int *phc_index, + char *iface); /** * Queries the TAI-UTC offset and the current leap adjustment from the -- 2.35.1 |
From: Richard C. <ric...@gm...> - 2022-03-08 14:59:24
|
On Tue, Mar 08, 2022 at 11:46:57AM +0100, Miroslav Lichvar wrote: > v3: > - rebased on current HEAD > - added required ethtool netlink declarations to missing.h to build with > old kernel headers > - fixed return value when netlink socket cannot be opened > - fixed vclock setting to use the port-specific phc_index > - downgraded error message for missing ethtool netlink to debug message > > v2: > - rebased on current HEAD > - fixed order of phc_index option in config_tab array > - renamed search_vclocks() to rtnl_search_vclocks() > > v1: > - fixed rtnl_linkinfo_parse() to make space for the _MAX attrs > - added new TLV for PHC index and flags > - added support to timemaster > - updated defaults.cfg Series applied. Thanks, Richard |
From: Vladimir O. <ol...@gm...> - 2022-05-16 15:19:17
|
On Tue, Mar 08, 2022 at 11:46:59AM +0100, Miroslav Lichvar wrote: > Add a function using ethtool netlink to check whether a PHC is a virtual > clock of an interface. > > Signed-off-by: Miroslav Lichvar <mli...@re...> > Acked-by: Hangbin Liu <liu...@gm...> > --- > incdefs.sh | 4 +++ > missing.h | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++ > rtnl.c | 85 ++++++++++++++++++++++++++++++++++++++++++++ > rtnl.h | 9 +++++ > 4 files changed, 199 insertions(+) > > 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 > } > diff --git a/rtnl.c b/rtnl.c > index f8bdbe6..fa02388 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> I will take this opportunity to state that incdefs.h is broken with cross-compilation, because it wants me to set $KBUILD_OUTPUT to the sysroot path, otherwise it searches the system-wide /usr/include/linux/net_tstamp.h for SOF_TIMESTAMPING_BIND_PHC, and finds it, and says "oh, yeah, I have vclocks". Then when I go ahead and compile linuxptp, it fails to find linux/ethtool_netlink.h, because the actual _sysroot_ doesn't have vclock support (only the system kernel headers do). You're probably going to say "just set KBUILD_OUTPUT", which I am indeed forced to do. But in fact I have an environment script that I just source for cross-compilation. And this variable isn't only used by linuxptp, it also affects where the Linux kernel output gets compiled to. Simply put, KBUILD_OUTPUT is _not_ the right choice to determine whether linuxptp will be compiled with kernel headers that have a certain symbol exported. It's also frustrating to have an env file that works for cross compiling everything, including the kernel itself, except linuxptp. What I usually do when I need to determine whether a feature is available is to compile a dummy C program using the same CFLAGS as the main program itself. No dumpster diving through paths on the host system, depending on variables you're not supposed to, etc. Like this: cat toolchain_deps.sh #!/bin/bash CC="$1" CFLAGS="$2" EXTRA_CFLAGS="" ${CC} ${CFLAGS} -x c -c -o $(mktemp) - > /dev/null 2>&1 << EOF #include <linux/net_tstamp.h> int main(void) { return SOF_TIMESTAMPING_BIND_PHC; } EOF if [ $? = 0 ]; then EXTRA_CFLAGS="${EXTRA_CFLAGS} -DHAVE_VCLOCKS" fi echo ${EXTRA_CFLAGS} Used like this: cat Makefile CFLAGS += $(shell ./toolchain_deps.sh "$(CC)" "$(CFLAGS)") |
From: Richard C. <ric...@gm...> - 2022-05-16 16:30:35
|
On Mon, May 16, 2022 at 06:19:01PM +0300, Vladimir Oltean wrote: > What I usually do when I need to determine whether a feature is > available is to compile a dummy C program using the same CFLAGS as the > main program itself. Well you probably love autoconf then. I don't. What I have for linuxptp is based on decades of my own very frustrating experience in embedded. I am not going to change the build system, especially not compiling test programs to find things out. That method is simply hacky. Sorry, Richard |
From: Vladimir O. <ol...@gm...> - 2022-05-16 16:54:14
|
On Mon, May 16, 2022 at 09:30:22AM -0700, Richard Cochran wrote: > On Mon, May 16, 2022 at 06:19:01PM +0300, Vladimir Oltean wrote: > > > What I usually do when I need to determine whether a feature is > > available is to compile a dummy C program using the same CFLAGS as the > > main program itself. > > Well you probably love autoconf then. > > I don't. > > What I have for linuxptp is based on decades of my own very > frustrating experience in embedded. > > I am not going to change the build system, especially not compiling > test programs to find things out. That method is simply hacky. > > Sorry, > Richard No, I didn't say I love autoconf or that you should use autoconf. I just pointed out that incdefs.sh doesn't work unless you point it to the kernel headers, because it insists on grepping through files rather than just working with the provided C environment, which is what you _actually_ want. I also compile the Linux kernel so I won't set KBUILD_OUTPUT unless I want to garble my toolchain sysroot with junk. The main problem is that incdefs.sh guesses incorrectly where the header files are, if I don't point KBUILD_OUTPUT to them. It can stop doing that, and guess better, if you can just reimplement incdefs.sh using some other approach, not migrate to the full-blown autotools. I'd rather have something hacky that works and plays nice with other people's environments rather than something hacky that doesn't. But ok, maybe I'm doing something wrong. How do you set up a cross compilation environment that works for linuxptp and for the kernel? |
From: Richard C. <ric...@gm...> - 2022-07-24 14:34:17
|
On Mon, May 16, 2022 at 07:54:02PM +0300, Vladimir Oltean wrote: > I also compile the Linux kernel so I won't set KBUILD_OUTPUT unless I > want to garble my toolchain sysroot with junk. Um, the header files that ship with the toolchain are not "junk". They are essential to the compiler (unless you are building bare metal programs without any libraries at all, not even C runtime!) > The main problem is that > incdefs.sh guesses incorrectly where the header files are, if I don't > point KBUILD_OUTPUT to them. Yes, I see that the script does the wrong thing when KBUILD_OUTPUT is set but the path does not exist. In this case the script uses the build system headers, and that is wrong. Instead, it should throw an error. > But ok, maybe I'm doing something wrong. How do you set up a cross > compilation environment that works for linuxptp and for the kernel? Cross toolchains built for Linux always include header files. If you want to use the headers from the toolchain, just set CROSS_COMPILE and KBUILD_OUTPUT appropriately. Examples: # arm 32 bit: LINARO=gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf export CROSS_COMPILE=/opt/x-tools/$LINARO/bin/arm-linux-gnueabihf- export KBUILD_OUTPUT=/opt/x-tools/$LINARO/arm-linux-gnueabihf/libc # arm 64 bit: LINARO=gcc-linaro-6.5.0-2018.12-x86_64_aarch64-linux-gnu export CROSS_COMPILE=/opt/x-tools/$LINARO/bin/aarch64-linux-gnu- export KBUILD_OUTPUT=/opt/x-tools/$LINARO/aarch64-linux-gnu/libc # bonus: native compile with older kernel headers: cd ~/git/linux git checkout v3.0 make INSTALL_HDR_PATH=~/build/linuxptp/l3oh headers_install export KBUILD_OUTPUT=~/build/linuxptp/l3oh If you want to cross compile your own kernel and use its headers, then have your build system install the kernel headers, like: make INSTALL_HDR_PATH=/path/in/staging/area headers_install and then set KBUILD_OUTPUT to that path for the linuxptp recipe. Example from the the SLIM build system: https://github.com/richardcochran/slim/blob/6fa2d2edc552409f0f6d2bd811259fe933646892/pkg/linux/makefile#L73 Thanks, Richard |
From: Richard C. <ric...@gm...> - 2022-07-24 14:42:30
|
On Sun, Jul 24, 2022 at 07:34:10AM -0700, Richard Cochran wrote: > # bonus: native compile with older kernel headers: > cd ~/git/linux > git checkout v3.0 > make INSTALL_HDR_PATH=~/build/linuxptp/l3oh headers_install forgot a step: mkdir ~/build/linuxptp/l3oh/usr mv ~/build/linuxptp/l3oh/include ~/build/linuxptp/l3oh/usr/ > export KBUILD_OUTPUT=~/build/linuxptp/l3oh But you get the idea... Thanks, Richard |