Thread: [Linuxptp-devel] [PATCH 00/11] synce4l: add software for Synchronous Ethernet
PTP IEEE 1588 stack for Linux
Brought to you by:
rcochran
From: Arkadiusz K. <ark...@in...> - 2022-05-02 09:18:43
|
synce4l is a software implementation of Synchronous Ethernet (Sync-E) according to ITU-T Recommendation G.8264. The design goal is to provide logic to supported hardware by processing Ethernet Synchronization Messaging Channel (ESMC) and control Digital Phase Locked Loop (DPLL) clock on Network Card Interface (NIC). Application can operate in two mutually exclusive modes. If both modes are configured simultaneously, the external input takes precedence over internal one (internal is disabled implicitly). External input mode If synce4l is configured to run in "external input" mode, then DPLL needs to have external 1PPS source attached (GPS or another generator). In this scenario synce4l always broadcasts clock quality level (QL) defined in configuration file. Additionally, for this mode incoming Sync-E frames do not participate in best source selection algorithm for DPLL. Internal input mode In "internal input" mode incoming Sync-E frames are processed and best clock source is extracted from the link having the best quality level. synce4l configures such "best quality" port as a source to recover clock for DPLL. The recovered QL is broadcasted to all other interfaces then. An external clock cannot be used in this mode. Hardware synce4l is not bound to any specific NIC hardware. Theoretically any NIC could be used for enabling Sync-E device in External input mode - the NIC in this mode is in leader role (similar to Grand Master role in PTP), providing its Quality Level information in ESMC frames to its neighbors. It will not syntonize to any of its neighbor ports, since RX frames are not monitored. Only other Sync-E enabled neighbor ports could recover clock from that peer and syntonize to it. Practically this mode shall be used only if the NIC PHY ports are fed with stable and reliable frequency clock, Quality Level values that are sent to the peers are configured by the user. This is user responsibility to decide and set proper Quality-Level value for the device, corresponding to the quality of frequency clock generator of used hardware. When enabling Internal input mode, the NIC must be a device that actually, supports Synchronous Ethernet, which means it is able to syntonize frequency of all configured ports within the device to one chosen port - clock is recovered from its RX path, then is fed to other ports with Phase-Locked Loop (PLL) circuit powering clock of other ports. The NIC driver or dedicated software must provide a user with ability to: obtain current state of a DPLL used to syntonize the ports select one of the ports as a candidate for clock recovery. Both abilities are configurable in config file - the user must provide a shell commands - as explained in manual and descriptions inside of example config file. Arkadiusz Kubalewski (11): synce4l: add config knobs for SyncE synce4l: add synce in interface and util code synce4l: add esmc_socket interface synce4l: add synce_clock interface synce4l: add synce_dev interface synce4l: add synce_dev_ctrl interface synce4l: add synce_port interface synce4l: add synce_port_ctrl interface synce4l: add synce_msg interface synce4l: add synce_transport interface synce4l: add synce4l application README.org | 2 + config.c | 198 +++++-- config.h | 8 + configs/synce.cfg | 194 +++++++ esmc_socket.c | 111 ++++ esmc_socket.h | 53 ++ interface.c | 29 +- interface.h | 24 + makefile | 17 +- synce4l.8 | 239 ++++++++ synce4l.c | 144 +++++ synce4l_README.md | 194 +++++++ synce_clock.c | 296 ++++++++++ synce_clock.h | 52 ++ synce_dev.c | 634 ++++++++++++++++++++++ synce_dev.h | 76 +++ synce_dev_ctrl.c | 165 ++++++ synce_dev_ctrl.h | 76 +++ synce_msg.c | 271 ++++++++++ synce_msg.h | 186 +++++++ synce_msg_private.h | 99 ++++ synce_port.c | 504 +++++++++++++++++ synce_port.h | 197 +++++++ synce_port_ctrl.c | 1077 +++++++++++++++++++++++++++++++++++++ synce_port_ctrl.h | 177 ++++++ synce_transport.c | 116 ++++ synce_transport.h | 67 +++ synce_transport_private.h | 30 ++ util.c | 22 + util.h | 8 + 30 files changed, 5216 insertions(+), 50 deletions(-) create mode 100644 configs/synce.cfg create mode 100644 esmc_socket.c create mode 100644 esmc_socket.h create mode 100644 synce4l.8 create mode 100644 synce4l.c create mode 100644 synce4l_README.md create mode 100644 synce_clock.c create mode 100644 synce_clock.h create mode 100644 synce_dev.c create mode 100644 synce_dev.h create mode 100644 synce_dev_ctrl.c create mode 100644 synce_dev_ctrl.h create mode 100644 synce_msg.c create mode 100644 synce_msg.h create mode 100644 synce_msg_private.h create mode 100644 synce_port.c create mode 100644 synce_port.h create mode 100644 synce_port_ctrl.c create mode 100644 synce_port_ctrl.h create mode 100644 synce_transport.c create mode 100644 synce_transport.h create mode 100644 synce_transport_private.h -- 2.34.1 |
From: Arkadiusz K. <ark...@in...> - 2022-05-02 09:18:48
|
Add interface for sending and receiving ESMC frames. Ethernet Synchronization Messaging Channel (ESMC) requires to operate on multicast raw L2 socket. This patch adds features to open socket, send and receive ESMC frames. Co-developed-by: Anatolii Gerasymenko <ana...@in...> Signed-off-by: Anatolii Gerasymenko <ana...@in...> Co-developed-by: Michal Michalik <mic...@in...> Signed-off-by: Michal Michalik <mic...@in...> Signed-off-by: Arkadiusz Kubalewski <ark...@in...> --- esmc_socket.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++ esmc_socket.h | 53 ++++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 esmc_socket.c create mode 100644 esmc_socket.h diff --git a/esmc_socket.c b/esmc_socket.c new file mode 100644 index 000000000000..887b3c98f285 --- /dev/null +++ b/esmc_socket.c @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_socket.c + * @brief Implements the ESMC socket. + * @note Copyright (C) 2022 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include <errno.h> +#include <unistd.h> +#include <net/if.h> +#include <sys/socket.h> +#include <linux/if_ether.h> + +#include "sk.h" +#include "print.h" +#include "synce_msg.h" +#include "synce_msg_private.h" +#include "esmc_socket.h" + +int open_esmc_socket(const char *iface) +{ + unsigned char multicast_macaddr[MAC_LEN] = SYNCE_DEST_MACADDR; + struct sockaddr_ll addr; + struct packet_mreq mreq; + int fd, index, err; + + fd = socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, htons(SYNCE_ETHERTYPE)); + if (fd < 0) { + pr_err("socket failed: %m"); + return -1; + } + index = sk_interface_index(fd, iface); + if (index < 0) { + goto no_option; + } + + memset(&addr, 0, sizeof(addr)); + addr.sll_ifindex = index; + addr.sll_family = AF_PACKET; + addr.sll_protocol = htons(SYNCE_ETHERTYPE); + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr))) { + pr_err("bind failed: %m"); + goto no_option; + } + err = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, iface, + strnlen(iface, IFNAMSIZ)); + if (err) { + pr_err("setsockopt SO_BINDTODEVICE failed: %m"); + goto no_option; + } + + memset(&mreq, 0, sizeof(mreq)); + mreq.mr_ifindex = index; + mreq.mr_type = PACKET_MR_MULTICAST; + mreq.mr_alen = MAC_LEN; + if (sizeof(mreq.mr_address) * sizeof(mreq.mr_address[0]) < + sizeof(multicast_macaddr) * sizeof(multicast_macaddr[0])) { + pr_err("setting multicast address failed"); + goto no_option; + } + memset(mreq.mr_address, 0, sizeof(mreq.mr_address)); + // we need to copy only 6 bytes to mreq.mr_address + memcpy(mreq.mr_address, multicast_macaddr, sizeof(multicast_macaddr)); + + err = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + if (err) { + pr_warning("setsockopt PACKET_MR_MULTICAST failed: %m"); + } + + return fd; + +no_option: + close(fd); + + return -1; +} + +int send_raw_esmc_frame(int socket, void *frame, int frame_len, int ifindex) +{ + struct sockaddr_ll saddrll; + int ret, frame_size; + + memset(&saddrll, 0, sizeof(saddrll)); + saddrll.sll_ifindex = ifindex; + + frame_size = frame_len > ETH_ZLEN ? frame_len : ETH_ZLEN; + ret = sendto(socket, frame, frame_size, 0, + (struct sockaddr *)&saddrll, sizeof(saddrll)); + if (ret < 0) { + pr_err("%s failed: %m", __func__); + return errno; + } + + return 0; +} + +int recv_raw_esmc_frame(int socket, void *buff) +{ + return recv(socket, buff, ETH_DATA_LEN, 0); +} diff --git a/esmc_socket.h b/esmc_socket.h new file mode 100644 index 000000000000..a496f7bd7690 --- /dev/null +++ b/esmc_socket.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file esmc_socket.h + * @brief Implements the ESMC socket. + * @note Copyright (C) 2022 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#ifndef HAVE_ESMC_SOCKET_H +#define HAVE_ESMC_SOCKET_H + +/** + * Creates a raw ESMC socket and binds it to given interface. + * + * This high level API creates raw socket and binds it to specified + * interface and binds it to ESMC L2 multicast address. Thanks to + * those binds we are able to receive frames only on specified interface. + * + * @param iface A name of interface to bind to raw socket + * @return A raw socket file descriptor, negative if error occurred + */ +int open_esmc_socket(const char *iface); + +/** + * Sends a raw ESMC frame on given interface. + * + * @param socket A file descriptor of open raw ESMC socket + * @param frame A pointer to raw ESMC frame buffer + * @param frame_len A size of frame to be sent + * @return Zero on success, non-zero if the sending failed + */ +int send_raw_esmc_frame(int socket, void *frame, int frame_len, int ifindex); + +/** + * Receives a raw ESMC frame on given interface. + * + * @param socket A file descriptor of open raw ESMC socket + * @param buff A pointer to raw ESMC frame buffer + * @return Zero on success, non-zero if the reception failed + */ +int recv_raw_esmc_frame(int socket, void *buff); + +#endif -- 2.34.1 |
From: Arkadiusz K. <ark...@in...> - 2022-05-02 09:18:45
|
Allow config interface to parse SyncE related config files. Co-developed-by: Piotr Kwapulinski <pio...@in...> Signed-off-by: Piotr Kwapulinski <pio...@in...> Co-developed-by: Michal Michalik <mic...@in...> Signed-off-by: Michal Michalik <mic...@in...> Signed-off-by: Arkadiusz Kubalewski <ark...@in...> --- config.c | 198 ++++++++++++++++++++++++++++++++++++---------- config.h | 8 ++ configs/synce.cfg | 194 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 358 insertions(+), 42 deletions(-) create mode 100644 configs/synce.cfg diff --git a/config.c b/config.c index 4f3ceb8a20d7..f0ad329a7f18 100644 --- a/config.c +++ b/config.c @@ -41,6 +41,7 @@ enum config_section { GLOBAL_SECTION, UC_MTAB_SECTION, PORT_SECTION, + DEVICE_SECTION, UNKNOWN_SECTION, }; @@ -68,6 +69,7 @@ typedef union { #define CFG_ITEM_LOCKED (1 << 1) /* command line value, may not be changed */ #define CFG_ITEM_PORT (1 << 2) /* item may appear in port sections */ #define CFG_ITEM_DYNSTR (1 << 4) /* string value dynamically allocated */ +#define CFG_ITEM_DEVICE (1 << 8) /* item may appear in device sections */ struct config_item { char label[CONFIG_LABEL_SIZE]; @@ -79,12 +81,18 @@ struct config_item { any_t max; }; -#define N_CONFIG_ITEMS (sizeof(config_tab) / sizeof(config_tab[0])) +#define N_CONFIG_ITEMS_PTP (sizeof(config_tab_ptp) / sizeof(config_tab_ptp[0])) +#define N_CONFIG_ITEMS_SYNCE ((sizeof(config_tab_synce) / \ + sizeof(config_tab_synce[0]))) + +#define PORT_TO_FLAG(_port) (_port == PORT_SECTION ? CFG_ITEM_PORT : \ + _port == DEVICE_SECTION ? CFG_ITEM_DEVICE : \ + CFG_ITEM_STATIC) #define CONFIG_ITEM_DBL(_label, _port, _default, _min, _max) { \ .label = _label, \ .type = CFG_TYPE_DOUBLE, \ - .flags = _port ? CFG_ITEM_PORT : 0, \ + .flags = PORT_TO_FLAG(_port), \ .val.d = _default, \ .min.d = _min, \ .max.d = _max, \ @@ -92,14 +100,14 @@ struct config_item { #define CONFIG_ITEM_ENUM(_label, _port, _default, _table) { \ .label = _label, \ .type = CFG_TYPE_ENUM, \ - .flags = _port ? CFG_ITEM_PORT : 0, \ + .flags = PORT_TO_FLAG(_port), \ .tab = _table, \ .val.i = _default, \ } #define CONFIG_ITEM_INT(_label, _port, _default, _min, _max) { \ .label = _label, \ .type = CFG_TYPE_INT, \ - .flags = _port ? CFG_ITEM_PORT : 0, \ + .flags = PORT_TO_FLAG(_port), \ .val.i = _default, \ .min.i = _min, \ .max.i = _max, \ @@ -107,33 +115,39 @@ struct config_item { #define CONFIG_ITEM_STRING(_label, _port, _default) { \ .label = _label, \ .type = CFG_TYPE_STRING, \ - .flags = _port ? CFG_ITEM_PORT : 0, \ + .flags = PORT_TO_FLAG(_port), \ .val.s = _default, \ } #define GLOB_ITEM_DBL(label, _default, min, max) \ - CONFIG_ITEM_DBL(label, 0, _default, min, max) + CONFIG_ITEM_DBL(label, GLOBAL_SECTION, _default, min, max) #define GLOB_ITEM_ENU(label, _default, table) \ - CONFIG_ITEM_ENUM(label, 0, _default, table) + CONFIG_ITEM_ENUM(label, GLOBAL_SECTION, _default, table) #define GLOB_ITEM_INT(label, _default, min, max) \ - CONFIG_ITEM_INT(label, 0, _default, min, max) + CONFIG_ITEM_INT(label, GLOBAL_SECTION, _default, min, max) #define GLOB_ITEM_STR(label, _default) \ - CONFIG_ITEM_STRING(label, 0, _default) + CONFIG_ITEM_STRING(label, GLOBAL_SECTION, _default) #define PORT_ITEM_DBL(label, _default, min, max) \ - CONFIG_ITEM_DBL(label, 1, _default, min, max) + CONFIG_ITEM_DBL(label, PORT_SECTION, _default, min, max) #define PORT_ITEM_ENU(label, _default, table) \ - CONFIG_ITEM_ENUM(label, 1, _default, table) + CONFIG_ITEM_ENUM(label, PORT_SECTION, _default, table) #define PORT_ITEM_INT(label, _default, min, max) \ - CONFIG_ITEM_INT(label, 1, _default, min, max) + CONFIG_ITEM_INT(label, PORT_SECTION, _default, min, max) #define PORT_ITEM_STR(label, _default) \ - CONFIG_ITEM_STRING(label, 1, _default) + CONFIG_ITEM_STRING(label, PORT_SECTION, _default) + +#define DEV_ITEM_INT(label, _default, min, max) \ + CONFIG_ITEM_INT(label, DEVICE_SECTION, _default, min, max) + +#define DEV_ITEM_STR(label, _default) \ + CONFIG_ITEM_STRING(label, DEVICE_SECTION, _default) static struct config_enum clock_servo_enu[] = { { "pi", CLOCK_SERVO_PI }, @@ -220,7 +234,7 @@ static struct config_enum bmca_enu[] = { { NULL, 0 }, }; -struct config_item config_tab[] = { +struct config_item config_tab_ptp[] = { PORT_ITEM_INT("announceReceiptTimeout", 3, 2, UINT8_MAX), PORT_ITEM_ENU("asCapable", AS_CAPABLE_AUTO, as_capable_enu), GLOB_ITEM_INT("assume_two_step", 0, 0, 1), @@ -341,7 +355,37 @@ struct config_item config_tab[] = { GLOB_ITEM_INT("write_phase_mode", 0, 0, 1), }; +struct config_item config_tab_synce[] = { + GLOB_ITEM_INT("logging_level", LOG_INFO, PRINT_LEVEL_MIN, PRINT_LEVEL_MAX), + GLOB_ITEM_STR("message_tag", NULL), + GLOB_ITEM_INT("use_syslog", 1, 0, 1), + GLOB_ITEM_STR("userDescription", ""), + GLOB_ITEM_INT("verbose", 0, 0, 1), + DEV_ITEM_INT("internal_input", 1, 0, 1), + DEV_ITEM_INT("external_input", 0, 0, 1), + DEV_ITEM_INT("external_input_QL", 0, 0, 15), + DEV_ITEM_INT("external_input_ext_QL", 0, 0, 255), + DEV_ITEM_INT("extended_tlv", 0, 0, 1), + DEV_ITEM_INT("network_option", 1, 1, 2), + DEV_ITEM_INT("recover_time", 300, 10, 720), + DEV_ITEM_STR("dpll_get_state_cmd", NULL), + DEV_ITEM_STR("dpll_holdover_value", NULL), + DEV_ITEM_STR("dpll_locked_ho_value", NULL), + DEV_ITEM_STR("dpll_locked_value", NULL), + DEV_ITEM_STR("dpll_freerun_value", NULL), + DEV_ITEM_STR("dpll_invalid_value", NULL), + PORT_ITEM_STR("allowed_qls", NULL), + PORT_ITEM_STR("allowed_ext_qls", NULL), + PORT_ITEM_INT("sync", 0, 0, 1), + PORT_ITEM_STR("recover_clock_enable_cmd", NULL), + PORT_ITEM_STR("recover_clock_disable_cmd", NULL), + PORT_ITEM_INT("tx_heartbeat_msec", 1000, 100, 3000), + PORT_ITEM_INT("rx_heartbeat_msec", 50, 10, 500), +}; + static struct unicast_master_table *current_uc_mtab; +static struct interface *__config_create_interface(const char *name, struct config *cfg, + const char *type); static enum parser_result parse_fault_interval(struct config *cfg, const char *section, @@ -392,6 +436,8 @@ static struct config_item *config_item_alloc(struct config *cfg, } strncpy(ci->label, name, CONFIG_LABEL_SIZE - 1); ci->type = type; + ci->val.s = NULL; + ci->flags = 0; snprintf(buf, sizeof(buf), "%s.%s", section, ci->label); if (hash_insert(cfg->htab, buf, ci)) { @@ -406,8 +452,11 @@ static struct config_item *config_item_alloc(struct config *cfg, static void config_item_free(void *ptr) { struct config_item *ci = ptr; - if (ci->type == CFG_TYPE_STRING && ci->flags & CFG_ITEM_DYNSTR) + if (ci->type == CFG_TYPE_STRING && ci->flags & CFG_ITEM_DYNSTR + && ci->val.s != NULL) { free(ci->val.s); + ci->val.s = NULL; + } if (ci->flags & CFG_ITEM_STATIC) return; free(ci); @@ -512,10 +561,13 @@ static enum parser_result parse_section_line(char *s, enum config_section *secti current_uc_mtab = NULL; } else if (s[0] == '[') { char c; - *section = PORT_SECTION; - /* Replace square brackets with white space. */ + if (s[1] == '<') + *section = DEVICE_SECTION; + else + *section = PORT_SECTION; + /* Replace brackets with white space. */ while (0 != (c = *s)) { - if (c == '[' || c == ']') + if (c == '[' || c == ']' || c == '<' || c == '>') *s = ' '; s++; } @@ -573,7 +625,8 @@ static enum parser_result parse_item(struct config *cfg, } if (section) { - if (!(cgi->flags & CFG_ITEM_PORT)) { + if (!(cgi->flags & CFG_ITEM_PORT) && + !(cgi->flags & CFG_ITEM_DEVICE)) { return NOT_PARSED; } /* Create or update this port specific item. */ @@ -601,8 +654,9 @@ static enum parser_result parse_item(struct config *cfg, dst->val.d = df; break; case CFG_TYPE_STRING: - if (dst->flags & CFG_ITEM_DYNSTR) { + if (dst->flags & CFG_ITEM_DYNSTR && dst->val.s != NULL) { free(dst->val.s); + dst->val.s = NULL; } dst->val.s = strdup(value); if (!dst->val.s) { @@ -728,18 +782,26 @@ static void check_deprecated_options(const char **option) } } -static struct option *config_alloc_longopts(void) +static struct option *config_alloc_longopts(enum feature_type type) { - struct config_item *ci; + struct config_item *ci, *ci_tab; struct option *opts; - int i; + int i, n_items; - opts = calloc(1, (1 + N_CONFIG_ITEMS) * sizeof(*opts)); + if (type == PTP) { + ci_tab = &config_tab_ptp[0]; + n_items = N_CONFIG_ITEMS_PTP; + } else { + ci_tab = &config_tab_synce[0]; + n_items = N_CONFIG_ITEMS_SYNCE; + } + n_items = (type == PTP ? N_CONFIG_ITEMS_PTP : N_CONFIG_ITEMS_SYNCE); + opts = calloc(1, (1 + n_items) * sizeof(*opts)); if (!opts) { return NULL; } - for (i = 0; i < N_CONFIG_ITEMS; i++) { - ci = &config_tab[i]; + for (i = 0; i < n_items; i++) { + ci = &ci_tab[i]; opts[i].name = ci->label; opts[i].has_arg = required_argument; /* Avoid bug in detection of ambiguous options in glibc */ @@ -757,6 +819,8 @@ int config_read(const char *name, struct config *cfg) char buf[1024], *line, *c; const char *option, *value; struct interface *current_port = NULL; + struct interface *current_device = NULL; + bool is_synce = (cfg->type == SYNCE); int line_num; fp = 0 == strncmp(name, "-", 2) ? stdin : fopen(name, "r"); @@ -787,6 +851,7 @@ int config_read(const char *name, struct config *cfg) if (parse_section_line(line, ¤t_section) == PARSED_OK) { if (current_section == PORT_SECTION) { char port[17]; + if (1 != sscanf(line, " %16s", port)) { fprintf(stderr, "could not parse port name on line %d\n", line_num); @@ -795,6 +860,27 @@ int config_read(const char *name, struct config *cfg) current_port = config_create_interface(port, cfg); if (!current_port) goto parse_error; + if (is_synce) { + if (current_device) { + interface_se_set_parent_dev(current_port, + interface_name(current_device)); + } else { + goto parse_error; + } + } + } else if (current_section == DEVICE_SECTION) { + /* clear port on new device found in config */ + current_port = NULL; + char device[17]; + + if (sscanf(line, " %16s", device) != 1) { + fprintf(stderr, "could not parse device name on line %d\n", + line_num); + goto parse_error; + } + current_device = __config_create_interface(device, cfg, "device"); + if (!current_device) + goto parse_error; } continue; } @@ -814,14 +900,16 @@ int config_read(const char *name, struct config *cfg) if (parse_setting_line(line, &option, &value)) { fprintf(stderr, "could not parse line %d in %s section\n", line_num, current_section == GLOBAL_SECTION ? - "global" : interface_name(current_port)); + "global" : interface_name(current_port ? + current_port : current_device)); goto parse_error; } check_deprecated_options(&option); parser_res = parse_item(cfg, 0, current_section == GLOBAL_SECTION ? - NULL : interface_name(current_port), + NULL : interface_name(current_port ? + current_port : current_device), option, value); switch (parser_res) { case PARSED_OK: @@ -830,7 +918,8 @@ int config_read(const char *name, struct config *cfg) fprintf(stderr, "unknown option %s at line %d in %s section\n", option, line_num, current_section == GLOBAL_SECTION ? "global" : - interface_name(current_port)); + interface_name(current_port ? + current_port : current_device)); goto parse_error; case BAD_VALUE: fprintf(stderr, "%s is a bad value for option %s at line %d\n", @@ -856,7 +945,7 @@ parse_error: return -2; } -struct interface *config_create_interface(const char *name, struct config *cfg) +struct interface *__config_create_interface(const char *name, struct config *cfg, const char *type) { struct interface *iface; const char *ifname; @@ -870,7 +959,7 @@ struct interface *config_create_interface(const char *name, struct config *cfg) iface = interface_create(name); if (!iface) { - fprintf(stderr, "cannot allocate memory for a port\n"); + fprintf(stderr, "cannot allocate memory for a %s\n", type); return NULL; } STAILQ_INSERT_TAIL(&cfg->interfaces, iface, list); @@ -879,12 +968,17 @@ struct interface *config_create_interface(const char *name, struct config *cfg) return iface; } -struct config *config_create(void) +struct interface *config_create_interface(const char *name, struct config *cfg) +{ + return __config_create_interface(name, cfg, "port"); +} + +struct config *__config_create(enum feature_type type) { char buf[CONFIG_LABEL_SIZE + 8]; - struct config_item *ci; + struct config_item *ci, *ci_tab; struct config *cfg; - int i; + int i, end; cfg = calloc(1, sizeof(*cfg)); if (!cfg) { @@ -893,7 +987,7 @@ struct config *config_create(void) STAILQ_INIT(&cfg->interfaces); STAILQ_INIT(&cfg->unicast_master_tables); - cfg->opts = config_alloc_longopts(); + cfg->opts = config_alloc_longopts(type); if (!cfg->opts) { free(cfg); return NULL; @@ -906,9 +1000,18 @@ struct config *config_create(void) return NULL; } + cfg->type = type; + if (type == PTP) { + ci_tab = &config_tab_ptp[0]; + end = N_CONFIG_ITEMS_PTP; + } else { + ci_tab = &config_tab_synce[0]; + end = N_CONFIG_ITEMS_SYNCE; + } + /* Populate the hash table with global defaults. */ - for (i = 0; i < N_CONFIG_ITEMS; i++) { - ci = &config_tab[i]; + for (i = 0; i < end; i++) { + ci = &ci_tab[i]; ci->flags |= CFG_ITEM_STATIC; snprintf(buf, sizeof(buf), "global.%s", ci->label); if (hash_insert(cfg->htab, buf, ci)) { @@ -918,12 +1021,12 @@ struct config *config_create(void) } /* Perform a Built In Self Test.*/ - for (i = 0; i < N_CONFIG_ITEMS; i++) { - ci = &config_tab[i]; + for (i = 0; i < end; i++) { + ci = &ci_tab[i]; ci = config_global_item(cfg, ci->label); - if (ci != &config_tab[i]) { + if (ci != &ci_tab[i]) { fprintf(stderr, "config BIST failed at %s\n", - config_tab[i].label); + ci_tab[i].label); goto fail; } } @@ -935,6 +1038,16 @@ fail: return NULL; } +struct config *config_create(void) +{ + return __config_create(PTP); +} + +struct config *config_create_synce(void) +{ + return __config_create(SYNCE); +} + void config_destroy(struct config *cfg) { struct unicast_master_address *address; @@ -1137,8 +1250,9 @@ int config_set_string(struct config *cfg, const char *option, return -1; } ci->flags |= CFG_ITEM_LOCKED; - if (ci->flags & CFG_ITEM_DYNSTR) { + if (ci->flags & CFG_ITEM_DYNSTR && ci->val.s != NULL) { free(ci->val.s); + ci->val.s = NULL; } ci->val.s = strdup(val); if (!ci->val.s) { diff --git a/config.h b/config.h index 14d2f64415dc..a32e0079cf21 100644 --- a/config.h +++ b/config.h @@ -32,10 +32,16 @@ #include "servo.h" #include "sk.h" +enum feature_type { + PTP = 0, + SYNCE +}; + struct config { /* configured interfaces */ STAILQ_HEAD(interfaces_head, interface) interfaces; int n_interfaces; + enum feature_type type; /* for parsing command line options */ struct option *opts; @@ -55,6 +61,8 @@ void config_destroy(struct config *cfg); struct config *config_create(void); +struct config *config_create_synce(void); + double config_get_double(struct config *cfg, const char *section, const char *option); diff --git a/configs/synce.cfg b/configs/synce.cfg new file mode 100644 index 000000000000..eadb318bad6f --- /dev/null +++ b/configs/synce.cfg @@ -0,0 +1,194 @@ +# Global section is for debuging mostly +[global] +# +# Runtime options +# +logging_level 7 +use_syslog 0 +verbose 1 +message_tag [synce4l] + + +# +# Device section +# Per-device configuration +# +# User defined name of a one logical device configured for SyncE in the system. +# All the ports configured after this section will be a part of this device +# (until next device section). +[<synce1>] + +# +# If internal inputs are allowed +# 0 if internal input sources shall not be used +# 1 if internal input sources shall be used +# default: 1 +# +# Internal inputs mean the inputs recovered from the PHY's. +# The ports configured (in the port-sections [<dev name>], under the device +# section) will be monitored for the QL (Quality Level). +# QL is sent by the peer connected to the port and represents the Holdover +# performance of the peer. +# The best QL (from all the ports where "sync = 1") is selected and frequency +# recovered on that port shall be used to feed its frequency to all the other +# ports. +# +internal_input 1 + +# +# If external inputs are allowed +# 0 if external input sources shall not be used +# 1 if external input sources shall be used +# default: 0 +# +# External inputs are either 1PPS from buil-in GPS module or 1PPS from the +# on-board SMA connectors +# +# Device must be pre-confiured to use this setting. +# Before running the application, one of the external inputs shall be selected. +# This is done through the interface supplied by the NIC vendor. +# +# In this mode synce4l application is only responsible for sending +# the QL to the peers on all configured ports (where "sync = 1") +# The QL value that is sent equals configured "external_input_QL" +# (and "external_input_ext_QL" in case of "extended_tlv=1") +# +external_input 0 + +# +# These values are sent to the peers on configured ports ONLY when 'external +# input' mode is enabled. +# Valid values are defined in Table 11-7 and Table 11-8 of recommendation +# ITU-T G.8264. +# They shall be configured appropriately so they are understood by the peer. +# +# external_input_QL corresponds to the SSM code column. +# +# external_input_ext_QL corresponds to the Enhanced SSM code column +# (is used only if "extended_tlv = 1") +# +external_input_QL 2 +external_input_ext_QL 255 + +# +# If extended TLV shall be supported on the device. +# 0 if no extended tlv shall be supported +# 1 if extended tlv shall be supported +# default: 0 +# +# In case of 0: +# - the port will always TX the non-extended TLV, for RX only +# non-extended TLV will be processed for reference signal selection +# In case of 1: +# - If port is configured with external_input=1, the TX will always use +# extended TLV (no RX is required in this case) +# - If port is configured with external_input=0 and internal_input=1, the +# TX version of TLV will be propagated from the port that was chosen as +# candidate for frequency synchronization +# +extended_tlv 1 + +# +# Which network option shall be supported +# +# 1 or 2 as defined in T-REC-G.8264 +# default: 1 +# +# This is rather per-network option, all device in SyncE network +# shall have this configured for the same value +# +network_option 1 + +# +# Seconds indicating minimum time to recover from the QL-failed state on the +# port. +# Range: 10-720 +# Default: 300 +# +# If valid QL was not received from one of the source ports within 5 seconds +# the port is no longer a valid source (marked as QL-failed) +# +# Valid QL must be received for more then "recover_time" seconds on that port +# to use its PHY recovered signal again as a valid source +# +recover_time 10 + +# +# Shell command to be executed in order to obtain current DPLL status of a +# device. +# +dpll_get_state_cmd cat /sys/class/net/enp1s0f0/device/cgu_state + +# +# DPLL state values, must equal to values produced by stdout of +# "dpll_get_state_cmd" command +# +dpll_holdover_value 4 +dpll_locked_ho_value 3 +dpll_locked_value 2 +dpll_freerun_value 1 +dpll_invalid_value 0 + +# +# Port section(s) +# +# It starts per-port configuration. +# Each port (of the device) that is used for SyncE, shall have its own section +# with at least sync = 1 (which defines port as synchronous mode) +# +[enp1s0f0] + +# +# msec resolution of TX the QL from this port to the peer +# [100-3000], default:1000 (1000 = 1 second is expected by the standard) +# +# As the standard expects 1 sec, it is not recommended to use different +# than a 1000. +# +tx_heartbeat_msec 2000 + +# +# recovered PHY signal can be lost at anytime, this is msec resolution of +# reading the socket, acting on signal lost shall be done just after +# [10-500], default:50 +# +rx_heartbeat_msec 500 + +# +# Sync mode as defined by standard +# 0 (non-sync mode) - port not used for best signal source selection or TX its QL +# 1 (sync mode) - port used for best signal source selection or TX its QL +# +sync 1 + +# +# Shell commands for enabling/disabling this port as main recovered clock on a +# device. +# +recover_clock_enable_cmd echo 1 0 > /sys/class/net/enp1s0f0/device/phy/synce +recover_clock_disable_cmd echo 0 0 > /sys/class/net/enp1s0f0/device/phy/synce + +# +# next configured interface for the device +# +[enp1s0f1] +sync 1 +recover_clock_enable_cmd echo 1 0 > /sys/class/net/enp1s0f1/device/phy/synce +recover_clock_disable_cmd echo 0 0 > /sys/class/net/enp1s0f1/device/phy/synce + + + +############################################################ +# +# next SyncE device section +# +#[<synce2>] +#internal_input 0 +#external_input 1 + +# +# new port belonging to the "new" device +# +#[enp7s0f0] +#sync 1 + -- 2.34.1 |
From: Arkadiusz K. <ark...@in...> - 2022-05-02 09:18:50
|
synce_clock interface allows creation, polling and destruction of SyncE clock, designed to step and drive SyncE logic on all configured SyncE devices. SyncE clock is created with a SyncE-type configuration, where SyncE-type configuration is prepared by parsing SyncE config file. Poll of a SyncE clock, will step each configured SyncE device. Destruction of SyncE clock releases all resources obtained by SyncE clock and devices. Co-developed-by: Piotr Kwapulinski <pio...@in...> Signed-off-by: Piotr Kwapulinski <pio...@in...> Co-developed-by: Michal Michalik <mic...@in...> Signed-off-by: Michal Michalik <mic...@in...> Signed-off-by: Arkadiusz Kubalewski <ark...@in...> --- synce_clock.c | 296 ++++++++++++++++++++++++++++++++++++++++++++++++++ synce_clock.h | 52 +++++++++ 2 files changed, 348 insertions(+) create mode 100644 synce_clock.c create mode 100644 synce_clock.h diff --git a/synce_clock.c b/synce_clock.c new file mode 100644 index 000000000000..8007fa390bbd --- /dev/null +++ b/synce_clock.c @@ -0,0 +1,296 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_clock.c + * @brief Implements a SYNCE clock interface. + * @note Copyright (C) 2022 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <sys/queue.h> + +#include "synce_clock.h" +#include "interface.h" +#include "synce_dev.h" +#include "print.h" +#include "config.h" +#include "missing.h" + +#define SYNCE_CLOCK_DELAY_USEC 20000 +#define SYNCE_CLOCK_INIT_DELAY_USEC 200000 +#define SYNCE_CLOCK_INIT_N_TRIES 10 + +struct interface { + STAILQ_ENTRY(interface) list; +}; + +struct synce_dev { + LIST_ENTRY(synce_dev) list; +}; + +enum synce_clock_state { + SYNCE_CLK_UNKNOWN = 0, + SYNCE_CLK_INITED, + SYNCE_CLK_DEV_RDY, + SYNCE_CLK_DEV_INITED, + SYNCE_CLK_RUNNING, + SYNCE_CLK_FAILED, +}; + +struct synce_clock { + int num_devices; + int state; + LIST_HEAD(devices_head, synce_dev) devices; +}; + +static struct synce_clock *create_clock(void) +{ + static struct synce_clock clk; + + if (clk.state != SYNCE_CLK_UNKNOWN) { + synce_clock_destroy(&clk); + pr_info("old synce_clock destroyed"); + } + clk.state = SYNCE_CLK_INITED; + + return &clk; +} + +static void add_device(struct synce_clock *clk, struct synce_dev *dev) +{ + struct synce_dev *dev_iter, *last_dev = NULL; + + LIST_FOREACH(dev_iter, &clk->devices, list) { + last_dev = dev_iter; + } + + if (last_dev) { + LIST_INSERT_AFTER(last_dev, dev, list); + } else { + LIST_INSERT_HEAD(&clk->devices, dev, list); + } +} + +static int create_synce_devices(struct synce_clock *clk, struct config *cfg) +{ + struct interface *iface; + struct synce_dev *dev; + const char *dev_name; + int count = 0; + + if (clk->state != SYNCE_CLK_INITED) { + goto err; + } + + LIST_INIT(&clk->devices); + STAILQ_FOREACH(iface, &cfg->interfaces, list) { + /* only parent devices shall be addresed */ + if (!interface_se_has_parent_dev(iface)) { + dev_name = interface_name(iface); + dev = synce_dev_create(dev_name); + if (!dev) { + pr_err("failed to create device %s", dev_name); + continue; + } + + pr_debug("device init %s addr %p", dev_name, dev); + add_device(clk, dev); + count++; + } + } + + if (!count) { + pr_err("no devices created"); + goto err; + } + + pr_info("created num_devices: %d", count); + clk->num_devices = count; + clk->state = SYNCE_CLK_DEV_RDY; + + return 0; +err: + clk->state = SYNCE_CLK_FAILED; + return -EINVAL; +} + +static int init_synce_devices(struct synce_clock *clk, struct config *cfg) +{ + struct synce_dev *dev, *tmp; + int count = 0; + + if (clk->state != SYNCE_CLK_DEV_RDY) { + goto err; + } + + LIST_FOREACH_SAFE(dev, &clk->devices, list, tmp) { + /* Each parent device will init its ports */ + if (synce_dev_init(dev, cfg)) { + pr_err("failed to init device %s", + synce_dev_name(dev)); + synce_dev_destroy(dev); + LIST_REMOVE(dev, list); + free(dev); + continue; + } else { + pr_debug("device inited %s", synce_dev_name(dev)); + count++; + } + } + + if (count == 0) { + pr_err("no Sync-E devices initialized"); + goto err; + } else if (count != clk->num_devices) { + pr_warning("initialized only %d from %d Sync-E devices", + count, clk->num_devices); + clk->num_devices = count; + } + clk->state = SYNCE_CLK_DEV_INITED; + + return 0; +err: + clk->state = SYNCE_CLK_FAILED; + return -EINVAL; +} + +static void remove_failed_devices(struct synce_clock *clk) +{ + struct synce_dev *dev, *tmp; + int failed_cnt = 0; + + LIST_FOREACH_SAFE(dev, &clk->devices, list, tmp) { + if (!synce_dev_is_running(dev)) { + synce_dev_destroy(dev); + LIST_REMOVE(dev, list); + free(dev); + failed_cnt++; + } + } + clk->num_devices -= failed_cnt; + pr_warning("Found dead devices: %d", failed_cnt); + pr_info("devices still running: %d", clk->num_devices); +} + +static int verify_clock_state(struct synce_clock *clk) +{ + int i, running, timeout = SYNCE_CLOCK_INIT_N_TRIES; + struct synce_dev *dev; + + if (clk->state < SYNCE_CLK_DEV_INITED) { + return -ENODEV; + } + + /* let threads get running */ + for (i = 0; i < timeout; ++i) { + running = 0; + LIST_FOREACH(dev, &clk->devices, list) { + if (synce_dev_is_running(dev)) + running++; + } + + if (running == clk->num_devices) { + clk->state = SYNCE_CLK_RUNNING; + break; + } + usleep(SYNCE_CLOCK_INIT_DELAY_USEC); + } + + pr_debug("running num_devices %d configured %d", + running, clk->num_devices); + + /* If at least one dev is running we leave clock running + * while removing failed devices. + * Previous traces shall indicate which ones have failed. + */ + if (!running) { + pr_err("no device is running"); + return -ENODEV; + } else if (running != clk->num_devices) { + remove_failed_devices(clk); + } + + return 0; +} + +struct synce_clock *synce_clock_create(struct config *cfg) +{ + struct synce_clock *clk; + int err; + + if (!cfg) { + pr_err("%s cfg is NULL", __func__); + return NULL; + } + + clk = create_clock(); + if (!clk) { + return NULL; + } + err = create_synce_devices(clk, cfg); + if (err) { + goto destroy; + } + err = init_synce_devices(clk, cfg); + if (err) { + goto destroy; + } + err = verify_clock_state(clk); + if (err) { + goto destroy; + } + + return clk; + +destroy: + synce_clock_destroy(clk); + + return NULL; +} + +void synce_clock_destroy(struct synce_clock *clk) +{ + struct synce_dev *dev, *tmp; + + pr_debug("%s", __func__); + + LIST_FOREACH_SAFE(dev, &clk->devices, list, tmp) { + synce_dev_destroy(dev); + LIST_REMOVE(dev, list); + free(dev); + } + clk->num_devices = 0; + clk->state = SYNCE_CLK_UNKNOWN; + + return; +} + +int synce_clock_poll(struct synce_clock *clk) +{ + struct synce_dev *dev; + int ret = -ENODEV; + + if (clk->state == SYNCE_CLK_RUNNING) { + LIST_FOREACH(dev, &clk->devices, list) { + ret = synce_dev_step(dev); + if (ret) { + pr_err("dev_step fail"); + } + } + } + usleep(SYNCE_CLOCK_DELAY_USEC); + + return ret; +} diff --git a/synce_clock.h b/synce_clock.h new file mode 100644 index 000000000000..e5559a4cc9cc --- /dev/null +++ b/synce_clock.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_clock.h + * @brief Implements a Sync-E clock behavior. + * @note Copyright (C) 2022 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#ifndef HAVE_SYNCE_CLOCK_H +#define HAVE_SYNCE_CLOCK_H + +#include "config.h" + +/* Opaque type */ +struct synce_clock; + +/** + * Create a Sync-E clock instance. There can only be one synce_clock in a + * system so subsequent calls will destroy the previous clock instance. + * + * @param cfg Pointer to the SYNCE-type configuration database + * @return Pointer to the single global Sync-E clock instance + */ +struct synce_clock *synce_clock_create(struct config *cfg); + +/** + * Destroy resources associated with the synce clock. + * + * @param clk Pointer to synce_clock instance + */ +void synce_clock_destroy(struct synce_clock *clk); + +/** + * Poll for synce events and dispatch them. + * + * @param clk A pointer to a synce_clock instance obtained with + * synce_clock_create(). + * @return Zero on success, non-zero otherwise + */ +int synce_clock_poll(struct synce_clock *clk); + +#endif -- 2.34.1 |
From: Arkadiusz K. <ark...@in...> - 2022-05-02 09:18:50
|
Used by synce_dev to acquire current state of its DPLL. Co-developed-by: Anatolii Gerasymenko <ana...@in...> Signed-off-by: Anatolii Gerasymenko <ana...@in...> Co-developed-by: Piotr Kwapulinski <pio...@in...> Signed-off-by: Piotr Kwapulinski <pio...@in...> Co-developed-by: Michal Michalik <mic...@in...> Signed-off-by: Michal Michalik <mic...@in...> Signed-off-by: Arkadiusz Kubalewski <ark...@in...> --- synce_dev_ctrl.c | 165 +++++++++++++++++++++++++++++++++++++++++++++++ synce_dev_ctrl.h | 76 ++++++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 synce_dev_ctrl.c create mode 100644 synce_dev_ctrl.h diff --git a/synce_dev_ctrl.c b/synce_dev_ctrl.c new file mode 100644 index 000000000000..a2a7d6a4cbd8 --- /dev/null +++ b/synce_dev_ctrl.c @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_dev_ctrl.c + * @brief Interface for acquiring Sync-E capable device DPLL state changes + * @note Copyright (C) 2022 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include <errno.h> +#include <stdlib.h> +#include "print.h" +#include "synce_dev_ctrl.h" + +#define DPLL_STATE_STR_RETURN_SIZE 0xff +struct synce_dev_ctrl { + const char *dpll_get_state_cmd; + struct dpll_state_str dss; +}; + +static int dpll_str_state_to_enum(struct synce_dev_ctrl *dc, char *str_state, + enum dpll_state *enum_state) +{ + if (!strncmp(str_state, dc->dss.holdover, DPLL_STATE_STR_RETURN_SIZE)) { + *enum_state = DPLL_HOLDOVER; + } else if (!strncmp(str_state, dc->dss.locked_ho, + DPLL_STATE_STR_RETURN_SIZE)) { + *enum_state = DPLL_LOCKED_HO_ACQ; + } else if (!strncmp(str_state, dc->dss.locked, + DPLL_STATE_STR_RETURN_SIZE)) { + *enum_state = DPLL_LOCKED; + } else if (!strncmp(str_state, dc->dss.freerun, + DPLL_STATE_STR_RETURN_SIZE)) { + *enum_state = DPLL_FREERUN; + } else if (!strncmp(str_state, dc->dss.invalid, + DPLL_STATE_STR_RETURN_SIZE)) { + *enum_state = DPLL_INVALID; + } else { + *enum_state = DPLL_UNKNOWN; + } + + if (*enum_state == DPLL_UNKNOWN) { + pr_err("dpll state missing for str_state: '%s'", str_state); + return -EINVAL; + } + + return 0; +} + +static int get_dpll_state_from_cmd(struct synce_dev_ctrl *dc, + enum dpll_state *state) +{ + char buf[DPLL_STATE_STR_RETURN_SIZE] = {0}, *c; + FILE *fp; + int ret; + + fp = popen(dc->dpll_get_state_cmd, "r"); + if (!fp) { + pr_err("failed open process: '%s'", dc->dpll_get_state_cmd); + return DPLL_UNKNOWN; + } + + if (!fgets(buf, sizeof(buf), fp)) { + pr_err("failed read process output: '%s'", dc->dpll_get_state_cmd); + goto out; + } + + c = buf; + while (*c != '\0') { + if (*(++c) == '\n') { + *c = '\0'; + } + } + +out: + ret = pclose(fp); + if (ret) { + pr_err("process '%s' exit status: %d", + dc->dpll_get_state_cmd, ret); + } + + return ret ? DPLL_UNKNOWN : dpll_str_state_to_enum(dc, buf, state); +} + +int synce_dev_ctrl_get_state(struct synce_dev_ctrl *dc, + enum dpll_state *state) +{ + int ret = -EINVAL; + + if (!dc) { + pr_err("%s: dc is NULL", __func__); + return ret; + } + + if (!state) { + pr_err("%s: state is NULL", __func__); + return ret; + } + + if (!dc->dpll_get_state_cmd) { + pr_err("%s: dc->dpll_get_state_cmd is NULL", __func__); + return ret; + } + + ret = get_dpll_state_from_cmd(dc, state); + if (ret || *state < DPLL_INVALID || *state > DPLL_HOLDOVER) { + return ret; + } + + return 0; +} + +struct synce_dev_ctrl *synce_dev_ctrl_create(void) +{ + struct synce_dev_ctrl *dc = malloc(sizeof(struct synce_dev_ctrl)); + return dc; +} + +int synce_dev_ctrl_init(struct synce_dev_ctrl *dc, const char *dev_name, + const char *dpll_get_state_cmd, + struct dpll_state_str *dss) +{ + if (!dc || !dev_name) { + return -ENODEV; + } + + if (!dpll_get_state_cmd) { + pr_err("failure: dpll_get_state_cmd is NULL on %s", dev_name); + return -ENXIO; + } + if (!dss->holdover) { + pr_err("failure: dss.holdover is NULL on %s", dev_name); + return -ENXIO; + } + if (!dss->locked_ho) { + pr_err("failure: dss.locked_ho is NULL on %s", dev_name); + return -ENXIO; + } + if (!dss->locked) { + pr_err("failure: dss.locked is NULL on %s", dev_name); + return -ENXIO; + } + if (!dss->freerun) { + pr_err("failure: dss.freerun is NULL on %s", dev_name); + return -ENXIO; + } + if (!dss->invalid) { + pr_err("failure: dss.invalid is NULL on %s", dev_name); + return -ENXIO; + } + + dc->dpll_get_state_cmd = dpll_get_state_cmd; + dc->dss = *dss; + + return 0; +} diff --git a/synce_dev_ctrl.h b/synce_dev_ctrl.h new file mode 100644 index 000000000000..27f947d43879 --- /dev/null +++ b/synce_dev_ctrl.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_dev_ctrl.h + * @brief Interface for acquiring Sync-E capable device DPLL state changes + * @note Copyright (C) 2022 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#ifndef HAVE_SYNCE_DEV_CTRL_H +#define HAVE_SYNCE_DEV_CTRL_H + +/* possible DPLL states */ +enum dpll_state { + DPLL_UNKNOWN = -1, + DPLL_INVALID, + DPLL_FREERUN, + DPLL_LOCKED, + DPLL_LOCKED_HO_ACQ, + DPLL_HOLDOVER, +}; + +/* possibe DPLL state strings */ +struct dpll_state_str { + const char *holdover; + const char *locked_ho; + const char *locked; + const char *freerun; + const char *invalid; +}; + +/* Opaque type */ +struct synce_dev_ctrl; + +/** + * Acquire current state of DPLL of Sync-E capable device. + * + * @param dc Instance of DPLL device controller + * @param state State acquired from the device + * @return Zero on success, non-zero if failure + */ +int synce_dev_ctrl_get_state(struct synce_dev_ctrl *dc, + enum dpll_state *state); + +/** + * Initialize DPLL device controller instance. + * + * @param dc Instance of DPLL device controller to be + * initialized + * @param dev_name Name of device + * @param dpll_get_state_cmd A command to obtain current dpll state + * @param dss Pointer to a struct holding valid dpll state + strings + * @return Zero on success, non-zero if failure + */ +int synce_dev_ctrl_init(struct synce_dev_ctrl *dc, const char *dev_name, + const char *dpll_get_state_cmd, + struct dpll_state_str *dss); + +/** + * Allocate memory for a single DPLL device controller instance. + * + * @return Pointer to allocated instance or NULL if allocation failed + */ +struct synce_dev_ctrl *synce_dev_ctrl_create(void); + +#endif /* HAVE_SYNCE_DEV_CTRL_H */ -- 2.34.1 |
From: Arkadiusz K. <ark...@in...> - 2022-05-02 09:18:50
|
Extend interface struct, allow to keep information about SyncE parent device. Add helper function (util.c) to acquire Do Not Use - Quality Level value. Co-developed-by: Anatolii Gerasymenko <ana...@in...> Signed-off-by: Anatolii Gerasymenko <ana...@in...> Co-developed-by: Michal Michalik <mic...@in...> Signed-off-by: Michal Michalik <mic...@in...> Signed-off-by: Arkadiusz Kubalewski <ark...@in...> --- interface.c | 29 +++++++++++++++++++++++++++-- interface.h | 24 ++++++++++++++++++++++++ util.c | 22 ++++++++++++++++++++++ util.h | 8 ++++++++ 4 files changed, 81 insertions(+), 2 deletions(-) diff --git a/interface.c b/interface.c index 445a27084da1..6fc11bfd41a8 100644 --- a/interface.c +++ b/interface.c @@ -7,11 +7,19 @@ #include <stdlib.h> #include "interface.h" +#define SYNCE_PARENT_EXIST (1 << 0) + struct interface { STAILQ_ENTRY(interface) list; char name[MAX_IFNAME_SIZE + 1]; - char ts_label[MAX_IFNAME_SIZE + 1]; - struct sk_ts_info ts_info; + union { + char ts_label[MAX_IFNAME_SIZE + 1]; + char synce_parent_label[MAX_IFNAME_SIZE + 1]; + }; + union { + struct sk_ts_info ts_info; + int synce_flags; + }; int vclock; }; @@ -88,3 +96,20 @@ int interface_get_vclock(struct interface *iface) { return iface->vclock; } + +void interface_se_set_parent_dev(struct interface *iface, const char *dev_name) +{ + strncpy(iface->synce_parent_label, dev_name, MAX_IFNAME_SIZE); + iface->synce_flags |= SYNCE_PARENT_EXIST; +} + +const char *interface_se_get_parent_dev_label(struct interface *iface) +{ + return iface->synce_parent_label; +} + +bool interface_se_has_parent_dev(struct interface *iface) +{ + return !!(iface->synce_flags & SYNCE_PARENT_EXIST); +} + diff --git a/interface.h b/interface.h index 752f4f140b47..47cc25fe2996 100644 --- a/interface.h +++ b/interface.h @@ -105,4 +105,28 @@ void interface_set_vclock(struct interface *iface, int vclock); */ int interface_get_vclock(struct interface *iface); +/** + * Set the synce parent device for a given interface. + * @param iface The interface of interest. + * @param dev_name The desired label for the interface. + */ +void interface_se_set_parent_dev(struct interface *iface, const char *dev_name); + +/** + * Obtain the name of the parent synce device to which an interface belongs. + * The parent device must provde an interface to control device-level synce + * configuration. + * @param iface The interface of interest. + * @return Name of the parent synce device + */ +const char *interface_se_get_parent_dev_label(struct interface *iface); + +/** + * Tests whether an interface has a synce parent device. + * Which means it is regular port configured for synce. + * @param iface The interface of interest. + * @return True if the interface is a synce parent device + */ +bool interface_se_has_parent_dev(struct interface *iface); + #endif diff --git a/util.c b/util.c index a59b559ddf1c..1c04870fa763 100644 --- a/util.c +++ b/util.c @@ -30,6 +30,7 @@ #include "print.h" #include "sk.h" #include "util.h" +#include "synce_msg.h" #define NS_PER_SEC 1000000000LL #define NS_PER_HOUR (3600 * NS_PER_SEC) @@ -779,3 +780,24 @@ int rate_limited(int interval, time_t *last) return 0; } + +uint8_t synce_get_dnu_value(int network_option, int extended_tlv) +{ + uint8_t ret = O1N_QL_DNU_ENHSSM; + + if (extended_tlv) { + if (network_option == SYNCE_NETWORK_OPT_1) { + ret = O1N_QL_DNU_ENHSSM; + } else if (network_option == SYNCE_NETWORK_OPT_2) { + ret = O2N_QL_DUS_ENHSSM; + } + } else { + if (network_option == SYNCE_NETWORK_OPT_1) { + ret = O1N_QL_DNU_SSM; + } else if (network_option == SYNCE_NETWORK_OPT_2) { + ret = O2N_QL_DUS_SSM; + } + } + + return ret; +} diff --git a/util.h b/util.h index 558a6757131e..c52dc2eb4468 100644 --- a/util.h +++ b/util.h @@ -458,4 +458,12 @@ void parray_extend(void ***a, ...); */ int rate_limited(int interval, time_t *last); +/** + * Acquire proper SynceE Do Not Use signal value basing on given arguments + * + * @param network_option Type of the network + * @param extended_tlv If QL for extended tlv + * @return Do Not Use signal value + */ +uint8_t synce_get_dnu_value(int netwotk_option, int extended_tlv); #endif -- 2.34.1 |
From: Arkadiusz K. <ark...@in...> - 2022-05-02 09:18:50
|
synce_dev interface allows creation, initialization, stepping and destroying of a single SyncE device. Newly created device must be given a device name (same as in config file). After creation the SyncE device is initialized with config struct, where device-level configuration for this device must exists. All ports belonging to this device are also initialized (at least one port configured is required). Once initialized SyncE device is ready for stepping. Each step will: - verify if device is in running state - acquire current state of the DPLL - performs actual step of a device, either as internal or external powered frequency provider for all the ports confgiured Destroying the SyncE device releases all its resources. Co-developed-by: Anatolii Gerasymenko <ana...@in...> Signed-off-by: Anatolii Gerasymenko <ana...@in...> Co-developed-by: Piotr Kwapulinski <pio...@in...> Signed-off-by: Piotr Kwapulinski <pio...@in...> Co-developed-by: Michal Michalik <mic...@in...> Signed-off-by: Michal Michalik <mic...@in...> Signed-off-by: Arkadiusz Kubalewski <ark...@in...> --- synce_dev.c | 634 ++++++++++++++++++++++++++++++++++++++++++++++++++++ synce_dev.h | 76 +++++++ 2 files changed, 710 insertions(+) create mode 100644 synce_dev.c create mode 100644 synce_dev.h diff --git a/synce_dev.c b/synce_dev.c new file mode 100644 index 000000000000..6f0b85334806 --- /dev/null +++ b/synce_dev.c @@ -0,0 +1,634 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_dev.c + * @brief Interface for handling Sync-E capable devices and its ports + * @note Copyright (C) 2022 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include <stdlib.h> +#include <sys/queue.h> +#include <net/if.h> +#include <errno.h> +#include "synce_dev.h" +#include "print.h" +#include "config.h" +#include "util.h" +#include "synce_port.h" +#include "missing.h" +#include "synce_dev_ctrl.h" +#include "synce_msg.h" + +struct interface { + STAILQ_ENTRY(interface) list; +}; + +struct synce_dev_ops { + int (*update_ql)(struct synce_dev *dev); + int (*step)(struct synce_dev *dev); +}; + +enum synce_dev_state { + DEVICE_UNKNOWN, + DEVICE_CREATED, + DEVICE_INITED, + DEVICE_RUNNING, + DEVICE_FAILED, +}; + +struct synce_dev { + LIST_ENTRY(synce_dev) list; + enum synce_dev_state state; + char name[IF_NAMESIZE]; + LIST_HEAD(synce_ports_head, synce_port) ports; + struct synce_port *best_source; + int num_ports; + int internal_input; + int external_input; + int network_option; + uint8_t ql; + uint8_t ext_ql; + int extended_tlv; + int recover_time; + enum dpll_state d_state; + enum dpll_state last_d_state; + struct synce_dev_ctrl *dc; + struct synce_dev_ops ops; +}; + +static int add_port(struct synce_dev *dev, struct synce_port *port) +{ + struct synce_port *port_iter, *last_port = NULL; + + LIST_FOREACH(port_iter, &dev->ports, list) { + last_port = port_iter; + } + + if (last_port) { + LIST_INSERT_AFTER(last_port, port, list); + } else { + LIST_INSERT_HEAD(&dev->ports, port, list); + } + return 0; +} + +static int rx_enabled(struct synce_dev *dev) +{ + return (dev->external_input == 0 && + dev->internal_input != 0); +} + +static void destroy_ports(struct synce_dev *dev) +{ + struct synce_port *port, *tmp; + + LIST_FOREACH_SAFE(port, &dev->ports, list, tmp) { + synce_port_destroy(port); + LIST_REMOVE(port, list); + free(port); + } + dev->num_ports = 0; +} + +static void destroy_dev_ctrl(struct synce_dev *dev) +{ + free(dev->dc); + dev->dc = NULL; +} + +static int init_ports(int *count, struct synce_dev *dev, struct config *cfg) +{ + struct interface *iface; + struct synce_port *port; + const char *port_name; + + *count = 0; + + STAILQ_FOREACH(iface, &cfg->interfaces, list) { + /* given device takes care only of its child ports */ + if (interface_se_has_parent_dev(iface) && + (strncmp(interface_se_get_parent_dev_label(iface), + dev->name, sizeof(dev->name)) == 0)) { + port_name = interface_name(iface); + + /* If no sync mode, no need to maintain the port */ + if (!synce_port_in_sync_mode(cfg, port_name)) { + continue; + } + + port = synce_port_create(port_name); + if (!port) { + pr_err("failed to create port %s on device %s", + port_name, dev->name); + continue; + } + + if (synce_port_init(port, cfg, dev->network_option, + dev->extended_tlv, rx_enabled(dev), + dev->recover_time, dev->ql, + dev->ext_ql)) { + pr_err("failed to configure port %s on device %s", + port_name, dev->name); + synce_port_destroy(port); + continue; + } + + if (add_port(dev, port)) { + pr_err("failed to add port %s to device %s", + port_name, dev->name); + synce_port_destroy(port); + continue; + } else { + (*count)++; + pr_debug("port %s added on device %s addr %p", + port_name, dev->name, port); + } + } + } + + if (*count == 0) { + pr_err("device %s has no ports configured", dev->name); + return -ENODEV; + } + + return 0; +} + +static void update_dev_state(struct synce_dev *dev) +{ + struct synce_port *p; + int count = 0; + + LIST_FOREACH(p, &dev->ports, list) { + if (synce_port_thread_running(p)) { + count++; + } + } + + if (dev->num_ports == count) { + dev->state = DEVICE_RUNNING; + } else { + pr_warning("found %d ports running - %d configured on %s", + count, dev->num_ports, dev->name); + } +} + +static int port_set_dnu(struct synce_port *p, int extended_tlv) +{ + int ret; + + if (!p) { + pr_err("%s port is NULL", __func__); + ret = -EFAULT; + return ret; + } + + ret = synce_port_set_tx_ql_dnu(p, extended_tlv); + if (ret) { + pr_err("set tx DNU fail on %s", synce_port_get_name(p)); + return ret; + } + + return ret; +} + +static int port_set_ql_external_input(struct synce_port *p, int extended) +{ + int ret = synce_port_set_tx_ql_forced(p, extended); + + if (ret) { + pr_err("set QL external failed"); + return ret; + } + + return ret; +} + +static int update_ql_external_input(struct synce_dev *dev) +{ + struct synce_port *p; + int ret = 0; + + LIST_FOREACH(p, &dev->ports, list) { + if (dev->d_state == DPLL_HOLDOVER) { + ret = port_set_dnu(p, dev->extended_tlv); + } else if (dev->d_state == DPLL_LOCKED || + dev->d_state == DPLL_LOCKED_HO_ACQ) { + ret = port_set_ql_external_input(p, dev->extended_tlv); + } + + if (ret) { + pr_err("update QL failed d_state %d, err:%d on %s", + dev->d_state, ret, dev->name); + break; + } + + } + + return ret; +} + +static int port_set_ql_internal_input(struct synce_dev *dev, + struct synce_port *p, + struct synce_port *best_p) +{ + int ret = synce_port_set_tx_ql_from_best_input(p, best_p, + dev->extended_tlv); + + if (ret) { + pr_err("set QL failed"); + return ret; + } + + if (!ret) { + pr_debug("%s on %s", __func__, dev->name); + } + + return ret; +} + +static int update_ql_internal_input(struct synce_dev *dev) +{ + struct synce_port *p, *best_p = dev->best_source; + int ret = 0; + + LIST_FOREACH(p, &dev->ports, list) { + if (dev->d_state == DPLL_HOLDOVER) { + pr_debug("act on DPLL_HOLDOVER for %s", + synce_port_get_name(p)); + ret = port_set_dnu(p, dev->extended_tlv); + if (ret) { + pr_err("%s set DNU failed on %s", + __func__, dev->name); + return ret; + } + } else if ((dev->d_state == DPLL_LOCKED || + dev->d_state == DPLL_LOCKED_HO_ACQ) && best_p) { + pr_debug("act on DPLL_LOCKED/DPLL_LOCKED_HO_ACQ for %s", + synce_port_get_name(p)); + /* on best port send DNU, all the others + * propagate what came from best source + */ + if (p != best_p) { + ret = port_set_ql_internal_input(dev, p, + best_p); + } else { + ret = port_set_dnu(p, dev->extended_tlv); + } + + if (ret) { + pr_err("%s set failed on %s", + __func__, dev->name); + return ret; + } + } else { + pr_debug("nothing to do for %s d_state %d, best_p %p", + synce_port_get_name(p), dev->d_state, best_p); + } + } + + return ret; +} + +static void detach_port_dpll(struct synce_port *port, struct synce_dev *dev) +{ + int ret = synce_port_disable_recover_clock(port); + + if (ret) { + pr_err("disable recover clock cmd failed on %s", dev->name); + return; + } +} + +static void force_all_dplls_detach(struct synce_dev *dev) +{ + enum dpll_state state; + struct synce_port *p; + + LIST_FOREACH(p, &dev->ports, list) { + pr_debug("trying to detach DPLL RCLK for %s", + synce_port_get_name(p)); + detach_port_dpll(p, dev); + } + + if (synce_dev_ctrl_get_state(dev->dc, &state)) { + pr_err("failed getting DPLL state"); + dev->last_d_state = DPLL_UNKNOWN; + dev->d_state = DPLL_UNKNOWN; + } else { + dev->last_d_state = state; + dev->d_state = state; + } +}; + +static void dev_update_ql(struct synce_dev *dev) +{ + if (dev->ops.update_ql(dev)) { + pr_err("update QL fail on %s", dev->name); + } +} + +static int rx_ql_changed(struct synce_dev *dev) +{ + struct synce_port *p; + int ret = 0; + + LIST_FOREACH(p, &dev->ports, list) { + ret = synce_port_rx_ql_changed(p); + if (ret) { + break; + } + } + + return ret; +} + +static struct synce_port *find_dev_best_source(struct synce_dev *dev) +{ + struct synce_port *p, *best_p = dev->best_source; + + LIST_FOREACH(p, &dev->ports, list) { + if (best_p != p) { + if (synce_port_compare_ql(best_p, p) == p) { + pr_debug("old best %s replaced by %s on %s", + synce_port_get_name(best_p), + synce_port_get_name(p), dev->name); + best_p = p; + } + } + } + + if (best_p) { + if (synce_port_is_rx_dnu(best_p)) { + return NULL; + } + } + + return best_p; +} + +static int set_input_source(struct synce_dev *dev, + struct synce_port *new_best_source) +{ + const char *best_name = synce_port_get_name(new_best_source); + int ret; + + if (!best_name) { + pr_err("get best input name failed on %s", dev->name); + return -ENXIO; + } + + ret = synce_port_enable_recover_clock(new_best_source); + if (ret) { + pr_err("enable recover clock cmd failed on %s", dev->name); + return ret; + } + + return ret; +} + +static int act_on_d_state(struct synce_dev *dev) +{ + int ret = 0; + + if (dev->d_state != dev->last_d_state) { + ret = dev->ops.update_ql(dev); + if (ret) { + pr_err("update QL fail on %s", dev->name); + } else { + dev->last_d_state = dev->d_state; + pr_debug("%s QL updated on %s", __func__, dev->name); + } + } + + return ret; +} + +static int dev_step_external_input(struct synce_dev *dev) +{ + return act_on_d_state(dev); +} + +static void choose_best_source(struct synce_dev *dev) +{ + struct synce_port *new_best; + + new_best = find_dev_best_source(dev); + if (!new_best) { + pr_info("best source not found on %s", dev->name); + force_all_dplls_detach(dev); + dev_update_ql(dev); + dev->best_source = NULL; + } else if (new_best != dev->best_source) { + force_all_dplls_detach(dev); + if (set_input_source(dev, new_best)) { + pr_err("set best source failed on %s", + dev->name); + } else { + /* if input source is changing + * current input is invalid, send DNU and wait + * for DPLL being locked in further dev_step + */ + dev_update_ql(dev); + /* DPLL was invalidated we can now set new + * best_source for further use + */ + dev->best_source = new_best; + } + } else { + pr_info("clock source has not changed on %s", dev->name); + /* no port change, just update QL on TX */ + dev_update_ql(dev); + + } +} + +static int dev_step_internal_input(struct synce_dev *dev) +{ + int ret; + + ret = act_on_d_state(dev); + if (ret) { + pr_err("act on d_state fail on %s", dev->name); + return ret; + } + + if (rx_ql_changed(dev)) { + choose_best_source(dev); + } else if (dev->best_source) { + if (synce_port_rx_ql_failed(dev->best_source)) { + synce_port_invalidate_rx_ql(dev->best_source); + force_all_dplls_detach(dev); + dev_update_ql(dev); + dev->best_source = NULL; + choose_best_source(dev); + } + } + + return ret; +} + +static void init_ops(struct synce_dev *dev) +{ + if (rx_enabled(dev)) { + dev->ops.update_ql = &update_ql_internal_input; + dev->ops.step = &dev_step_internal_input; + } else { + dev->ops.update_ql = &update_ql_external_input; + dev->ops.step = &dev_step_external_input; + } +} + +int synce_dev_init(struct synce_dev *dev, struct config *cfg) +{ + const char *dpll_get_state_cmd; + struct dpll_state_str dss; + int count, ret; + + if (dev->state != DEVICE_CREATED) { + goto err; + } + + LIST_INIT(&dev->ports); + dev->internal_input = config_get_int(cfg, dev->name, "internal_input"); + dev->external_input = config_get_int(cfg, dev->name, "external_input"); + dev->ql = config_get_int(cfg, dev->name, "external_input_QL"); + dev->ext_ql = config_get_int(cfg, dev->name, "external_input_ext_QL"); + dev->extended_tlv = config_get_int(cfg, dev->name, "extended_tlv"); + dev->network_option = config_get_int(cfg, dev->name, "network_option"); + dev->recover_time = config_get_int(cfg, dev->name, "recover_time"); + dev->best_source = NULL; + dpll_get_state_cmd = config_get_string(cfg, dev->name, "dpll_get_state_cmd"); + dss.holdover = config_get_string(cfg, dev->name, "dpll_holdover_value"); + dss.locked_ho = config_get_string(cfg, dev->name, "dpll_locked_ho_value"); + dss.locked = config_get_string(cfg, dev->name, "dpll_locked_value"); + dss.freerun = config_get_string(cfg, dev->name, "dpll_freerun_value"); + dss.invalid = config_get_string(cfg, dev->name, "dpll_invalid_value"); + dev->dc = synce_dev_ctrl_create(); + if (!dev->dc) { + pr_err("device_ctrl create fail on %s", dev->name); + goto err; + } + + if (dev->external_input && dev->internal_input) { + pr_warning("external_input and internal_input are mutually exclusive - disabling internal_input cfg option"); + dev->internal_input = 0; + } else if (!dev->external_input && !dev->internal_input) { + pr_err("either external_input or internal_input need to be set to 1 - aborting initialization"); + goto err; + } + + ret = synce_dev_ctrl_init(dev->dc, dev->name, dpll_get_state_cmd, &dss); + if (ret) { + pr_err("synce_dev_ctrl init ret %d on %s", ret, dev->name); + goto err; + } + + if (init_ports(&count, dev, cfg)) + goto err; + + init_ops(dev); + dev->num_ports = count; + dev->state = DEVICE_INITED; + + dev->d_state = DPLL_HOLDOVER; + dev->ops.update_ql(dev); + + /* in case somebody manually set recovered clock before */ + if (dev->internal_input) { + force_all_dplls_detach(dev); + } + pr_info("inited num_ports %d for %s", count, dev->name); + + return 0; + +err: + dev->state = DEVICE_FAILED; + pr_err("%s failed for %s", __func__, dev->name); + return -ENODEV; +} + +struct synce_dev *synce_dev_create(const char *dev_name) +{ + struct synce_dev *dev = NULL; + + if (!dev_name) { + return NULL; + } + + dev = malloc(sizeof(struct synce_dev)); + if (!dev) { + return NULL; + } + + memcpy(dev->name, dev_name, sizeof(dev->name)); + dev->state = DEVICE_CREATED; + dev->d_state = DPLL_UNKNOWN; + dev->last_d_state = DPLL_UNKNOWN; + pr_debug("created %s", dev->name); + + return dev; +} + +int synce_dev_step(struct synce_dev *dev) +{ + int ret = -EFAULT; + + if (!dev) { + pr_err("%s dev is NULL", __func__); + return ret; + } + + update_dev_state(dev); + if (dev->state != DEVICE_RUNNING) { + pr_err("dev %s is not running", dev->name); + return ret; + } + + ret = synce_dev_ctrl_get_state(dev->dc, &dev->d_state); + if (ret) { + pr_warning("could not acquire dpll state on %s", dev->name); + return ret; + } + + ret = dev->ops.step(dev); + + return ret; +} + +const char *synce_dev_name(struct synce_dev *dev) +{ + return dev->name; +} + +int synce_dev_is_running(struct synce_dev *dev) +{ + update_dev_state(dev); + + return !!(dev->state & DEVICE_RUNNING); +} + +void synce_dev_destroy(struct synce_dev *dev) +{ + if (!dev) { + pr_err("%s dev is NULL", __func__); + return; + } + + if (dev->internal_input && dev->state != DEVICE_FAILED) { + force_all_dplls_detach(dev); + } + + destroy_ports(dev); + destroy_dev_ctrl(dev); +} diff --git a/synce_dev.h b/synce_dev.h new file mode 100644 index 000000000000..84c3befb3867 --- /dev/null +++ b/synce_dev.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_dev.h + * @brief Interface for handling Sync-E capable devices and its ports. + * @note Copyright (C) 2022 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#ifndef HAVE_SYNCE_DEV_H +#define HAVE_SYNCE_DEV_H + +#include <stdint.h> + +struct config; +struct synce_dev; + +/** + * Initialize Sync-E device and its ports after device was created. + * + * @param dev Device to be initialized + * @param cfg Configuration struct based on SYNCE type, must hold + * properities of configured device ports + * @return 0 on success, failure otherwise + */ +int synce_dev_init(struct synce_dev *dev, struct config *cfg); + +/** + * Alloc memory for a single Sync-E device. + * + * @param dev_name Human readable name of a device + * @return 0 on success, failure otherwise + */ +struct synce_dev *synce_dev_create(const char *dev_name); + +/** + * Step a Sync-E device. Probe for events, changes and act on them. + * + * @param dev Device to be stepped + * @return 0 on success, failure otherwise + */ +int synce_dev_step(struct synce_dev *dev); + +/** + * Acquire Sync-E device name. + * + * @param dev Questioned SyncE device instance + * @return The device name + */ +const char *synce_dev_name(struct synce_dev *dev); + +/** + * Clean-up on memory allocated for device and its ports. + * + * @param dev SyncE device to be cleared + */ +void synce_dev_destroy(struct synce_dev *dev); + +/** + * Check if Sync-E device is running. + * + * @param dev Questioned SyncE device + * @return 0 if false, 1 if true + */ +int synce_dev_is_running(struct synce_dev *dev); + +#endif -- 2.34.1 |
From: Arkadiusz K. <ark...@in...> - 2022-05-02 09:18:52
|
Used by synce_dev to interact with its ports. Co-developed-by: Anatolii Gerasymenko <ana...@in...> Signed-off-by: Anatolii Gerasymenko <ana...@in...> Co-developed-by: Michal Michalik <mic...@in...> Signed-off-by: Michal Michalik <mic...@in...> Signed-off-by: Arkadiusz Kubalewski <ark...@in...> --- synce_port.c | 504 +++++++++++++++++++++++++++++++++++++++++++++++++++ synce_port.h | 197 ++++++++++++++++++++ 2 files changed, 701 insertions(+) create mode 100644 synce_port.c create mode 100644 synce_port.h diff --git a/synce_port.c b/synce_port.c new file mode 100644 index 000000000000..40e6ed12e360 --- /dev/null +++ b/synce_port.c @@ -0,0 +1,504 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_port.c + * @brief Interface between synce device and port controller module. + * Prepares data to be passed to a wire and allows to read from the wire. + * @note Copyright (C) 2022 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include <stdlib.h> +#include <errno.h> +#include <sys/queue.h> +#include <net/if.h> +#include <linux/limits.h> + +#include "synce_port.h" +#include "print.h" +#include "config.h" +#include "synce_port_ctrl.h" +#include "synce_msg.h" + +enum port_mode { + NON_SYNC_MODE = 0, + SYNC_MODE, +}; + +enum port_state { + PORT_UNKNOWN = 0, + PORT_CREATED, + PORT_INITED, + PORT_RUNNING, + PORT_FAILED, + PORT_NOT_USED, +}; + +static int init_ext_tlv(struct synce_port *port, struct synce_msg_ext_ql *msg) +{ + memset(msg, 0, sizeof(*msg)); + + if (generate_clock_identity(&msg->clockIdentity, port->name)) { + pr_err("failed to generate a clock identity"); + return -ENXIO; + } + + return 0; +} + +static int ext_ql_msg_start_chain(struct synce_msg_ext_ql *ext_ql_msg) +{ + if (!ext_ql_msg) { + pr_err("ext_ql_msg is NULL"); + return -EFAULT; + } + + /* This is first node in chain */ + ext_ql_msg->cascaded_EEcs = 0; + ext_ql_msg->cascaded_eEEcs = 1; + + return 0; +} + +static int ext_ql_msg_update_chain(struct synce_port *port) +{ + int rx_ext_tlv = synce_port_ctrl_rx_ext_tlv(port->pc); + + if (rx_ext_tlv == 1) { + /* if extended tlv came on best port just increase eEEC by one */ + port->ext_ql_msg.cascaded_eEEcs++; + } else if (rx_ext_tlv == 0) { + /* If extended tlv was not on the wire, the chain has just started. + * Previous known number of EEC is 1. + * This node is first eEEC in the new chain. + * The flags set accordingly. + * This behavior is described in the SyncE specification. + */ + port->ext_ql_msg.cascaded_EEcs = 1; + port->ext_ql_msg.cascaded_eEEcs = 1; + port->ext_ql_msg.flag |= (MIXED_EEC_CHAIN_FLAG | + PARTIAL_EEC_CHAIN_FLAG); + } else { + pr_err("failed rx_ext_tlv on %s", port->name); + return -ENODEV; + } + + return 0; +} +static int init_port_ql_val(struct synce_port *port, uint8_t forced_ql, + int network_option) +{ + if (!port) { + pr_err("%s port is NULL", __func__); + return -EFAULT; + } + + port->ql_forced = forced_ql; + port->ql_dnu = synce_get_dnu_value(network_option, false); + port->ql = port->ql_dnu; + + return 0; +} + +static int init_port_ext_ql(struct synce_port *port, uint8_t forced_ext_ql, + int network_option) +{ + int ret; + + if (!port) { + pr_err("%s port is NULL", __func__); + return -EFAULT; + } + + ret = init_ext_tlv(port, &port->ext_ql_msg_dnu); + if (ret) { + pr_err("init ext_ql_msg_dnu failed on %s", port->name); + return ret; + } + + port->ext_ql_msg_dnu.enhancedSsmCode = + synce_get_dnu_value(network_option, true); + ret = ext_ql_msg_start_chain(&port->ext_ql_msg_dnu); + if (ret) { + pr_err("start chain failed on %s", port->name); + return ret; + } + + memcpy(&port->ext_ql_msg, &port->ext_ql_msg_dnu, + sizeof(port->ext_ql_msg)); + memcpy(&port->ext_ql_msg_forced, &port->ext_ql_msg, + sizeof(port->ext_ql_msg_forced)); + + port->ext_ql_msg_forced.enhancedSsmCode = forced_ext_ql; + + return ret; +} + +static int new_tx_ql_and_rebuild(struct synce_port *port, uint8_t ql, + struct synce_msg_ext_ql *ext_ql_msg) +{ + int ret = synce_port_ctrl_set_tx_ql(port->pc, ql); + + if (ret) { + pr_err("set QL on %s failed", port->name); + return ret; + } + + if (ext_ql_msg) { + ret = synce_port_ctrl_set_tx_ext_ql(port->pc, + ext_ql_msg); + if (ret) { + pr_err("set ext QL on %s failed", port->name); + return ret; + } + } + + ret = synce_port_ctrl_rebuild_tx(port->pc); + if (ret) { + pr_err("set rebuild tx on %s failed", port->name); + } + + return ret; +} + +struct synce_port *synce_port_create(const char *port_name) +{ + struct synce_port *p = NULL; + + if (!port_name) { + pr_err("%s failed - port_name not provided", __func__); + return NULL; + } + + p = malloc(sizeof(struct synce_port)); + if (!p) { + pr_err("%s failed", __func__); + return NULL; + } + memcpy(p->name, port_name, sizeof(p->name)); + p->state = PORT_CREATED; + + return p; +} + +int synce_port_init(struct synce_port *port, struct config *cfg, + int network_option, int is_extended, + int rx_enabled, int recovery_time, + uint8_t forced_ql, uint8_t forced_ext_ql) +{ + int ret; + + if (!port) { + pr_err("%s port is NULL", __func__); + return -ENODEV; + } + + if (port->state != PORT_CREATED) { + goto err_port; + } + + port->sync_mode = config_get_int(cfg, port->name, "sync"); + + if (port->sync_mode != SYNC_MODE) { + pr_err("port %s not in a sync mode", port->name); + goto err_port; + } + + if (rx_enabled) { + port->recover_clock_enable_cmd = + config_get_string(cfg, port->name, + "recover_clock_enable_cmd"); + if (!port->recover_clock_enable_cmd) { + pr_err("recover_clock_enable_cmd config not provided for %s", + port->name); + goto err_port; + } + port->recover_clock_disable_cmd = + config_get_string(cfg, port->name, + "recover_clock_disable_cmd"); + if (!port->recover_clock_disable_cmd) { + pr_err("recover_clock_disable_cmd config not provided for %s", + port->name); + goto err_port; + } + } else { + port->recover_clock_enable_cmd = NULL; + port->recover_clock_disable_cmd = NULL; + } + + port->pc = synce_port_ctrl_create(port->name); + if (!port->pc) { + pr_err("port_ctrl create failed on %s", port->name); + goto err_port; + } + + ret = init_port_ql_val(port, forced_ql, network_option); + if (ret) { + pr_err("init port QL values failed on %s", port->name); + return ret; + } + + if (is_extended) { + ret = init_port_ext_ql(port, forced_ext_ql, network_option); + if (ret) { + pr_err("init port ext QL values failed on %s", + port->name); + return ret; + } + } + + ret = synce_port_ctrl_init(port->pc, cfg, rx_enabled, is_extended, + recovery_time, network_option); + if (ret) { + pr_err("port_ctrl init failed on port %s", port->name); + return ret; + } + + ret = synce_port_set_tx_ql_dnu(port, is_extended); + if (ret) { + pr_err("tlv init failed on port %s", port->name); + return ret; + } + + ret = synce_port_ctrl_enable_tx(port->pc); + if (ret) { + pr_err("enabled tx failed on port %s", port->name); + return ret; + } + + port->state = PORT_INITED; + + return ret; +err_port: + port->state = PORT_FAILED; + return -ENODEV; +} + +int synce_port_in_sync_mode(struct config *cfg, const char *port_name) +{ + if (!cfg) { + pr_err("%s cfg is NULL", __func__); + return -EFAULT; + } + + if (!port_name) { + pr_err("%s port_name is NULL", __func__); + return -EFAULT; + } + + return (config_get_int(cfg, port_name, "sync") == SYNC_MODE); +} + +void synce_port_destroy(struct synce_port *port) +{ + if (!port) { + pr_err("%s port is NULL", __func__); + return; + } + + if (port->pc) { + synce_port_ctrl_destroy(port->pc); + free(port->pc); + } else { + pr_warning("%s pc is NULL", __func__); + } +} + +int synce_port_thread_running(struct synce_port *port) +{ + if (!port) { + pr_err("%s port is NULL", __func__); + return -EFAULT; + } + + return synce_port_ctrl_running(port->pc); +} + +int synce_port_rx_ql_failed(struct synce_port *port) +{ + if (!port) { + pr_err("%s port is NULL", __func__); + return -EFAULT; + } + + return synce_port_ctrl_rx_ql_failed(port->pc); +} + +int synce_port_rx_ql_changed(struct synce_port *port) +{ + if (!port) { + pr_err("%s port is NULL", __func__); + return -EFAULT; + } + + return synce_port_ctrl_rx_ql_changed(port->pc); +} + +int synce_port_set_tx_ql_dnu(struct synce_port *port, int extended) +{ + if (!port) { + pr_err("%s port is NULL", __func__); + return -EFAULT; + } + + return new_tx_ql_and_rebuild(port, port->ql_dnu, + extended ? + &port->ext_ql_msg_dnu : NULL); +} + +int synce_port_set_tx_ql_forced(struct synce_port *port, int extended) +{ + if (!port) { + pr_err("%s port is NULL", __func__); + return -EFAULT; + } + + return new_tx_ql_and_rebuild(port, port->ql_forced, + extended ? + &port->ext_ql_msg_forced : NULL); +} + +int synce_port_set_tx_ql_from_best_input(struct synce_port *port, + struct synce_port *best_p, + int extended) +{ + struct synce_msg_ext_ql rx_ext_ql_msg; + int ret = -EFAULT; + uint8_t rx_ql; + + if (!port) { + pr_err("%s port is NULL", __func__); + return ret; + } + + if (!best_p) { + pr_err("%s best_p is NULL", __func__); + return ret; + } + + ret = synce_port_ctrl_get_rx_ql(best_p->pc, &rx_ql); + if (ret) { + pr_err("get rx QL failed on %s", best_p->name); + return ret; + } + port->ql = rx_ql; + + if (extended) { + ret = synce_port_ctrl_get_rx_ext_ql(best_p->pc, + &rx_ext_ql_msg); + if (ret) { + pr_err("get ext rx QL failed on %s", best_p->name); + return ret; + } + memcpy(&port->ext_ql_msg, &rx_ext_ql_msg, + sizeof(port->ext_ql_msg)); + ret = generate_clock_identity(&port->ext_ql_msg.clockIdentity, + port->name); + if (ret) { + pr_err("failed to generate clock identity on %s", + port->name); + return ret; + } + + ret = ext_ql_msg_update_chain(port); + if (ret) { + pr_err("failed to update chain on %s", + port->name); + return ret; + } + } + ret = new_tx_ql_and_rebuild(port, port->ql, + extended ? &port->ext_ql_msg : NULL); + + return ret; +} + +int synce_port_is_rx_dnu(struct synce_port *port) +{ + if (!port) { + pr_err("%s port is NULL", __func__); + return -EFAULT; + } + + return synce_port_ctrl_rx_dnu(port->pc, port->ql_dnu); +} + +struct synce_port *synce_port_compare_ql(struct synce_port *left, + struct synce_port *right) +{ + struct synce_port_ctrl *best; + + best = synce_port_ctrl_compare_ql(left ? left->pc : NULL, + right ? right->pc : NULL); + if (!best) { + return NULL; + } + + if (left && best == left->pc) { + return left; + } else if (right && best == right->pc) { + return right; + } + + return NULL; +} + +const char *synce_port_get_name(struct synce_port *port) +{ + if (!port) { + pr_err("%s port is NULL", __func__); + return NULL; + } + + return port->name; +} + +int synce_port_enable_recover_clock(struct synce_port *port) +{ + if (!port) { + pr_err("%s port is NULL", __func__); + return -EINVAL; + } + + if (!port->recover_clock_disable_cmd) { + pr_err("recover_clock_enable_cmd is null on %s", port->name); + return -EINVAL; + } + + pr_debug("using recover_clock_enable_cmd: %s on %s", + port->recover_clock_enable_cmd, port->name); + + return system(port->recover_clock_enable_cmd); +} + +int synce_port_disable_recover_clock(struct synce_port *port) +{ + if (!port) { + pr_err("%s port is NULL", __func__); + return -EINVAL; + } + + if (!port->recover_clock_disable_cmd) { + pr_err("recover_clock_disable_cmd is null on %s", port->name); + return -EINVAL; + } + + pr_debug("using recover_clock_disable_cmd: %s on %s", + port->recover_clock_disable_cmd, port->name); + + return system(port->recover_clock_disable_cmd); +} + +void synce_port_invalidate_rx_ql(struct synce_port *port) +{ + synce_port_ctrl_invalidate_rx_ql(port->pc); +} diff --git a/synce_port.h b/synce_port.h new file mode 100644 index 000000000000..e97ba816ef79 --- /dev/null +++ b/synce_port.h @@ -0,0 +1,197 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_port.h + * @brief Interface between synce device and port controller module. + * Prepares data to be passed to a wire and allows to read from the wire. + * @note Copyright (C) 2022 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#ifndef HAVE_SYNCE_PORT_H +#define HAVE_SYNCE_PORT_H +#include <stdint.h> +#include "synce_msg.h" + +struct synce_dev; +struct config; + +struct synce_port { + LIST_ENTRY(synce_port) list; + int sync_mode; + int state; + struct synce_port_ctrl *pc; + uint8_t ql; + uint8_t ql_dnu; + uint8_t ql_forced; + struct synce_msg_ext_ql ext_ql_msg; + struct synce_msg_ext_ql ext_ql_msg_dnu; + struct synce_msg_ext_ql ext_ql_msg_forced; + char name[IF_NAMESIZE]; + char *recover_clock_enable_cmd; + char *recover_clock_disable_cmd; +}; + +/** + * Alloc memory for a single synce_port instance. + * + * @param port_name Human readable name of a port + * @return Pointer to allocated instance + */ +struct synce_port *synce_port_create(const char *port_name); + +/** + * Initialize synce device capable port after port was created. + * + * @param port synce_port instance to be initialized + * @param cfg Configuration struct based on SYNCE type, + * must hold properities of the configured port. + * @param network_option Network option that shall be used on the device + * @param is_extended If extended tlv support is on + * @param rx_enabled If rx of ESMC shall start + * @param recovery_time Seconds for period of recovering from QL-failed + * state. + * @param forced_ql Value of QL when QL is forced for the device, + * used in external input mode + * @param forced_ext_ql Value of ext QL when QL is forced for the + * device, used in external input mode + * @return 0 on success, failure otherwise + */ +int synce_port_init(struct synce_port *port, struct config *cfg, + int network_option, int is_extended, + int rx_enabled, int recovery_time, + uint8_t forced_ql, uint8_t forced_ext_ql); + +/** + * Check if port shall be configured for synchronous mode. + * + * @param cfg Configuration struct based on SYNCE type, must hold + * properities of the configured port. + * @param port_name Human readable name of a port being questioned + * @return 0 if false, otherwise true + */ +int synce_port_in_sync_mode(struct config *cfg, const char *port_name); + +/** + * Free resource under the synce_port instance. Caller shall free the passed + * pointer afterwards. + * + * @param port Pointer to the port being released + */ +void synce_port_destroy(struct synce_port *port); + +/** + * Check if port ctrl threads are running. + * + * @param port Questioned port + * @return 0 if false, otherwise true + */ +int synce_port_thread_running(struct synce_port *port); + +/** + * Check if QL-failed condition is present. + * + * @param port Questioned instance + * @return 1 if true, 0 if false, negative on failure + */ +int synce_port_rx_ql_failed(struct synce_port *port); + +/** + * Check if QL has changed on RX. + * + * @param port Questioned instance + * @return 1 if true, 0 if false, negative on failure + */ +int synce_port_rx_ql_changed(struct synce_port *port); + +/** + * Set QL-DNU on TX TLV of associated port. + * + * @param port Managed port + * @param extended If new extended TLV shall be created + * @return 0 on success, negative otherwise + */ +int synce_port_set_tx_ql_dnu(struct synce_port *port, int extended); + +/** + * Set QL from config file on TX TLV of associated port. Useful in + * external_input scenario. + * + * + * @param port Managed port + * @param extended If new extended TLV shall be created + * @return 0 on success, negative otherwise + */ +int synce_port_set_tx_ql_forced(struct synce_port *port, int extended); + +/** + * Set QL for TX thread - but copy QL from best port. + * + * @param port Managed instance + * @param best_p Best port instance + * @param extended If new extended TLV shall be created + * @return 0 on success, negative on failure + */ +int synce_port_set_tx_ql_from_best_input(struct synce_port *port, + struct synce_port *best_p, + int extended); + +/** + * Check if given port has Do Not Use QL. + * + * @param port Questioned instance + * @return 1 if DNU is present, 0 if not, negative on failure + */ +int synce_port_is_rx_dnu(struct synce_port *port); + +/** + * Compare left with right port, which has higher incoming Quality Level. + * + * @param left Port instance for comparison + * @param righ Port instance for comparison + * @return Pointer to best QL instance, NULL on failure or equal + */ +struct synce_port *synce_port_compare_ql(struct synce_port *left, + struct synce_port *right); + +/** + * Get name of a port. + * + * @param port Questioned instance + * @return Name of a port + */ +const char *synce_port_get_name(struct synce_port *port); + +/** + * Enable recover clock on a port. + * + * @param port Questioned instance + * @return 0 on success, negative on failure + */ +int synce_port_enable_recover_clock(struct synce_port *port); + +/** + * Enable recover clock on a port. + * + * @param port Questioned instance + * @return 0 on success, negative on failure + */ +int synce_port_disable_recover_clock(struct synce_port *port); + +/** + * Invalidate QL received on the port in the past. + * + * @param port Questioned instance + */ +void synce_port_invalidate_rx_ql(struct synce_port *port); + +#endif /* HAVE_SYNCE_PORT_H */ -- 2.34.1 |
From: Arkadiusz K. <ark...@in...> - 2022-05-02 09:18:59
|
Used by synce_port to create, control and destroy RX/TX threads. Ports in sync mode can use only TX thread (device in external input mode) or both RX and TX threads (device in internal input mode). RX thread is responsible for: - receiving ESMC frames - verifying received QL - storing last received valid QL and time it was received - determining if QL-failed state occurred or port recovered from QL-failed state. TX thread is responsible for: - building new TX ESMC if requested - sending ESMC on timer. synce_port_ctrl interface allows: - initialize threads - obtain last received valid QL - compare QL's between 2 ports - check if RX QL has changed - check if RX QL has Do Not Use QL - check if RX QL-failed state occurred - invalidate RX QL's. Co-developed-by: Anatolii Gerasymenko <ana...@in...> Signed-off-by: Anatolii Gerasymenko <ana...@in...> Co-developed-by: Michal Michalik <mic...@in...> Signed-off-by: Michal Michalik <mic...@in...> Signed-off-by: Arkadiusz Kubalewski <ark...@in...> --- synce_port_ctrl.c | 1077 +++++++++++++++++++++++++++++++++++++++++++++ synce_port_ctrl.h | 177 ++++++++ 2 files changed, 1254 insertions(+) create mode 100644 synce_port_ctrl.c create mode 100644 synce_port_ctrl.h diff --git a/synce_port_ctrl.c b/synce_port_ctrl.c new file mode 100644 index 000000000000..b0364c4827ef --- /dev/null +++ b/synce_port_ctrl.c @@ -0,0 +1,1077 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_port_ctrl.c + * @brief Interface between synce port and socket handling theads, used + * for controling data on the wire. Allows acquire incoming data and + * submit new outgoing data. + * tx thread is always present, rx only if required (internal_input mode). + * @note Copyright (C) 2022 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include <stdlib.h> +#include <limits.h> +#include <pthread.h> +#include <unistd.h> +#include <net/if.h> +#include <errno.h> +#include "print.h" +#include "ddt.h" +#include "config.h" +#include "synce_port_ctrl.h" +#include "synce_transport.h" +#include "synce_msg.h" + +#define MSEC_TO_USEC(X) (X * 1000) +#define THREAD_STOP_SLEEP_USEC MSEC_TO_USEC(50) +#define THREAD_START_SLEEP_USEC MSEC_TO_USEC(20) +#define SYNCE_THREAD_STACK_SIZE 0xffff +#define RX_THREAD 0 +#define TX_THREAD 1 +#define TASK_COMM_LEN 16 + +#define ENHANCED_SSM_SHIFT 8 +#define QL_PRIORITY(r, e) ((e << ENHANCED_SSM_SHIFT) | r) + +#define O1N_PRIORITY_COUNT 5 +static const uint16_t O1N_priority[O1N_PRIORITY_COUNT] = { + QL_PRIORITY(O1N_QL_EPRTC_SSM, O1N_QL_EPRTC_ENHSSM), + QL_PRIORITY(O1N_QL_PRTC_SSM, O1N_QL_PRTC_ENHSSM), + QL_PRIORITY(O1N_QL_PRC_SSM, O1N_QL_PRC_ENHSSM), + QL_PRIORITY(O1N_QL_SSU_A_SSM, O1N_QL_SSU_A_ENHSSM), + QL_PRIORITY(O1N_QL_SSU_B_SSM, O1N_QL_SSU_B_ENHSSM) +}; + +#define O2N_PRIORITY_COUNT 8 +static const uint16_t O2N_priority[O2N_PRIORITY_COUNT] = { + QL_PRIORITY(O2N_QL_EPRTC_SSM, O2N_QL_EPRTC_ENHSSM), + QL_PRIORITY(O2N_QL_PRTC_SSM, O2N_QL_PRTC_ENHSSM), + QL_PRIORITY(O2N_QL_PRS_SSM, O2N_QL_PRS_ENHSSM), + QL_PRIORITY(O2N_QL_STU_SSM, O2N_QL_STU_ENHSSM), + QL_PRIORITY(O2N_QL_ST2_SSM, O2N_QL_ST2_ENHSSM), + QL_PRIORITY(O2N_QL_TNC_SSM, O2N_QL_TNC_ENHSSM), + QL_PRIORITY(O2N_QL_ST3E_SSM, O2N_QL_ST3E_ENHSSM), + QL_PRIORITY(O2N_QL_PROV_SSM, O2N_QL_PROV_ENHSSM) +}; + +struct ql { + STAILQ_ENTRY(ql) list; + uint8_t value; +}; + +struct thread_common_data { + int heartbeat_usec; + int state; + uint8_t ql; + int extended; + struct synce_msg_ext_ql ext_ql; + struct synce_transport *transport; + struct synce_pdu *pdu; + char *name; + int enabled; +}; + +struct synce_port_tx { + struct thread_common_data cd; + int rebuild_tlv; +}; + +struct synce_port_rx { + struct thread_common_data cd; + uint8_t last_ql; + struct synce_msg_ext_ql last_ext_ql; + int ql_failed; + struct timespec last_recv_ts; + struct timespec first_valid_ts; + uint64_t n_recv; + int recover_time; + int ext_tlv_recvd; + uint8_t ql_dnu_val; + + STAILQ_HEAD(allowed_qls_head, ql) allowed_qls; + STAILQ_HEAD(allowed_ext_qls_head, ql) allowed_ext_qls; +}; + +struct synce_port_ctrl { + char name[IF_NAMESIZE]; + struct synce_port_tx tx; + struct synce_port_rx rx; + pthread_t tx_thread_id; + pthread_t rx_thread_id; + struct synce_transport *transport; + const uint16_t *priority_list; + int priority_list_count; +}; + +enum thread_state { + THREAD_NOT_STARTED = 0, + THREAD_STARTED, + THREAD_STOPPING, + THREAD_STOPPED, + THREAD_FAILED, +}; + +static int tx_rebuild_tlv(struct synce_port_tx *tx) +{ + struct thread_common_data *cd; + int ret = -ENXIO; + + if (!tx) { + pr_err("synce_port_tx is NULL"); + return ret; + } + + cd = &tx->cd; + if (!cd) { + pr_err("%s cd is NULL", __func__); + return ret; + } + + if (!cd->pdu) { + pr_err("tx pdu is NULL"); + return ret; + } + + synce_msg_reset_tlvs(cd->pdu); + + ret = synce_msg_attach_ql_tlv(cd->pdu, cd->ql); + if (ret) { + pr_err("attach QL=%u TLV failed on %s", cd->ql, cd->name); + goto err; + } else { + pr_info("%s: attached new TLV, QL=%d on %s", + __func__, cd->ql, cd->name); + } + + if (cd->extended) { + ret = synce_msg_attach_extended_ql_tlv(cd->pdu, + &cd->ext_ql); + if (ret) { + pr_err("attach EXT_QL TLV failed on %s", cd->name); + goto err; + } else { + pr_info("%s: attached new extended TLV, EXT_QL=%d on %s", + __func__, cd->ext_ql.enhancedSsmCode, + cd->name); + } + } + + tx->rebuild_tlv = 0; + + return ret; +err: + cd->state = THREAD_FAILED; + return ret; +} + +static void *tx_thread(void *data) +{ + struct synce_port_tx *tx = (struct synce_port_tx *) data; + struct thread_common_data *cd; + volatile int *state; + + if (!tx) { + pr_err("%s tx data is NULL", __func__); + pthread_exit(NULL); + } + + cd = &tx->cd; + state = &cd->state; + if (*state != THREAD_NOT_STARTED) { + pr_err("tx wrong state"); + goto out; + } + + pr_debug("tx thread started on port %s", cd->name); + *state = THREAD_STARTED; + while (*state == THREAD_STARTED) { + if (tx->rebuild_tlv) { + if (tx_rebuild_tlv(tx)) { + pr_err("tx rebuild failed"); + goto out; + } + } + + /* any errors are traced inside */ + if (cd->enabled) { + synce_transport_send_pdu(cd->transport, cd->pdu); + } + usleep(cd->heartbeat_usec); + }; + +out: + *state = (*state == THREAD_STOPPING) ? THREAD_STOPPED : *state; + pr_debug("tx thread exit state %d=%s port %s", *state, + *state == THREAD_STOPPED ? "OK" : "failed", cd->name); + pthread_exit(NULL); +} + +static int diff_sec(struct timespec now, struct timespec before) +{ + return (now.tv_sec - before.tv_sec); +} + +static void update_ql(struct thread_common_data *cd, int ext_tlv_recvd, + uint8_t ql, const struct synce_msg_ext_ql *ext_ql) +{ + cd->ql = ql; + + if (ext_tlv_recvd == 1) { + memcpy(&cd->ext_ql, ext_ql, sizeof(cd->ext_ql)); + } +} + +static int is_ql_allowed(struct allowed_qls_head *qls_stailq_head, uint8_t ql) +{ + struct ql *checked_ql; + + if (STAILQ_EMPTY(qls_stailq_head)) { + /* no filter list - accept all */ + return 1; + } + + STAILQ_FOREACH(checked_ql, qls_stailq_head, list) { + if (checked_ql->value == ql) { + return 1; + } + } + + return 0; +} + +static int get_rx_qls(struct synce_port_rx *rx, uint8_t *ql, + struct synce_msg_ext_ql *ext_ql) +{ + struct thread_common_data *cd = &rx->cd; + + if (synce_msg_get_ql_tlv(cd->pdu, ql)) { + return -EAGAIN; + } + + pr_debug("QL=%d found on %s", *ql, cd->name); + + if (!is_ql_allowed(&rx->allowed_qls, *ql)) { + pr_debug("Received not allowed QL: %i, discarding", *ql); + return -EBADMSG; + } + + if (cd->extended) { + if (synce_msg_get_extended_ql_tlv(cd->pdu, ext_ql)) { + rx->ext_tlv_recvd = 0; + + /* only extended missing - not an error */ + return 0; + } + + pr_debug("extended QL=%d found on %s", + ext_ql->enhancedSsmCode, cd->name); + + if (!is_ql_allowed((struct allowed_qls_head *) + &rx->allowed_ext_qls, + ext_ql->enhancedSsmCode)) { + pr_debug("Received not allowed ext_QL: %i, discarding", + ext_ql->enhancedSsmCode); + rx->ext_tlv_recvd = 0; + return -EBADMSG; + } + + rx->ext_tlv_recvd = 1; + } + + return 0; +} + +static int rx_act(struct synce_port_rx *rx) +{ + struct thread_common_data *cd = &rx->cd; + struct synce_msg_ext_ql ext_ql; + struct timespec now; + uint8_t ql; + int err; + + /* read socket for ESMC and fill pdu */ + err = synce_transport_recv_pdu(cd->transport, cd->pdu); + if (!err) { + rx->n_recv++; + } + + /* wait for first frame received before starting any logic */ + if (!rx->n_recv) { + return -EAGAIN; + } + + err = get_rx_qls(rx, &ql, &ext_ql); + if (err) { + /* go to ql_failed state if continue missing frames */ + if (rx->ql_failed == 0) { + clock_gettime(CLOCK_REALTIME, &now); + if (diff_sec(now, rx->last_recv_ts) >= + QL_FAILED_PERIOD_SEC) { + pr_info("QL not received on %s within %d s", + cd->name, QL_FAILED_PERIOD_SEC); + rx->ql_failed = 1; + /* clear first_valid_ts so we can recover from + * ql_failed state + */ + memset(&rx->first_valid_ts, 0, + sizeof(rx->first_valid_ts)); + } + } + } else { + clock_gettime(CLOCK_REALTIME, &rx->last_recv_ts); + now.tv_sec = rx->last_recv_ts.tv_sec; + if (rx->ql_failed == 1) { + if (rx->first_valid_ts.tv_sec == 0) { + clock_gettime(CLOCK_REALTIME, + &rx->first_valid_ts); + } else { + /* May be required to add counter for number + * of received frames before exit ql_failed + */ + if (diff_sec(now, rx->first_valid_ts) >= + rx->recover_time) { + update_ql(cd, rx->ext_tlv_recvd, + ql, &ext_ql); + rx->ql_failed = 0; + pr_info("QL-failed recovered on %s", + cd->name); + } + } + } else { + update_ql(cd, rx->ext_tlv_recvd, ql, &ext_ql); + pr_debug("QL=%u received on %s", cd->ql, cd->name); + } + } + + return 0; +} + +static void *rx_thread(void *data) +{ + struct synce_port_rx *rx = (struct synce_port_rx *) data; + struct thread_common_data *cd; + volatile int *state; + + if (!rx) { + pr_err("%s rx data NULL", __func__); + pthread_exit(NULL); + } + + cd = &rx->cd; + state = &cd->state; + if (*state != THREAD_NOT_STARTED) { + pr_err("rx wrong state on %s", cd->name); + goto out; + } + + pr_debug("rx thread started on port %s", cd->name); + *state = THREAD_STARTED; + while (*state == THREAD_STARTED) { + rx_act(rx); + usleep(cd->heartbeat_usec); + }; + +out: + *state = (*state == THREAD_STOPPING) ? THREAD_STOPPED : *state; + pr_debug("rx thread exit state %d=%s port %s", *state, + *state == THREAD_STOPPED ? "OK" : "failed", cd->name); + pthread_exit(NULL); +} + +static int tx_init(struct synce_port_tx *tx, int heartbeat_msec, + int extended_tlv, struct synce_transport *transport, + char *name) +{ + struct thread_common_data *cd; + + if (!tx) { + pr_err("%s rx NULL", __func__); + return -EFAULT; + } + + if (!transport) { + pr_err("%s transport NULL", __func__); + return -EFAULT; + } + + if (!name) { + pr_err("%s name NULL", __func__); + return -EFAULT; + } + + cd = &tx->cd; + memset(tx, 0, sizeof(*tx)); + + if (extended_tlv) { + memset(&cd->ext_ql, 0, sizeof(cd->ext_ql)); + cd->extended = extended_tlv; + } + cd->heartbeat_usec = MSEC_TO_USEC(heartbeat_msec); + cd->name = name; + cd->pdu = synce_msg_create(cd->name); + cd->transport = transport; + cd->state = THREAD_NOT_STARTED; + tx->rebuild_tlv = 0; + cd->enabled = 0; + + return 0; +} + +static void free_allowed_qls(struct allowed_qls_head *head) +{ + struct ql *q; + + while ((q = STAILQ_FIRST(head))) { + STAILQ_REMOVE_HEAD(head, list); + free(q); + } +} + +#define QL_STR_MAX_LEN 256 +#define QL_STR_BASE 10 +static int init_ql_str(struct allowed_qls_head *qls_stailq_head, + const char *allowed_qls) +{ + unsigned int allowed_qls_len; + char *endptr = NULL; + const char *ptr; + + if (allowed_qls == NULL) { + return 0; + } + + allowed_qls_len = strnlen(allowed_qls, QL_STR_MAX_LEN); + if (allowed_qls_len == QL_STR_MAX_LEN) { + pr_err("QLs list string too long (max %i)", QL_STR_MAX_LEN); + return -E2BIG; + } + + ptr = allowed_qls; + while (*ptr) { + unsigned long value; + struct ql *newql; + + if (ptr > allowed_qls + allowed_qls_len) { + break; + } + + value = strtoul(ptr, &endptr, QL_STR_BASE); + if (endptr == ptr) { + pr_err("QL list item read failed - please verify"); + free_allowed_qls(qls_stailq_head); + return -EINVAL; + } + if (value > UCHAR_MAX) { + pr_err("QL list item outside of range - please verify"); + free_allowed_qls(qls_stailq_head); + return -EINVAL; + } + + newql = malloc(sizeof(struct ql)); + if (!newql) { + pr_err("could not alloc ql"); + return -EINVAL; + } + + newql->value = value; + STAILQ_INSERT_HEAD(qls_stailq_head, newql, list); + + ptr = endptr + 1; + } + + if (endptr != allowed_qls + allowed_qls_len) { + pr_err("QL list malformed - not all read - please verify"); + free_allowed_qls(qls_stailq_head); + return -EINVAL; + } + + return 0; +} + +static int init_allowed_qls(struct synce_port_rx *rx, struct config *cfg, + const char *name) +{ + const char *allowed_qls; + + STAILQ_INIT(&rx->allowed_qls); + + allowed_qls = config_get_string(cfg, name, "allowed_qls"); + if (allowed_qls == NULL) { + pr_warning("No allowed QLs list found - filtering disabled"); + return 0; + } + + return init_ql_str(&rx->allowed_qls, allowed_qls); +} + +static int init_allowed_ext_qls(struct synce_port_rx *rx, struct config *cfg, + const char *name) +{ + const char *allowed_qls; + + STAILQ_INIT((struct allowed_qls_head *)&rx->allowed_ext_qls); + + allowed_qls = config_get_string(cfg, name, "allowed_ext_qls"); + + if (allowed_qls == NULL) { + pr_warning("No allowed ext_QLs list found - filtering disabled"); + return 0; + } + + return init_ql_str((struct allowed_qls_head *)&rx->allowed_ext_qls, + allowed_qls); +} + +static int rx_init(struct synce_port_rx *rx, int heartbeat_msec, + int extended_tlv, int recover_time, + struct synce_transport *transport, char *name, + struct config *cfg, int network_option) +{ + struct thread_common_data *cd; + + if (!rx) { + pr_err("%s rx NULL", __func__); + return -EFAULT; + } + + if (!transport) { + pr_err("%s transport NULL", __func__); + return -EFAULT; + } + + if (!name) { + pr_err("%s name NULL", __func__); + return -EFAULT; + } + + cd = &rx->cd; + memset(rx, 0, sizeof(*rx)); + + if (extended_tlv) { + memset(&cd->ext_ql, 0, sizeof(cd->ext_ql)); + memcpy(&rx->last_ext_ql, &cd->ext_ql, sizeof(rx->last_ext_ql)); + cd->extended = extended_tlv; + } + cd->heartbeat_usec = MSEC_TO_USEC(heartbeat_msec); + cd->name = name; + cd->pdu = synce_msg_create(cd->name); + cd->transport = transport; + cd->state = THREAD_NOT_STARTED; + rx->ql_dnu_val = synce_get_dnu_value(network_option, false); + rx->last_ql = rx->ql_dnu_val; + memset(&rx->last_recv_ts, 0, sizeof(rx->last_recv_ts)); + memset(&rx->first_valid_ts, 0, sizeof(rx->first_valid_ts)); + rx->n_recv = 0; + rx->recover_time = recover_time; + cd->enabled = 1; + + init_allowed_qls(rx, cfg, name); + init_allowed_ext_qls(rx, cfg, name); + + return 0; +} + +static int thread_stop_wait(struct thread_common_data *cd) +{ + int cnt = (cd->heartbeat_usec / THREAD_STOP_SLEEP_USEC) + 1; + + if (cd->state == THREAD_STARTED) { + cd->state = THREAD_STOPPING; + } else { + return -ESRCH; + } + + while (cnt-- && cd->state != THREAD_STOPPED) { + usleep(THREAD_STOP_SLEEP_USEC); + }; + + return (cd->state == THREAD_STOPPED ? 0 : -ENXIO); +} + +static int thread_start_wait(struct thread_common_data *cd) +{ + int cnt = (cd->heartbeat_usec / THREAD_STOP_SLEEP_USEC) + 1; + + if (cd->state == THREAD_STARTED) { + pr_debug("THREAD_STARTED"); + + return 0; + } + + while (cnt-- && cd->state != THREAD_STARTED) { + usleep(THREAD_STOP_SLEEP_USEC); + } + + if (cd->state != THREAD_STARTED) { + pr_err("THREAD_FAILED"); + + return -ESRCH; + } + + pr_debug("THREAD_STARTED"); + + return 0; +} + +static int synce_port_ctrl_thread_create(char *name, void *data, int tx, + pthread_t *thread_id) +{ + char thread_name[TASK_COMM_LEN]; + pthread_attr_t attr; + int err; + + err = pthread_attr_init(&attr); + if (err) { + pr_err("init %s thread attr failed for %s", + tx ? "tx" : "rx", name); + goto err_attr; + } + + err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (err) { + pr_err("set %s thread detached failed for %s", + tx ? "tx" : "rx", name); + goto err_attr; + } + + err = pthread_attr_setstacksize(&attr, SYNCE_THREAD_STACK_SIZE); + if (err) { + pr_err("set %s thread stack failed for %s", + tx ? "tx" : "rx", name); + goto err_attr; + } + + if (tx) { + err = pthread_create(thread_id, &attr, tx_thread, data); + } else { + err = pthread_create(thread_id, &attr, rx_thread, data); + } + if (err) { + pr_err("create %s thread failed for %s", + tx ? "tx" : "rx", name); + goto err_attr; + } + + snprintf(thread_name, TASK_COMM_LEN, "%s-%s", + tx ? "tx" : "rx", name); + err = pthread_setname_np(*thread_id, thread_name); + if (err) { + pr_info("failed to set %s thread's name for %s", + tx ? "tx" : "rx", name); + } + + pthread_attr_destroy(&attr); + return 0; + +err_attr: + pthread_attr_destroy(&attr); + return -ECHILD; +} + +static uint16_t get_ql_priority(struct synce_port_ctrl *pc) +{ + if (pc->rx.cd.extended) { + return QL_PRIORITY(pc->rx.cd.ql, + pc->rx.cd.ext_ql.enhancedSsmCode); + } else { + return QL_PRIORITY(pc->rx.cd.ql, + QL_OTHER_CLOCK_TYPES_ENHSSM); + } +} + +static struct synce_port_ctrl *is_valid_source(struct synce_port_ctrl *pc) +{ + uint16_t ql_priority; + int i; + + if (!pc) { + pr_debug("pc is NULL"); + return NULL; + } + + ql_priority = get_ql_priority(pc); + + if (pc->rx.n_recv > 0 && !pc->rx.ql_failed) { + for (i = 0; i < pc->priority_list_count; i++) { + if (ql_priority == pc->priority_list[i]) { + return pc; + } + } + } + pr_info("not valid source: %s", pc->name); + + return NULL; +} + +int synce_port_ctrl_running(struct synce_port_ctrl *pc) +{ + int ret; + + if (!pc) { + pr_err("%s pc is NULL", __func__); + return -EFAULT; + } + + if (!pc->rx.cd.enabled) { + ret = (pc->tx.cd.state == THREAD_STARTED); + } else { + ret = (pc->tx.cd.state == THREAD_STARTED) && + (pc->rx.cd.state == THREAD_STARTED); + } + + return ret; +} + +int synce_port_ctrl_destroy(struct synce_port_ctrl *pc) +{ + if (!pc) { + pr_err("%s pc is NULL", __func__); + return -EFAULT; + } + pr_debug("%s on %s", __func__, pc->name); + + thread_stop_wait(&pc->tx.cd); + if (pc->tx.cd.pdu) { + synce_msg_delete(pc->tx.cd.pdu); + } + + if (pc->rx.cd.enabled) { + thread_stop_wait(&pc->rx.cd); + if (pc->tx.cd.pdu) { + synce_msg_delete(pc->rx.cd.pdu); + } + + free_allowed_qls(&pc->rx.allowed_qls); + free_allowed_qls((struct allowed_qls_head *)&pc->rx.allowed_ext_qls); + } + + if (pc->transport) { + synce_transport_delete(pc->transport); + } + memset(pc, 0, sizeof(*pc)); + + return 0; +} + +int synce_port_ctrl_rx_ql_failed(struct synce_port_ctrl *pc) +{ + if (!pc) { + pr_err("%s pc is NULL", __func__); + return -EFAULT; + } + + return (pc->rx.ql_failed != 0); +} + +int synce_port_ctrl_rx_dnu(struct synce_port_ctrl *pc, uint8_t dnu_val) +{ + if (!pc) { + pr_err("%s pc is NULL", __func__); + return -EFAULT; + } + + return pc->rx.n_recv && !pc->rx.ql_failed ? + pc->rx.cd.ql == dnu_val : -EAGAIN; +} + +int synce_port_ctrl_rx_ql_changed(struct synce_port_ctrl *pc) +{ + struct thread_common_data *cd; + int ret; + + if (!pc) { + pr_err("%s pc is NULL", __func__); + return -EFAULT; + } + + cd = &pc->rx.cd; + if (!pc->rx.ext_tlv_recvd) { + ret = (cd->ql != pc->rx.last_ql); + pc->rx.last_ql = cd->ql; + } else { + ret = (cd->ql != pc->rx.last_ql) || + (memcmp(&cd->ext_ql, + &pc->rx.last_ext_ql, + sizeof(cd->ext_ql)) != 0); + pc->rx.last_ql = cd->ql; + memcpy(&pc->rx.last_ext_ql, &cd->ext_ql, sizeof(pc->rx.last_ext_ql)); + } + + if (ret) { + pr_debug("%s on %s", __func__, pc->name); + } + + return ret; +} + +int synce_port_ctrl_rx_ext_tlv(struct synce_port_ctrl *pc) +{ + if (!pc) { + pr_err("%s pc is NULL", __func__); + return -EFAULT; + } + + return pc->rx.ext_tlv_recvd; +} + +int synce_port_ctrl_get_rx_ql(struct synce_port_ctrl *pc, uint8_t *ql) +{ + if (!pc) { + pr_err("%s pc is NULL", __func__); + return -EFAULT; + } + + if (!ql) { + pr_err("%s ql is NULL", __func__); + return -EFAULT; + } + + *ql = pc->rx.cd.ql; + + return 0; +} + +int synce_port_ctrl_get_rx_ext_ql(struct synce_port_ctrl *pc, + struct synce_msg_ext_ql *ext_ql) +{ + if (!pc) { + pr_err("%s pc is NULL", __func__); + return -EFAULT; + } + + if (!ext_ql) { + pr_err("%s ext_ql is NULL", __func__); + return -EFAULT; + } + + if (!pc->rx.cd.extended) { + pr_err("ext_ql was not enabled for %s", pc->name); + return -EFAULT; + } + + memcpy(ext_ql, &pc->rx.cd.ext_ql, sizeof(*ext_ql)); + + return 0; +} + +int synce_port_ctrl_set_tx_ql(struct synce_port_ctrl *pc, uint8_t ql) +{ + if (!pc) { + pr_err("%s pc is NULL", __func__); + return -EFAULT; + } + + pc->tx.cd.ql = ql; + + return 0; +} + +int synce_port_ctrl_set_tx_ext_ql(struct synce_port_ctrl *pc, + struct synce_msg_ext_ql *ext_ql) +{ + if (!pc) { + pr_err("%s pc is NULL", __func__); + return -EFAULT; + } + + if (!ext_ql) { + pr_err("%s ext_ql is NULL", __func__); + return -EFAULT; + } + + memcpy(&pc->tx.cd.ext_ql, ext_ql, sizeof(pc->tx.cd.ext_ql)); + + return 0; +} + +int synce_port_ctrl_rebuild_tx(struct synce_port_ctrl *pc) +{ + if (!pc) { + pr_err("%s pc is NULL", __func__); + return -EFAULT; + } + + pc->tx.rebuild_tlv = 1; + + return 0; +} + +int synce_port_ctrl_enable_tx(struct synce_port_ctrl *pc) +{ + if (!pc) { + pr_err("%s pc is NULL", __func__); + return -EFAULT; + } + + pc->tx.cd.enabled = 1; + return 0; +} + +struct synce_port_ctrl * +synce_port_ctrl_compare_ql(struct synce_port_ctrl *left, + struct synce_port_ctrl *right) +{ + uint16_t left_ql_priority, right_ql_priority; + struct synce_port_ctrl *best = NULL; + int i; + + if (!left && !right) { + pr_err("%s both left and right are NULL", __func__); + goto out; + } + + left = is_valid_source(left); + right = is_valid_source(right); + + if (!left && !right) { + pr_err("both left and right are invalid"); + goto out; + } else if (!left != !right) { + best = left ? left : right; + pr_debug("only one valid source %s QL=%u", + best->name, best->rx.cd.ql); + goto out; + } + + left_ql_priority = get_ql_priority(left); + right_ql_priority = get_ql_priority(right); + + /* the left and right lists should be the same */ + if (left->priority_list != right->priority_list || + left->priority_list_count != right->priority_list_count) { + pr_err("priority lists on compared ports are different"); + return NULL; + } + /* we can use either left or right priority list */ + for (i = 0; i < left->priority_list_count; i++) { + if (left->priority_list[i] == left_ql_priority) { + best = left; + goto out; + } + if (left->priority_list[i] == right_ql_priority) { + best = right; + goto out; + } + } + + pr_debug("didn't found neither of QLs on priorities list"); +out: + if (!best) { + pr_debug("no valid source found"); + } + return best; +} + +int synce_port_ctrl_init(struct synce_port_ctrl *pc, struct config *cfg, + int rx_enabled, int extended_tlv, int recover_time, + int network_option) +{ + if (!pc) { + pr_err("%s pc is NULL", __func__); + return -ENODEV; + } + + if (!cfg) { + pr_err("%s cfg is NULL", __func__); + return -ENXIO; + } + + pc->transport = synce_transport_create(pc->name); + if (!pc->transport) { + pr_err("init synce_transport failed for port %s", pc->name); + return -ENXIO; + } + + tx_init(&pc->tx, + config_get_int(cfg, pc->name, "tx_heartbeat_msec"), + extended_tlv, pc->transport, pc->name); + if (synce_port_ctrl_thread_create(pc->tx.cd.name, &pc->tx, TX_THREAD, + &pc->tx_thread_id)) { + pr_err("tx thread create failed on port %s", pc->name); + goto transport_err; + } + + if (thread_start_wait(&pc->tx.cd)) { + pr_err("tx thread start wait failed for %s", pc->name); + goto transport_err; + } + + switch (network_option) { + case SYNCE_NETWORK_OPT_1: + pc->priority_list = O1N_priority; + pc->priority_list_count = O1N_PRIORITY_COUNT; + break; + case SYNCE_NETWORK_OPT_2: + pc->priority_list = O2N_priority; + pc->priority_list_count = O2N_PRIORITY_COUNT; + break; + default: + pr_err("wrong network option - only 1 and 2 supported"); + goto transport_err; + } + + if (rx_enabled) { + rx_init(&pc->rx, + config_get_int(cfg, pc->name, "rx_heartbeat_msec"), + extended_tlv, recover_time, pc->transport, pc->name, + cfg, network_option); + + if (synce_port_ctrl_thread_create(pc->rx.cd.name, + &pc->rx, RX_THREAD, + &pc->rx_thread_id)) { + pr_err("rx thread create failed on port %s", pc->name); + goto rx_err; + } + if (thread_start_wait(&pc->rx.cd)) { + pr_err("tx thread start wait failed for %s", pc->name); + goto rx_err; + } + } else { + pc->rx.cd.enabled = 0; + pr_debug("rx thread not needed on port %s", pc->name); + } + + return 0; +rx_err: + thread_stop_wait(&pc->tx.cd); +transport_err: + synce_transport_delete(pc->transport); + + return -ECHILD; +} + +struct synce_port_ctrl *synce_port_ctrl_create(const char *name) +{ + struct synce_port_ctrl *p = NULL; + + if (!name) { + pr_err("name not profided in %s", __func__); + return NULL; + } + + p = malloc(sizeof(struct synce_port_ctrl)); + if (!p) { + pr_err("could not alloc synce_port_ctrl for %s", name); + return NULL; + } + + memcpy(p->name, name, sizeof(p->name)); + + return p; +} + +void synce_port_ctrl_invalidate_rx_ql(struct synce_port_ctrl *pc) +{ + pc->rx.last_ql = pc->rx.ql_dnu_val; + pc->rx.cd.ql = pc->rx.ql_dnu_val; +} diff --git a/synce_port_ctrl.h b/synce_port_ctrl.h new file mode 100644 index 000000000000..ae022564880f --- /dev/null +++ b/synce_port_ctrl.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_port_ctrl.h + * @brief Interface between synce port and socket handling theads, used + * for controling data on the wire. Allows acquire incoming data and + * submit new outgoing data. + * TX thread is always present, RX only if required (internal_input mode). + * @note Copyright (C) 2022 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#ifndef HAVE_SYNC_PORT_CTRL_H +#define HAVE_SYNC_PORT_CTRL_H +#include <stdint.h> + +/* Opaque types */ +struct synce_port_ctrl; +struct config; +struct synce_msg_ext_ql; + +/** + * Check if created threads are running. + * + * @param pc Questioned instance + * @return 1 if true, 0 if false, negative on failure + */ +int synce_port_ctrl_running(struct synce_port_ctrl *pc); + +/** + * Stop threads, deinit given instance. + * + * @param pc Managed instance + * @return 0 on success, otherwise fault + */ +int synce_port_ctrl_destroy(struct synce_port_ctrl *pc); + +/** + * Check if QL-failed condition is present. + * + * @param pc Questioned instance + * @return 1 if true, 0 if false, negative on failure + */ +int synce_port_ctrl_rx_ql_failed(struct synce_port_ctrl *pc); + +/** + * Check if Do Not Use QL is present on a port. + * + * @param pc Questioned instance + * @param dnu Value to compare against + * @return 1 if true, 0 if false, negative on failure + */ +int synce_port_ctrl_rx_dnu(struct synce_port_ctrl *pc, uint8_t dnu); + +/** + * Check if QL has changed on RX. + * + * @param pc Questioned instance + * @return 1 if true, 0 if false, negative on failure + */ +int synce_port_ctrl_rx_ql_changed(struct synce_port_ctrl *pc); + +/** + * Check if extended TLV was acquired on RX wire. + * + * @param pc Questioned instance + * @return 1 if true, 0 if false, negative on failure + */ +int synce_port_ctrl_rx_ext_tlv(struct synce_port_ctrl *pc); + +/** + * Acquire last QL on the RX wire. + * + * @param pc Questioned instance + * @param ql Returned QL + * @return 0 on success, negative on failure + */ +int synce_port_ctrl_get_rx_ql(struct synce_port_ctrl *pc, uint8_t *ql); + +/** + * Acquire last extended QL on the RX wire. + * + * @param pc Questioned instance + * @param ext_ql Returned extended QL struct + * @return 0 on success, negative on failure + */ +int synce_port_ctrl_get_rx_ext_ql(struct synce_port_ctrl *pc, + struct synce_msg_ext_ql *ext_ql); + +/** + * Set QL for TX thread. + * + * @param pc Managed instance + * @param ql QL to be sent + * @return 0 on success, negative on failure + */ +int synce_port_ctrl_set_tx_ql(struct synce_port_ctrl *pc, uint8_t ql); + +/** + * Set extended QL for TX thread. + * + * @param pc Managed instance + * @param ext_ql Extended QL to be sent + * @return 0 on success, negative on failure + */ +int synce_port_ctrl_set_tx_ext_ql(struct synce_port_ctrl *pc, + struct synce_msg_ext_ql *ext_ql); +/** + * Whenever new QL was set for TX thread, rebuild must be invoked explicitly. + * + * @param pc Managed instance + * @return 0 on success, negative on failure + */ +int synce_port_ctrl_rebuild_tx(struct synce_port_ctrl *pc); + +/** + * Explicit start sending QL that was set for TX thread, used once init and set + * QL are finished. + * + * @param pc Managed instance + * @return 0 on success, negative on failure + */ +int synce_port_ctrl_enable_tx(struct synce_port_ctrl *pc); + +/** + * Check if sources given port sources are valid, than compare them, + * choose the one with higher priority in terms of its received QL. + * + * @param left Port instance for comparison + * @param right Port instance for comparison + * @return Pointer to a higher quality input port instance, + * NULL on failure or equal + */ +struct synce_port_ctrl +*synce_port_ctrl_compare_ql(struct synce_port_ctrl *left, + struct synce_port_ctrl *right); + +/** + * Initialize given instance with the given config. + * + * @param pc Instance to be initialized + * @param cfg Configuration of SYNCE type + * @param rx_enabled If RX thread shall also start + * @param extended_tlv If extended tlv was enabled + * @param recover_time What time was set for recovery [s] + * @param network_option Network option, either 0 or 1 + * @return 0 on success, otherwise fail + */ +int synce_port_ctrl_init(struct synce_port_ctrl *pc, struct config *cfg, + int rx_enabled, int extended_tlv, int recover_time, + int network_option); + +/** + * Create instance and set name of its port. + * + * @param name Port name + * @return Pointer to allocated instance + */ +struct synce_port_ctrl *synce_port_ctrl_create(const char *name); + +/** + * Invalidate QL received in the past. + * + * @param pc Port control instance + */ +void synce_port_ctrl_invalidate_rx_ql(struct synce_port_ctrl *pc); + +#endif /* HAVE_SYNC_PORT_CTRL_H */ -- 2.34.1 |
From: Arkadiusz K. <ark...@in...> - 2022-05-02 09:19:01
|
Interface is used to create and store an opened socket for sending and receiving SyncE PDU's on a given NIC interface. Co-developed-by: Michal Michalik <mic...@in...> Signed-off-by: Michal Michalik <mic...@in...> Signed-off-by: Arkadiusz Kubalewski <ark...@in...> --- synce_transport.c | 116 ++++++++++++++++++++++++++++++++++++++ synce_transport.h | 67 ++++++++++++++++++++++ synce_transport_private.h | 30 ++++++++++ 3 files changed, 213 insertions(+) create mode 100644 synce_transport.c create mode 100644 synce_transport.h create mode 100644 synce_transport_private.h diff --git a/synce_transport.c b/synce_transport.c new file mode 100644 index 000000000000..d32e63a6e72b --- /dev/null +++ b/synce_transport.c @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_transport.c + * @brief Implements the Sync-E transport interface. + * @note Copyright (C) 2022 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include <errno.h> +#include <net/if.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/socket.h> +#include <linux/if_ether.h> + +#include "sk.h" +#include "print.h" +#include "synce_msg.h" +#include "synce_msg_private.h" +#include "esmc_socket.h" +#include "synce_transport.h" +#include "synce_transport_private.h" + + +struct synce_transport *synce_transport_create(const char *iface) +{ + struct synce_transport *transport; + + transport = malloc(sizeof(struct synce_transport)); + if (!transport) { + pr_err("transport creation in Sync-E failed"); + return NULL; + } + + if (strnlen(iface, IFNAMSIZ) == IFNAMSIZ) { + pr_err("interface name too long"); + goto err; + } + strncpy(transport->iface, iface, + (sizeof(transport->iface)/sizeof(*transport->iface))); + + transport->raw_socket_fd = open_esmc_socket(iface); + if (transport->raw_socket_fd < 0) { + pr_err("socket creation in Sync-E transport failed"); + goto err; + } + + transport->iface_index = sk_interface_index(transport->raw_socket_fd, + iface); + if (transport->iface_index < 0) { + goto err_socket; + } + + return transport; + +err_socket: + close(transport->raw_socket_fd); +err: + free(transport); + + return NULL; +} + +void synce_transport_delete(struct synce_transport *transport) +{ + close(transport->raw_socket_fd); + free(transport); +} + +int synce_transport_send_pdu(struct synce_transport *transport, + struct synce_pdu *pdu) +{ + int tlvs_size, pdu_size; + + tlvs_size = synce_msg_get_esmc_tlvs_size(pdu); + + pdu_size = sizeof(pdu->header) + tlvs_size; + + if (send_raw_esmc_frame(transport->raw_socket_fd, (void *)pdu, + pdu_size, transport->iface_index)) { + return -EIO; + } + + return 0; +} + +int synce_transport_recv_pdu(struct synce_transport *transport, + struct synce_pdu *pdu) +{ + int ret; + + synce_msg_reset_tlvs(pdu); + + ret = recv_raw_esmc_frame(transport->raw_socket_fd, pdu); + if (ret < 0) { + return -EAGAIN; + } else if (ret > 0 && ret < ETH_ZLEN) { + pr_err("%s failed: received only %i bytes", __func__, ret); + return -EBADMSG; + } + + synce_msg_recover_tlvs(pdu); + + return 0; +} diff --git a/synce_transport.h b/synce_transport.h new file mode 100644 index 000000000000..a74b5329d949 --- /dev/null +++ b/synce_transport.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_transport.h + * @brief Implements the Sync-E transport interface. + * @note Copyright (C) 2022 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#ifndef HAVE_SYNCE_TRANSPORT_H +#define HAVE_SYNCE_TRANSPORT_H + +#include <stdint.h> + +struct synce_transport; +struct ClockIdentity; +struct synce_pdu; + +/** + * Create a Sync-E transport. + * + * This high level API creates Sync-E transport but and initialize it. + * + * @param iface A name of interface to create transport for + * @return A Sync-E transport structure + */ +struct synce_transport *synce_transport_create(const char *iface); + +/** + * Delete a Sync-E transport. + * + * This high level API deletes Sync-E transport and frees all memory allocations. + * + * @param transport A Sync-E transport interface + */ +void synce_transport_delete(struct synce_transport *transport); + +/** + * Send PDU via Sync-E transport. + * + * @param transport A Sync-E transport interface + * @param pdu A pointer to a ESMC Sync-E PDU + * @return Zero on success, non-zero if failure + */ +int synce_transport_send_pdu(struct synce_transport *transport, + struct synce_pdu *pdu); + +/** + * Recv PDU via Sync-E transport. + * + * @param transport A Sync-E transport interface + * @param pdu A pointer to a ESMC Sync-E PDU + * @return Zero on success, non-zero if failure + */ +int synce_transport_recv_pdu(struct synce_transport *transport, + struct synce_pdu *pdu); + +#endif diff --git a/synce_transport_private.h b/synce_transport_private.h new file mode 100644 index 000000000000..f73e879bf6ca --- /dev/null +++ b/synce_transport_private.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_transport_private.h + * @brief Implements the Sync-E transport private structures. + * @note Copyright (C) 2021 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#ifndef HAVE_SYNCE_TRANSPORT_PRIVATE_H +#define HAVE_SYNCE_TRANSPORT_PRIVATE_H + +#include <net/if.h> + +struct synce_transport { + char iface[IFNAMSIZ]; + int iface_index; + int raw_socket_fd; +}; + +#endif -- 2.34.1 |
From: Arkadiusz K. <ark...@in...> - 2022-05-02 09:19:00
|
Interface used for: - creation, building and storing new SyncE PDU's for later transmission - obtain Quality Level TLV and Extended Quality Level TLV from existing SyncE PDU's synce_msg_private.h has constants and structs as defined by Synchronous Ethernet standard - Recommendation ITU-T G.8264//Y.1364. Co-developed-by: Anatolii Gerasymenko <ana...@in...> Signed-off-by: Anatolii Gerasymenko <ana...@in...> Co-developed-by: Michal Michalik <mic...@in...> Signed-off-by: Michal Michalik <mic...@in...> Signed-off-by: Arkadiusz Kubalewski <ark...@in...> --- synce_msg.c | 271 ++++++++++++++++++++++++++++++++++++++++++++ synce_msg.h | 186 ++++++++++++++++++++++++++++++ synce_msg_private.h | 99 ++++++++++++++++ 3 files changed, 556 insertions(+) create mode 100644 synce_msg.c create mode 100644 synce_msg.h create mode 100644 synce_msg_private.h diff --git a/synce_msg.c b/synce_msg.c new file mode 100644 index 000000000000..213e7c9394d4 --- /dev/null +++ b/synce_msg.c @@ -0,0 +1,271 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_msg.c + * @brief Implements the ESMC message type. + * @note Copyright (C) 2022 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include <stdlib.h> +#include <stdbool.h> +#include <sys/queue.h> + +#include "sk.h" +#include "print.h" +#include "address.h" +#include "synce_msg.h" +#include "synce_msg_private.h" + +static void init_tlvs(struct synce_pdu *pdu) +{ + TAILQ_INIT(&pdu->tlv_list); +} + +struct synce_pdu *synce_msg_create(const char *iface) +{ + const unsigned char dstMac[] = SYNCE_DEST_MACADDR; + const unsigned char ituOui[] = SYNCE_ITUOUI; + struct synce_pdu *pdu; + struct address srcMac; + + if (sk_interface_macaddr(iface, &srcMac)) { + pr_err("mac get failed"); + return NULL; + } + + pdu = (struct synce_pdu *) malloc(sizeof(struct synce_pdu)); + if (!pdu) { + pr_err("memory allocation for Sync-E PDU failed"); + return NULL; + } + memset(pdu, 0, sizeof(struct synce_pdu)); + memcpy(&pdu->header.dstAddr, &dstMac, sizeof(pdu->header.dstAddr)); + pdu->header.ethertype = htons(ETH_P_SLOW); + pdu->header.ethersubtype = SYNCE_ETHERSUBTYPE; + memcpy(&pdu->header.ituOui, &ituOui, sizeof(pdu->header.ituOui)); + pdu->header.ituSubtype = htons(SYNCE_ITUSUBTYPE); + pdu->header.verEvtflag = SYNCE_VERSION << SYNCE_VERSION_SHIFT; + memset(&pdu->header.reserved, 0, sizeof(pdu->header.reserved)); + memcpy(&pdu->header.srcAddr, &srcMac.sll.sll_addr, + sizeof(pdu->header.srcAddr)); + + init_tlvs(pdu); + + return pdu; +} + +void synce_msg_delete(struct synce_pdu *pdu) +{ + synce_msg_reset_tlvs(pdu); + free(pdu); +} + +static void attach_esmc_tlv(struct synce_pdu *pdu, struct esmc_tlv *tlv) +{ + TAILQ_INSERT_TAIL(&pdu->tlv_list, tlv, list); +} + +int synce_msg_get_esmc_tlvs_size(struct synce_pdu *pdu) +{ + struct esmc_tlv *tlv; + int size = 0; + + TAILQ_FOREACH(tlv, &pdu->tlv_list, list) { + size += ntohs(tlv->ql_tlv.length); + } + + return size; +} + +static int generate_tlvs(struct synce_pdu *pdu) +{ + struct esmc_tlv *tlv; + void *cur; + int size; + + size = synce_msg_get_esmc_tlvs_size(pdu); + if (ETH_DATA_LEN - ETH_FCS_LEN - size < 0) { + pr_err("too many tlvs"); + return -EMSGSIZE; + } + + cur = (void *)pdu + sizeof(struct esmc_header); + TAILQ_FOREACH(tlv, &pdu->tlv_list, list) { + size = ntohs(tlv->ql_tlv.length); + memcpy(cur, tlv, size); + cur += size; + } + + return 0; +} + +int synce_msg_attach_ql_tlv(struct synce_pdu *pdu, uint8_t ssmCode) +{ + struct esmc_tlv *tlv; + + if (~QL_TLV_SSM_MASK & ssmCode) { + pr_err("4 upper bits of QL TLV ssmCode should not be used"); + return -EINVAL; + } + + tlv = malloc(sizeof(struct esmc_tlv)); + if (!tlv) { + pr_err("malloc failed for TLV: %m"); + return -EINVAL; + } + + tlv->ql_tlv.type = QL_TLV_TYPE; + tlv->ql_tlv.length = htons(QL_TLV_LEN); + tlv->ql_tlv.ssmCode = ssmCode; + + attach_esmc_tlv(pdu, tlv); + + generate_tlvs(pdu); + + return 0; +} + +int synce_msg_get_ql_tlv(struct synce_pdu *pdu, uint8_t *ssmCode) +{ + struct esmc_tlv *tlv; + + if (!pdu || !ssmCode) { + return -ENXIO; + } + + TAILQ_FOREACH(tlv, &pdu->tlv_list, list) { + if (tlv->ql_tlv.type == QL_TLV_TYPE) { + break; + } + } + + if (!tlv) { + return -EAGAIN; + } + + *ssmCode = tlv->ql_tlv.ssmCode; + + return 0; +} + +int synce_msg_attach_extended_ql_tlv(struct synce_pdu *pdu, + struct synce_msg_ext_ql *ext_ql) +{ + struct esmc_tlv *tlv; + + if (!pdu || !ext_ql) { + pr_err("either pdu or ext_ql not provided"); + return -ENXIO; + } + + tlv = malloc(sizeof(struct esmc_tlv)); + if (!tlv) { + pr_err("malloc failed for TLV: %m"); + return -ENOMEM; + } + + tlv->extended_ql_tlv.type = EXT_QL_TLV_TYPE; + tlv->extended_ql_tlv.length = htons(EXT_QL_TLV_LEN); + tlv->extended_ql_tlv.enhancedSsmCode = ext_ql->enhancedSsmCode; + memcpy(&tlv->extended_ql_tlv.clockIdentity, &ext_ql->clockIdentity, + sizeof(tlv->extended_ql_tlv.clockIdentity)); + tlv->extended_ql_tlv.flag = ext_ql->flag; + tlv->extended_ql_tlv.cascaded_eEEcs = ext_ql->cascaded_eEEcs; + tlv->extended_ql_tlv.cascaded_EEcs = ext_ql->cascaded_EEcs; + + attach_esmc_tlv(pdu, tlv); + + generate_tlvs(pdu); + + return 0; +} + +int synce_msg_get_extended_ql_tlv(struct synce_pdu *pdu, + struct synce_msg_ext_ql *ext_ql) +{ + struct esmc_tlv *tlv; + + if (!pdu || !ext_ql) { + return -ENXIO; + } + + TAILQ_FOREACH(tlv, &pdu->tlv_list, list) { + if (tlv->ql_tlv.type == EXT_QL_TLV_TYPE) { + break; + } + } + + if (!tlv) { + return -EAGAIN; + } + + ext_ql->enhancedSsmCode = tlv->extended_ql_tlv.enhancedSsmCode; + memcpy(&ext_ql->clockIdentity, &tlv->extended_ql_tlv.clockIdentity, + sizeof(ext_ql->clockIdentity)); + ext_ql->flag = tlv->extended_ql_tlv.flag; + ext_ql->cascaded_eEEcs = tlv->extended_ql_tlv.cascaded_eEEcs; + ext_ql->cascaded_EEcs = tlv->extended_ql_tlv.cascaded_EEcs; + + return 0; +} + +void synce_msg_reset_tlvs(struct synce_pdu *pdu) +{ + struct esmc_tlv *tlv, *tlv_temp; + + tlv = TAILQ_FIRST(&pdu->tlv_list); + while (tlv != NULL) { + tlv_temp = TAILQ_NEXT(tlv, list); + free(tlv); + tlv = tlv_temp; + } + init_tlvs(pdu); +} + +static bool is_valid_synce_tlv(struct esmc_tlv *tlv) +{ + if (tlv->ql_tlv.type == QL_TLV_TYPE && + ntohs(tlv->ql_tlv.length) == QL_TLV_LEN) + return true; + if (tlv->ql_tlv.type == EXT_QL_TLV_TYPE && + ntohs(tlv->ql_tlv.length) == EXT_QL_TLV_LEN) + return true; + return false; +} + +void synce_msg_recover_tlvs(struct synce_pdu *pdu) +{ + struct esmc_tlv *tlv, *new_tlv; + + if (TAILQ_EMPTY(&pdu->tlv_list)) { + init_tlvs(pdu); + } + + synce_msg_reset_tlvs(pdu); + + tlv = (struct esmc_tlv *)((void *)pdu + sizeof(struct esmc_header)); + while (is_valid_synce_tlv(tlv)) { + new_tlv = malloc(sizeof(struct esmc_tlv)); + if (!new_tlv) { + pr_err("allocate new_tlv failed"); + return; + } + memset(new_tlv, 0, sizeof(*new_tlv)); + /* We copy data from receiving buffer into larger structure. The + * TLV size is validated in is_valid_synce_tlv before copying. + */ + memcpy(new_tlv, tlv, ntohs(tlv->ql_tlv.length)); + TAILQ_INSERT_TAIL(&pdu->tlv_list, new_tlv, list); + tlv = (struct esmc_tlv *)((void *)tlv + ntohs(tlv->ql_tlv.length)); + } +} diff --git a/synce_msg.h b/synce_msg.h new file mode 100644 index 000000000000..46245d21c415 --- /dev/null +++ b/synce_msg.h @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_msg.h + * @brief Implements the ESMC message type. + * @note Copyright (C) 2022 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#ifndef HAVE_SYNCE_MSG_H +#define HAVE_SYNCE_MSG_H + +#include "ddt.h" + +#define SYNCE_NETWORK_OPT_1 1 +#define SYNCE_NETWORK_OPT_2 2 + +/* Enhanced SSM codes for SyncE */ +#define QL_EEC1_ENHSSM 0xFF +#define QL_EEC2_ENHSSM 0xFF +#define QL_OTHER_CLOCK_TYPES_ENHSSM 0xFF +#define QL_PRTC_ENHSSM 0x20 +#define QL_ePRTC_ENHSSM 0x21 +#define QL_eEEC_ENHSSM 0x22 +#define QL_ePRC_ENHSSM 0x23 + +/* SSM codes and enhanced SSM codes for SyncE in option 1 networks */ +#define O1N_QL_PRC_SSM 0x2 +#define O1N_QL_PRC_ENHSSM 0xFF +#define O1N_QL_SSU_A_SSM 0x4 +#define O1N_QL_SSU_A_ENHSSM 0xFF +#define O1N_QL_SSU_B_SSM 0x8 +#define O1N_QL_SSU_B_ENHSSM 0xFF +#define O1N_QL_EEC1_SSM 0xB +#define O1N_QL_EEC1_ENHSSM 0xFF +#define O1N_QL_DNU_SSM 0xF +#define O1N_QL_DNU_ENHSSM 0xFF +#define O1N_QL_PRTC_SSM 0x2 +#define O1N_QL_PRTC_ENHSSM 0x20 +#define O1N_QL_EPRTC_SSM 0x2 +#define O1N_QL_EPRTC_ENHSSM 0x21 +#define O1N_QL_EEEC_SSM 0xB +#define O1N_QL_EEEC_ENHSSM 0x22 +#define O1N_QL_EPRC_SSM 0x2 +#define O1N_QL_EPRC_ENHSSM 0x23 + +/* SSM codes and enhanced SSM codes for SyncE in option 2 networks */ +#define O2N_QL_PRS_SSM 0x1 +#define O2N_QL_PRS_ENHSSM 0xFF +#define O2N_QL_STU_SSM 0x0 +#define O2N_QL_STU_ENHSSM 0xFF +#define O2N_QL_ST2_SSM 0x7 +#define O2N_QL_ST2_ENHSSM 0xFF +#define O2N_QL_TNC_SSM 0x4 +#define O2N_QL_TNC_ENHSSM 0xFF +#define O2N_QL_ST3E_SSM 0xD +#define O2N_QL_ST3E_ENHSSM 0xFF +#define O2N_QL_ST3_SSM 0xA +#define O2N_QL_ST3_ENHSSM 0xFF +#define O2N_QL_EEC2_SSM 0xA +#define O2N_QL_EEC2_ENHSSM 0xFF +#define O2N_QL_PROV_SSM 0xE +#define O2N_QL_PROV_ENHSSM 0xFF +#define O2N_QL_DUS_SSM 0xF +#define O2N_QL_DUS_ENHSSM 0xFF +#define O2N_QL_PRTC_SSM 0x1 +#define O2N_QL_PRTC_ENHSSM 0x20 +#define O2N_QL_EPRTC_SSM 0x1 +#define O2N_QL_EPRTC_ENHSSM 0x21 +#define O2N_QL_EEEC_SSM 0xA +#define O2N_QL_EEEC_ENHSSM 0x22 +#define O2N_QL_EPRC_SSM 0x1 +#define O2N_QL_EPRC_ENHSSM 0x23 + +/* Flags as defined in SyncE specification */ +#define MIXED_EEC_CHAIN_FLAG (1 << 0) +#define PARTIAL_EEC_CHAIN_FLAG (1 << 1) + +/* 5 seconds is a period defined by standard for QL-failed state */ +#define QL_FAILED_PERIOD_SEC 5 + +struct synce_msg_ext_ql { + uint8_t enhancedSsmCode; + struct ClockIdentity clockIdentity; + uint8_t flag; + uint8_t cascaded_eEEcs; + uint8_t cascaded_EEcs; +}; + +/** + * Create a ESMC Protocol Data Unit (PDU) template. + * + * This high level API creates structure for storing ESMC PDU and + * initializes TLV vector for conveniently storing and processing + * TLV structures (esmc_tlv). All the fields are stored in network + * byte ordering. + * + * @param iface A name of interface to create prepopulated template for + * @return A pointer to a ESMC Sync-E PDU prepopulated template + */ +struct synce_pdu *synce_msg_create(const char *iface); + +/** + * Delete a ESMC Protocol Data Unit (PDU) from memory. + * + * This high level API frees all the memory stored in TLV vector and + * then free ESMC PDU itself. + * + * @param pdu A pointer to a ESMC Sync-E PDU + */ +void synce_msg_delete(struct synce_pdu *pdu); + +/** + * Attach QL TLV to Sync-E PDU. + * + * @param pdu A pointer to a ESMC Sync-E PDU + * @param ssmCode SSM Code of newly created QL TLV + * @return Zero on success, non-zero if failure + */ +int synce_msg_attach_ql_tlv(struct synce_pdu *pdu, uint8_t ssmCode); + +/** + * Get QL TLVs SSM Code from Sync-E PDU. + * + * @param pdu A pointer to a ESMC Sync-E PDU + * @param ssmCode SSM Code variable to store + * @return Zero on success, non-zero if failure + */ +int synce_msg_get_ql_tlv(struct synce_pdu *pdu, uint8_t *ssmCode); + +/** + * Attach Extended QL TLV to Sync-E PDU. + * + * @param pdu A pointer to a ESMC Sync-E PDU + * @param enhancedSsmCode Enhanced SSM Code of newly created QL TLV + * @param clockIdentity SyncE clockIdentity of the originator of the extended QL TLV + * @param flag Flag (see ITU-T G.8264) + * @param cascaded_eEEcs Number of cascaded eEECs from the nearest SSU/PRC/ePRC + * @param cascaded_EEcs Number of cascaded EECs from the nearest SSU/PRC/ePRC + * @return Zero on success, non-zero if failure + */ +int synce_msg_attach_extended_ql_tlv(struct synce_pdu *pdu, + struct synce_msg_ext_ql *ext_ql); + +/** + * Get QL Extended TLVs from Sync-E PDU in. + * + * @param pdu A pointer to a ESMC Sync-E PDU + * @param ext_ql A pointer to Extended Quality level + * @return Zero on success, non-zero if failure + */ +int synce_msg_get_extended_ql_tlv(struct synce_pdu *pdu, + struct synce_msg_ext_ql *ext_ql); + +/** + * Reset (clear and reinitialize) all the TLV vector entries. + * + * @param pdu A pointer to a ESMC Sync-E PDU + */ +void synce_msg_reset_tlvs(struct synce_pdu *pdu); + +/** + * Recover TLVs from ESMC frame into TLV vector entries in PDU structure. + * + * @param pdu A pointer to a ESMC Sync-E PDU + */ +void synce_msg_recover_tlvs(struct synce_pdu *pdu); + +/** + * Returns the size of TLV vector entries in PDU. + * + * @param pdu A pointer to a ESMC Sync-E PDU + * @return Number of bytes of all TLV entries attached to PDU + */ +int synce_msg_get_esmc_tlvs_size(struct synce_pdu *pdu); + +#endif diff --git a/synce_msg_private.h b/synce_msg_private.h new file mode 100644 index 000000000000..bca319c01f41 --- /dev/null +++ b/synce_msg_private.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * @file synce_msg_private.h + * @brief Implements the ESMC message private structures. + * @note Copyright (C) 2021 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#ifndef HAVE_SYNCE_MSG_PRIVATE_H +#define HAVE_SYNCE_MSG_PRIVATE_H + +/* Sync-E Frame */ +#define SYNCE_DEST_MACADDR { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x02 } +#define SYNCE_ETHERTYPE ETH_P_SLOW +#define SYNCE_ETHERSUBTYPE 0x0A +#define SYNCE_ITUOUI { 0x00, 0x19, 0xA7 } +#define SYNCE_ITUOUI_SIZE 3 +#define SYNCE_ITUSUBTYPE 0x0001 +#define SYNCE_VERSION 1 +#define SYNCE_VERSION_SHIFT 4 +#define SYNCE_EVENT_SHIFT 4 + +/* QL TLV */ +#define QL_TLV_TYPE 1 +#define QL_TLV_LEN 0x4 +#define QL_TLV_SSM_MASK 0x0f +#define EXT_QL_TLV_TYPE 2 +#define EXT_QL_TLV_LEN 0x14 + +#include <errno.h> +#include <sys/queue.h> +#include <linux/if_ether.h> + +#include "ddt.h" +#include "ether.h" + +struct macaddr { + uint8_t addr[MAC_LEN]; +} PACKED; + +struct ql_tlv { + uint8_t type; + uint16_t length; + uint8_t ssmCode; /* unused:4 | SSM code:4 */ +} PACKED; + +struct extended_ql_tlv { + uint8_t type; + uint16_t length; + uint8_t enhancedSsmCode; + struct ClockIdentity clockIdentity; + uint8_t flag; + uint8_t cascaded_eEEcs; + uint8_t cascaded_EEcs; + uint8_t reserved[5]; +} PACKED; + +struct esmc_tlv { + union { + struct ql_tlv ql_tlv; + struct extended_ql_tlv extended_ql_tlv; + }; + TAILQ_ENTRY(esmc_tlv) list; +} PACKED; + +struct esmc_header { + struct macaddr dstAddr; + struct macaddr srcAddr; + uint16_t ethertype; + uint8_t ethersubtype; + uint8_t ituOui[SYNCE_ITUOUI_SIZE]; /* Organizationally Unique Identifier */ + uint16_t ituSubtype; + uint8_t verEvtflag; /* version:4 | event flag:1 | reserved:3 */ + uint8_t reserved[3]; +} PACKED; + +struct esmc_data { + uint8_t buffer[ETH_DATA_LEN]; +}; + +struct synce_pdu { + union { + struct esmc_header header; + struct esmc_data data; + } PACKED; + TAILQ_HEAD(ql_tlv_list, esmc_tlv) tlv_list; +}; + +#endif -- 2.34.1 |
From: Arkadiusz K. <ark...@in...> - 2022-05-02 09:19:02
|
Add main code of synce4l - user space application implementing standard - Recommendation ITU-T G.8264/Y.1364. On init the application parses SyncE config file and initializes a synce_clock with SyncE devices and configured ports. synce_clock is later polled until application termination. Add documentation in form of Linux manual and 'synce4l_README.md'. Add synce4l to the linuxptp makefile. Co-developed-by: Andrzej Sawula <and...@in...> Signed-off-by: Andrzej Sawula <and...@in...> Co-developed-by: Piotr Kwapulinski <pio...@in...> Signed-off-by: Piotr Kwapulinski <pio...@in...> Co-developed-by: Michal Michalik <mic...@in...> Signed-off-by: Michal Michalik <mic...@in...> Signed-off-by: Arkadiusz Kubalewski <ark...@in...> --- README.org | 2 + makefile | 17 ++-- synce4l.8 | 239 ++++++++++++++++++++++++++++++++++++++++++++++ synce4l.c | 144 ++++++++++++++++++++++++++++ synce4l_README.md | 194 +++++++++++++++++++++++++++++++++++++ 5 files changed, 590 insertions(+), 6 deletions(-) create mode 100644 synce4l.8 create mode 100644 synce4l.c create mode 100644 synce4l_README.md diff --git a/README.org b/README.org index 063b5425da43..ab0d5a02022e 100644 --- a/README.org +++ b/README.org @@ -51,6 +51,8 @@ - Supports bonded, IPoIB, and vlan interfaces. + - Supports Synchronous Ethernet (Recommendation ITU-T G.8264/Y.1364). + * Getting the Code You can download the latest released version at Source Forge. diff --git a/makefile b/makefile index 33e7ca0f038b..1973c1dd2fa9 100644 --- a/makefile +++ b/makefile @@ -22,20 +22,22 @@ CC = $(CROSS_COMPILE)gcc VER = -DVER=$(version) CFLAGS = -Wall $(VER) $(incdefs) $(DEBUG) $(EXTRA_CFLAGS) LDLIBS = -lm -lrt -pthread $(EXTRA_LDFLAGS) -PRG = ptp4l hwstamp_ctl nsm phc2sys phc_ctl pmc timemaster ts2phc +PRG = ptp4l hwstamp_ctl nsm phc2sys phc_ctl pmc timemaster ts2phc synce4l FILTERS = filter.o mave.o mmedian.o SERVOS = linreg.o ntpshm.o nullf.o pi.o servo.o TRANSP = raw.o transport.o udp.o udp6.o uds.o TS2PHC = ts2phc.o lstab.o nmea.o serial.o sock.o ts2phc_generic_master.o \ ts2phc_master.o ts2phc_phc_master.o ts2phc_nmea_master.o ts2phc_slave.o +SYNCE = esmc_socket.o synce_clock.o synce_dev.o synce_dev_ctrl.o \ + synce_msg.o synce_port.o synce_port_ctrl.o synce_transport.o + OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o designated_fsm.o \ e2e_tc.o fault.o $(FILTERS) fsm.o hash.o interface.o monitor.o msg.o phc.o \ port.o port_signaling.o pqueue.o print.o ptp4l.o p2p_tc.o rtnl.o $(SERVOS) \ - sk.o stats.o tc.o $(TRANSP) telecom.o tlv.o tsproc.o unicast_client.o \ - unicast_fsm.o unicast_service.o util.o version.o - -OBJECTS = $(OBJ) hwstamp_ctl.o nsm.o phc2sys.o phc_ctl.o pmc.o pmc_agent.o \ - pmc_common.o sysoff.o timemaster.o $(TS2PHC) + sk.o stats.o tc.o $(TRANSP) telecom.o tlv.o tsproc.o \ + unicast_client.o unicast_fsm.o unicast_service.o util.o version.o +OBJECTS = $(OBJ) hwstamp_ctl.o nsm.o phc2sys.o phc_ctl.o pmc.o pmc_common.o \ + pmc_common.o $(SYNCE) sysoff.o timemaster.o $(TS2PHC) SRC = $(OBJECTS:.o=.c) DEPEND = $(OBJECTS:.o=.d) srcdir := $(dir $(lastword $(MAKEFILE_LIST))) @@ -50,6 +52,9 @@ man8dir = $(mandir)/man8 all: $(PRG) +synce4l: config.o hash.o interface.o phc.o print.o sk.o $(SYNCE) util.o \ + version.o + ptp4l: $(OBJ) nsm: config.o $(FILTERS) hash.o interface.o msg.o nsm.o phc.o print.o \ diff --git a/synce4l.8 b/synce4l.8 new file mode 100644 index 000000000000..3f7c7845fe55 --- /dev/null +++ b/synce4l.8 @@ -0,0 +1,239 @@ +.\" Manpage for synce4l. +.\" Contact lin...@li... to correct errors or typos. +.TH man 8 "21 Apr 2022" "1.0" "synce4l man page" + +.SH NAME +synce4l \- Synchronous Ethernet (Sync-E) controller application + +.SH SYNOPSIS +synce4l -f [file] [-l <num>] [-m] [-q] [-v] [-h] + +.SH DESCRIPTION +synce4l is an implementation of the Synchronous Ethernet (Sync-E) protocol +according to ITU-T Rec. G.8264. The design goal is to provide logic to supported +hardware by processing Ethernet Synchronization Messaging Channel (ESMC) and +control Digital Phase Locked Loop (DPLL) clock on Network Card Interface (NIC). +.P +Application can operate in two mutually exclusive modes. If both modes are +configured simultaneously, the external input takes precedence over internal one +(internal is disabled implicitly). + +.TP +External input +If `synce4l` is configured to run in "external input" mode then DPLL needs to +have external 1PPS source attached (GPS or other generator). +In this scenario `synce4l` always broadcasts clock quality level (QL) defined +in configuration file. Additionally, for "external input" mode incoming Sync-E +frames do not participate in best source selection algorithm for DPLL. + +.TP +Internal input +In "internal input" mode incoming Sync-E frames are processed and best clock +source is extracted from the link having the best quality level. +`synce4l` configures such "best quality" port as a source to recover clock +for DPLL. The recovered QL is broadcasted to all other interfaces then. +An external clock cannot be used in this mode. + +.SH OPTIONS +.TP +.B \-f [file] +read configuration from 'file' (config file takes precedence over +command line arguments) +.TP +.B \-l [num] +set the logging level to 'num' (0: least detailed, 7: most detailed) +.TP +.B \-m +print messages to stdout +.TP +.B \-q +do not print messages to the syslog +.TP +.B \-v +print synce4l version and exit +.TP +.B \-h +print this message and exit + +.SH CONFIGURATION FILE +Configuration file contains three sections: +.IP - +global, +.IP - +device, +.IP - +port. + +.SS Global section +This section starts with `[global]` keyword. It sets the logging options. +.RS +Available options: +.IP logging_level +Minimum log level required to appear in a log. +.P +.RS +Defaults to 6, valid values are 0 - 7 +.RE +.IP message_tag +Tag reported in a log. +.IP use_syslog +Set to 1 if `syslog` should be used. +.P +.RS +Defaults to 1, valid values are 0 or 1 +.RE +.IP verbose +Set to 1 to log extra information. +.P +.RS +Defaults to 0, valid values are 0 or 1 +.RE +.RE + +.SS Device section +This section specifies the configuration of a device e.g. ptp1. +It starts with the full path name of a device e.g. [/dev/ptp1]. +DPLL state is retrieved using shell command defined by the user. Expected values +for each dpll state are also defined by the user. +.RS +Available options: +.IP internal_input +Set to 1 to enable "internal input" mode. Mutually exclusive with +"external_input" parameter. +.P +.RS +Defaults to 1, valid values are 0 or 1 +.RE +.IP external_input +Set to 1 to enable "external input" mode. Mutually exclusive with +"internal_input" parameter. Takes precedence over "internal_input" parameter. +.P +.RS +Defaults to 0, valid values are 0 or 1 +.RE +.IP external_input_QL +Quality Level (QL) for "external input" mode. +.P +.RS +Valid values specified in ITU-T Recommendations +.RE +.IP external_input_ext_QL +Extended Quality Level for "external input" mode. +.P +.RS +Extended Quality Level for "external input" mode. +.RE +.IP extended_tlv +Set to 1 to enable extended QL. +.P +.RS +Defaults to 0, valid values are 0 or 1 +.RE +.IP network_option +Network option according to T-REC-G.8264. All devices in Sync-E domain +should have the same option configured. +.P +.RS +Defaults to 1, valid values are 1 or 2 +.RE +.IP recover_time +Seconds indicating the minimum time to recover from the QL-failed state on the port. +.P +.RS +Defaults to 60, valid values are 10 - 720 +.RE +.IP get_dpll_state_cmd +Defines a shell command which will be executed by synce4l to acquire current +state of a Sync-E DPLL on the device. Separated command must be provided by the +user for each device confgured for frequency synchronization. The command shall +output current state of DPLL to stdout, expected values are defined (also by the +user) with following configuration items: +dpll_holdover_value, dpll_locked_ho_value, dpll_locked_value, +dpll_freerun_value, dpll_invalid_value. +.RS +.RE +.IP dpll_holdover_value +Defines a string value expected on stdout stream when DPLL is in HOLDOVER state. +.P +.RS +.RE +.IP dpll_locked_ho_value +Defines a string value expected on stdout stream when DPLL is in LOCKED HOLDOVER +state. +.P +.RS +.RE +.IP dpll_locked_value +Defines a string value expected on stdout stream when DPLL is in LOCKED state. +.P +.RS +.RE +.IP dpll_freerun_value +Defines a string value expected on stdout stream when DPLL is in FREERUN state. +.P +.RS +.RE +.IP dpll_invalid_value +Defines a string value expected on stdout stream when DPLL is in INVALID state. +.P +.RE + +.SS Port section +Any other section not starting with `/` (e.g. [eth0]) is the port section. +Multiple port sections are allowed. Each port participates in Sync-E +communication. +.RS +Available options: +.IP sync +Set to 1 to enable the port for Sync-E communication (i.e. best source +selection and QLs advertisement). +.P +.RS +Defaults to 0, valid values are 0 or 1 +.RE +.IP tx_heartbeat_msec +Interval between consecutive Sync-E frame transmissions (1000ms recommended). +.P +.RS +Defaults to 1000, valid values are 100 - 3000 +.RE +.IP rx_heartbeat_msec +Interval between consecutive Sync-E socket polls (frame receive). +.P +.RS +Defaults to 50, valid values are 10 - 500 +.RE +.IP recover_clock_enable_cmd +A shell command which enables PHY port pointed by this Port section as a source +of frequency for the Sync-E DPLL on this device (required only in +"internal input" mode). +.RS +.RE +.IP recover_clock_disable_cmd +A shell command which disables PHY port pointed by this Port section as a source +of frequency for the Sync-E DPLL on this device (required only in +"internal input" mode). +.IP allowed_qls +List of integers containing allowed SSM QLs separated by comma (`,`), other +received ones would be discarded. If parameter is not provided, all QLs will be +accepted. +.P +.IP allowed_ext_qls +List of integers containing allowed extended SSM QLs separated by comma (`,`), +other received ones would be discarded. If parameter is not provided, all QLs will +be accepted. +.P +.RE + +.SH SEE ALSO +.BR ptp4l (8) + +.SH BUGS +No known bugs. + +.SH AUTHOR +Arkadiusz Kubalewski <ark...@in...> +.P +Michal Michalik <mic...@in...> +.P +Piotr Kwapulinski <pio...@in...> diff --git a/synce4l.c b/synce4l.c new file mode 100644 index 000000000000..26d39376892a --- /dev/null +++ b/synce4l.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-only +/** + * @file synce4l.c + * @brief Synchronous Ethernet (Sync-E) userspace client + * @note Copyright (C) 2021 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/queue.h> +#include <errno.h> + +#include "synce_clock.h" +#include "config.h" +#include "print.h" +#include "version.h" + +static void usage(char *progname) +{ + fprintf(stderr, + "\nusage: %s [options]\n\n" + " ...\n\n" + " Other Options\n\n" + " -f [file] read configuration from 'file'\n" + " (config file takes precedence over command line arguments)\n" + " -l [num] set the logging level to 'num'\n" + " (%d: least detailed, %d: most detailed)\n" + " -m print messages to stdout\n" + " -q do not print messages to the syslog\n" + " -v print synce4l version and exit\n" + " -h print this message and exit\n" + "\n", + progname, PRINT_LEVEL_MIN, PRINT_LEVEL_MAX); +} + +static void synce4l_cleanup(struct config *cfg) +{ + if (cfg) + config_destroy(cfg); +} + +int main(int argc, char *argv[]) +{ + int c, err = -EACCES, index, print_level; + char *config = NULL, *progname; + struct synce_clock *clock; + struct option *opts; + struct config *cfg; + + if (handle_term_signals()) + return -EPERM; + + cfg = config_create_synce(); + if (!cfg) { + return -EINVAL; + } + opts = config_long_options(cfg); + + /* Process the command line arguments. */ + progname = strrchr(argv[0], '/'); + progname = progname ? 1+progname : argv[0]; + while (EOF != (c = getopt_long(argc, argv, "f:l:mqvh", + opts, &index))) { + switch (c) { + case 'f': + config = optarg; + break; + case 'l': + if (get_arg_val_i(c, optarg, &print_level, + PRINT_LEVEL_MIN, PRINT_LEVEL_MAX)) + goto out; + config_set_int(cfg, "logging_level", print_level); + break; + case 'm': + config_set_int(cfg, "verbose", 1); + break; + case 'q': + config_set_int(cfg, "use_syslog", 0); + break; + case 'v': + version_show(stdout); + synce4l_cleanup(cfg); + return 0; + case 'h': + usage(progname); + synce4l_cleanup(cfg); + return 0; + case '?': + usage(progname); + goto out; + default: + usage(progname); + goto out; + } + } + + if (config && (c = config_read(config, cfg))) { + synce4l_cleanup(cfg); + return c; + } + + print_set_progname(progname); + print_set_tag(config_get_string(cfg, NULL, "message_tag")); + print_set_verbose(config_get_int(cfg, NULL, "verbose")); + print_set_syslog(config_get_int(cfg, NULL, "use_syslog")); + print_set_level(config_get_int(cfg, NULL, "logging_level")); + + if (STAILQ_EMPTY(&cfg->interfaces)) { + fprintf(stderr, "%s", "no interface specified\n"); + usage(progname); + goto out; + } + + clock = synce_clock_create(cfg); + if (!clock) { + fprintf(stderr, "%s", "failed to create a synce clock\n"); + goto out; + } + + while (is_running()) + if (synce_clock_poll(clock)) + break; + + synce_clock_destroy(clock); + + err = 0; +out: + synce4l_cleanup(cfg); + return err; +} diff --git a/synce4l_README.md b/synce4l_README.md new file mode 100644 index 000000000000..a1fb59324939 --- /dev/null +++ b/synce4l_README.md @@ -0,0 +1,194 @@ +# Table of contents + +1. Introduction +2. Compilation +3. Installation +4. Usage +5. Config + +--- +## 1\. Introduction +`synce4l` is a software implementation of Synchronous Ethernet (Sync-E) according +to ITU-T Recommendation G.8264. The design goal is to provide logic to supported +hardware by processing Ethernet Synchronization Messaging Channel (ESMC) and +control Digital Phase Locked Loop (DPLL) clock on Network Card Interface (NIC). + +Application can operate in two mutually exclusive modes. If both modes are +configured simultaneously, the "external input" mode takes precedence over +internal one ("internal input" mode is disabled implicitly). + +The best source selection is done according to ITU-T Recommendations G.781 and +G.8264. Two network options are supported: option 1 and option 2. + +Table showing priority of quality levels (QLs) in option 1 networks: +| Quality Level | Priority* | SSM | Extended SSM** | +| ------------- | --------- | --- | -------------- | +| ePRTC | 0 | 0x2 | 0x21 | +| PRTC | 1 | 0x2 | 0x20 | +| PRC | 2 | 0x2 | 0xFF | +| SSU-A | 3 | 0x4 | 0xFF | +| SSU-B | 4 | 0x8 | 0xFF | + +Table showing priority of quality levels (QLs) in option 2 networks: +| Quality Level | Priority* | SSM | Extended SSM** | +| ------------- | --------- | --- | -------------- | +| ePRTC | 0 | 0x1 | 0x21 | +| PRTC | 1 | 0x1 | 0x20 | +| PRS | 2 | 0x1 | 0xFF | +| STU | 3 | 0x0 | 0xFF | +| ST2 | 4 | 0x7 | 0xFF | +| TNC | 5 | 0x4 | 0xFF | +| ST3E | 6 | 0xD | 0xFF | +| PROV | 7 | 0xE | 0xFF | + +> *Remark:* *Lower number means higher priority + +> *Remark:* **If extended SSM is not enabled, it's implicitly assumed as `0xFF` + +### External input +If `synce4l` is configured to run in "external" input mode then DPLL needs to +have external 1PPS source attached (GPS or other generator). +In this scenario `synce4l` always broadcasts clock quality level (QL) defined +in configuration file. Additionally, for “external” mode incoming Sync-E +frames do not participate in best source selection algorithm for DPLL. + +### Internal input +In "internal input" mode incoming Sync-E frames are processed and best clock +source is extracted from the link having the best quality level. +`synce4l` configures such "best quality" port as a source to recover clock +for DPLL. The recovered QL is broadcasted to all other interfaces then. +An external clock cannot be used in this mode. + +--- +## 2\. Compilation +Makefile rules are included in linuxptp `makefile` and compilation is done using +the command: +``` +make synce4l +``` + +--- +## 3\. Installation +Use `make install` target to install `synce4l` in a system. + +--- +## 4\. Usage +Use `-h` command line argument to print the help page: +``` +$? ./synce4l -h +usage: synce4l [options] + + ... + + Other Options + + -f [file] read configuration from 'file' + (config file takes precedence over command line arguments) + -l [num] set the logging level to 'num' + (0: least detailed, 7: most detailed) + -m print messages to stdout + -q do not print messages to the syslog + -v print synce4l version and exit + -h print this message and exit +``` + +> *Remark:* `synce4l` requires root privileges since it opens raw sockets. + +--- +## 5\. Config +### Configuration file contains three sections: +- global, +- device, +- port. + +### Global section +This section starts with `[global]` keyword. It sets the logging options. + +| Parameter | Default | Valid values | Description | +| --------------- | ------- | ------------ | ---------------------------------------------- | +| `logging_level` | `6` | `0-7` | Minimum log level required to appear in a log. | +| `message_tag` | None | string | Tag reported in a log. | +| `use_syslog` | `1` | `0`, `1` | Set to 1 if `syslog` should be used. | +| `verbose` | `0` | `0`, `1` | Set to 1 to log extra information. | + +### Device section +This section specifies the configuration of a one logical device e.g. 'synce1'. +The name is defined by the user. The name has no impact for any functionality +except traces. +The name must be enclosed in extra angle bracket when defining new device +section e.g. [<synce1>]. +All ports defined by port sections after the device section will create one +SyncE device (until next device section). + +| Parameter | Default | Valid values | Description | +| ----------------------- | ------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `internal_input` | `1` | `0`, `1` | Set to 1 to enable "internal input" mode. Mutually exclusive with `external_input` parameter. | +| `external_input` | `0` | `0`, `1` | Set to 1 to enable "external input" mode. Mutually exclusive with `internal_input` parameter. Takes precedence over `internal_input` parameter. | +| `external_input_QL` | `0` | `0-15` | Quality Level (QL) for "external input" mode. | +| `external_input_ext_QL` | `0` | `0-255` | Extended Quality Level for "external input" mode. | +| `extended_tlv` | `0` | `0`, `1` | Set to 1 to enable extended QL. | +| `network_option` | `1` | `1`, `2` | Network option according to T-REC-G.8264. All devices in Sync-E domain should have the same option configured. | +| `recover_time` | `60` | `10-720` | Seconds indicating the minimum time to recover from the QL-failed state on the port. | +| `get_dpll_state_cmd` | None | string | Shell command which will be executed by synce4l to acquire current state of a Sync-E DPLL on the device. The command shall output current state of DPLL to stdout. | +| `dpll_holdover_value` | None | string | Value expected on stdout stream when DPLL is in HOLDOVER state, after calling command defined in get_dpll_state_cmd | +| `dpll_locked_ho_value` | None | string | Value expected on stdout stream when DPLL is in LOCKED HOLDOVER state, after calling command defined in get_dpll_state_cmd | +| `dpll_locked_value` | None | string | Value expected on stdout stream when DPLL is in LOCKED state, after calling command defined in get_dpll_state_cmd | +| `dpll_freerun_value` | None | string | Value expected on stdout stream when DPLL is in FREERUN state, after calling command defined in get_dpll_state_cmd | +| `dpll_invalid_value` | None | string | Value expected on stdout stream when DPLL is in INVALID state, after calling command defined in get_dpll_state_cmd | + +### Port section +Any other section not starting with `<` (e.g. [eth0]) is the port section. +Multiple port sections are allowed. Each port participates in Sync-E +communication. + +| Parameter | Default | Valid values | Description | +| --------------------------- | ------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `sync` | `0` | `0`, `1` | Set to 1 to enable the port for Sync-E communication (i.e. best source selection and QLs advertisement). | +| `tx_heartbeat_msec` | `1000` | `100-3000` | Interval between consecutive Sync-E frame transmissions (1000ms is recommended). | +| `rx_heartbeat_msec` | `50` | `10-500` | Interval between consecutive Sync-E socket polls (frame receive). | +| `recover_clock_enable_cmd` | None | string | Shell command which enables PHY port pointed by this Port section as a source of frequency for the Sync-E DPLL on this device (required only in "internal_input" mode). | +| `recover_clock_disable_cmd` | None | string | Shell command which disables PHY port pointed by this Port section as a source of frequency for the Sync-E DPLL on this device (required only in "internal_input" mode). | +| `allowed_qls` | None | string | List of integers containing allowed SSM QLs separated by comma (`,`), other received ones would be discarded - if parameter is not provided, all QLs will be accepted | +| `allowed_ext_qls` | None | string | List of integers containing allowed extended SSM QLs separated by comma (`,`), other received ones would be discarded - if parameter is not provided, all QLs will be accepted | + +> *Remark:* Please do not use backslashes in config file in 'string' fields - for example do not use it like this: `"/sys/kernel/debug/ice/0000\:5e\:00\.0/cgu_state"` + +### Config example + +``` +[global] +logging_level 7 +use_syslog 0 +verbose 1 +message_tag [synce4l] + +[<synce1>] +internal_input 0 +external_input 1 +network_option 1 +external_input_QL 11 +external_input_ext_QL 33 +extended_tlv 1 +recover_time 20 +dpll_get_state_cmd cat /sys/class/net/eth0/device/cgu_state +dpll_holdover_value 4 +dpll_locked_ho_value 3 +dpll_locked_value 2 +dpll_freerun_value 1 +dpll_invalid_value 0 + +[eth0] +sync 1 +tx_heartbeat_msec 1000 +rx_heartbeat_msec 500 +recover_clock_enable_cmd echo 1 0 > /sys/class/net/eth0/device/phy/synce +recover_clock_disable_cmd echo 0 0 > /sys/class/net/eth0/device/phy/synce +allowed_qls 3,4,7 +allowed_ext_qls 20,21 + +[eth1] +sync 0 +``` + +--- + -- 2.34.1 |
From: Miroslav L. <mli...@re...> - 2022-05-03 09:14:56
|
Thanks for your work on this feature. I don't have hardware to test it. Just some comments on the code. On Mon, May 02, 2022 at 11:05:55AM +0200, Arkadiusz Kubalewski wrote: > Allow config interface to parse SyncE related config files. > > + if (type == PTP) { > + ci_tab = &config_tab_ptp[0]; > + n_items = N_CONFIG_ITEMS_PTP; > + } else { > + ci_tab = &config_tab_synce[0]; > + n_items = N_CONFIG_ITEMS_SYNCE; > + } > + n_items = (type == PTP ? N_CONFIG_ITEMS_PTP : N_CONFIG_ITEMS_SYNCE); This looks duplicated. The last line shouldn't be there? > +# Shell command to be executed in order to obtain current DPLL status of a > +# device. > +# > +dpll_get_state_cmd cat /sys/class/net/enp1s0f0/device/cgu_state If I understand it correctly, this shell command is executed 50 times per second. Wouldn't it be sufficient to specify it as a file to be read directly instead instead of calling popen()? I assume this is a temporary solution until the kernel provides a standard ethtool/netlink API for monitoring and configuring SyncE. -- Miroslav Lichvar |
From: Kubalewski, A. <ark...@in...> - 2022-05-04 12:46:04
|
>-----Original Message----- >From: Miroslav Lichvar <mli...@re...> >Sent: Tuesday, May 3, 2022 11:15 AM >To: Kubalewski, Arkadiusz <ark...@in...> >Cc: lin...@li...; Kwapulinski, Piotr <pio...@in...>; Sawula, Andrzej <and...@in...>; Gerasymenko, Anatolii <ana...@in...> >Subject: Re: [Linuxptp-devel] [PATCH 01/11] synce4l: add config knobs for SyncE > >Thanks for your work on this feature. I don't have hardware to test >it. Just some comments on the code. Thank you very much for your comments! > >On Mon, May 02, 2022 at 11:05:55AM +0200, Arkadiusz Kubalewski wrote: >> Allow config interface to parse SyncE related config files. >> > >> + if (type == PTP) { >> + ci_tab = &config_tab_ptp[0]; >> + n_items = N_CONFIG_ITEMS_PTP; >> + } else { >> + ci_tab = &config_tab_synce[0]; >> + n_items = N_CONFIG_ITEMS_SYNCE; >> + } >> + n_items = (type == PTP ? N_CONFIG_ITEMS_PTP : N_CONFIG_ITEMS_SYNCE); > >This looks duplicated. The last line shouldn't be there? > Definitely, will fix in next patch. > >> +# Shell command to be executed in order to obtain current DPLL status of a >> +# device. >> +# >> +dpll_get_state_cmd cat /sys/class/net/enp1s0f0/device/cgu_state > >If I understand it correctly, this shell command is executed 50 times >per second. Wouldn't it be sufficient to specify it as a file to be >read directly instead instead of calling popen()? > That is true, but also tricky. As a matter of fact previously we have had it hardcoded, where only sysfs was possible. But now, we already know that some users want to control DPLL with i2c directly, thus we tried to let users configure full command to fulfil those needs, this approach makes SyncE logic usable by any hardware vendor. > >I assume this is a temporary solution until the kernel provides a >standard ethtool/netlink API for monitoring and configuring SyncE. > We have work ongoing on new kernel interface, hopefully it will happen in 5.19. Although, I am not sure if this is only temporary solution. As far as we know, even if the interface would get into next-queue kernel, only some Linux distros will port it to their older kernels. >From this perspective, wouldn't it be better to leave the user with an ability to either use in-kernel interface (once available) or product specific interface (Out-Of-Tree driver / i2c / other external application)? Please share us any thoughts on that matter. Thanks you, Arkadiusz >-- >Miroslav Lichvar > |
From: Miroslav L. <mli...@re...> - 2022-05-03 09:22:42
|
On Mon, May 02, 2022 at 11:05:57AM +0200, Arkadiusz Kubalewski wrote: > Add interface for sending and receiving ESMC frames. > Ethernet Synchronization Messaging Channel (ESMC) requires > to operate on multicast raw L2 socket. This patch adds features > to open socket, send and receive ESMC frames. > +++ b/esmc_socket.c > @@ -0,0 +1,111 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ linuxptp is GPLv2+, so I think the new code should be submitted under the same or at least compatible license to not build binaries with different licenses. > +int recv_raw_esmc_frame(int socket, void *buff) > +{ > + return recv(socket, buff, ETH_DATA_LEN, 0); > +} This looks dangerous to me. I'd suggest to add the length of the buffer as an argument, or maybe pass it as esmc_data and sizeof the data field. -- Miroslav Lichvar |
From: Erez <ere...@gm...> - 2022-05-03 16:43:05
|
On Tue, 3 May 2022 at 11:25, Miroslav Lichvar <mli...@re...> wrote: > On Mon, May 02, 2022 at 11:05:57AM +0200, Arkadiusz Kubalewski wrote: > > Add interface for sending and receiving ESMC frames. > > Ethernet Synchronization Messaging Channel (ESMC) requires > > to operate on multicast raw L2 socket. This patch adds features > > to open socket, send and receive ESMC frames. > > > +++ b/esmc_socket.c > > @@ -0,0 +1,111 @@ > > +/* SPDX-License-Identifier: GPL-2.0-only */ > > linuxptp is GPLv2+, so I think the new code should be submitted under > the same or at least compatible license to not build binaries with > different licenses. > I agree, new code should use the same license: "GPL-2.0+" We could also add the "SPDX-FileCopyrightText:" tag. It helps finding the copyright with the license per file :-) Maybe it is time to update all the source headers to a format like: /** * @file XXX.c * @brief XXXXX * @note SPDX-FileCopyrightText: (C) 20XX NN FF <use@mailserver> * @note SPDX-License-Identifier: GPL-2.0+ */ Seems we are not uniform on that. I think each copyright holder should update its own sources. Erez > > > +int recv_raw_esmc_frame(int socket, void *buff) > > +{ > > + return recv(socket, buff, ETH_DATA_LEN, 0); > > +} > > This looks dangerous to me. I'd suggest to add the length of the > buffer as an argument, or maybe pass it as esmc_data and sizeof the > data field. > > -- > Miroslav Lichvar > > > > _______________________________________________ > Linuxptp-devel mailing list > Lin...@li... > https://lists.sourceforge.net/lists/listinfo/linuxptp-devel > |
From: Michalik, M. <mic...@in...> - 2022-05-04 14:05:22
|
Hi Miroslav, Great catches - appreciate that. > > -----Original Message----- > From: Miroslav Lichvar <mli...@re...> > Sent: Tuesday, May 3, 2022 11:22 AM > To: Kubalewski, Arkadiusz <ark...@in...> > Cc: Kwapulinski, Piotr <pio...@in...>; Sawula, Andrzej <and...@in...>; lin...@li...; Gerasymenko, Anatolii <ana...@in...> > Subject: Re: [Linuxptp-devel] [PATCH 03/11] synce4l: add esmc_socket interface > > On Mon, May 02, 2022 at 11:05:57AM +0200, Arkadiusz Kubalewski wrote: > > Add interface for sending and receiving ESMC frames. > > Ethernet Synchronization Messaging Channel (ESMC) requires > > to operate on multicast raw L2 socket. This patch adds features > > to open socket, send and receive ESMC frames. > > > +++ b/esmc_socket.c > > @@ -0,0 +1,111 @@ > > +/* SPDX-License-Identifier: GPL-2.0-only */ > > linuxptp is GPLv2+, so I think the new code should be submitted under > the same or at least compatible license to not build binaries with > different licenses. > It makes sense - thanks. We will fix it. > > +int recv_raw_esmc_frame(int socket, void *buff) > > +{ > > + return recv(socket, buff, ETH_DATA_LEN, 0); > > +} > > This looks dangerous to me. I'd suggest to add the length of the > buffer as an argument, or maybe pass it as esmc_data and sizeof the > data field. > Thanks for noticing, we will work on that in the next version. > -- > Miroslav Lichvar Best regards, MM |
From: Miroslav L. <mli...@re...> - 2022-05-03 09:35:39
|
On Mon, May 02, 2022 at 11:06:02AM +0200, Arkadiusz Kubalewski wrote: > Used by synce_port to create, control and destroy RX/TX threads. > Ports in sync mode can use only TX thread (device in external input > mode) or both RX and TX threads (device in internal input mode). > +++ b/synce_port_ctrl.c > @@ -0,0 +1,1077 @@ > +static int init_ql_str(struct allowed_qls_head *qls_stailq_head, > + const char *allowed_qls) > +{ ... > + ptr = allowed_qls; > + while (*ptr) { > + unsigned long value; > + struct ql *newql; > + > + if (ptr > allowed_qls + allowed_qls_len) { > + break; > + } This check needs to be performed before the "*ptr" in the while condition to avoid invalid memory access. > +static int init_allowed_ext_qls(struct synce_port_rx *rx, struct config *cfg, > + const char *name) > +{ > + const char *allowed_qls; > + > + STAILQ_INIT((struct allowed_qls_head *)&rx->allowed_ext_qls); The casting looks wrong/unnecessary, or at least it is causing compiler warnings about breaking strict aliasing. > +static int thread_start_wait(struct thread_common_data *cd) > +{ > + int cnt = (cd->heartbeat_usec / THREAD_STOP_SLEEP_USEC) + 1; > + > + if (cd->state == THREAD_STARTED) { > + pr_debug("THREAD_STARTED"); > + > + return 0; > + } > + > + while (cnt-- && cd->state != THREAD_STARTED) { > + usleep(THREAD_STOP_SLEEP_USEC); > + } This should be THREAD_START_SLEEP_USEC? And shouldn't be there some locking between the threads, e.g. with mutexes? -- Miroslav Lichvar |
From: Kubalewski, A. <ark...@in...> - 2022-05-04 12:45:48
|
>-----Original Message----- >From: Miroslav Lichvar <mli...@re...> >Sent: Tuesday, May 3, 2022 11:35 AM >To: Kubalewski, Arkadiusz <ark...@in...> >Cc: lin...@li...; Kwapulinski, Piotr <pio...@in...>; Sawula, Andrzej <and...@in...>; Gerasymenko, Anatolii <ana...@in...> >Subject: Re: [Linuxptp-devel] [PATCH 08/11] synce4l: add synce_port_ctrl interface > >On Mon, May 02, 2022 at 11:06:02AM +0200, Arkadiusz Kubalewski wrote: >> Used by synce_port to create, control and destroy RX/TX threads. >> Ports in sync mode can use only TX thread (device in external input >> mode) or both RX and TX threads (device in internal input mode). > >> +++ b/synce_port_ctrl.c >> @@ -0,0 +1,1077 @@ > >> +static int init_ql_str(struct allowed_qls_head *qls_stailq_head, >> + const char *allowed_qls) >> +{ >... >> + ptr = allowed_qls; >> + while (*ptr) { >> + unsigned long value; >> + struct ql *newql; >> + >> + if (ptr > allowed_qls + allowed_qls_len) { >> + break; >> + } > >This check needs to be performed before the "*ptr" in the while >condition to avoid invalid memory access. Makes perfect sense, will fix it. > >> +static int init_allowed_ext_qls(struct synce_port_rx *rx, struct config *cfg, >> + const char *name) >> +{ >> + const char *allowed_qls; >> + >> + STAILQ_INIT((struct allowed_qls_head *)&rx->allowed_ext_qls); > >The casting looks wrong/unnecessary, or at least it is causing >compiler warnings about breaking strict aliasing. Agree, will fix it. > >> +static int thread_start_wait(struct thread_common_data *cd) >> +{ >> + int cnt = (cd->heartbeat_usec / THREAD_STOP_SLEEP_USEC) + 1; >> + >> + if (cd->state == THREAD_STARTED) { >> + pr_debug("THREAD_STARTED"); >> + >> + return 0; >> + } >> + >> + while (cnt-- && cd->state != THREAD_STARTED) { >> + usleep(THREAD_STOP_SLEEP_USEC); >> + } > >This should be THREAD_START_SLEEP_USEC? That's true, will fix it. > >And shouldn't be there some locking between the threads, e.g. with >mutexes? Sure, we can introduce locks on accessing child-threads data. Benefits from introducing locks: - TX thread - almost instant reaction on rebuild request - RX thread - almost instant reaction on new QL received Initially we were not sure if increasing complexity of threads is worth the benefits. ESMC is a slow protocol and the Synchronous Ethernet standard describes automatic adjustment of the clock recovered from one of the ports while feeding its clock to the others, but SyncE-enabled network configuration is rather static and change of QL would happen rarely (mostly due to errors, loosing proper reference signal on the SyncE-leader). As we can see some benefits from having locks in place, I will add those in the next patch version. Thank you, Arkadiusz > >-- >Miroslav Lichvar > |
From: Miroslav L. <mli...@re...> - 2022-05-03 09:47:01
|
On Mon, May 02, 2022 at 11:06:04AM +0200, Arkadiusz Kubalewski wrote: > Interface is used to create and store an opened socket for sending and > receiving SyncE PDU's on a given NIC interface. > +++ b/synce_transport.c > +struct synce_transport *synce_transport_create(const char *iface) > +{ ... > + if (strnlen(iface, IFNAMSIZ) == IFNAMSIZ) { > + pr_err("interface name too long"); > + goto err; > + } > + strncpy(transport->iface, iface, > + (sizeof(transport->iface)/sizeof(*transport->iface))); That division looks odd here as strncpy works with chars. Anyway, this code seems to be confusing the compiler, assuming it can produce an unterminated string, so maybe just use "if (snprintf(...) >= IFNAMSIZ)" instead of strnlen() + strncpy()? -- Miroslav Lichvar |
From: Michalik, M. <mic...@in...> - 2022-05-04 14:00:43
|
Hello Miroslav, Much thanks for your review. > On Mon, May 02, 2022 at 11:06:04AM +0200, Arkadiusz Kubalewski wrote: > > Interface is used to create and store an opened socket for sending and > > receiving SyncE PDU's on a given NIC interface. > > > +++ b/synce_transport.c > > > +struct synce_transport *synce_transport_create(const char *iface) > > +{ > ... > > + if (strnlen(iface, IFNAMSIZ) == IFNAMSIZ) { > > + pr_err("interface name too long"); > > + goto err; > > + } > > + strncpy(transport->iface, iface, > > + (sizeof(transport->iface)/sizeof(*transport->iface))); > > That division looks odd here as strncpy works with chars. > > Anyway, this code seems to be confusing the compiler, assuming it can > produce an unterminated string, so maybe just use > "if (snprintf(...) >= IFNAMSIZ)" instead of strnlen() + strncpy()? > That is a great suggestion - thanks for it. We will update it in next version. > -- > Miroslav Lichvar > |
From: Miroslav L. <mli...@re...> - 2022-05-03 09:51:25
|
On Mon, May 02, 2022 at 11:06:05AM +0200, Arkadiusz Kubalewski wrote: > Add main code of synce4l - user space application implementing > standard - Recommendation ITU-T G.8264/Y.1364. > On init the application parses SyncE config file and initializes > a synce_clock with SyncE devices and configured ports. > synce_clock is later polled until application termination. > > Add documentation in form of Linux manual and 'synce4l_README.md'. > +++ b/synce4l.8 > @@ -0,0 +1,239 @@ > +.IP external_input_ext_QL > +Extended Quality Level for "external input" mode. > +.P > +.RS > +Extended Quality Level for "external input" mode. This sentence is duplicated. -- Miroslav Lichvar |
From: Kubalewski, A. <ark...@in...> - 2022-05-04 13:57:42
|
>-----Original Message----- >From: Miroslav Lichvar <mli...@re...> >Sent: Tuesday, May 3, 2022 11:47 AM >To: Kubalewski, Arkadiusz <ark...@in...> >Cc: lin...@li...; Kwapulinski, Piotr ><pio...@in...>; Sawula, Andrzej ><and...@in...>; Gerasymenko, Anatolii ><ana...@in...> >Subject: Re: [Linuxptp-devel] [PATCH 10/11] synce4l: add >synce_transport interface > >On Mon, May 02, 2022 at 11:06:05AM +0200, Arkadiusz Kubalewski wrote: >> Add main code of synce4l - user space application implementing >> standard - Recommendation ITU-T G.8264/Y.1364. >> On init the application parses SyncE config file and initializes a >> synce_clock with SyncE devices and configured ports. >> synce_clock is later polled until application termination. >> >> Add documentation in form of Linux manual and 'synce4l_README.md'. > >> +++ b/synce4l.8 >> @@ -0,0 +1,239 @@ > >> +.IP external_input_ext_QL >> +Extended Quality Level for "external input" mode. >> +.P >> +.RS >> +Extended Quality Level for "external input" mode. > >This sentence is duplicated. > Yes, thanks, I will fix that. Thank you, Arkadiusz >-- >Miroslav Lichvar |
From: Kubalewski, A. <ark...@in...> - 2022-05-04 12:46:06
|
>-----Original Message----- >From: Miroslav Lichvar <mli...@re...> >Sent: Tuesday, May 3, 2022 11:47 AM >To: Kubalewski, Arkadiusz <ark...@in...> >Cc: lin...@li...; Kwapulinski, Piotr <pio...@in...>; Sawula, Andrzej <and...@in...>; Gerasymenko, Anatolii <ana...@in...> >Subject: Re: [Linuxptp-devel] [PATCH 10/11] synce4l: add synce_transport interface > >On Mon, May 02, 2022 at 11:06:05AM +0200, Arkadiusz Kubalewski wrote: >> Add main code of synce4l - user space application implementing >> standard - Recommendation ITU-T G.8264/Y.1364. >> On init the application parses SyncE config file and initializes a >> synce_clock with SyncE devices and configured ports. >> synce_clock is later polled until application termination. >> >> Add documentation in form of Linux manual and 'synce4l_README.md'. > >> +++ b/synce4l.8 >> @@ -0,0 +1,239 @@ > >> +.IP external_input_ext_QL >> +Extended Quality Level for "external input" mode. >> +.P >> +.RS >> +Extended Quality Level for "external input" mode. > >This sentence is duplicated. > Yes, thanks, I will fix that. Thank you, Arkadiusz >-- >Miroslav Lichvar |
From: Kubalewski, A. <ark...@in...> - 2022-05-04 13:58:05
|
>>-----Original Message----- >>From: Miroslav Lichvar <mli...@re...> >>Sent: Tuesday, May 3, 2022 11:47 AM >>To: Kubalewski, Arkadiusz <ark...@in...> >>Cc: lin...@li...; Kwapulinski, Piotr >><pio...@in...>; Sawula, Andrzej >><and...@in...>; Gerasymenko, Anatolii >><ana...@in...> >>Subject: Re: [Linuxptp-devel] [PATCH 10/11] synce4l: add >>synce_transport interface >> >>On Mon, May 02, 2022 at 11:06:05AM +0200, Arkadiusz Kubalewski wrote: >>> Add main code of synce4l - user space application implementing >>> standard - Recommendation ITU-T G.8264/Y.1364. >>> On init the application parses SyncE config file and initializes a >>> synce_clock with SyncE devices and configured ports. >>> synce_clock is later polled until application termination. >>> >>> Add documentation in form of Linux manual and 'synce4l_README.md'. >> >>> +++ b/synce4l.8 >>> @@ -0,0 +1,239 @@ >> >>> +.IP external_input_ext_QL >>> +Extended Quality Level for "external input" mode. >>> +.P >>> +.RS >>> +Extended Quality Level for "external input" mode. >> >>This sentence is duplicated. >> > >Yes, thanks, I will fix that. > >Thank you, >Arkadiusz > >>-- >>Miroslav Lichvar Sorry I messed up this mail thread, my answer was for patch: [PATCH 11/11] synce4l: add synce4l application Michal will shortly answers properly on 10/11 patch. Thank you, Arkadiusz |