Thread: [Linuxptp-devel] [PATCH RFC 0/6] Transparent Clock - third and final part
PTP IEEE 1588 stack for Linux
Brought to you by:
rcochran
From: Richard C. <ric...@gm...> - 2018-04-14 16:45:50
|
This series add support for running a TC over multiple ports. Comments, testing, and review are most welcome. Thanks, Richard Richard Cochran (6): tc: Add the transparent clock implementation. p2p_tc: Implement a peer to peer transparent clock. e2e_tc: Implement an end to end transparent clock. config: Add a configuration option for TC mode. Add an example configuration for a peer to peer transparent clock. Add an example configuration for an end to end transparent clock. E2E-TC.cfg | 91 ++++++++++++ P2P-TC.cfg | 91 ++++++++++++ clock.c | 5 +- config.c | 10 ++ default.cfg | 1 + e2e_tc.c | 193 +++++++++++++++++++++++++ gPTP.cfg | 1 + makefile | 4 +- p2p_tc.c | 213 ++++++++++++++++++++++++++++ port.c | 17 +++ port.h | 5 + port_private.h | 15 ++ ptp4l.8 | 15 +- ptp4l.c | 44 +++++- tc.c | 441 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tc.h | 94 ++++++++++++ 16 files changed, 1229 insertions(+), 11 deletions(-) create mode 100644 E2E-TC.cfg create mode 100644 P2P-TC.cfg create mode 100644 e2e_tc.c create mode 100644 p2p_tc.c create mode 100644 tc.c create mode 100644 tc.h -- 2.11.0 |
From: Richard C. <ric...@gm...> - 2018-04-14 16:45:51
|
This patch adds code that sends an event messages received on one port out all the other ports and calculates the residence time. The correction, ingress port, and the original message are remembered in a TC transmit descriptor. These descriptors are recycled in a memory pool in a similar way to the message buffers. Signed-off-by: Richard Cochran <ric...@gm...> --- clock.c | 1 + makefile | 2 +- port.c | 3 + port.h | 5 + port_private.h | 9 ++ tc.c | 441 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tc.h | 94 ++++++++++++ 7 files changed, 554 insertions(+), 1 deletion(-) create mode 100644 tc.c create mode 100644 tc.h diff --git a/clock.c b/clock.c index 9ffb43e..87a98d2 100644 --- a/clock.c +++ b/clock.c @@ -282,6 +282,7 @@ void clock_destroy(struct clock *c) } memset(c, 0, sizeof(*c)); msg_cleanup(); + tc_cleanup(); } static int clock_fault_timeout(struct port *port, int set) diff --git a/makefile b/makefile index 6f5321c..d9dbb04 100644 --- a/makefile +++ b/makefile @@ -25,7 +25,7 @@ LDLIBS = -lm -lrt $(EXTRA_LDFLAGS) PRG = ptp4l hwstamp_ctl nsm phc2sys phc_ctl pmc timemaster OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o fault.o \ filter.o fsm.o hash.o linreg.o mave.o mmedian.o msg.o ntpshm.o nullf.o phc.o \ - pi.o port.o print.o ptp4l.o raw.o rtnl.o servo.o sk.o stats.o tlv.o \ + pi.o port.o print.o ptp4l.o raw.o rtnl.o servo.o sk.o stats.o tc.o tlv.o \ transport.o tsproc.o udp.o udp6.o uds.o util.o version.o OBJECTS = $(OBJ) hwstamp_ctl.o nsm.o phc2sys.o phc_ctl.o pmc.o pmc_common.o \ diff --git a/port.c b/port.c index 1757dfe..2ddb2e2 100644 --- a/port.c +++ b/port.c @@ -36,6 +36,7 @@ #include "print.h" #include "rtnl.h" #include "sk.h" +#include "tc.h" #include "tlv.h" #include "tmv.h" #include "tsproc.h" @@ -1534,6 +1535,7 @@ void port_disable(struct port *p) { int i; + tc_flush(p); flush_last_sync(p); flush_delay_req(p); flush_peer_delay(p); @@ -2770,6 +2772,7 @@ struct port *port_open(int phc_index, } memset(p, 0, sizeof(*p)); + TAILQ_INIT(&p->tc_transmitted); switch (type) { case CLOCK_TYPE_ORDINARY: diff --git a/port.h b/port.h index d828875..9639193 100644 --- a/port.h +++ b/port.h @@ -322,4 +322,9 @@ enum fault_type last_fault_type(struct port *port); void fault_interval(struct port *port, enum fault_type ft, struct fault_interval *i); +/** + * Release all of the memory in the TC transmit descriptor cache. + */ +void tc_cleanup(void); + #endif diff --git a/port_private.h b/port_private.h index 860a2bb..558c31b 100644 --- a/port_private.h +++ b/port_private.h @@ -50,6 +50,13 @@ struct nrate_estimator { int ratio_valid; }; +struct tc_txd { + TAILQ_ENTRY(tc_txd) list; + struct ptp_message *msg; + tmv_t residence; + int ingress_port; +}; + struct port { LIST_ENTRY(port) list; char *name; @@ -120,6 +127,8 @@ struct port { unsigned int versionNumber; /*UInteger4*/ /* foreignMasterDS */ LIST_HEAD(fm, foreign_clock) foreign_masters; + /* TC book keeping */ + TAILQ_HEAD(tct, tc_txd) tc_transmitted; }; #define portnum(p) (p->portIdentity.portNumber) diff --git a/tc.c b/tc.c new file mode 100644 index 0000000..c41850a --- /dev/null +++ b/tc.c @@ -0,0 +1,441 @@ +/** + * @file tc.c + * @note Copyright (C) 2018 Richard Cochran <ric...@gm...> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA. + */ +#include <stdlib.h> + +#include "port.h" +#include "print.h" +#include "tc.h" +#include "tmv.h" + +enum tc_match { + TC_MISMATCH, + TC_SYNC_FUP, + TC_FUP_SYNC, + TC_DELAY_REQRESP, +}; + +static TAILQ_HEAD(tc_pool, tc_txd) tc_pool = TAILQ_HEAD_INITIALIZER(tc_pool); + +static int tc_match_delay(int ingress_port, struct ptp_message *resp, + struct tc_txd *txd); +static int tc_match_syfup(int ingress_port, struct ptp_message *msg, + struct tc_txd *txd); +static void tc_recycle(struct tc_txd *txd); + +static struct tc_txd *tc_allocate(void) +{ + struct tc_txd *txd = TAILQ_FIRST(&tc_pool); + + if (txd) { + TAILQ_REMOVE(&tc_pool, txd, list); + memset(txd, 0, sizeof(*txd)); + return txd; + } + txd = calloc(1, sizeof(*txd)); + return txd; +} + +static int tc_blocked(struct port *p) +{ + if (portnum(p) == 0) { + return 1; + } + enum port_state s = port_state(p); + switch (s) { + case PS_INITIALIZING: + case PS_FAULTY: + case PS_DISABLED: + case PS_PASSIVE: + break; + case PS_LISTENING: + case PS_PRE_MASTER: + case PS_MASTER: + case PS_UNCALIBRATED: + case PS_SLAVE: + case PS_GRAND_MASTER: + return 0; + } + return 1; +} + +static void tc_complete_request(struct port *q, struct port *p, + struct ptp_message *req, tmv_t residence) +{ + struct tc_txd *txd = tc_allocate(); + if (!txd) { + port_dispatch(p, EV_FAULT_DETECTED, 0); + return; + } +#ifdef DEBUG + pr_err("stash delay request from port %hd to %hd seqid %hu residence %lu", + portnum(q), portnum(p), ntohs(req->header.sequenceId), + (unsigned long) tmv_to_nanoseconds(residence)); +#endif + msg_get(req); + txd->msg = req; + txd->residence = residence; + txd->ingress_port = portnum(q); + TAILQ_INSERT_TAIL(&p->tc_transmitted, txd, list); +} + +static void tc_complete_response(struct port *q, struct port *p, + struct ptp_message *resp, tmv_t residence) +{ + enum tc_match type = TC_MISMATCH; + struct tc_txd *txd; + Integer64 c1, c2; + int cnt; + +#ifdef DEBUG + pr_err("complete delay response from port %hd to %hd seqid %hu", + portnum(q), portnum(p), ntohs(resp->header.sequenceId)); +#endif + TAILQ_FOREACH(txd, &q->tc_transmitted, list) { + type = tc_match_delay(portnum(p), resp, txd); + if (type == TC_DELAY_REQRESP) { + residence = txd->residence; + break; + } + } + if (type != TC_DELAY_REQRESP) { + return; + } + c1 = net2host64(resp->header.correction); + c2 = c1 + tmv_to_TimeInterval(residence); + resp->header.correction = host2net64(c2); + cnt = transport_send(p->trp, &p->fda, TRANS_GENERAL, resp); + if (cnt <= 0) { + pr_err("tc failed to forward response on port %d", portnum(p)); + port_dispatch(p, EV_FAULT_DETECTED, 0); + } + /* Restore original correction value for next egress port. */ + resp->header.correction = host2net64(c1); + TAILQ_REMOVE(&q->tc_transmitted, txd, list); + msg_put(txd->msg); + tc_recycle(txd); +} + +static void tc_complete_syfup(struct port *q, struct port *p, + struct ptp_message *msg, tmv_t residence) +{ + enum tc_match type = TC_MISMATCH; + struct ptp_message *fup; + struct tc_txd *txd; + Integer64 c1, c2; + int cnt; + + TAILQ_FOREACH(txd, &p->tc_transmitted, list) { + type = tc_match_syfup(portnum(q), msg, txd); + switch (type) { + case TC_MISMATCH: + break; + case TC_SYNC_FUP: + fup = msg; + residence = txd->residence; + break; + case TC_FUP_SYNC: + fup = txd->msg; + break; + case TC_DELAY_REQRESP: + pr_err("tc: unexpected match of delay request - sync!"); + return; + } + if (type != TC_MISMATCH) { + break; + } + } + + if (type == TC_MISMATCH) { + txd = tc_allocate(); + if (!txd) { + port_dispatch(p, EV_FAULT_DETECTED, 0); + return; + } + msg_get(msg); + txd->msg = msg; + txd->residence = residence; + txd->ingress_port = portnum(q); + TAILQ_INSERT_TAIL(&p->tc_transmitted, txd, list); + return; + } + + c1 = net2host64(fup->header.correction); + c2 = c1 + tmv_to_TimeInterval(residence); + c2 += tmv_to_TimeInterval(q->peer_delay); + c2 += q->asymmetry; + fup->header.correction = host2net64(c2); + cnt = transport_send(p->trp, &p->fda, TRANS_GENERAL, fup); + if (cnt <= 0) { + pr_err("tc failed to forward follow up on port %d", portnum(p)); + port_dispatch(p, EV_FAULT_DETECTED, 0); + } + /* Restore original correction value for next egress port. */ + fup->header.correction = host2net64(c1); + TAILQ_REMOVE(&p->tc_transmitted, txd, list); + msg_put(txd->msg); + tc_recycle(txd); +} + +static void tc_complete(struct port *q, struct port *p, + struct ptp_message *msg, tmv_t residence) +{ + switch (msg_type(msg)) { + case SYNC: + case FOLLOW_UP: + tc_complete_syfup(q, p, msg, residence); + break; + case DELAY_REQ: + tc_complete_request(q, p, msg, residence); + break; + case DELAY_RESP: + tc_complete_response(q, p, msg, residence); + break; + } +} + +static int tc_current(struct ptp_message *m, struct timespec now) +{ + int64_t t1, t2, tmo; + + tmo = 1LL * NSEC2SEC; + t1 = m->ts.host.tv_sec * NSEC2SEC + m->ts.host.tv_nsec; + t2 = now.tv_sec * NSEC2SEC + now.tv_nsec; + + return t2 - t1 < tmo; +} + +static int tc_fwd_event(struct port *q, struct ptp_message *msg) +{ + tmv_t egress, ingress = msg->hwts.ts, residence; + struct port *p; + int cnt, err; + double rr; + + clock_gettime(CLOCK_MONOTONIC, &msg->ts.host); + + /* First send the event message out. */ + for (p = clock_first_port(q->clock); p; p = LIST_NEXT(p, list)) { + if (p == q || tc_blocked(p)) { + continue; + } + cnt = transport_send(p->trp, &p->fda, TRANS_DEFER_EVENT, msg); + if (cnt <= 0) { + pr_err("failed to forward event from port %hd to %hd", + portnum(q), portnum(p)); + port_dispatch(p, EV_FAULT_DETECTED, 0); + } + } + + /* Go back and gather the transmit time stamps. */ + for (p = clock_first_port(q->clock); p; p = LIST_NEXT(p, list)) { + if (p == q || tc_blocked(p)) { + continue; + } + err = transport_txts(p->trp, &p->fda, msg); + if (err || !msg_sots_valid(msg)) { + pr_err("failed to fetch txts on port %hd to %hd event", + portnum(q), portnum(p)); + port_dispatch(p, EV_FAULT_DETECTED, 0); + continue; + } + ts_add(&msg->hwts.ts, p->tx_timestamp_offset); + egress = msg->hwts.ts; + residence = tmv_sub(egress, ingress); + rr = clock_rate_ratio(q->clock); + if (rr != 1.0) { + residence = dbl_tmv(tmv_dbl(residence) * rr); + } + tc_complete(q, p, msg, residence); + } + + return 0; +} + +static int tc_match_delay(int ingress_port, struct ptp_message *resp, + struct tc_txd *txd) +{ + struct ptp_message *req = txd->msg; + + if (ingress_port != txd->ingress_port) { + return TC_MISMATCH; + } + if (req->header.sequenceId != resp->header.sequenceId) { + return TC_MISMATCH; + } + if (!pid_eq(&req->header.sourcePortIdentity, + &resp->delay_resp.requestingPortIdentity)) { + return TC_MISMATCH; + } + if (msg_type(req) == DELAY_REQ && msg_type(resp) == DELAY_RESP) { + return TC_DELAY_REQRESP; + } + return TC_MISMATCH; +} + +static int tc_match_syfup(int ingress_port, struct ptp_message *msg, + struct tc_txd *txd) +{ + if (ingress_port != txd->ingress_port) { + return TC_MISMATCH; + } + if (msg->header.sequenceId != txd->msg->header.sequenceId) { + return TC_MISMATCH; + } + if (!source_pid_eq(msg, txd->msg)) { + return TC_MISMATCH; + } + if (msg_type(txd->msg) == SYNC && msg_type(msg) == FOLLOW_UP) { + return TC_SYNC_FUP; + } + if (msg_type(txd->msg) == FOLLOW_UP && msg_type(msg) == SYNC) { + return TC_FUP_SYNC; + } + return TC_MISMATCH; +} + +static void tc_recycle(struct tc_txd *txd) +{ + TAILQ_INSERT_HEAD(&tc_pool, txd, list); +} + +/* public methods */ + +void tc_cleanup(void) +{ + struct tc_txd *txd; + + while ((txd = TAILQ_FIRST(&tc_pool)) != NULL) { + TAILQ_REMOVE(&tc_pool, txd, list); + free(txd); + } +} + +void tc_flush(struct port *q) +{ + struct tc_txd *txd; + + while ((txd = TAILQ_FIRST(&q->tc_transmitted)) != NULL) { + TAILQ_REMOVE(&q->tc_transmitted, txd, list); + msg_put(txd->msg); + tc_recycle(txd); + } +} + +int tc_forward(struct port *q, struct ptp_message *msg) +{ + struct port *p; + int cnt; + + for (p = clock_first_port(q->clock); p; p = LIST_NEXT(p, list)) { + if (p == q || tc_blocked(p)) { + continue; + } + cnt = transport_send(p->trp, &p->fda, TRANS_GENERAL, msg); + if (cnt <= 0) { + pr_err("tc failed to forward message on port %d", + portnum(p)); + port_dispatch(p, EV_FAULT_DETECTED, 0); + } + } + return 0; +} + +int tc_fwd_folup(struct port *q, struct ptp_message *msg) +{ + struct port *p; + + clock_gettime(CLOCK_MONOTONIC, &msg->ts.host); + + for (p = clock_first_port(q->clock); p; p = LIST_NEXT(p, list)) { + if (p == q || tc_blocked(p)) { + continue; + } + tc_complete(q, p, msg, tmv_zero()); + } + return 0; +} + +int tc_fwd_request(struct port *q, struct ptp_message *msg) +{ + return tc_fwd_event(q, msg); +} + +int tc_fwd_response(struct port *q, struct ptp_message *msg) +{ + struct port *p; + + clock_gettime(CLOCK_MONOTONIC, &msg->ts.host); + + for (p = clock_first_port(q->clock); p; p = LIST_NEXT(p, list)) { + if (p == q || tc_blocked(p)) { + continue; + } + tc_complete(q, p, msg, tmv_zero()); + } + return 0; +} + +int tc_fwd_sync(struct port *q, struct ptp_message *msg) +{ + struct ptp_message *fup = NULL; + int err; + + if (one_step(msg)) { + fup = msg_allocate(); + if (!fup) { + return -1; + } + fup->header.tsmt = FOLLOW_UP | (msg->header.tsmt & 0xf0); + fup->header.ver = msg->header.ver; + fup->header.messageLength = sizeof(struct follow_up_msg); + fup->header.domainNumber = msg->header.domainNumber; + fup->header.sourcePortIdentity = msg->header.sourcePortIdentity; + fup->header.sequenceId = msg->header.sequenceId; + fup->header.control = CTL_FOLLOW_UP; + fup->header.logMessageInterval = msg->header.logMessageInterval; + fup->follow_up.preciseOriginTimestamp = msg->sync.originTimestamp; + msg->header.flagField[0] |= TWO_STEP; + } + err = tc_fwd_event(q, msg); + if (err) { + return err; + } + if (fup) { + err = tc_fwd_folup(q, fup); + msg_put(fup); + } + return err; +} + +void tc_prune(struct port *q) +{ + struct timespec now; + struct tc_txd *txd; + + clock_gettime(CLOCK_MONOTONIC, &now); + + while ((txd = TAILQ_FIRST(&q->tc_transmitted)) != NULL) { + if (tc_current(txd->msg, now)) { + break; + } + TAILQ_REMOVE(&q->tc_transmitted, txd, list); + msg_put(txd->msg); + tc_recycle(txd); + } +} diff --git a/tc.h b/tc.h new file mode 100644 index 0000000..b5b0a3f --- /dev/null +++ b/tc.h @@ -0,0 +1,94 @@ +/** + * @file tc.h + * @brief Provides Transparent Clock logic. + * @note Copyright (C) 2018 Richard Cochran <ric...@gm...> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA. + */ +#ifndef HAVE_TC_H +#define HAVE_TC_H + +#include "msg.h" +#include "port_private.h" + +/** + * Flushes the list of remembered residence times. + * @param q Port whose list should be flushed + */ +void tc_flush(struct port *q); + +/** + * Forwards a given general message out all other ports. + * @param q The ingress port + * @param msg The message to be sent + * @return Zero on success, non-zero otherwise. + */ +int tc_forward(struct port *q, struct ptp_message *msg); + +/** + * Forwards a given Follow-Up message out all other ports. + * + * This function adds the unique, per egress port residence time into + * the correction field for the transmitted follow up message. + * + * @param q The ingress port + * @param msg The message to be sent + * @return Zero on success, non-zero otherwise. + */ +int tc_fwd_folup(struct port *q, struct ptp_message *msg); + +/** + * Forwards a given delay request message out all other ports. + * + * This function computes the unique residence time for each egress + * port, remembering it in that egress port. + * + * @param q The ingress port + * @param msg The message to be sent + * @return Zero on success, non-zero otherwise. + */ +int tc_fwd_request(struct port *q, struct ptp_message *msg); + +/** + * Forwards a given response message out all other ports. + * + * This function adds the unique, per egress port residence time into + * the correction field for the transmitted delay response message. + * + * @param q The ingress port + * @param msg The message to be sent + * @return Zero on success, non-zero otherwise. + */ +int tc_fwd_response(struct port *q, struct ptp_message *msg); + +/** + * Forwards a given sync message out all other ports. + * + * This function computes the unique residence time for each egress + * port, remembering it in that egress port. + * + * @param q The ingress port + * @param msg The message to be sent + * @return Zero on success, non-zero otherwise. + */ +int tc_fwd_sync(struct port *q, struct ptp_message *msg); + +/** + * Prunes stale entries from the list of remembered residence times. + * @param q Port whose list should be pruned. + */ +void tc_prune(struct port *q); + +#endif -- 2.11.0 |
From: Richard C. <ric...@gm...> - 2018-04-14 16:45:51
|
The P2P TC forwards Announce, Management, Signaling, and Sync messages, consumes P2P Delay messages, and drops E2E Delay messages. This implementation tracks the GM using the BMCA in order to syntonize (or possibly even synchronize) with it. Signed-off-by: Richard Cochran <ric...@gm...> --- clock.c | 2 +- makefile | 2 +- p2p_tc.c | 213 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ port.c | 7 ++ port_private.h | 3 + 5 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 p2p_tc.c diff --git a/clock.c b/clock.c index 87a98d2..3977317 100644 --- a/clock.c +++ b/clock.c @@ -869,9 +869,9 @@ struct clock *clock_create(enum clock_type type, struct config *config, switch (type) { case CLOCK_TYPE_ORDINARY: case CLOCK_TYPE_BOUNDARY: + case CLOCK_TYPE_P2P: c->type = type; break; - case CLOCK_TYPE_P2P: case CLOCK_TYPE_E2E: case CLOCK_TYPE_MANAGEMENT: return NULL; diff --git a/makefile b/makefile index d9dbb04..9bc4be6 100644 --- a/makefile +++ b/makefile @@ -25,7 +25,7 @@ LDLIBS = -lm -lrt $(EXTRA_LDFLAGS) PRG = ptp4l hwstamp_ctl nsm phc2sys phc_ctl pmc timemaster OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o fault.o \ filter.o fsm.o hash.o linreg.o mave.o mmedian.o msg.o ntpshm.o nullf.o phc.o \ - pi.o port.o print.o ptp4l.o raw.o rtnl.o servo.o sk.o stats.o tc.o tlv.o \ + pi.o port.o print.o ptp4l.o p2p_tc.o raw.o rtnl.o servo.o sk.o stats.o tc.o tlv.o \ transport.o tsproc.o udp.o udp6.o uds.o util.o version.o OBJECTS = $(OBJ) hwstamp_ctl.o nsm.o phc2sys.o phc_ctl.o pmc.o pmc_common.o \ diff --git a/p2p_tc.c b/p2p_tc.c new file mode 100644 index 0000000..0daead6 --- /dev/null +++ b/p2p_tc.c @@ -0,0 +1,213 @@ +/** + * @file p2p_tc.c + * @note Copyright (C) 2018 Richard Cochran <ric...@gm...> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA. + */ +#include <errno.h> + +#include "port.h" +#include "port_private.h" +#include "print.h" +#include "rtnl.h" +#include "tc.h" + +static int p2p_delay_request(struct port *p) +{ + switch (p->state) { + case PS_INITIALIZING: + case PS_FAULTY: + case PS_DISABLED: + return 0; + case PS_LISTENING: + case PS_PRE_MASTER: + case PS_MASTER: + case PS_PASSIVE: + case PS_UNCALIBRATED: + case PS_SLAVE: + case PS_GRAND_MASTER: + break; + } + return port_delay_request(p); +} + +void p2p_dispatch(struct port *p, enum fsm_event event, int mdiff) +{ + if (!port_state_update(p, event, mdiff)) { + return; + } + if (!portnum(p)) { + /* UDS needs no timers. */ + return; + } + + port_clr_tmo(p->fda.fd[FD_ANNOUNCE_TIMER]); + port_clr_tmo(p->fda.fd[FD_SYNC_RX_TIMER]); + /* Leave FD_DELAY_TIMER running. */ + port_clr_tmo(p->fda.fd[FD_QUALIFICATION_TIMER]); + port_clr_tmo(p->fda.fd[FD_MANNO_TIMER]); + port_clr_tmo(p->fda.fd[FD_SYNC_TX_TIMER]); + + /* + * Handle the side effects of the state transition. + */ + switch (p->state) { + case PS_INITIALIZING: + break; + case PS_FAULTY: + case PS_DISABLED: + port_disable(p); + break; + case PS_LISTENING: + port_set_announce_tmo(p); + port_set_delay_tmo(p); + break; + case PS_PRE_MASTER: + case PS_MASTER: + case PS_GRAND_MASTER: + break; + case PS_PASSIVE: + port_set_announce_tmo(p); + break; + case PS_UNCALIBRATED: + case PS_SLAVE: + port_set_announce_tmo(p); + break; + }; +} + +enum fsm_event p2p_event(struct port *p, int fd_index) +{ + int cnt, fd = p->fda.fd[fd_index]; + enum fsm_event event = EV_NONE; + struct ptp_message *msg, *dup; + + switch (fd_index) { + case FD_ANNOUNCE_TIMER: + case FD_SYNC_RX_TIMER: + pr_debug("port %hu: %s timeout", portnum(p), + fd_index == FD_SYNC_RX_TIMER ? "rx sync" : "announce"); + if (p->best) { + fc_clear(p->best); + } + port_set_announce_tmo(p); + return EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES; + + case FD_DELAY_TIMER: + pr_debug("port %hu: delay timeout", portnum(p)); + port_set_delay_tmo(p); + tc_prune(p); + return p2p_delay_request(p) ? EV_FAULT_DETECTED : EV_NONE; + + case FD_QUALIFICATION_TIMER: + case FD_MANNO_TIMER: + case FD_SYNC_TX_TIMER: + pr_err("unexpected timer expiration"); + return EV_NONE; + + case FD_RTNL: + pr_debug("port %hu: received link status notification", portnum(p)); + rtnl_link_status(fd, p->name, port_link_status, p); + if (p->link_status == (LINK_UP|LINK_STATE_CHANGED)) { + return EV_FAULT_CLEARED; + } else if ((p->link_status == (LINK_DOWN|LINK_STATE_CHANGED)) || + (p->link_status & TS_LABEL_CHANGED)) { + return EV_FAULT_DETECTED; + } else { + return EV_NONE; + } + } + + msg = msg_allocate(); + if (!msg) { + return EV_FAULT_DETECTED; + } + msg->hwts.type = p->timestamping; + + cnt = transport_recv(p->trp, fd, msg); + if (cnt <= 0) { + pr_err("port %hu: recv message failed", portnum(p)); + msg_put(msg); + return EV_FAULT_DETECTED; + } + if (msg_sots_valid(msg)) { + ts_add(&msg->hwts.ts, -p->rx_timestamp_offset); + } + if (msg->header.flagField[0] & UNICAST) { + pl_warning(600, "cannot switch unicast messages!"); + msg_put(msg); + return EV_NONE; + } + + dup = msg_duplicate(msg, cnt); + if (!dup) { + msg_put(msg); + return EV_NONE; + } + + switch (msg_type(msg)) { + case SYNC: + if (tc_fwd_sync(p, msg)) { + event = EV_FAULT_DETECTED; + break; + } + process_sync(p, dup); + break; + case DELAY_REQ: + break; + case PDELAY_REQ: + if (process_pdelay_req(p, dup)) { + event = EV_FAULT_DETECTED; + } + break; + case PDELAY_RESP: + if (process_pdelay_resp(p, dup)) { + event = EV_FAULT_DETECTED; + } + break; + case FOLLOW_UP: + if (tc_fwd_folup(p, msg)) { + event = EV_FAULT_DETECTED; + break; + } + process_follow_up(p, dup); + break; + case DELAY_RESP: + break; + case PDELAY_RESP_FOLLOW_UP: + process_pdelay_resp_fup(p, dup); + break; + case ANNOUNCE: + if (tc_forward(p, msg)) { + event = EV_FAULT_DETECTED; + break; + } + if (process_announce(p, dup)) { + event = EV_STATE_DECISION_EVENT; + } + break; + case SIGNALING: + case MANAGEMENT: + if (tc_forward(p, msg)) { + event = EV_FAULT_DETECTED; + } + break; + } + + msg_put(msg); + msg_put(dup); + + return event; +} diff --git a/port.c b/port.c index 2ddb2e2..c37a480 100644 --- a/port.c +++ b/port.c @@ -2781,6 +2781,9 @@ struct port *port_open(int phc_index, p->event = bc_event; break; case CLOCK_TYPE_P2P: + p->dispatch = p2p_dispatch; + p->event = p2p_event; + break; case CLOCK_TYPE_E2E: case CLOCK_TYPE_MANAGEMENT: return NULL; @@ -2835,6 +2838,10 @@ struct port *port_open(int phc_index, p->delayMechanism = config_get_int(cfg, p->name, "delay_mechanism"); p->versionNumber = PTP_VERSION; + if (number && type == CLOCK_TYPE_P2P && p->delayMechanism != DM_P2P) { + pr_err("port %d: P2P TC needs P2P ports", number); + goto err_port; + } if (p->hybrid_e2e && p->delayMechanism != DM_E2E) { pr_warning("port %d: hybrid_e2e only works with E2E", number); } diff --git a/port_private.h b/port_private.h index 558c31b..f1294db 100644 --- a/port_private.h +++ b/port_private.h @@ -133,6 +133,9 @@ struct port { #define portnum(p) (p->portIdentity.portNumber) +void p2p_dispatch(struct port *p, enum fsm_event event, int mdiff); +enum fsm_event p2p_event(struct port *p, int fd_index); + int clear_fault_asap(struct fault_interval *faint); void fc_clear(struct foreign_clock *fc); int port_clr_tmo(int fd); -- 2.11.0 |
From: Richard C. <ric...@gm...> - 2018-04-14 16:45:52
|
The E2E TC forwards Announce, Delay_Req, Delay_Resp, Management, Signaling, and Sync messages, and drops P2P Delay messages. This implementation tracks the GM using the BMCA in order to syntonize (or possibly even synchronize) with it. Signed-off-by: Richard Cochran <ric...@gm...> --- clock.c | 2 +- e2e_tc.c | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ makefile | 2 +- port.c | 7 +++ port_private.h | 3 + 5 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 e2e_tc.c diff --git a/clock.c b/clock.c index 3977317..2c29950 100644 --- a/clock.c +++ b/clock.c @@ -870,9 +870,9 @@ struct clock *clock_create(enum clock_type type, struct config *config, case CLOCK_TYPE_ORDINARY: case CLOCK_TYPE_BOUNDARY: case CLOCK_TYPE_P2P: + case CLOCK_TYPE_E2E: c->type = type; break; - case CLOCK_TYPE_E2E: case CLOCK_TYPE_MANAGEMENT: return NULL; } diff --git a/e2e_tc.c b/e2e_tc.c new file mode 100644 index 0000000..35f92d9 --- /dev/null +++ b/e2e_tc.c @@ -0,0 +1,193 @@ +/** + * @file e2e_tc.c + * @note Copyright (C) 2018 Richard Cochran <ric...@gm...> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA. + */ +#include <errno.h> + +#include "port.h" +#include "port_private.h" +#include "print.h" +#include "rtnl.h" +#include "tc.h" + +void e2e_dispatch(struct port *p, enum fsm_event event, int mdiff) +{ + if (!port_state_update(p, event, mdiff)) { + return; + } + if (!portnum(p)) { + /* UDS needs no timers. */ + return; + } + + port_clr_tmo(p->fda.fd[FD_ANNOUNCE_TIMER]); + port_clr_tmo(p->fda.fd[FD_SYNC_RX_TIMER]); + /* Leave FD_DELAY_TIMER running. */ + port_clr_tmo(p->fda.fd[FD_QUALIFICATION_TIMER]); + port_clr_tmo(p->fda.fd[FD_MANNO_TIMER]); + port_clr_tmo(p->fda.fd[FD_SYNC_TX_TIMER]); + + /* + * Handle the side effects of the state transition. + */ + switch (p->state) { + case PS_INITIALIZING: + break; + case PS_FAULTY: + case PS_DISABLED: + port_disable(p); + break; + case PS_LISTENING: + port_set_announce_tmo(p); + port_set_delay_tmo(p); + break; + case PS_PRE_MASTER: + case PS_MASTER: + case PS_GRAND_MASTER: + break; + case PS_PASSIVE: + port_set_announce_tmo(p); + break; + case PS_UNCALIBRATED: + case PS_SLAVE: + port_set_announce_tmo(p); + break; + }; +} + +enum fsm_event e2e_event(struct port *p, int fd_index) +{ + int cnt, fd = p->fda.fd[fd_index]; + enum fsm_event event = EV_NONE; + struct ptp_message *msg, *dup; + + switch (fd_index) { + case FD_ANNOUNCE_TIMER: + case FD_SYNC_RX_TIMER: + pr_debug("port %hu: %s timeout", portnum(p), + fd_index == FD_SYNC_RX_TIMER ? "rx sync" : "announce"); + if (p->best) { + fc_clear(p->best); + } + port_set_announce_tmo(p); + return EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES; + + case FD_DELAY_TIMER: + pr_debug("port %hu: delay timeout", portnum(p)); + port_set_delay_tmo(p); + tc_prune(p); + return EV_NONE; + + case FD_QUALIFICATION_TIMER: + case FD_MANNO_TIMER: + case FD_SYNC_TX_TIMER: + pr_err("unexpected timer expiration"); + return EV_NONE; + + case FD_RTNL: + pr_debug("port %hu: received link status notification", portnum(p)); + rtnl_link_status(fd, p->name, port_link_status, p); + if (p->link_status == (LINK_UP|LINK_STATE_CHANGED)) { + return EV_FAULT_CLEARED; + } else if ((p->link_status == (LINK_DOWN|LINK_STATE_CHANGED)) || + (p->link_status & TS_LABEL_CHANGED)) { + return EV_FAULT_DETECTED; + } else { + return EV_NONE; + } + } + + msg = msg_allocate(); + if (!msg) { + return EV_FAULT_DETECTED; + } + msg->hwts.type = p->timestamping; + + cnt = transport_recv(p->trp, fd, msg); + if (cnt <= 0) { + pr_err("port %hu: recv message failed", portnum(p)); + msg_put(msg); + return EV_FAULT_DETECTED; + } + if (msg_sots_valid(msg)) { + ts_add(&msg->hwts.ts, -p->rx_timestamp_offset); + } + if (msg->header.flagField[0] & UNICAST) { + pl_warning(600, "cannot handle unicast messages!"); + msg_put(msg); + return EV_NONE; + } + + dup = msg_duplicate(msg, cnt); + if (!dup) { + msg_put(msg); + return EV_NONE; + } + + switch (msg_type(msg)) { + case SYNC: + if (tc_fwd_sync(p, msg)) { + event = EV_FAULT_DETECTED; + break; + } + process_sync(p, dup); + break; + case DELAY_REQ: + if (tc_fwd_request(p, msg)) { + event = EV_FAULT_DETECTED; + } + break; + case PDELAY_REQ: + break; + case PDELAY_RESP: + break; + case FOLLOW_UP: + if (tc_fwd_folup(p, msg)) { + event = EV_FAULT_DETECTED; + break; + } + process_follow_up(p, dup); + break; + case DELAY_RESP: + if (tc_fwd_response(p, msg)) { + event = EV_FAULT_DETECTED; + } + break; + case PDELAY_RESP_FOLLOW_UP: + break; + case ANNOUNCE: + if (tc_forward(p, msg)) { + event = EV_FAULT_DETECTED; + break; + } + if (process_announce(p, dup)) { + event = EV_STATE_DECISION_EVENT; + } + break; + case SIGNALING: + case MANAGEMENT: + if (tc_forward(p, msg)) { + event = EV_FAULT_DETECTED; + } + break; + } + + msg_put(msg); + msg_put(dup); + + return event; +} diff --git a/makefile b/makefile index 9bc4be6..b5ab1d2 100644 --- a/makefile +++ b/makefile @@ -23,7 +23,7 @@ VER = -DVER=$(version) CFLAGS = -Wall $(VER) $(incdefs) $(DEBUG) $(EXTRA_CFLAGS) LDLIBS = -lm -lrt $(EXTRA_LDFLAGS) PRG = ptp4l hwstamp_ctl nsm phc2sys phc_ctl pmc timemaster -OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o fault.o \ +OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o e2e_tc.o fault.o \ filter.o fsm.o hash.o linreg.o mave.o mmedian.o msg.o ntpshm.o nullf.o phc.o \ pi.o port.o print.o ptp4l.o p2p_tc.o raw.o rtnl.o servo.o sk.o stats.o tc.o tlv.o \ transport.o tsproc.o udp.o udp6.o uds.o util.o version.o diff --git a/port.c b/port.c index c37a480..34503cf 100644 --- a/port.c +++ b/port.c @@ -2785,6 +2785,9 @@ struct port *port_open(int phc_index, p->event = p2p_event; break; case CLOCK_TYPE_E2E: + p->dispatch = e2e_dispatch; + p->event = e2e_event; + break; case CLOCK_TYPE_MANAGEMENT: return NULL; } @@ -2842,6 +2845,10 @@ struct port *port_open(int phc_index, pr_err("port %d: P2P TC needs P2P ports", number); goto err_port; } + if (number && type == CLOCK_TYPE_E2E && p->delayMechanism != DM_E2E) { + pr_err("port %d: E2E TC needs E2E ports", number); + goto err_port; + } if (p->hybrid_e2e && p->delayMechanism != DM_E2E) { pr_warning("port %d: hybrid_e2e only works with E2E", number); } diff --git a/port_private.h b/port_private.h index f1294db..12372c8 100644 --- a/port_private.h +++ b/port_private.h @@ -133,6 +133,9 @@ struct port { #define portnum(p) (p->portIdentity.portNumber) +void e2e_dispatch(struct port *p, enum fsm_event event, int mdiff); +enum fsm_event e2e_event(struct port *p, int fd_index); + void p2p_dispatch(struct port *p, enum fsm_event event, int mdiff); enum fsm_event p2p_event(struct port *p, int fd_index); -- 2.11.0 |
From: Richard C. <ric...@gm...> - 2018-04-14 16:45:54
|
Signed-off-by: Richard Cochran <ric...@gm...> --- P2P-TC.cfg | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 P2P-TC.cfg diff --git a/P2P-TC.cfg b/P2P-TC.cfg new file mode 100644 index 0000000..119627c --- /dev/null +++ b/P2P-TC.cfg @@ -0,0 +1,91 @@ +[global] +# +# Default Data Set +# +twoStepFlag 1 +slaveOnly 0 +priority1 128 +priority2 128 +domainNumber 0 +#utc_offset 37 +clockClass 248 +clockAccuracy 0xFE +offsetScaledLogVariance 0xFFFF +free_running 1 +freq_est_interval 3 +dscp_event 0 +dscp_general 0 +# +# Port Data Set +# +logAnnounceInterval 1 +logSyncInterval 0 +logMinDelayReqInterval 0 +logMinPdelayReqInterval 0 +announceReceiptTimeout 3 +syncReceiptTimeout 0 +delayAsymmetry 0 +fault_reset_interval 4 +neighborPropDelayThresh 20000000 +# +# Run time options +# +assume_two_step 0 +logging_level 6 +path_trace_enabled 0 +follow_up_info 0 +hybrid_e2e 0 +net_sync_monitor 0 +tx_timestamp_timeout 1 +use_syslog 1 +verbose 0 +summary_interval 1 +kernel_leap 1 +check_fup_sync 0 +# +# Servo Options +# +pi_proportional_const 0.0 +pi_integral_const 0.0 +pi_proportional_scale 0.0 +pi_proportional_exponent -0.3 +pi_proportional_norm_max 0.7 +pi_integral_scale 0.0 +pi_integral_exponent 0.4 +pi_integral_norm_max 0.3 +step_threshold 0.0 +first_step_threshold 0.00002 +max_frequency 900000000 +clock_servo pi +sanity_freq_limit 200000000 +ntpshm_segment 0 +# +# Transport options +# +transportSpecific 0x0 +ptp_dst_mac 01:1B:19:00:00:00 +p2p_dst_mac 01:80:C2:00:00:0E +udp_ttl 1 +udp6_scope 0x0E +uds_address /var/run/ptp4l +# +# Default interface options +# +clock_type P2P_TC +network_transport L2 +delay_mechanism P2P +time_stamping hardware +tsproc_mode filter +delay_filter moving_median +delay_filter_length 10 +egressLatency 0 +ingressLatency 0 +boundary_clock_jbod 0 +# +# Clock description +# +productDescription ;; +revisionData ;; +manufacturerIdentity 00:00:00 +userDescription ; +timeSource 0xA0 -- 2.11.0 |
From: Richard C. <ric...@gm...> - 2018-04-14 16:45:53
|
This patch adds a new configuation option that enables transparent clock mode. When TC mode is specified, the configured delay mechanism must also match. The legacy behavior of upgrading a multi-port clock from OC to BC is preserved. Signed-off-by: Richard Cochran <ric...@gm...> --- config.c | 10 ++++++++++ default.cfg | 1 + gPTP.cfg | 1 + ptp4l.8 | 15 +++++++++++---- ptp4l.c | 44 +++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 64 insertions(+), 7 deletions(-) diff --git a/config.c b/config.c index 320cc1b..ba1e59b 100644 --- a/config.c +++ b/config.c @@ -22,6 +22,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include "clock.h" #include "config.h" #include "ether.h" #include "hash.h" @@ -133,6 +134,14 @@ static struct config_enum clock_servo_enu[] = { { NULL, 0 }, }; +static struct config_enum clock_type_enu[] = { + { "OC", CLOCK_TYPE_ORDINARY }, + { "BC", CLOCK_TYPE_BOUNDARY }, + { "P2P_TC", CLOCK_TYPE_P2P }, + { "E2E_TC", CLOCK_TYPE_E2E }, + { NULL, 0 }, +}; + static struct config_enum delay_filter_enu[] = { { "moving_average", FILTER_MOVING_AVERAGE }, { "moving_median", FILTER_MOVING_MEDIAN }, @@ -178,6 +187,7 @@ struct config_item config_tab[] = { GLOB_ITEM_INT("clockAccuracy", 0xfe, 0, UINT8_MAX), GLOB_ITEM_INT("clockClass", 248, 0, UINT8_MAX), GLOB_ITEM_ENU("clock_servo", CLOCK_SERVO_PI, clock_servo_enu), + GLOB_ITEM_ENU("clock_type", CLOCK_TYPE_ORDINARY, clock_type_enu), PORT_ITEM_INT("delayAsymmetry", 0, INT_MIN, INT_MAX), PORT_ITEM_ENU("delay_filter", FILTER_MOVING_MEDIAN, delay_filter_enu), PORT_ITEM_INT("delay_filter_length", 10, 1, INT_MAX), diff --git a/default.cfg b/default.cfg index e76aeae..3b7743e 100644 --- a/default.cfg +++ b/default.cfg @@ -71,6 +71,7 @@ uds_address /var/run/ptp4l # # Default interface options # +clock_type OC network_transport UDPv4 delay_mechanism E2E time_stamping hardware diff --git a/gPTP.cfg b/gPTP.cfg index 1e7a33e..9f17377 100644 --- a/gPTP.cfg +++ b/gPTP.cfg @@ -67,6 +67,7 @@ uds_address /var/run/ptp4l # # Default interface options # +clock_type OC network_transport L2 delay_mechanism P2P time_stamping hardware diff --git a/ptp4l.8 b/ptp4l.8 index 950e07c..87b3a02 100644 --- a/ptp4l.8 +++ b/ptp4l.8 @@ -1,6 +1,6 @@ -.TH PTP4l 8 "Novemver 2017" "linuxptp" +.TH PTP4l 8 "March 2018" "linuxptp" .SH NAME -ptp4l - PTP Boundary/Ordinary Clock +ptp4l - PTP Boundary/Ordinary/Transparent Clock .SH SYNOPSIS .B ptp4l @@ -23,8 +23,8 @@ ptp4l - PTP Boundary/Ordinary Clock .SH DESCRIPTION .B ptp4l is an implementation of the Precision Time Protocol (PTP) according to IEEE -standard 1588 for Linux. It implements Boundary Clock (BC) and Ordinary Clock -(OC). +standard 1588 for Linux. It implements Boundary Clock (BC), Ordinary Clock +(OC), and Transparent Clock (TC). .SH OPTIONS .TP @@ -389,6 +389,13 @@ number is set to the domain number), and "nullf" for a servo that always dials frequency offset zero (for use in SyncE nodes). The default is "pi." .TP +.B clock_type +Specifies the kind of PTP clock. Valid values are "OC" for ordinary +clock, "BC" for boundary clock, "P2P_TC" for peer to peer transparent +clock, and "E2E_TC" for end to end transparent clock. An multi-port +ordinary clock will automatically be configured as a boundary clock. +The default is "OC". +.TP .B pi_proportional_const The proportional constant of the PI controller. When set to 0.0, the proportional constant will be set by the following formula from the current diff --git a/ptp4l.c b/ptp4l.c index ee31718..9ef8169 100644 --- a/ptp4l.c +++ b/ptp4l.c @@ -1,6 +1,6 @@ /** * @file ptp4l.c - * @brief PTP Boundary Clock main program + * @brief PTP Boundary Clock or Transparent Clock main program * @note Copyright (C) 2011 Richard Cochran <ric...@gm...> * * This program is free software; you can redistribute it and/or modify @@ -59,6 +59,7 @@ static void usage(char *progname) " -p [dev] PTP hardware clock device to use, default auto\n" " (ignored for SOFTWARE/LEGACY HW time stamping)\n" " -s slave only mode (overrides configuration file)\n" + " -t transparent clock\n" " -l [num] set the logging level to 'num'\n" " -m print messages to stdout\n" " -q do not print messages to the syslog\n" @@ -71,6 +72,7 @@ static void usage(char *progname) int main(int argc, char *argv[]) { char *config = NULL, *req_phc = NULL, *progname; + enum clock_type type = CLOCK_TYPE_ORDINARY; int c, err = -1, index, print_level; struct clock *clock = NULL; struct option *opts; @@ -201,8 +203,44 @@ int main(int argc, char *argv[]) goto out; } - clock = clock_create(cfg->n_interfaces > 1 ? CLOCK_TYPE_BOUNDARY : - CLOCK_TYPE_ORDINARY, cfg, req_phc); + type = config_get_int(cfg, NULL, "clock_type"); + switch (type) { + case CLOCK_TYPE_ORDINARY: + if (cfg->n_interfaces > 1) { + type = CLOCK_TYPE_BOUNDARY; + } + break; + case CLOCK_TYPE_BOUNDARY: + if (cfg->n_interfaces < 2) { + fprintf(stderr, "BC needs at least two interfaces\n"); + goto out; + } + break; + case CLOCK_TYPE_P2P: + if (cfg->n_interfaces < 2) { + fprintf(stderr, "TC needs at least two interfaces\n"); + goto out; + } + if (DM_P2P != config_get_int(cfg, NULL, "delay_mechanism")) { + fprintf(stderr, "P2P_TC needs P2P delay mechanism\n"); + goto out; + } + break; + case CLOCK_TYPE_E2E: + if (cfg->n_interfaces < 2) { + fprintf(stderr, "TC needs at least two interfaces\n"); + goto out; + } + if (DM_E2E != config_get_int(cfg, NULL, "delay_mechanism")) { + fprintf(stderr, "E2E_TC needs E2E delay mechanism\n"); + goto out; + } + break; + case CLOCK_TYPE_MANAGEMENT: + goto out; + } + + clock = clock_create(type, cfg, req_phc); if (!clock) { fprintf(stderr, "failed to create a clock\n"); goto out; -- 2.11.0 |
From: Richard C. <ric...@gm...> - 2018-04-14 16:45:55
|
Signed-off-by: Richard Cochran <ric...@gm...> --- E2E-TC.cfg | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 E2E-TC.cfg diff --git a/E2E-TC.cfg b/E2E-TC.cfg new file mode 100644 index 0000000..9d9aacc --- /dev/null +++ b/E2E-TC.cfg @@ -0,0 +1,91 @@ +[global] +# +# Default Data Set +# +twoStepFlag 1 +slaveOnly 0 +priority1 128 +priority2 128 +domainNumber 0 +#utc_offset 37 +clockClass 248 +clockAccuracy 0xFE +offsetScaledLogVariance 0xFFFF +free_running 1 +freq_est_interval 3 +dscp_event 0 +dscp_general 0 +# +# Port Data Set +# +logAnnounceInterval 1 +logSyncInterval 0 +logMinDelayReqInterval 0 +logMinPdelayReqInterval 0 +announceReceiptTimeout 3 +syncReceiptTimeout 0 +delayAsymmetry 0 +fault_reset_interval 4 +neighborPropDelayThresh 20000000 +# +# Run time options +# +assume_two_step 0 +logging_level 6 +path_trace_enabled 0 +follow_up_info 0 +hybrid_e2e 0 +net_sync_monitor 0 +tx_timestamp_timeout 1 +use_syslog 1 +verbose 0 +summary_interval 1 +kernel_leap 1 +check_fup_sync 0 +# +# Servo Options +# +pi_proportional_const 0.0 +pi_integral_const 0.0 +pi_proportional_scale 0.0 +pi_proportional_exponent -0.3 +pi_proportional_norm_max 0.7 +pi_integral_scale 0.0 +pi_integral_exponent 0.4 +pi_integral_norm_max 0.3 +step_threshold 0.0 +first_step_threshold 0.00002 +max_frequency 900000000 +clock_servo pi +sanity_freq_limit 200000000 +ntpshm_segment 0 +# +# Transport options +# +transportSpecific 0x0 +ptp_dst_mac 01:1B:19:00:00:00 +p2p_dst_mac 01:80:C2:00:00:0E +udp_ttl 1 +udp6_scope 0x0E +uds_address /var/run/ptp4l +# +# Default interface options +# +clock_type E2E_TC +network_transport L2 +delay_mechanism E2E +time_stamping hardware +tsproc_mode filter +delay_filter moving_median +delay_filter_length 10 +egressLatency 0 +ingressLatency 0 +boundary_clock_jbod 0 +# +# Clock description +# +productDescription ;; +revisionData ;; +manufacturerIdentity 00:00:00 +userDescription ; +timeSource 0xA0 -- 2.11.0 |
From: Miroslav L. <mli...@re...> - 2018-04-18 13:28:15
|
On Sat, Apr 14, 2018 at 09:45:34AM -0700, Richard Cochran wrote: > This series add support for running a TC over multiple ports. > Comments, testing, and review are most welcome. This is an amazing feature. I'm glad that we'll finally be able to test the processing of the correction field without having to have a PTP-TC switch (I have yet to see one). The code looks good to me, but I didn't look very thoroughly. I ran some tests and it seems to be working nicely. I used two machines with dual-port NICs connected directly to each other. One was running as a E2E/P2P TC using the two ports as JBOD and the other was running two separate ptp4l instances, a grandmaster and slave. phc2sys was used to compare the two clocks to see if there are any errors in the correction. It was close to zero as expected. With E2E TC the delay measured by the slave was the sum of the two links' delays. With P2P TC the delay was just the link it was connected to. E2E TC with the P2P delay mechanism doesn't seem to work. The e2e_tc code drops pdelay req/resp. If I'm reading the 1588 spec correctly, that should be allowed. It took me a while to realize I need to enable free_running to enable the E2E TC to correct for the measured frequency offset. Would it make sense to imply that option automatically? Also, should the TC drop sync/delay messages until it has estimated the offset to minimize disturbances when the TC is restarted? With P2P TC free_running can be disabled, but I'm not sure how much sense such a configuration makes. Are there any plans to support a combined OC+TC? Thanks for your work on this. -- Miroslav Lichvar |
From: Richard C. <ric...@gm...> - 2018-04-21 06:01:23
|
On Wed, Apr 18, 2018 at 03:28:08PM +0200, Miroslav Lichvar wrote: > This is an amazing feature. I'm glad that we'll finally be able to > test the processing of the correction field without having to have a > PTP-TC switch (I have yet to see one). Me neither! > With E2E TC the delay measured by the slave was the sum of the two > links' delays. With P2P TC the delay was just the link it was > connected to. Right. (That is expected. The P2P TC accumulate the delays into the correction field.) > E2E TC with the P2P delay mechanism doesn't seem to work. The e2e_tc > code drops pdelay req/resp. If I'm reading the 1588 spec correctly, > that should be allowed. Not exactly. There is this: NOTE -- Peer-to-peer clocks are normally used only in a homogeneous system of peer-to-peer clocks. The provisions of 11.5.4 allow the use of end-to-end transparent clocks in systems based on future versions of the standard that might specify how to implement mixed systems with one-to-many connections between peer-to-peer clocks. So I don't feel any great need to support P2P over E2E until we hear how this 1:N relation is supposed to work! > It took me a while to realize I need to enable free_running to enable > the E2E TC to correct for the measured frequency offset. Would it make > sense to imply that option automatically? Both configuration files for TC do have free_running enabled. > Also, should the TC drop > sync/delay messages until it has estimated the offset to minimize > disturbances when the TC is restarted? But that would make cold start up take longer, wouldn't it? (I think shortening startup is more important than restart behavior.) > With P2P TC free_running can be disabled, but I'm not sure how much > sense such a configuration makes. Are there any plans to support a > combined OC+TC? Right, so the P2P TC with free_running=0 will synchronize to the GM. My tests show that that causes larger errors at the OC slaves, but still someone may want to have the TC synchronized, and I think the user should be able to have it that way. For E2E, I suppose we could snoop the delay from the switch port that is the slave state... Thanks, Richard |
From: Miroslav L. <mli...@re...> - 2018-04-26 07:09:19
|
On Fri, Apr 20, 2018 at 11:01:09PM -0700, Richard Cochran wrote: > On Wed, Apr 18, 2018 at 03:28:08PM +0200, Miroslav Lichvar wrote: > > E2E TC with the P2P delay mechanism doesn't seem to work. The e2e_tc > > code drops pdelay req/resp. If I'm reading the 1588 spec correctly, > > that should be allowed. > > Not exactly. There is this: > > NOTE -- Peer-to-peer clocks are normally used only in a > homogeneous system of peer-to-peer clocks. The provisions of > 11.5.4 allow the use of end-to-end transparent clocks in systems > based on future versions of the standard that might specify how to > implement mixed systems with one-to-many connections between > peer-to-peer clocks. I don't really understand what this means. Does it apply to TC or rather BC/OC? If some future standard will figure it out, will it work with the current TC implementation? FWIW, 10.2 clearly says that a E2E TC should forward all PTPv2 messages and correct all event messages, and it shouldn't make its own P2P delay measurements. > > Also, should the TC drop > > sync/delay messages until it has estimated the offset to minimize > > disturbances when the TC is restarted? > > But that would make cold start up take longer, wouldn't it? I'm not sure. I guess it will depend on the servo and delay filtering used by the OCs. If the TC makes inaccurate corrections, it might take longer for the OCs to converge to the corrected values later. > > With P2P TC free_running can be disabled, but I'm not sure how much > > sense such a configuration makes. Are there any plans to support a > > combined OC+TC? > > Right, so the P2P TC with free_running=0 will synchronize to the GM. > My tests show that that causes larger errors at the OC slaves, Have you tried it with a slower servo configuration? > but > still someone may want to have the TC synchronized, and I think the > user should be able to have it that way. > > For E2E, I suppose we could snoop the delay from the switch port that > is the slave state... I think a combined OC+TC should make its own delay measurements. There are some notes at the end of the sections 6.5.4 and 6.5.5 in the spec. I have another question about the TC implementation. What happens when it's forwarding messages from different domains? I have not tried it. The TC code doesn't seem to care much about the number. For estimating the frequency offset, shouldn't it avoid mixing measurements from different domains? -- Miroslav Lichvar |
From: Richard C. <ric...@gm...> - 2018-04-26 14:15:19
|
On Thu, Apr 26, 2018 at 09:09:10AM +0200, Miroslav Lichvar wrote: > On Fri, Apr 20, 2018 at 11:01:09PM -0700, Richard Cochran wrote: > > > > NOTE -- Peer-to-peer clocks are normally used only in a > > homogeneous system of peer-to-peer clocks. The provisions of > > 11.5.4 allow the use of end-to-end transparent clocks in systems > > based on future versions of the standard that might specify how to > > implement mixed systems with one-to-many connections between > > peer-to-peer clocks. > > I don't really understand what this means. Does it apply to TC or > rather BC/OC? Clause 11.5.4 is about correcting P2P delay residence in an E2E TC. > If some future standard will figure it out, will it work > with the current TC implementation? No, because we drop P2P delay frames when in E2E mode. > FWIW, 10.2 clearly says that a E2E TC should forward all PTPv2 > messages and correct all event messages, and it shouldn't make its own > P2P delay measurements. But does it make any sense to send P2P delay frames out multiple ports? Correcting P2P delay requires keeping track of all outstanding peer delay exchanges. IMHO it is a waste of effort to support mis-configured PTP networks that mix E2E and P2P in some undefined way. We can always add this later on, if and when this mixing becomes standardized. > > But that would make cold start up take longer, wouldn't it? > > I'm not sure. I guess it will depend on the servo and delay filtering > used by the OCs. If the TC makes inaccurate corrections, it might take > longer for the OCs to converge to the corrected values later. So the TC doesn't make inaccurate corrections, it simply makes no correction at all (residence * 1.0) until the rate ratio is known. This behavior is actually allowed by 1588, as syntonization is optional. > > Right, so the P2P TC with free_running=0 will synchronize to the GM. > > My tests show that that causes larger errors at the OC slaves, > > Have you tried it with a slower servo configuration? No, I only ran with the default automatic PI weights. So maybe TC synchronization can be improved by end user configuration. > > For E2E, I suppose we could snoop the delay from the switch port that > > is the slave state... > > I think a combined OC+TC should make its own delay measurements. There > are some notes at the end of the sections 6.5.4 and 6.5.5 in the spec. Hm. Well in any case I don't like how my E2E TC acts differently than a P2P TC with respect to free_running. The E2E mode needs to synchronize when free_running=0. > I have another question about the TC implementation. What happens > when it's forwarding messages from different domains? I have not tried > it. The TC code doesn't seem to care much about the number. For > estimating the frequency offset, shouldn't it avoid mixing > measurements from different domains? Yes, good point. Thanks, Richard |
From: Miroslav L. <mli...@re...> - 2018-04-27 13:24:57
|
On Thu, Apr 26, 2018 at 07:15:08AM -0700, Richard Cochran wrote: > On Thu, Apr 26, 2018 at 09:09:10AM +0200, Miroslav Lichvar wrote: > > FWIW, 10.2 clearly says that a E2E TC should forward all PTPv2 > > messages and correct all event messages, and it shouldn't make its own > > P2P delay measurements. > > But does it make any sense to send P2P delay frames out multiple > ports? Probably not, at least until it is specified by the standard. For a E2E TC which has only two PTP ports (and other non-PTP ports) this is not a problem though. It could be inserted between two clocks using the P2P delay mechanism and they wouldn't see a difference except non-zero corrections in received packets. > Correcting P2P delay requires keeping track of all outstanding peer > delay exchanges. IMHO it is a waste of effort to support > mis-configured PTP networks that mix E2E and P2P in some undefined > way. We can always add this later on, if and when this mixing becomes > standardized. Ok, fair enough. > > > But that would make cold start up take longer, wouldn't it? > > > > I'm not sure. I guess it will depend on the servo and delay filtering > > used by the OCs. If the TC makes inaccurate corrections, it might take > > longer for the OCs to converge to the corrected values later. > > So the TC doesn't make inaccurate corrections, it simply makes no > correction at all (residence * 1.0) until the rate ratio is known. > This behavior is actually allowed by 1588, as syntonization is > optional. I think that might be even worse than making inaccurate corrections with zero frequency offset. If a TC and slave are (re)started at the same time, the slave's initial correction may have a large error, which will need to corrected later (when steps may already be disabled). It will also disrupt the slave's frequency estimate if the TC starts correcting the messages before the slave's estimation interval ends. -- Miroslav Lichvar |
From: Richard C. <ric...@gm...> - 2018-04-27 13:41:59
|
On Fri, Apr 27, 2018 at 03:24:48PM +0200, Miroslav Lichvar wrote: > I think that might be even worse than making inaccurate corrections > with zero frequency offset. If a TC and slave are (re)started at the > same time, the slave's initial correction may have a large error, > which will need to corrected later (when steps may already be > disabled). It will also disrupt the slave's frequency estimate if the > TC starts correcting the messages before the slave's estimation > interval ends. So lets consider the magnitude of the error. On a switch based system on my desk, the residence time is about 2 milliseconds. (This is rather large because the timestamps come over MDIO). The frequency offset is typically about 30 ppm, and so the induced error is 60 nanoseconds. Using HW time stamping, such small offsets are corrected at the slave in a few seconds using small frequency adjustments. Thanks, Richard |
From: Miroslav L. <mli...@re...> - 2018-04-27 14:06:36
|
On Fri, Apr 27, 2018 at 06:41:49AM -0700, Richard Cochran wrote: > On Fri, Apr 27, 2018 at 03:24:48PM +0200, Miroslav Lichvar wrote: > > I think that might be even worse than making inaccurate corrections > > with zero frequency offset. If a TC and slave are (re)started at the > > same time, the slave's initial correction may have a large error, > > which will need to corrected later (when steps may already be > > disabled). It will also disrupt the slave's frequency estimate if the > > TC starts correcting the messages before the slave's estimation > > interval ends. > > So lets consider the magnitude of the error. On a switch based system > on my desk, the residence time is about 2 milliseconds. (This is > rather large because the timestamps come over MDIO). The frequency > offset is typically about 30 ppm, and so the induced error is 60 > nanoseconds. That's for the case when the TC starts correcting messages immediately, just not having the correct frequency offset yet, right? If the TC doesn't make any corrections after start, the error is the residence time. With heavy network traffic the delays can be in tens of milliseconds or maybe even more? > Using HW time stamping, such small offsets are corrected at the slave > in a few seconds using small frequency adjustments. I think that depends on the servo and its configuration. A slave with a stabilized clock could very well use a much slower servo than the default PI in ptp4l. A 10 millisecond error measured over 10 seconds is 1000 ppm. That could take a while to correct. I'm wondering how do real switches with TC support behave on start. -- Miroslav Lichvar |
From: Richard C. <ric...@gm...> - 2018-04-27 14:41:35
|
On Fri, Apr 27, 2018 at 04:06:27PM +0200, Miroslav Lichvar wrote: > If the TC doesn't make any corrections after start, the error is the > residence time. With heavy network traffic the delays can be in tens > of milliseconds or maybe even more? No, with proper HW support (DSA switch) we aren't bridging all the traffic in the kernel. Instead, the HW switches all the frames except the PTP frames. These are forwarded to the management CPU for processing in the PTP stack. So the residence time should stay constant in a real application. Thanks, Richard |
From: Richard C. <ric...@gm...> - 2018-04-27 14:40:52
|
On Fri, Apr 27, 2018 at 04:06:27PM +0200, Miroslav Lichvar wrote: > That's for the case when the TC starts correcting messages > immediately, just not having the correct frequency offset yet, right? Right. > If the TC doesn't make any corrections after start, the error is the > residence time. With heavy network traffic the delays can be in tens > of milliseconds or maybe even more? I must have misunderstood you. The TC must correct the residence time. The question is whether to apply the frequency correction to the residence times reported in the correction field. I thought you meant that the TC should drop packets until it has measured the frequency offset. Because the error in the residence time is so small when free running, I think it better to simply apply the frequency correction to the residence time when it becomes available (two or four seconds after the TC boots). > I'm wondering how do real switches with TC support behave on start. (*We* are going to be the "real" switch ;) Thanks, Richard |
From: Miroslav L. <mli...@re...> - 2018-04-27 15:44:36
|
On Fri, Apr 27, 2018 at 07:40:42AM -0700, Richard Cochran wrote: > On Fri, Apr 27, 2018 at 04:06:27PM +0200, Miroslav Lichvar wrote: > > If the TC doesn't make any corrections after start, the error is the > > residence time. With heavy network traffic the delays can be in tens > > of milliseconds or maybe even more? > > I must have misunderstood you. The TC must correct the residence > time. The question is whether to apply the frequency correction to > the residence times reported in the correction field. Ok. I probably misread your earlier mail and thought the TC doesn't touch the correction field on start. > I thought you meant that the TC should drop packets until it has > measured the frequency offset. Because the error in the residence > time is so small when free running, I think it better to simply apply > the frequency correction to the residence time when it becomes > available (two or four seconds after the TC boots). I just think it would be cleaner to wait for the frequency to be known before forwarding PTP messages, unless it breaks the protocol or has some other problem. It's like a boundary clock, which waits for its clock to be synchronized before switching its ports to the master state. It probably doesn't matter that much. -- Miroslav Lichvar |