diff --git a/.gitignore b/.gitignore index 63f735d..987eddf 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -SOURCES/linuxptp-1.3.tgz +SOURCES/clknetsim-e63178.tar.gz +SOURCES/linuxptp-20140718gitbdb6a3.tar.gz +SOURCES/testsuite-7c523f.tar.gz diff --git a/.linuxptp.metadata b/.linuxptp.metadata index 9b9f79b..8beb86f 100644 --- a/.linuxptp.metadata +++ b/.linuxptp.metadata @@ -1 +1,3 @@ -e55e5ce04bd5078d6ec1c5772ee8085e790efa1c SOURCES/linuxptp-1.3.tgz +29583b9a274e1d89e9fdcd400ce427a513a666c9 SOURCES/clknetsim-e63178.tar.gz +3bbf8fbe1db7b344d5d465728a83abf877a6032f SOURCES/linuxptp-20140718gitbdb6a3.tar.gz +1d4505f7bac98e143d22b2d84c812f4ec92bb127 SOURCES/testsuite-7c523f.tar.gz diff --git a/SOURCES/linuxptp-linreg_reset.patch b/SOURCES/linuxptp-linreg_reset.patch new file mode 100644 index 0000000..24eb16c --- /dev/null +++ b/SOURCES/linuxptp-linreg_reset.patch @@ -0,0 +1,25 @@ +commit 37459fd1ad1ac48a121ab0edae331090fb8e2833 +Author: Miroslav Lichvar +Date: Thu Nov 20 17:30:28 2014 +0100 + + linreg: fix servo resetting + + The stats for the maximum size were not reset, which caused the + the servo to reuse old data instead of returning with unlocked + state. + + Signed-off-by: Miroslav Lichvar + +diff --git a/linreg.c b/linreg.c +index b94c44e..fde604d 100644 +--- a/linreg.c ++++ b/linreg.c +@@ -294,7 +294,7 @@ static void linreg_reset(struct servo *servo) + s->last_update = 0; + s->frequency_ratio = 1.0; + +- for (i = MIN_SIZE; i < MAX_SIZE; i++) { ++ for (i = MIN_SIZE; i <= MAX_SIZE; i++) { + s->results[i - MIN_SIZE].slope = 0.0; + s->results[i - MIN_SIZE].err_updates = 0; + } diff --git a/SOURCES/linuxptp-peeraddress.patch b/SOURCES/linuxptp-peeraddress.patch new file mode 100644 index 0000000..db6f335 --- /dev/null +++ b/SOURCES/linuxptp-peeraddress.patch @@ -0,0 +1,65 @@ +commit 6b05b4e7d3255a3bbf49e20abccb511290b6196d +Author: Richard Cochran +Date: Thu Oct 9 22:09:06 2014 +0200 + + Restore the peer addresses in P2P mode. + + Commit ea7a7882 removed the calls to transport_peer(), inadvertently + substituting them with transport_send(), resulting in PDelay messages + being sent with an incorrect destination address. + + This patch fixes the issue by introducing peer_prepare_and_send(), + analogous to the port_prepare_and_send() function. + + Signed-off-by: Richard Cochran + Acked-by: Jiri Benc + +diff --git a/port.c b/port.c +index 18405c6..caea891 100644 +--- a/port.c ++++ b/port.c +@@ -484,6 +484,17 @@ static int path_trace_ignore(struct port *p, struct ptp_message *m) + return 0; + } + ++static int peer_prepare_and_send(struct port *p, struct ptp_message *msg, ++ int event) ++{ ++ int cnt; ++ if (msg_pre_send(msg)) { ++ return -1; ++ } ++ cnt = transport_peer(p->trp, &p->fda, event, msg); ++ return cnt <= 0 ? -1 : 0; ++} ++ + static int port_capable(struct port *p) + { + if (!port_is_ieee8021as(p)) { +@@ -1114,7 +1125,7 @@ static int port_pdelay_request(struct port *p) + msg->header.logMessageInterval = port_is_ieee8021as(p) ? + p->logMinPdelayReqInterval : 0x7f; + +- err = port_prepare_and_send(p, msg, 1); ++ err = peer_prepare_and_send(p, msg, 1); + if (err) { + pr_err("port %hu: send peer delay request failed", portnum(p)); + goto out; +@@ -1728,7 +1739,7 @@ static int process_pdelay_req(struct port *p, struct ptp_message *m) + + fup->pdelay_resp_fup.requestingPortIdentity = m->header.sourcePortIdentity; + +- err = port_prepare_and_send(p, rsp, 1); ++ err = peer_prepare_and_send(p, rsp, 1); + if (err) { + pr_err("port %hu: send peer delay response failed", portnum(p)); + goto out; +@@ -1741,7 +1752,7 @@ static int process_pdelay_req(struct port *p, struct ptp_message *m) + ts_to_timestamp(&rsp->hwts.ts, + &fup->pdelay_resp_fup.responseOriginTimestamp); + +- err = port_prepare_and_send(p, fup, 0); ++ err = peer_prepare_and_send(p, fup, 0); + if (err) + pr_err("port %hu: send pdelay_resp_fup failed", portnum(p)); + diff --git a/SOURCES/linuxptp-phc2sys_state.patch b/SOURCES/linuxptp-phc2sys_state.patch new file mode 100644 index 0000000..f0989f5 --- /dev/null +++ b/SOURCES/linuxptp-phc2sys_state.patch @@ -0,0 +1,35 @@ +commit 7455c241485e01da12d107f5b665e10794330967 +Author: Jiri Benc +Date: Thu Nov 13 18:18:12 2014 +0100 + + phc2sys: fix overwriting of the clock state + + The reconfigure function is missing a check whether state for the given + clock actually changed or not. This caused state for all unchanged ports to + be zeroed. + + Reported-by: Richard Cochran + Signed-off-by: Jiri Benc + +diff --git a/phc2sys.c b/phc2sys.c +index 22eb9c9..67d8a58 100644 +--- a/phc2sys.c ++++ b/phc2sys.c +@@ -305,11 +305,13 @@ static void reconfigure(struct node *node) + continue; + } + +- if (c->new_state == PS_MASTER) +- clock_reinit(c); ++ if (c->new_state) { ++ if (c->new_state == PS_MASTER) ++ clock_reinit(c); + +- c->state = c->new_state; +- c->new_state = 0; ++ c->state = c->new_state; ++ c->new_state = 0; ++ } + + if (c->state == PS_SLAVE) { + src = c; diff --git a/SOURCES/linuxptp-shm.patch b/SOURCES/linuxptp-shm.patch new file mode 100644 index 0000000..317c94c --- /dev/null +++ b/SOURCES/linuxptp-shm.patch @@ -0,0 +1,140 @@ +commit 3760f8b6537ec4f614d9aa07d957716ee2e51059 +Author: Miroslav Lichvar +Date: Wed Jul 9 16:25:08 2014 +0200 + + Add option to set NTP SHM segment number. + + Instead of setting it to the PTP domain number, add a new option to + ptp4l and phc2sys to set it as needed. The default value is 0. This + allows multiple ptp4l/phc2sys instances running in the same domain. + + Signed-off-by: Miroslav Lichvar + +diff --git a/config.c b/config.c +index 0bc85c1..0983c62 100644 +--- a/config.c ++++ b/config.c +@@ -284,7 +284,6 @@ static enum parser_result parse_global_setting(const char *option, + if (r != PARSED_OK) + return r; + dds->domainNumber = uval; +- *cfg->ntpshm_segment = uval; + + } else if (!strcmp(option, "clockClass")) { + r = get_ranged_uint(value, &uval, 0, UINT8_MAX); +@@ -408,6 +407,12 @@ static enum parser_result parse_global_setting(const char *option, + return r; + cfg->dds.sanity_freq_limit = val; + ++ } else if (!strcmp(option, "ntpshm_segment")) { ++ r = get_ranged_int(value, &val, INT_MIN, INT_MAX); ++ if (r != PARSED_OK) ++ return r; ++ *cfg->ntpshm_segment = val; ++ + } else if (!strcmp(option, "ptp_dst_mac")) { + if (MAC_LEN != sscanf(value, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5])) +diff --git a/default.cfg b/default.cfg +index 9f01eda..9e794ba 100644 +--- a/default.cfg ++++ b/default.cfg +@@ -53,6 +53,7 @@ first_step_threshold 0.00002 + max_frequency 900000000 + clock_servo pi + sanity_freq_limit 200000000 ++ntpshm_segment 0 + # + # Transport options + # +diff --git a/gPTP.cfg b/gPTP.cfg +index 4d0a38c..e15a05a 100644 +--- a/gPTP.cfg ++++ b/gPTP.cfg +@@ -53,6 +53,7 @@ first_step_threshold 0.00002 + max_frequency 900000000 + clock_servo pi + sanity_freq_limit 200000000 ++ntpshm_segment 0 + # + # Transport options + # +diff --git a/phc2sys.8 b/phc2sys.8 +index c4fb6f6..22d02c2 100644 +--- a/phc2sys.8 ++++ b/phc2sys.8 +@@ -116,7 +116,7 @@ option. + Specify which clock servo should be used. Valid values are pi for a PI + controller, linreg for an adaptive controller using linear regression, and + ntpshm for the NTP SHM reference clock to allow another process to synchronize +-the local clock (the SHM segment number is set to the domain number). ++the local clock. + The default is pi. + .TP + .BI \-P " kp" +@@ -166,6 +166,10 @@ the synchronized clock. When a larger offset is measured, a warning message + will be printed and the servo will be reset. When set to 0, the sanity check is + disabled. The default is 200000000 (20%). + .TP ++.BI \-M " segment" ++The number of the SHM segment used by ntpshm servo. ++The default is 0. ++.TP + .BI \-u " summary-updates" + Specify the number of clock updates included in summary statistics. The + statistics include offset root mean square (RMS), maximum absolute offset, +diff --git a/phc2sys.c b/phc2sys.c +index 391ae62..22eb9c9 100644 +--- a/phc2sys.c ++++ b/phc2sys.c +@@ -1159,6 +1159,7 @@ static void usage(char *progname) + " -R [rate] slave clock update rate in HZ (1.0)\n" + " -N [num] number of master clock readings per update (5)\n" + " -L [limit] sanity frequency limit in ppb (200000000)\n" ++ " -M [num] NTP SHM segment number (0)\n" + " -u [num] number of clock updates in summary stats (0)\n" + " -n [num] domain number (0)\n" + " -x apply leap seconds by servo instead of kernel\n" +@@ -1199,7 +1200,7 @@ int main(int argc, char *argv[]) + progname = strrchr(argv[0], '/'); + progname = progname ? 1+progname : argv[0]; + while (EOF != (c = getopt(argc, argv, +- "arc:d:s:E:P:I:S:F:R:N:O:L:i:u:wn:xz:l:mqvh"))) { ++ "arc:d:s:E:P:I:S:F:R:N:O:L:M:i:u:wn:xz:l:mqvh"))) { + switch (c) { + case 'a': + autocfg = 1; +@@ -1276,6 +1277,10 @@ int main(int argc, char *argv[]) + if (get_arg_val_i(c, optarg, &node.sanity_freq_limit, 0, INT_MAX)) + return -1; + break; ++ case 'M': ++ if (get_arg_val_i(c, optarg, &ntpshm_segment, INT_MIN, INT_MAX)) ++ return -1; ++ break; + case 'u': + if (get_arg_val_ui(c, optarg, &node.stats_max_count, + 0, UINT_MAX)) +@@ -1287,7 +1292,6 @@ int main(int argc, char *argv[]) + case 'n': + if (get_arg_val_i(c, optarg, &domain_number, 0, 255)) + return -1; +- ntpshm_segment = domain_number; + break; + case 'x': + node.kernel_leap = 0; +diff --git a/ptp4l.8 b/ptp4l.8 +index 1bae78c..687beb6 100644 +--- a/ptp4l.8 ++++ b/ptp4l.8 +@@ -376,6 +376,10 @@ the synchronized clock. When a larger offset is measured, a warning message + will be printed and the servo will be reset. When set to 0, the sanity check is + disabled. The default is 200000000 (20%). + .TP ++.B ntpshm_segment ++The number of the SHM segment used by ntpshm servo. ++The default is 0. ++.TP + .B ptp_dst_mac + The MAC address where should be PTP messages sent. + Relevant only with L2 transport. The default is 01:1B:19:00:00:00. diff --git a/SOURCES/linuxptp-timemaster.patch b/SOURCES/linuxptp-timemaster.patch new file mode 100644 index 0000000..ee23915 --- /dev/null +++ b/SOURCES/linuxptp-timemaster.patch @@ -0,0 +1,1887 @@ +commit 48046e593efc9583335632f25805153cc2ca8114 +Author: Miroslav Lichvar +Date: Fri Oct 3 14:13:50 2014 +0200 + + Don't include config.h in util.h + + The config module is used by ptp4l only, but util is shared with other + programs. + + Signed-off-by: Miroslav Lichvar + +diff --git a/config.h b/config.h +index 9b74f12..d580496 100644 +--- a/config.h ++++ b/config.h +@@ -51,14 +51,6 @@ struct interface { + #define CFG_IGNORE_USE_SYSLOG (1 << 5) + #define CFG_IGNORE_VERBOSE (1 << 6) + +-enum parser_result { +- PARSED_OK, +- NOT_PARSED, +- BAD_VALUE, +- MALFORMED, +- OUT_OF_RANGE, +-}; +- + struct config { + /* configuration override */ + int cfg_ignore; +diff --git a/phc_ctl.c b/phc_ctl.c +index dc9c29c..461f2ac 100644 +--- a/phc_ctl.c ++++ b/phc_ctl.c +@@ -43,6 +43,7 @@ + #include "missing.h" + #include "phc.h" + #include "print.h" ++#include "sk.h" + #include "sysoff.h" + #include "util.h" + #include "version.h" +diff --git a/util.h b/util.h +index cf05e49..bb29e11 100644 +--- a/util.h ++++ b/util.h +@@ -20,7 +20,6 @@ + #ifndef HAVE_UTIL_H + #define HAVE_UTIL_H + +-#include "config.h" + #include "ddt.h" + + /** +@@ -126,6 +125,17 @@ int is_utc_ambiguous(uint64_t ts); + int leap_second_status(uint64_t ts, int leap_set, int *leap, int *utc_offset); + + /** ++ * Values returned by get_ranged_*(). ++ */ ++enum parser_result { ++ PARSED_OK, ++ NOT_PARSED, ++ BAD_VALUE, ++ MALFORMED, ++ OUT_OF_RANGE, ++}; ++ ++/** + * Get an integer value from string with error checking and range + * specification. + * + +commit 2098d7c1626e934e50da24ef4c5810a5b69064b4 +Author: Miroslav Lichvar +Date: Fri Oct 3 14:13:51 2014 +0200 + + Add string and pointer array utility functions. + + Add some functions to work with strings and arrays of pointers that will + be useful later. + + Signed-off-by: Miroslav Lichvar + +diff --git a/incdefs.sh b/incdefs.sh +index cf00eaf..5bbdcea 100755 +--- a/incdefs.sh ++++ b/incdefs.sh +@@ -23,12 +23,15 @@ + # + user_flags() + { ++ # Needed for vasprintf(). ++ printf " -D_GNU_SOURCE" ++ + dirs=$(echo "" | ${CROSS_COMPILE}cpp -Wp,-v 2>&1 >/dev/null | grep ^" /") + for d in $dirs; do + files=$(find $d -type f -name time.h) + for f in $files; do + if grep -q clock_adjtime $f; then +- printf " -D_GNU_SOURCE -DHAVE_CLOCK_ADJTIME" ++ printf " -DHAVE_CLOCK_ADJTIME" + return + fi + done +diff --git a/util.c b/util.c +index cb428b1..06c3296 100644 +--- a/util.c ++++ b/util.c +@@ -18,6 +18,7 @@ + */ + #include + #include ++#include + #include + #include + #include +@@ -342,3 +343,97 @@ int is_running(void) + { + return running; + } ++ ++char *string_newf(const char *format, ...) ++{ ++ va_list ap; ++ char *s; ++ ++ va_start(ap, format); ++ if (vasprintf(&s, format, ap) < 0) ++ s = NULL; ++ va_end(ap); ++ ++ return s; ++} ++ ++void string_append(char **s, const char *str) ++{ ++ size_t len1, len2; ++ ++ len1 = strlen(*s); ++ len2 = strlen(str); ++ *s = realloc(*s, len1 + len2 + 1); ++ if (*s) ++ memcpy((*s) + len1, str, len2 + 1); ++} ++ ++void string_appendf(char **s, const char *format, ...) ++{ ++ va_list ap; ++ size_t len1, len2; ++ char *s2; ++ ++ len1 = strlen(*s); ++ ++ va_start(ap, format); ++ len2 = vasprintf(&s2, format, ap); ++ va_end(ap); ++ ++ if (len2 < 0) { ++ *s = NULL; ++ return; ++ } ++ ++ *s = realloc(*s, len1 + len2 + 1); ++ if (*s) ++ memcpy((*s) + len1, s2, len2 + 1); ++ free(s2); ++} ++ ++void **parray_new(void) ++{ ++ void **a = malloc(sizeof(*a)); ++ ++ if (a) ++ *a = NULL; ++ return a; ++} ++ ++void parray_append(void ***a, void *p) ++{ ++ parray_extend(a, p, NULL); ++} ++ ++void parray_extend(void ***a, ...) ++{ ++ va_list ap; ++ int ilen, len, alloced; ++ void *p; ++ ++ for (len = 0; (*a)[len]; len++) ++ ; ++ len++; ++ ++ va_start(ap, a); ++ for (ilen = 0; va_arg(ap, void *); ilen++) ++ ; ++ va_end(ap); ++ ++ /* Reallocate in exponentially increasing sizes. */ ++ for (alloced = 1; alloced < len; alloced <<= 1) ++ ; ++ if (alloced < len + ilen) { ++ while (alloced < len + ilen) ++ alloced *= 2; ++ *a = realloc(*a, alloced * sizeof **a); ++ if (!*a) ++ return; ++ } ++ ++ va_start(ap, a); ++ while ((p = va_arg(ap, void *))) ++ (*a)[len++ - 1] = p; ++ va_end(ap); ++ (*a)[len - 1] = NULL; ++} +diff --git a/util.h b/util.h +index bb29e11..98b395b 100644 +--- a/util.h ++++ b/util.h +@@ -236,4 +236,61 @@ int handle_term_signals(void); + */ + int is_running(void); + ++/** ++ * Get an allocated and formatted string. This is a wrapper around asprintf(). ++ * ++ * @param format printf() format string. ++ * @param ... printf() arguments. ++ * @return Pointer to the allocated string, NULL on error. ++ */ ++#ifdef __GNUC__ ++__attribute__ ((format (printf, 1, 2))) ++#endif ++char *string_newf(const char *format, ...); ++ ++/** ++ * Reallocate a string and append another string to it. ++ * ++ * @param s String that should be extended, set to NULL on error. ++ * @param str String appended to s. ++ */ ++void string_append(char **s, const char *str); ++#ifdef __GNUC__ ++__attribute__ ((format (printf, 2, 3))) ++#endif ++/** ++ * Reallocate a string and append a formatted string to it. ++ * ++ * @param s String that should be extended, set to NULL on error. ++ * @param format printf() format string. ++ * @param ... printf() arguments. ++ */ ++void string_appendf(char **s, const char *format, ...); ++ ++/** ++ * Get an empty array of pointers terminated by NULL. ++ * ++ * @return Pointer to the allocated array, NULL on error. ++ */ ++void **parray_new(void); ++ ++/** ++ * Append pointer to a NULL-terminated pointer array. The array is reallocated ++ * in exponentially increasing sizes. ++ * ++ * @param a Pointer to pointer array, set to NULL on error. ++ * @param p Pointer appended to the array. ++ */ ++void parray_append(void ***a, void *p); ++ ++ ++/** ++ * Append pointers to a NULL-terminated pointer array. The array is reallocated ++ * in exponentially increasing sizes. ++ * ++ * @param a Pointer to pointer array, set to NULL on error. ++ * @param ... NULL-terminated list of pointers. ++ */ ++void parray_extend(void ***a, ...); ++ + #endif + +commit 82f13c594a8860274e62bc999520b8a434a80a53 +Author: Miroslav Lichvar +Date: Fri Oct 3 14:13:52 2014 +0200 + + Add timemaster. + + timemaster is a program that uses ptp4l and phc2sys in combination with + chronyd or ntpd to synchronize the system clock to NTP and PTP time + sources. The PTP time is provided by phc2sys and ptp4l via SHM reference + clocks to chronyd/ntpd, which can compare all time sources and use the + best sources to synchronize the system clock. + + Signed-off-by: Miroslav Lichvar + +diff --git a/incdefs.sh b/incdefs.sh +index 5bbdcea..34e227f 100755 +--- a/incdefs.sh ++++ b/incdefs.sh +@@ -19,20 +19,34 @@ + # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + # +-# Look for the clock_adjtime functional prototype in the C library. ++# Look for functional prototypes in the C library. + # + user_flags() + { + # Needed for vasprintf(). + printf " -D_GNU_SOURCE" + ++ # Get list of directories searched for header files. + dirs=$(echo "" | ${CROSS_COMPILE}cpp -Wp,-v 2>&1 >/dev/null | grep ^" /") ++ ++ # Look for clock_adjtime(). + for d in $dirs; do + files=$(find $d -type f -name time.h) + for f in $files; do + if grep -q clock_adjtime $f; then + printf " -DHAVE_CLOCK_ADJTIME" +- return ++ break 2 ++ fi ++ done ++ done ++ ++ # Look for posix_spawn(). ++ for d in $dirs; do ++ files=$(find $d -type f -name spawn.h) ++ for f in $files; do ++ if grep -q posix_spawn $f; then ++ printf " -DHAVE_POSIX_SPAWN" ++ break 2 + fi + done + done +diff --git a/makefile b/makefile +index 74a7fe2..5469301 100644 +--- a/makefile ++++ b/makefile +@@ -22,13 +22,14 @@ CC = $(CROSS_COMPILE)gcc + VER = -DVER=$(version) + CFLAGS = -Wall $(VER) $(incdefs) $(DEBUG) $(EXTRA_CFLAGS) + LDLIBS = -lm -lrt $(EXTRA_LDFLAGS) +-PRG = ptp4l pmc phc2sys hwstamp_ctl phc_ctl ++PRG = ptp4l pmc phc2sys hwstamp_ctl phc_ctl timemaster + OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o fault.o \ + filter.o fsm.o linreg.o mave.o mmedian.o msg.o ntpshm.o phc.o \ + pi.o port.o print.o ptp4l.o raw.o servo.o sk.o stats.o tlv.o \ + transport.o udp.o udp6.o uds.o util.o version.o + +-OBJECTS = $(OBJ) hwstamp_ctl.o phc2sys.o pmc.o pmc_common.o sysoff.o phc_ctl.o ++OBJECTS = $(OBJ) hwstamp_ctl.o phc2sys.o phc_ctl.o pmc.o pmc_common.o \ ++ sysoff.o timemaster.o + SRC = $(OBJECTS:.o=.c) + DEPEND = $(OBJECTS:.o=.d) + srcdir := $(dir $(lastword $(MAKEFILE_LIST))) +@@ -56,6 +57,8 @@ hwstamp_ctl: hwstamp_ctl.o version.o + + phc_ctl: phc_ctl.o phc.o sk.o util.o clockadj.o sysoff.o print.o version.o + ++timemaster: print.o sk.o timemaster.o util.o version.o ++ + version.o: .version version.sh $(filter-out version.d,$(DEPEND)) + + .version: force +diff --git a/timemaster.8 b/timemaster.8 +new file mode 100644 +index 0000000..9a3ddb4 +--- /dev/null ++++ b/timemaster.8 +@@ -0,0 +1,335 @@ ++.TH TIMEMASTER 8 "October 2014" "linuxptp" ++.SH NAME ++ ++timemaster \- run NTP with PTP as reference clocks ++ ++.SH SYNOPSIS ++ ++.B timemaster ++[ ++.B \-nmqv ++] [ ++.BI \-l " print-level" ++] ++.BI \-f " file" ++ ++.SH DESCRIPTION ++\fBtimemaster\fR is a program that uses \fBptp4l\fR and \fBphc2sys\fR in ++combination with \fBchronyd\fR or \fBntpd\fR to synchronize the system clock to ++NTP and PTP time sources. The PTP time is provided by \fBphc2sys\fR and ++\fBptp4l\fR via SHM reference clocks to \fBchronyd\fR/\fBntpd\fR, which ++can compare all time sources and use the best sources to synchronize the system ++clock. ++ ++On start, \fBtimemaster\fR reads a configuration file that specifies the NTP ++and PTP time sources, checks which network interfaces have and share a PTP ++hardware clock (PHC), generates configuration files for \fBptp4l\fR and ++\fBchronyd\fR/\fBntpd\fR, and start the \fBptp4l\fR, \fBphc2sys\fR, ++\fBchronyd\fR/\fBntpd\fR processes as needed. Then, it waits for a signal to ++kill the processes, remove the generated configuration files and exit. ++ ++.SH OPTIONS ++ ++.TP ++.BI \-f " file" ++Specify the path to the \fBtimemaster\fR configuration file. ++.TP ++.BI \-n ++Don't start the programs, only print their configuration files and the commands ++that would be executed if this option wasn't specified. ++.TP ++.BI \-l " level" ++Set the maximum syslog level of messages which should be printed or sent to ++the system logger. The default value is 6 (LOG_INFO). ++.TP ++.B \-m ++Print messages to the standard output. ++.TP ++.B \-q ++Don't send messages to the system logger. ++.TP ++.B \-v ++Print the software version and exit. ++.TP ++.BI \-h ++Display a help message and exit. ++ ++.SH CONFIGURATION FILE ++ ++The configuration file is divided into sections. Each section starts with a ++line containing its name enclosed in brackets and it follows with settings. ++Each setting is placed on a separate line, it contains the name of the ++option and the value separated by whitespace characters. Empty lines and lines ++starting with # are ignored. ++ ++Sections that can used in the configuration file and options that can be set in ++them are described below. ++ ++.SS [timemaster] ++ ++.TP ++.B ntp_program ++Select which NTP implementation should be used. Possible values are ++\fBchronyd\fR and \fBntpd\fR. The default value is \fBchronyd\fR. Limitations ++of the implementations relevant to the timemaster configuration are listed in ++\fBNOTES\fR. ++ ++.TP ++.B rundir ++Specify the directory where should be generated \fBchronyd\fR, \fBntpd\fR and ++\fBptp4l\fR configuration files and sockets. The directory will be created if ++it doesn't exist. The default value is \fB/var/run/timemaster\fR. ++ ++.SS [ntp_server address] ++ ++The \fBntp_server\fR section specifies an NTP server that should be used as a ++time source. The address of the server is included in the name of the section. ++ ++.TP ++.B minpoll ++.TQ ++.B maxpoll ++Specify the minimum and maximum NTP polling interval as powers of two in ++seconds. The default values are 6 (64 seconds) and 10 (1024 seconds) ++respectively. Shorter polling intervals usually improve the accuracy ++significantly, but they should be used only when allowed by the operators of ++the NTP service (public NTP servers generally don't allow too frequent ++queries). If the NTP server is located on the same LAN, polling intervals ++around 4 (16 seconds) might give best accuracy. ++ ++.TP ++.B iburst ++Enable or disable sending a burst of NTP packets on start to speed up the ++initial synchronization. Possible values are 1 and 0. The default value is 0 ++(disabled). ++ ++.SS [ptp_domain number] ++ ++The \fBptp_domain\fR section specifies a PTP domain that should be used as a ++time source. The PTP domain number is included in the name of the section. The ++\fBptp4l\fR instances are configured to run in the \fBslaveOnly\fR mode. In ++this section at least the \fBinterfaces\fR option needs to be set, other ++options are optional. ++ ++.TP ++.B interfaces ++Specify which network interfaces should be used for this PTP domain. A separate ++\fBptp4l\fR instance will be started for each group of interfaces sharing the ++same PHC and for each interface that supports only SW time stamping. HW time ++stamping is enabled automatically. If an interface with HW time stamping is ++specified also in other PTP domains, only the \fBptp4l\fR instance from the ++first PTP domain will be using HW time stamping. ++ ++.TP ++.B ntp_poll ++Specify the polling interval of the NTP SHM reference clock reading samples ++from \fBptp4l\fR or \fBphc2sys\fR. It's specified as a power of two in seconds. ++The default value is 2 (4 seconds). ++ ++.TP ++.B phc2sys_poll ++Specify the polling interval used by \fBphc2sys\fR to read a PTP clock ++synchronized by \fBptp4l\fR and update the SHM sample for ++\fBchronyd\fR/\fBntpd\fR. It's specified as a power of two in seconds. The ++default value is 0 (1 second). ++ ++.TP ++.B delay ++Specify the maximum assumed roundtrip delay to the primary source of the time ++in this PTP domain. This value is included in the distance used by ++\fBchronyd\fR in the source selection algorithm to detect falsetickers and ++assign weights for source combining. The default value is 1e-4 (100 ++microseconds). With \fBntpd\fR, the \fBtos mindist\fR command can be used to ++set a limit with similar purpose globally for all time sources. ++ ++.TP ++.B ptp4l_option ++Specify an extra \fBptp4l\fR option specific to this PTP domain that should be ++added to the configuration files generated for \fBptp4l\fR. This option may be ++used multiple times in one \fBptp_domain\fR section. ++ ++.SS [chronyd] ++ ++.TP ++.B path ++Specify the path to the \fBchronyd\fR binary. The default value is ++\fBchronyd\fR to search for the binary in \fBPATH\fR. ++ ++.TP ++.B options ++Specify extra options that should be added to the \fBchronyd\fR command line. ++No extra options are added by default. ++ ++.SS [chrony.conf] ++ ++Settings specified in this section are copied directly to the configuration ++file generated for \fBchronyd\fR. If this section is not present in the ++\fBtimemaster\fR configuration file, the following setting will be added: ++ ++.EX ++makestep 1 3 ++.EE ++ ++This configures \fBchronyd\fR to step the system clock in the first three ++updates if the offset is larger than 1 second. ++ ++.SS [ntpd] ++ ++.TP ++.B path ++Specify the path to the \fBntpd\fR binary. The default value is \fBntpd\fR to ++search for the binary in \fBPATH\fR. ++ ++.TP ++.B options ++Specify extra options that should be added to the \fBntpd\fR command line. No ++extra options are added by default. ++ ++.SS [ntp.conf] ++ ++Settings specified in this section are copied directly to the configuration ++file generated for \fBntpd\fR. If this section is not present in the ++\fBtimemaster\fR configuration file, the following settings will be added: ++ ++.EX ++restrict default nomodify notrap nopeer noquery ++restrict 127.0.0.1 ++restrict ::1 ++.EE ++ ++This configures \fBntpd\fR to use safe default restrictions. ++ ++.SS [phc2sys] ++ ++.TP ++.B path ++Specify the path to the \fBphc2sys\fR binary. The default value is ++\fBphc2sys\fR to search for the binary in \fBPATH\fR. ++ ++.TP ++.B options ++Specify extra options that should be added to all \fBphc2sys\fR command lines. ++By default, \fB-l 5\fR is added to the command lines. ++ ++.SS [ptp4l] ++ ++.TP ++.B path ++Specify the path to the \fBptp4l\fR binary. The default value is \fBptp4l\fR to ++search for the binary in \fBPATH\fR. ++ ++.TP ++.B options ++Specify extra options that should be added to all \fBptp4l\fR command lines. By ++default, \fB-l 5\fR is added to the command lines. ++ ++.SS [ptp4l.conf] ++Settings specified in this section are copied directly to the configuration ++files generated for all \fBptp4l\fR instances. There is no default content of ++this section. ++ ++.SH NOTES ++For best accuracy, \fBchronyd\fR is usually preferred over \fBntpd\fR, it also ++synchronizes the system clock faster. Both NTP implementations, however, have ++some limitations that need to be considered before choosing the one to be used ++in a given \fBtimemaster\fR configuration. ++ ++The \fBchronyd\fR limitations are: ++ ++.RS ++In version 1.31 and older, the maximum number of reference clocks used at the ++same time is 8. This limits the number of PHCs and interfaces using SW time ++stamping that can be used for PTP. ++ ++Using polling intervals (\fBminpoll\fR, \fBmaxpoll\fR, \fBntp_poll\fR options) ++shorter than 2 (4 seconds) is not recommended with versions before 1.30. With ++1.30 and later values of 0 or 1 can be used for NTP sources and negative values ++for PTP sources (\fBntp_poll\fR) to specify a subsecond interval. ++.RE ++ ++The \fBntpd\fR limitations are: ++ ++.RS ++Only the first two shared-memory segments created by the SHM refclock driver ++in \fBntpd\fR have owner-only access. Other segments are created with world ++access, possibly allowing any user on the system writing to the segments and ++disrupting the synchronization. ++ ++The shortest polling interval for all sources is 3 (8 seconds). ++ ++Nanosecond resolution in the SHM refclock driver is supported in version ++4.2.7p303 and later, older versions have only microsecond resolution. ++.RE ++ ++.SH EXAMPLES ++ ++A minimal configuration file using one NTP source and two PTP sources would be: ++ ++.EX ++[ntp_server 10.1.1.1] ++ ++[ptp_domain 0] ++interfaces eth0 ++ ++[ptp_domain 1] ++interfaces eth1 ++.EE ++ ++A more complex example using all \fBtimemaster\fR options would be: ++ ++.EX ++[ntp_server 10.1.1.1] ++minpoll 3 ++maxpoll 4 ++iburst 1 ++ ++[ptp_domain 0] ++interfaces eth0 eth1 ++ntp_poll 0 ++phc2sys_poll -2 ++delay 10e-6 ++ptp4l_option clock_servo linreg ++ptp4l_option delay_mechanism P2P ++ ++[timemaster] ++ntp_program chronyd ++rundir /var/run/timemaster ++ ++[chronyd] ++path /usr/sbin/chronyd ++options ++ ++[chrony.conf] ++makestep 1 3 ++logchange 0.5 ++rtcsync ++driftfile /var/lib/chrony/drift ++ ++[ntpd] ++path /usr/sbin/ntpd ++options -u ntp:ntp ++ ++[ntp.conf] ++restrict default nomodify notrap nopeer noquery ++restrict 127.0.0.1 ++restrict ::1 ++driftfile /var/lib/ntp/drift ++ ++[phc2sys] ++path /usr/sbin/phc2sys ++options -l 5 ++ ++[ptp4l] ++path /usr/sbin/ptp4l ++options ++ ++[ptp4l.conf] ++logging_level 5 ++.EE ++ ++.SH SEE ALSO ++ ++.BR chronyd (8), ++.BR ntpd (8), ++.BR phc2sys (8), ++.BR ptp4l (8) +diff --git a/timemaster.c b/timemaster.c +new file mode 100644 +index 0000000..83a5b83 +--- /dev/null ++++ b/timemaster.c +@@ -0,0 +1,1173 @@ ++/** ++ * @file timemaster.c ++ * @brief Program to run NTP with PTP as reference clocks. ++ * @note Copyright (C) 2014 Miroslav Lichvar ++ * ++ * 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-1301 USA. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "print.h" ++#include "sk.h" ++#include "util.h" ++#include "version.h" ++ ++#define DEFAULT_RUNDIR "/var/run/timemaster" ++ ++#define DEFAULT_NTP_PROGRAM CHRONYD ++#define DEFAULT_NTP_MINPOLL 6 ++#define DEFAULT_NTP_MAXPOLL 10 ++#define DEFAULT_PTP_DELAY 1e-4 ++#define DEFAULT_PTP_NTP_POLL 2 ++#define DEFAULT_PTP_PHC2SYS_POLL 0 ++ ++#define DEFAULT_CHRONYD_SETTINGS \ ++ "makestep 1 3" ++#define DEFAULT_NTPD_SETTINGS \ ++ "restrict default nomodify notrap nopeer noquery", \ ++ "restrict 127.0.0.1", \ ++ "restrict ::1" ++#define DEFAULT_PTP4L_OPTIONS "-l", "5" ++#define DEFAULT_PHC2SYS_OPTIONS "-l", "5" ++ ++enum source_type { ++ NTP_SERVER, ++ PTP_DOMAIN, ++}; ++ ++enum ntp_program { ++ CHRONYD, ++ NTPD, ++}; ++ ++struct ntp_server { ++ char *address; ++ int minpoll; ++ int maxpoll; ++ int iburst; ++}; ++ ++struct ptp_domain { ++ int domain; ++ int ntp_poll; ++ int phc2sys_poll; ++ double delay; ++ char **interfaces; ++ char **ptp4l_settings; ++}; ++ ++struct source { ++ enum source_type type; ++ union { ++ struct ntp_server ntp; ++ struct ptp_domain ptp; ++ }; ++}; ++ ++struct program_config { ++ char *path; ++ char **options; ++ char **settings; ++}; ++ ++struct timemaster_config { ++ struct source **sources; ++ enum ntp_program ntp_program; ++ char *rundir; ++ struct program_config chronyd; ++ struct program_config ntpd; ++ struct program_config phc2sys; ++ struct program_config ptp4l; ++}; ++ ++struct config_file { ++ char *path; ++ char *content; ++}; ++ ++struct script { ++ struct config_file **configs; ++ char ***commands; ++}; ++ ++static void free_parray(void **a) ++{ ++ void **p; ++ ++ for (p = a; *p; p++) ++ free(*p); ++ free(a); ++} ++ ++static void extend_string_array(char ***a, char **strings) ++{ ++ char **s; ++ ++ for (s = strings; *s; s++) ++ parray_append((void ***)a, strdup(*s)); ++} ++ ++static void extend_config_string(char **s, char **lines) ++{ ++ for (; *lines; lines++) ++ string_appendf(s, "%s\n", *lines); ++} ++ ++static int parse_bool(char *s, int *b) ++{ ++ if (get_ranged_int(s, b, 0, 1) != PARSED_OK) ++ return 1; ++ ++ return 0; ++} ++ ++static int parse_int(char *s, int *i) ++{ ++ if (get_ranged_int(s, i, INT_MIN, INT_MAX) != PARSED_OK) ++ return 1; ++ ++ return 0; ++} ++ ++static int parse_double(char *s, double *d) ++{ ++ if (get_ranged_double(s, d, INT_MIN, INT_MAX) != PARSED_OK) ++ return 1; ++ ++ return 0; ++} ++ ++static char *parse_word(char *s) ++{ ++ while (*s && !isspace(*s)) ++ s++; ++ while (*s && isspace(*s)) ++ *(s++) = '\0'; ++ return s; ++} ++ ++static void parse_words(char *s, char ***a) ++{ ++ char *w; ++ ++ if (**a) { ++ free_parray((void **)(*a)); ++ *a = (char **)parray_new(); ++ } ++ while (*s) { ++ w = s; ++ s = parse_word(s); ++ parray_append((void ***)a, strdup(w)); ++ } ++} ++ ++static void replace_string(char *s, char **str) ++{ ++ if (*str) ++ free(*str); ++ *str = strdup(s); ++} ++ ++static char *parse_section_name(char *s) ++{ ++ char *s1, *s2; ++ ++ s1 = s + 1; ++ for (s2 = s1; *s2 && *s2 != ']'; s2++) ++ ; ++ *s2 = '\0'; ++ ++ return strdup(s1); ++} ++ ++static void parse_setting(char *s, char **name, char **value) ++{ ++ *name = s; ++ for (*value = s; **value && !isspace(**value); (*value)++) ++ ; ++ for (; **value && !isspace(**value); (*value)++) ++ ; ++ for (; **value && isspace(**value); (*value)++) ++ **value = '\0'; ++} ++ ++static void source_destroy(struct source *source) ++{ ++ switch (source->type) { ++ case NTP_SERVER: ++ free(source->ntp.address); ++ break; ++ case PTP_DOMAIN: ++ free_parray((void **)source->ptp.interfaces); ++ free_parray((void **)source->ptp.ptp4l_settings); ++ break; ++ } ++ free(source); ++} ++ ++static struct source *source_ntp_parse(char *parameter, char **settings) ++{ ++ char *name, *value; ++ struct ntp_server ntp_server; ++ struct source *source; ++ int r = 0; ++ ++ if (!*parameter) { ++ pr_err("missing address for ntp_server"); ++ return NULL; ++ } ++ ++ ntp_server.address = parameter; ++ ntp_server.minpoll = DEFAULT_NTP_MINPOLL; ++ ntp_server.maxpoll = DEFAULT_NTP_MAXPOLL; ++ ntp_server.iburst = 0; ++ ++ for (; *settings; settings++) { ++ parse_setting(*settings, &name, &value); ++ if (!strcasecmp(name, "minpoll")) { ++ r = parse_int(value, &ntp_server.minpoll); ++ } else if (!strcasecmp(name, "maxpoll")) { ++ r = parse_int(value, &ntp_server.maxpoll); ++ } else if (!strcasecmp(name, "iburst")) { ++ r = parse_bool(value, &ntp_server.iburst); ++ } else { ++ pr_err("unknown ntp_server setting %s", name); ++ return NULL; ++ } ++ if (r) { ++ pr_err("invalid value %s for %s", value, name); ++ return NULL; ++ } ++ } ++ ++ source = malloc(sizeof(*source)); ++ source->type = NTP_SERVER; ++ source->ntp = ntp_server; ++ source->ntp.address = strdup(source->ntp.address); ++ ++ return source; ++} ++ ++static struct source *source_ptp_parse(char *parameter, char **settings) ++{ ++ char *name, *value; ++ struct source *source; ++ int r = 0; ++ ++ source = malloc(sizeof(*source)); ++ source->type = PTP_DOMAIN; ++ source->ptp.delay = DEFAULT_PTP_DELAY; ++ source->ptp.ntp_poll = DEFAULT_PTP_NTP_POLL; ++ source->ptp.phc2sys_poll = DEFAULT_PTP_PHC2SYS_POLL; ++ source->ptp.interfaces = (char **)parray_new(); ++ source->ptp.ptp4l_settings = (char **)parray_new(); ++ ++ if (parse_int(parameter, &source->ptp.domain)) { ++ pr_err("invalid ptp_domain number %s", parameter); ++ goto failed; ++ } ++ ++ for (; *settings; settings++) { ++ parse_setting(*settings, &name, &value); ++ if (!strcasecmp(name, "delay")) { ++ r = parse_double(value, &source->ptp.delay); ++ } else if (!strcasecmp(name, "ntp_poll")) { ++ r = parse_int(value, &source->ptp.ntp_poll); ++ } else if (!strcasecmp(name, "phc2sys_poll")) { ++ r = parse_int(value, &source->ptp.phc2sys_poll); ++ } else if (!strcasecmp(name, "ptp4l_option")) { ++ parray_append((void ***)&source->ptp.ptp4l_settings, ++ strdup(value)); ++ } else if (!strcasecmp(name, "interfaces")) { ++ parse_words(value, &source->ptp.interfaces); ++ } else { ++ pr_err("unknown ptp_domain setting %s", name); ++ goto failed; ++ } ++ ++ if (r) { ++ pr_err("invalid value %s for %s", value, name); ++ goto failed; ++ } ++ } ++ ++ if (!*source->ptp.interfaces) { ++ pr_err("no interfaces specified for ptp_domain %d", ++ source->ptp.domain); ++ goto failed; ++ } ++ ++ return source; ++failed: ++ source_destroy(source); ++ return NULL; ++} ++ ++static int parse_program_settings(char **settings, ++ struct program_config *config) ++{ ++ char *name, *value; ++ ++ for (; *settings; settings++) { ++ parse_setting(*settings, &name, &value); ++ if (!strcasecmp(name, "path")) { ++ replace_string(value, &config->path); ++ } else if (!strcasecmp(name, "options")) { ++ parse_words(value, &config->options); ++ } else { ++ pr_err("unknown program setting %s", name); ++ return 1; ++ } ++ } ++ ++ return 0; ++} ++ ++static int parse_timemaster_settings(char **settings, ++ struct timemaster_config *config) ++{ ++ char *name, *value; ++ ++ for (; *settings; settings++) { ++ parse_setting(*settings, &name, &value); ++ if (!strcasecmp(name, "ntp_program")) { ++ if (!strcasecmp(value, "chronyd")) { ++ config->ntp_program = CHRONYD; ++ } else if (!strcasecmp(value, "ntpd")) { ++ config->ntp_program = NTPD; ++ } else { ++ pr_err("unknown ntp program %s", value); ++ return 1; ++ } ++ } else if (!strcasecmp(name, "rundir")) { ++ replace_string(value, &config->rundir); ++ } else { ++ pr_err("unknown timemaster setting %s", name); ++ return 1; ++ } ++ } ++ ++ return 0; ++} ++ ++static int parse_section(char **settings, char *name, ++ struct timemaster_config *config) ++{ ++ struct source *source = NULL; ++ char ***settings_dst = NULL; ++ char *parameter = parse_word(name); ++ ++ if (!strcasecmp(name, "ntp_server")) { ++ source = source_ntp_parse(parameter, settings); ++ if (!source) ++ return 1; ++ } else if (!strcasecmp(name, "ptp_domain")) { ++ source = source_ptp_parse(parameter, settings); ++ if (!source) ++ return 1; ++ } else if (!strcasecmp(name, "chrony.conf")) { ++ settings_dst = &config->chronyd.settings; ++ } else if (!strcasecmp(name, "ntp.conf")) { ++ settings_dst = &config->ntpd.settings; ++ } else if (!strcasecmp(name, "ptp4l.conf")) { ++ settings_dst = &config->ptp4l.settings; ++ } else if (!strcasecmp(name, "chronyd")) { ++ if (parse_program_settings(settings, &config->chronyd)) ++ return 1; ++ } else if (!strcasecmp(name, "ntpd")) { ++ if (parse_program_settings(settings, &config->ntpd)) ++ return 1; ++ } else if (!strcasecmp(name, "phc2sys")) { ++ if (parse_program_settings(settings, &config->phc2sys)) ++ return 1; ++ } else if (!strcasecmp(name, "ptp4l")) { ++ if (parse_program_settings(settings, &config->ptp4l)) ++ return 1; ++ } else if (!strcasecmp(name, "timemaster")) { ++ if (parse_timemaster_settings(settings, config)) ++ return 1; ++ } else { ++ pr_err("unknown section %s", name); ++ return 1; ++ } ++ ++ if (source) ++ parray_append((void ***)&config->sources, source); ++ ++ if (settings_dst) { ++ free_parray((void **)*settings_dst); ++ *settings_dst = (char **)parray_new(); ++ extend_string_array(settings_dst, settings); ++ } ++ ++ return 0; ++} ++ ++static void init_program_config(struct program_config *config, ++ const char *name, ...) ++{ ++ const char *s; ++ va_list ap; ++ ++ config->path = strdup(name); ++ config->settings = (char **)parray_new(); ++ config->options = (char **)parray_new(); ++ ++ va_start(ap, name); ++ ++ /* add default options and settings */ ++ while ((s = va_arg(ap, const char *))) ++ parray_append((void ***)&config->options, strdup(s)); ++ while ((s = va_arg(ap, const char *))) ++ parray_append((void ***)&config->settings, strdup(s)); ++ ++ va_end(ap); ++} ++ ++static void free_program_config(struct program_config *config) ++{ ++ free(config->path); ++ free_parray((void **)config->settings); ++ free_parray((void **)config->options); ++} ++ ++static void config_destroy(struct timemaster_config *config) ++{ ++ struct source **sources; ++ ++ for (sources = config->sources; *sources; sources++) ++ source_destroy(*sources); ++ free(config->sources); ++ ++ free_program_config(&config->chronyd); ++ free_program_config(&config->ntpd); ++ free_program_config(&config->phc2sys); ++ free_program_config(&config->ptp4l); ++ ++ free(config->rundir); ++ free(config); ++} ++ ++static struct timemaster_config *config_parse(char *path) ++{ ++ struct timemaster_config *config = calloc(1, sizeof(*config)); ++ FILE *f; ++ char buf[4096], *line, *section_name = NULL; ++ char **section_lines = NULL; ++ int ret = 0; ++ ++ config->sources = (struct source **)parray_new(); ++ config->ntp_program = DEFAULT_NTP_PROGRAM; ++ config->rundir = strdup(DEFAULT_RUNDIR); ++ ++ init_program_config(&config->chronyd, "chronyd", ++ NULL, DEFAULT_CHRONYD_SETTINGS, NULL); ++ init_program_config(&config->ntpd, "ntpd", ++ NULL, DEFAULT_NTPD_SETTINGS, NULL); ++ init_program_config(&config->phc2sys, "phc2sys", ++ DEFAULT_PHC2SYS_OPTIONS, NULL, NULL); ++ init_program_config(&config->ptp4l, "ptp4l", ++ DEFAULT_PTP4L_OPTIONS, NULL, NULL); ++ ++ f = fopen(path, "r"); ++ if (!f) { ++ pr_err("failed to open %s: %m", path); ++ free(config); ++ return NULL; ++ } ++ ++ while (fgets(buf, sizeof(buf), f)) { ++ /* remove trailing and leading whitespace */ ++ for (line = buf + strlen(buf) - 1; ++ line >= buf && isspace(*line); line--) ++ *line = '\0'; ++ for (line = buf; *line && isspace(*line); line++) ++ ; ++ /* skip comments and empty lines */ ++ if (!*line || *line == '#') ++ continue; ++ ++ if (*line == '[') { ++ /* parse previous section before starting another */ ++ if (section_name) { ++ if (parse_section(section_lines, section_name, ++ config)) { ++ ret = 1; ++ break; ++ } ++ free_parray((void **)section_lines); ++ free(section_name); ++ } ++ section_name = parse_section_name(line); ++ section_lines = (char **)parray_new(); ++ continue; ++ } ++ ++ if (!section_lines) { ++ pr_err("settings outside section"); ++ ret = 1; ++ break; ++ } ++ ++ parray_append((void ***)§ion_lines, strdup(line)); ++ } ++ ++ if (!ret && section_name && ++ parse_section(section_lines, section_name, config)) { ++ ret = 1; ++ } ++ ++ fclose(f); ++ ++ if (section_name) ++ free(section_name); ++ if (section_lines) ++ free_parray((void **)section_lines); ++ ++ if (ret) { ++ config_destroy(config); ++ return NULL; ++ } ++ ++ return config; ++} ++ ++static char **get_ptp4l_command(struct program_config *config, ++ struct config_file *file, char **interfaces, ++ int hw_ts) ++{ ++ char **command = (char **)parray_new(); ++ ++ parray_append((void ***)&command, strdup(config->path)); ++ extend_string_array(&command, config->options); ++ parray_extend((void ***)&command, ++ strdup("-f"), strdup(file->path), ++ strdup(hw_ts ? "-H" : "-S"), NULL); ++ ++ for (; *interfaces; interfaces++) ++ parray_extend((void ***)&command, ++ strdup("-i"), strdup(*interfaces), NULL); ++ ++ return command; ++} ++ ++static char **get_phc2sys_command(struct program_config *config, int domain, ++ int poll, int shm_segment, char *uds_path) ++{ ++ char **command = (char **)parray_new(); ++ ++ parray_append((void ***)&command, strdup(config->path)); ++ extend_string_array(&command, config->options); ++ parray_extend((void ***)&command, ++ strdup("-a"), strdup("-r"), ++ strdup("-R"), string_newf("%.2f", poll > 0 ? ++ 1.0 / (1 << poll) : 1 << -poll), ++ strdup("-z"), strdup(uds_path), ++ strdup("-n"), string_newf("%d", domain), ++ strdup("-E"), strdup("ntpshm"), ++ strdup("-M"), string_newf("%d", shm_segment), NULL); ++ ++ return command; ++} ++ ++static char *get_refid(char *prefix, unsigned int number) ++{ ++ if (number < 10) ++ return string_newf("%.3s%u", prefix, number); ++ else if (number < 100) ++ return string_newf("%.2s%u", prefix, number); ++ else if (number < 1000) ++ return string_newf("%.1s%u", prefix, number); ++ return NULL; ++}; ++ ++static void add_shm_source(int shm_segment, int poll, int dpoll, double delay, ++ char *prefix, struct timemaster_config *config, ++ char **ntp_config) ++{ ++ char *refid = get_refid(prefix, shm_segment); ++ ++ switch (config->ntp_program) { ++ case CHRONYD: ++ string_appendf(ntp_config, ++ "refclock SHM %d poll %d dpoll %d " ++ "refid %s precision 1.0e-9 delay %.1e\n", ++ shm_segment, poll, dpoll, refid, delay); ++ break; ++ case NTPD: ++ string_appendf(ntp_config, ++ "server 127.127.28.%d minpoll %d maxpoll %d\n" ++ "fudge 127.127.28.%d refid %s\n", ++ shm_segment, poll, poll, shm_segment, refid); ++ break; ++ } ++ ++ free(refid); ++} ++ ++static int add_ntp_source(struct ntp_server *source, char **ntp_config) ++{ ++ pr_debug("adding NTP server %s", source->address); ++ ++ string_appendf(ntp_config, "server %s minpoll %d maxpoll %d%s\n", ++ source->address, source->minpoll, source->maxpoll, ++ source->iburst ? " iburst" : ""); ++ return 0; ++} ++ ++static int add_ptp_source(struct ptp_domain *source, ++ struct timemaster_config *config, int *shm_segment, ++ int ***allocated_phcs, char **ntp_config, ++ struct script *script) ++{ ++ struct config_file *config_file; ++ char **command, *uds_path, **interfaces; ++ int i, j, num_interfaces, *phc, *phcs, hw_ts; ++ struct sk_ts_info ts_info; ++ ++ pr_debug("adding PTP domain %d", source->domain); ++ ++ hw_ts = SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RX_HARDWARE | ++ SOF_TIMESTAMPING_RAW_HARDWARE; ++ ++ for (num_interfaces = 0; ++ source->interfaces[num_interfaces]; num_interfaces++) ++ ; ++ ++ if (!num_interfaces) ++ return 0; ++ ++ /* get PHCs used by specified interfaces */ ++ phcs = malloc(num_interfaces * sizeof(int)); ++ for (i = 0; i < num_interfaces; i++) { ++ phcs[i] = -1; ++ ++ /* check if the interface has a usable PHC */ ++ if (sk_get_ts_info(source->interfaces[i], &ts_info)) { ++ pr_err("failed to get time stamping info for %s", ++ source->interfaces[i]); ++ free(phcs); ++ return 1; ++ } ++ ++ if (!ts_info.valid || ++ ((ts_info.so_timestamping & hw_ts) != hw_ts)) { ++ pr_debug("interface %s: no PHC", source->interfaces[i]); ++ continue; ++ } ++ ++ pr_debug("interface %s: PHC %d", source->interfaces[i], ++ ts_info.phc_index); ++ ++ /* and the PHC isn't already used in another source */ ++ for (j = 0; (*allocated_phcs)[j]; j++) { ++ if (*(*allocated_phcs)[j] == ts_info.phc_index) { ++ pr_debug("PHC %d already allocated", ++ ts_info.phc_index); ++ break; ++ } ++ } ++ if (!(*allocated_phcs)[j]) ++ phcs[i] = ts_info.phc_index; ++ } ++ ++ for (i = 0; i < num_interfaces; i++) { ++ /* skip if already used by ptp4l in this domain */ ++ if (phcs[i] == -2) ++ continue; ++ ++ interfaces = (char **)parray_new(); ++ parray_append((void ***)&interfaces, source->interfaces[i]); ++ ++ /* merge all interfaces sharing PHC to one ptp4l command */ ++ if (phcs[i] >= 0) { ++ for (j = i + 1; j < num_interfaces; j++) { ++ if (phcs[i] == phcs[j]) { ++ parray_append((void ***)&interfaces, ++ source->interfaces[j]); ++ /* mark the interface as used */ ++ phcs[j] = -2; ++ } ++ } ++ ++ /* don't use this PHC in other sources */ ++ phc = malloc(sizeof(int)); ++ *phc = phcs[i]; ++ parray_append((void ***)allocated_phcs, phc); ++ } ++ ++ uds_path = string_newf("%s/ptp4l.%d.socket", ++ config->rundir, *shm_segment); ++ ++ config_file = malloc(sizeof(*config_file)); ++ config_file->path = string_newf("%s/ptp4l.%d.conf", ++ config->rundir, *shm_segment); ++ config_file->content = strdup("[global]\n"); ++ extend_config_string(&config_file->content, ++ config->ptp4l.settings); ++ extend_config_string(&config_file->content, ++ source->ptp4l_settings); ++ string_appendf(&config_file->content, ++ "slaveOnly 1\n" ++ "domainNumber %d\n" ++ "uds_address %s\n", ++ source->domain, uds_path); ++ ++ if (phcs[i] >= 0) { ++ /* HW time stamping */ ++ command = get_ptp4l_command(&config->ptp4l, config_file, ++ interfaces, 1); ++ parray_append((void ***)&script->commands, command); ++ ++ command = get_phc2sys_command(&config->phc2sys, ++ source->domain, ++ source->phc2sys_poll, ++ *shm_segment, uds_path); ++ parray_append((void ***)&script->commands, command); ++ } else { ++ /* SW time stamping */ ++ command = get_ptp4l_command(&config->ptp4l, config_file, ++ interfaces, 0); ++ parray_append((void ***)&script->commands, command); ++ ++ string_appendf(&config_file->content, ++ "clock_servo ntpshm\n" ++ "ntpshm_segment %d\n", *shm_segment); ++ } ++ ++ parray_append((void ***)&script->configs, config_file); ++ ++ add_shm_source(*shm_segment, source->ntp_poll, ++ source->phc2sys_poll, source->delay, "PTP", ++ config, ntp_config); ++ ++ (*shm_segment)++; ++ ++ free(uds_path); ++ free(interfaces); ++ } ++ ++ free(phcs); ++ ++ return 0; ++} ++ ++static char **get_chronyd_command(struct program_config *config, ++ struct config_file *file) ++{ ++ char **command = (char **)parray_new(); ++ ++ parray_append((void ***)&command, strdup(config->path)); ++ extend_string_array(&command, config->options); ++ parray_extend((void ***)&command, strdup("-n"), ++ strdup("-f"), strdup(file->path), NULL); ++ ++ return command; ++} ++ ++static char **get_ntpd_command(struct program_config *config, ++ struct config_file *file) ++{ ++ char **command = (char **)parray_new(); ++ ++ parray_append((void ***)&command, strdup(config->path)); ++ extend_string_array(&command, config->options); ++ parray_extend((void ***)&command, strdup("-n"), ++ strdup("-c"), strdup(file->path), NULL); ++ ++ return command; ++} ++ ++static struct config_file *add_ntp_program(struct timemaster_config *config, ++ struct script *script) ++{ ++ struct config_file *ntp_config = malloc(sizeof(*ntp_config)); ++ char **command = NULL; ++ ++ ntp_config->content = strdup(""); ++ ++ switch (config->ntp_program) { ++ case CHRONYD: ++ extend_config_string(&ntp_config->content, ++ config->chronyd.settings); ++ ntp_config->path = string_newf("%s/chrony.conf", ++ config->rundir); ++ command = get_chronyd_command(&config->chronyd, ntp_config); ++ break; ++ case NTPD: ++ extend_config_string(&ntp_config->content, ++ config->ntpd.settings); ++ ntp_config->path = string_newf("%s/ntp.conf", config->rundir); ++ command = get_ntpd_command(&config->ntpd, ntp_config); ++ break; ++ } ++ ++ parray_append((void ***)&script->configs, ntp_config); ++ parray_append((void ***)&script->commands, command); ++ ++ return ntp_config; ++} ++ ++static void script_destroy(struct script *script) ++{ ++ char ***commands, **command; ++ struct config_file *config, **configs; ++ ++ for (configs = script->configs; *configs; configs++) { ++ config = *configs; ++ free(config->path); ++ free(config->content); ++ free(config); ++ } ++ free(script->configs); ++ ++ for (commands = script->commands; *commands; commands++) { ++ for (command = *commands; *command; command++) ++ free(*command); ++ free(*commands); ++ } ++ free(script->commands); ++ ++ free(script); ++} ++ ++static struct script *script_create(struct timemaster_config *config) ++{ ++ struct script *script = malloc(sizeof(*script)); ++ struct source *source, **sources; ++ struct config_file *ntp_config = NULL; ++ int **allocated_phcs = (int **)parray_new(); ++ int ret = 0, shm_segment = 0; ++ ++ script->configs = (struct config_file **)parray_new(); ++ script->commands = (char ***)parray_new(); ++ ++ ntp_config = add_ntp_program(config, script); ++ ++ for (sources = config->sources; (source = *sources); sources++) { ++ switch (source->type) { ++ case NTP_SERVER: ++ if (add_ntp_source(&source->ntp, &ntp_config->content)) ++ ret = 1; ++ break; ++ case PTP_DOMAIN: ++ if (add_ptp_source(&source->ptp, config, &shm_segment, ++ &allocated_phcs, ++ &ntp_config->content, script)) ++ ret = 1; ++ break; ++ } ++ } ++ ++ free_parray((void **)allocated_phcs); ++ ++ if (ret) { ++ script_destroy(script); ++ return NULL; ++ } ++ ++ return script; ++} ++ ++static int start_program(char **command, sigset_t *mask) ++{ ++ char **arg, *s; ++ pid_t pid; ++ ++#ifdef HAVE_POSIX_SPAWN ++ posix_spawnattr_t attr; ++ ++ if (posix_spawnattr_init(&attr)) { ++ pr_err("failed to init spawn attributes: %m"); ++ return 1; ++ } ++ ++ if (posix_spawnattr_setsigmask(&attr, mask) || ++ posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK) || ++ posix_spawnp(&pid, command[0], NULL, &attr, command, environ)) { ++ pr_err("failed to spawn %s: %m", command[0]); ++ posix_spawnattr_destroy(&attr); ++ return 1; ++ } ++ ++ posix_spawnattr_destroy(&attr); ++#else ++ pid = fork(); ++ ++ if (pid < 0) { ++ pr_err("fork() failed: %m"); ++ return 1; ++ } ++ ++ if (!pid) { ++ /* restore the signal mask */ ++ if (sigprocmask(SIG_SETMASK, mask, NULL) < 0) { ++ pr_err("sigprocmask() failed: %m"); ++ exit(100); ++ } ++ ++ execvp(command[0], (char **)command); ++ ++ pr_err("failed to execute %s: %m", command[0]); ++ ++ exit(101); ++ } ++#endif ++ ++ for (s = strdup(""), arg = command; *arg; arg++) ++ string_appendf(&s, "%s ", *arg); ++ ++ pr_info("process %d started: %s", pid, s); ++ ++ free(s); ++ ++ return 0; ++} ++ ++static int create_config_files(struct config_file **configs) ++{ ++ struct config_file *config; ++ FILE *file; ++ char *tmp, *dir; ++ struct stat st; ++ ++ for (; (config = *configs); configs++) { ++ tmp = strdup(config->path); ++ dir = dirname(tmp); ++ if (stat(dir, &st) < 0 && errno == ENOENT && ++ mkdir(dir, 0755) < 0) { ++ pr_err("failed to create %s: %m", dir); ++ free(tmp); ++ return 1; ++ } ++ free(tmp); ++ ++ pr_debug("creating %s", config->path); ++ ++ file = fopen(config->path, "w"); ++ if (!file) { ++ pr_err("failed to open %s: %m", config->path); ++ return 1; ++ } ++ ++ if (fwrite(config->content, ++ strlen(config->content), 1, file) != 1) { ++ pr_err("failed to write to %s", config->path); ++ fclose(file); ++ return 1; ++ } ++ ++ fclose(file); ++ } ++ ++ return 0; ++} ++ ++static int remove_config_files(struct config_file **configs) ++{ ++ struct config_file *config; ++ ++ for (; (config = *configs); configs++) { ++ pr_debug("removing %s", config->path); ++ ++ if (unlink(config->path)) ++ pr_err("failed to remove %s: %m", config->path); ++ } ++ ++ return 0; ++} ++ ++static int script_run(struct script *script) ++{ ++ sigset_t mask, old_mask; ++ siginfo_t info; ++ pid_t pid; ++ int status, ret = 0; ++ char ***command; ++ ++ if (create_config_files(script->configs)) ++ return 1; ++ ++ sigemptyset(&mask); ++ sigaddset(&mask, SIGCHLD); ++ sigaddset(&mask, SIGTERM); ++ sigaddset(&mask, SIGQUIT); ++ sigaddset(&mask, SIGINT); ++ ++ /* block the signals */ ++ if (sigprocmask(SIG_BLOCK, &mask, &old_mask) < 0) { ++ pr_err("sigprocmask() failed: %m"); ++ return 1; ++ } ++ ++ for (command = script->commands; *command; command++) { ++ if (start_program(*command, &old_mask)) { ++ kill(getpid(), SIGTERM); ++ break; ++ } ++ } ++ ++ /* wait for one of the blocked signals */ ++ while (1) { ++ if (sigwaitinfo(&mask, &info) > 0) ++ break; ++ if (errno != EINTR) { ++ pr_err("sigwaitinfo() failed: %m"); ++ break; ++ } ++ } ++ ++ pr_info("received signal %d", info.si_signo); ++ ++ /* kill the process group */ ++ kill(0, SIGTERM); ++ ++ while ((pid = wait(&status)) >= 0) { ++ if (!WIFEXITED(status)) { ++ pr_info("process %d terminated abnormally", pid); ++ ret = 1; ++ } else { ++ if (WEXITSTATUS(status)) ++ ret = 1; ++ pr_info("process %d terminated with status %d", pid, ++ WEXITSTATUS(status)); ++ } ++ } ++ ++ if (remove_config_files(script->configs)) ++ return 1; ++ ++ return ret; ++} ++ ++static void script_print(struct script *script) ++{ ++ char ***commands, **command; ++ struct config_file *config, **configs; ++ ++ for (configs = script->configs; *configs; configs++) { ++ config = *configs; ++ fprintf(stderr, "%s:\n\n%s\n", config->path, config->content); ++ } ++ ++ fprintf(stderr, "commands:\n\n"); ++ for (commands = script->commands; *commands; commands++) { ++ for (command = *commands; *command; command++) ++ fprintf(stderr, "%s ", *command); ++ fprintf(stderr, "\n"); ++ } ++} ++ ++static void usage(char *progname) ++{ ++ fprintf(stderr, ++ "\nusage: %s [options] -f file\n\n" ++ " -f file specify path to configuration file\n" ++ " -n only print generated files and commands\n" ++ " -l level set logging level (6)\n" ++ " -m print messages to stdout\n" ++ " -q do not print messages to syslog\n" ++ " -v print version and exit\n" ++ " -h print this message and exit\n", ++ progname); ++} ++ ++int main(int argc, char **argv) ++{ ++ struct timemaster_config *config; ++ struct script *script; ++ char *progname, *config_path = NULL; ++ int c, ret = 0, log_stdout = 0, log_syslog = 1, dry_run = 0; ++ ++ progname = strrchr(argv[0], '/'); ++ progname = progname ? progname + 1 : argv[0]; ++ ++ print_set_progname(progname); ++ print_set_verbose(1); ++ print_set_syslog(0); ++ ++ while (EOF != (c = getopt(argc, argv, "f:nl:mqvh"))) { ++ switch (c) { ++ case 'f': ++ config_path = optarg; ++ break; ++ case 'n': ++ dry_run = 1; ++ break; ++ case 'l': ++ print_set_level(atoi(optarg)); ++ break; ++ case 'm': ++ log_stdout = 1; ++ break; ++ case 'q': ++ log_syslog = 0; ++ break; ++ case 'v': ++ version_show(stdout); ++ return 0; ++ case 'h': ++ usage(progname); ++ return 0; ++ default: ++ usage(progname); ++ return 1; ++ } ++ } ++ ++ if (!config_path) { ++ pr_err("no configuration file specified"); ++ return 1; ++ } ++ ++ config = config_parse(config_path); ++ if (!config) ++ return 1; ++ ++ script = script_create(config); ++ config_destroy(config); ++ if (!script) ++ return 1; ++ ++ print_set_verbose(log_stdout); ++ print_set_syslog(log_syslog); ++ ++ if (dry_run) ++ script_print(script); ++ else ++ ret = script_run(script); ++ ++ script_destroy(script); ++ ++ if (!dry_run) ++ pr_info("exiting"); ++ ++ return ret; ++} diff --git a/SOURCES/linuxptp-uds.patch b/SOURCES/linuxptp-uds.patch new file mode 100644 index 0000000..e03c944 --- /dev/null +++ b/SOURCES/linuxptp-uds.patch @@ -0,0 +1,511 @@ +commit 28865f91df96e83b28bb40565f12e62bd86e451f +Author: Miroslav Lichvar +Date: Tue Jul 8 16:14:18 2014 +0200 + + phc2sys: Add option to set path to ptp4l UDS. + + Signed-off-by: Miroslav Lichvar + +diff --git a/phc2sys.8 b/phc2sys.8 +index a906fb3..c4fb6f6 100644 +--- a/phc2sys.8 ++++ b/phc2sys.8 +@@ -194,6 +194,10 @@ clock frequency (unless the + .B \-S + option is used). + .TP ++.BI \-z " uds-address" ++Specifies the address of the server's UNIX domain socket. ++The default is /var/run/ptp4l. ++.TP + .BI \-l " print-level" + Set the maximum syslog level of messages which should be printed or sent to + the system logger. The default is 6 (LOG_INFO). +diff --git a/phc2sys.c b/phc2sys.c +index 7815a8e..418f4ac 100644 +--- a/phc2sys.c ++++ b/phc2sys.c +@@ -53,6 +53,7 @@ + #include "stats.h" + #include "sysoff.h" + #include "tlv.h" ++#include "uds.h" + #include "util.h" + #include "version.h" + +@@ -1158,6 +1159,7 @@ static void usage(char *progname) + " -u [num] number of clock updates in summary stats (0)\n" + " -n [num] domain number (0)\n" + " -x apply leap seconds by servo instead of kernel\n" ++ " -z [path] server address for UDS (/var/run/ptp4l)\n" + " -l [num] set the logging level to 'num' (6)\n" + " -m print messages to stdout\n" + " -q do not print messages to the syslog\n" +@@ -1192,7 +1194,7 @@ int main(int argc, char *argv[]) + progname = strrchr(argv[0], '/'); + progname = progname ? 1+progname : argv[0]; + while (EOF != (c = getopt(argc, argv, +- "arc:d:s:E:P:I:S:F:R:N:O:L:i:u:wn:xl:mqvh"))) { ++ "arc:d:s:E:P:I:S:F:R:N:O:L:i:u:wn:xz:l:mqvh"))) { + switch (c) { + case 'a': + autocfg = 1; +@@ -1285,6 +1287,14 @@ int main(int argc, char *argv[]) + case 'x': + node.kernel_leap = 0; + break; ++ case 'z': ++ if (strlen(optarg) > MAX_IFNAME_SIZE) { ++ fprintf(stderr, "path %s too long, max is %d\n", ++ optarg, MAX_IFNAME_SIZE); ++ return -1; ++ } ++ strncpy(uds_path, optarg, MAX_IFNAME_SIZE); ++ break; + case 'l': + if (get_arg_val_i(c, optarg, &print_level, + PRINT_LEVEL_MIN, PRINT_LEVEL_MAX)) + +commit 2423357754ae7d64d844fc064c72b8d27adf40c4 +Author: Miroslav Lichvar +Date: Tue Jul 8 16:14:19 2014 +0200 + + Remove socket when closing UDS transport. + + [RC: added cast to sockaddr to avoid compiler warning. ] + + Signed-off-by: Miroslav Lichvar + +diff --git a/uds.c b/uds.c +index e98e32c..97edb97 100644 +--- a/uds.c ++++ b/uds.c +@@ -42,6 +42,14 @@ struct uds { + + static int uds_close(struct transport *t, struct fdarray *fda) + { ++ struct sockaddr_un sa; ++ socklen_t len = sizeof(sa); ++ ++ if (!getsockname(fda->fd[FD_GENERAL], (struct sockaddr *) &sa, &len) && ++ sa.sun_family == AF_LOCAL) { ++ unlink(sa.sun_path); ++ } ++ + close(fda->fd[FD_GENERAL]); + return 0; + } + +commit ca637b2067ae78c9830c38bb5e9a659255b1a4a2 +Author: Miroslav Lichvar +Date: Tue Jul 8 16:14:20 2014 +0200 + + Move signal handling to util.c. + + This will be useful in phc2sys and pmc. + + Signed-off-by: Miroslav Lichvar + +diff --git a/ptp4l.c b/ptp4l.c +index 25270c6..5080a79 100644 +--- a/ptp4l.c ++++ b/ptp4l.c +@@ -18,7 +18,6 @@ + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + #include +-#include + #include + #include + #include +@@ -40,8 +39,6 @@ + + int assume_two_step = 0; + +-static int running = 1; +- + static struct config cfg_settings = { + .dds = { + .dds = { +@@ -131,12 +128,6 @@ static struct config cfg_settings = { + .cfg_ignore = 0, + }; + +-static void handle_int_quit_term(int s) +-{ +- pr_notice("caught signal %d", s); +- running = 0; +-} +- + static void usage(char *progname) + { + fprintf(stderr, +@@ -184,18 +175,8 @@ int main(int argc, char *argv[]) + struct defaultDS *ds = &cfg_settings.dds.dds; + int phc_index = -1, required_modes = 0; + +- if (SIG_ERR == signal(SIGINT, handle_int_quit_term)) { +- fprintf(stderr, "cannot handle SIGINT\n"); ++ if (handle_term_signals()) + return -1; +- } +- if (SIG_ERR == signal(SIGQUIT, handle_int_quit_term)) { +- fprintf(stderr, "cannot handle SIGQUIT\n"); +- return -1; +- } +- if (SIG_ERR == signal(SIGTERM, handle_int_quit_term)) { +- fprintf(stderr, "cannot handle SIGTERM\n"); +- return -1; +- } + + /* Set fault timeouts to a default value */ + for (i = 0; i < FT_CNT; i++) { +@@ -404,7 +385,7 @@ int main(int argc, char *argv[]) + return -1; + } + +- while (running) { ++ while (is_running()) { + if (clock_poll(clock)) + break; + } +diff --git a/util.c b/util.c +index 1e86040..ae66bb1 100644 +--- a/util.c ++++ b/util.c +@@ -17,11 +17,13 @@ + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + #include ++#include + #include + #include + #include + + #include "address.h" ++#include "print.h" + #include "sk.h" + #include "util.h" + +@@ -29,6 +31,8 @@ + #define NS_PER_HOUR (3600 * NS_PER_SEC) + #define NS_PER_DAY (24 * NS_PER_HOUR) + ++static int running = 1; ++ + const char *ps_str[] = { + "NONE", + "INITIALIZING", +@@ -311,3 +315,31 @@ int get_arg_val_d(int op, const char *optarg, double *val, + } + return 0; + } ++ ++static void handle_int_quit_term(int s) ++{ ++ pr_notice("caught signal %d", s); ++ running = 0; ++} ++ ++int handle_term_signals(void) ++{ ++ if (SIG_ERR == signal(SIGINT, handle_int_quit_term)) { ++ fprintf(stderr, "cannot handle SIGINT\n"); ++ return -1; ++ } ++ if (SIG_ERR == signal(SIGQUIT, handle_int_quit_term)) { ++ fprintf(stderr, "cannot handle SIGQUIT\n"); ++ return -1; ++ } ++ if (SIG_ERR == signal(SIGTERM, handle_int_quit_term)) { ++ fprintf(stderr, "cannot handle SIGTERM\n"); ++ return -1; ++ } ++ return 0; ++} ++ ++int is_running(void) ++{ ++ return running; ++} +diff --git a/util.h b/util.h +index 3fae51c..cf05e49 100644 +--- a/util.h ++++ b/util.h +@@ -212,4 +212,18 @@ int get_arg_val_ui(int op, const char *optarg, unsigned int *val, + int get_arg_val_d(int op, const char *optarg, double *val, + double min, double max); + ++/** ++ * Setup a handler for terminating signals (SIGINT, SIGQUIT, SIGTERM). ++ * ++ * @return 0 on success, -1 on error. ++ */ ++int handle_term_signals(void); ++ ++/** ++ * Check if a terminating signal was received. ++ * ++ * @return 1 if no terminating signal was received, 0 otherwise. ++ */ ++int is_running(void); ++ + #endif + +commit 30841a68495b5e9ba11ff4b056c4a79b0b73d14a +Author: Miroslav Lichvar +Date: Tue Jul 8 16:14:21 2014 +0200 + + Close client UDS transport before exit. + + In pmc and phc2sys handle terminating signals and close the UDS + transport before exit to remove the sockets in /var/run. + + Signed-off-by: Miroslav Lichvar + +diff --git a/phc2sys.c b/phc2sys.c +index 418f4ac..3039e51 100644 +--- a/phc2sys.c ++++ b/phc2sys.c +@@ -526,7 +526,7 @@ static int do_pps_loop(struct node *node, struct clock *clock, int fd) + enable_pps_output(node->master->clkid); + } + +- while (1) { ++ while (is_running()) { + if (!read_pps(fd, &pps_offset, &pps_ts)) { + continue; + } +@@ -570,7 +570,7 @@ static int do_loop(struct node *node, int subscriptions) + interval.tv_sec = node->phc_interval; + interval.tv_nsec = (node->phc_interval - interval.tv_sec) * 1e9; + +- while (1) { ++ while (is_running()) { + clock_nanosleep(CLOCK_MONOTONIC, 0, &interval, NULL); + if (update_pmc(node, subscriptions) < 0) + continue; +@@ -611,7 +611,7 @@ static int do_loop(struct node *node, int subscriptions) + update_clock(node, clock, offset, ts, delay); + } + } +- return 0; /* unreachable */ ++ return 0; + } + + static int check_clock_identity(struct node *node, struct ptp_message *msg) +@@ -1187,6 +1187,8 @@ int main(int argc, char *argv[]) + .kernel_leap = 1, + }; + ++ handle_term_signals(); ++ + configured_pi_kp = KP; + configured_pi_ki = KI; + +@@ -1349,7 +1351,8 @@ int main(int argc, char *argv[]) + return -1; + if (auto_init_ports(&node, rt) < 0) + return -1; +- return do_loop(&node, 1); ++ r = do_loop(&node, 1); ++ goto end; + } + + src = clock_add(&node, src_name); +@@ -1377,14 +1380,16 @@ int main(int argc, char *argv[]) + goto bad_usage; + } + ++ r = -1; ++ + if (wait_sync) { + if (init_pmc(&node, domain_number)) +- return -1; ++ goto end; + +- while (1) { ++ while (is_running()) { + r = run_pmc_wait_sync(&node, 1000); + if (r < 0) +- return -1; ++ goto end; + if (r > 0) + break; + else +@@ -1395,7 +1400,7 @@ int main(int argc, char *argv[]) + r = run_pmc_get_utc_offset(&node, 1000); + if (r <= 0) { + pr_err("failed to get UTC offset"); +- return -1; ++ goto end; + } + } + +@@ -1409,11 +1414,16 @@ int main(int argc, char *argv[]) + /* only one destination clock allowed with PPS until we + * implement a mean to specify PTP port to PPS mapping */ + servo_sync_interval(dst->servo, 1.0); +- return do_pps_loop(&node, dst, pps_fd); ++ r = do_pps_loop(&node, dst, pps_fd); ++ } else { ++ r = do_loop(&node, 0); + } + +- return do_loop(&node, 0); ++end: ++ if (node.pmc) ++ close_pmc(&node); + ++ return r; + bad_usage: + usage(progname); + return -1; +diff --git a/pmc.c b/pmc.c +index ba06293..8cfae92 100644 +--- a/pmc.c ++++ b/pmc.c +@@ -714,6 +714,7 @@ int main(int argc, char *argv[]) + const char *iface_name = NULL; + char *progname; + int c, cnt, length, tmo = -1, batch_mode = 0, zero_datalen = 0; ++ int ret = 0; + char line[1024], *command = NULL; + enum transport_type transport_type = TRANS_UDP_IPV4; + UInteger8 boundary_hops = 1, domain_number = 0, transport_specific = 0; +@@ -721,6 +722,8 @@ int main(int argc, char *argv[]) + #define N_FD 2 + struct pollfd pollfd[N_FD]; + ++ handle_term_signals(); ++ + /* Process the command line arguments. */ + progname = strrchr(argv[0], '/'); + progname = progname ? 1+progname : argv[0]; +@@ -798,7 +801,7 @@ int main(int argc, char *argv[]) + pollfd[0].fd = batch_mode ? -1 : STDIN_FILENO; + pollfd[1].fd = pmc_get_transport_fd(pmc); + +- while (1) { ++ while (is_running()) { + if (batch_mode && !command) { + if (optind < argc) { + command = argv[optind++]; +@@ -823,7 +826,8 @@ int main(int argc, char *argv[]) + continue; + } else { + pr_emerg("poll failed"); +- return -1; ++ ret = -1; ++ break; + } + } else if (!cnt) { + break; +@@ -866,5 +870,5 @@ int main(int argc, char *argv[]) + + pmc_destroy(pmc); + msg_cleanup(); +- return 0; ++ return ret; + } + +commit 1773d21f26eb1aa2da0645af116d6ce27591a9cc +Author: Miroslav Lichvar +Date: Tue Jul 8 16:14:22 2014 +0200 + + Append PID to client UDS paths. + + This allows running multiple phc2sys and pmc instances at the same time. + + Signed-off-by: Miroslav Lichvar + +diff --git a/phc2sys.c b/phc2sys.c +index 3039e51..391ae62 100644 +--- a/phc2sys.c ++++ b/phc2sys.c +@@ -738,8 +738,11 @@ static void send_subscription(struct node *node) + + static int init_pmc(struct node *node, int domain_number) + { +- node->pmc = pmc_create(TRANS_UDS, "/var/run/phc2sys", 0, +- domain_number, 0, 1); ++ char uds_local[MAX_IFNAME_SIZE + 1]; ++ ++ snprintf(uds_local, sizeof(uds_local), "/var/run/phc2sys.%d", ++ getpid()); ++ node->pmc = pmc_create(TRANS_UDS, uds_local, 0, domain_number, 0, 1); + if (!node->pmc) { + pr_err("failed to create pmc"); + return -1; +diff --git a/pmc.8 b/pmc.8 +index 0d36354..b113c78 100644 +--- a/pmc.8 ++++ b/pmc.8 +@@ -73,7 +73,7 @@ Specify the boundary hops value in sent messages. The default is 1. + Specify the domain number in sent messages. The default is 0. + .TP + .BI \-i " interface" +-Specify the network interface. The default is /var/run/pmc for the Unix Domain ++Specify the network interface. The default is /var/run/pmc.$pid for the Unix Domain + Socket transport and eth0 for the other transports. + .TP + .BI \-s " uds-address" +diff --git a/pmc.c b/pmc.c +index 8cfae92..d58e190 100644 +--- a/pmc.c ++++ b/pmc.c +@@ -700,7 +700,7 @@ static void usage(char *progname) + " -d [num] domain number, default 0\n" + " -h prints this message and exits\n" + " -i [dev] interface device to use, default 'eth0'\n" +- " for network and '/var/run/pmc' for UDS.\n" ++ " for network and '/var/run/pmc.$pid' for UDS.\n" + " -s [path] server address for UDS, default '/var/run/ptp4l'.\n" + " -t [hex] transport specific field, default 0x0\n" + " -v prints the software version and exits\n" +@@ -715,7 +715,7 @@ int main(int argc, char *argv[]) + char *progname; + int c, cnt, length, tmo = -1, batch_mode = 0, zero_datalen = 0; + int ret = 0; +- char line[1024], *command = NULL; ++ char line[1024], *command = NULL, uds_local[MAX_IFNAME_SIZE + 1]; + enum transport_type transport_type = TRANS_UDP_IPV4; + UInteger8 boundary_hops = 1, domain_number = 0, transport_specific = 0; + struct ptp_message *msg; +@@ -781,7 +781,13 @@ int main(int argc, char *argv[]) + } + + if (!iface_name) { +- iface_name = transport_type == TRANS_UDS ? "/var/run/pmc" : "eth0"; ++ if (transport_type == TRANS_UDS) { ++ snprintf(uds_local, sizeof(uds_local), ++ "/var/run/pmc.%d", getpid()); ++ iface_name = uds_local; ++ } else { ++ iface_name = "eth0"; ++ } + } + if (optind < argc) { + batch_mode = 1; +commit aa24ba58e1901f9397624665c1f19a2432e426d0 +Author: Miroslav Lichvar +Date: Fri Oct 3 14:13:49 2014 +0200 + + Don't print messages in signal handler. + + Only reentrant functions should be called here. + + Signed-off-by: Miroslav Lichvar + +diff --git a/util.c b/util.c +index ae66bb1..cb428b1 100644 +--- a/util.c ++++ b/util.c +@@ -318,7 +318,6 @@ int get_arg_val_d(int op, const char *optarg, double *val, + + static void handle_int_quit_term(int s) + { +- pr_notice("caught signal %d", s); + running = 0; + } + diff --git a/SOURCES/linuxptp-warnings.patch b/SOURCES/linuxptp-warnings.patch new file mode 100644 index 0000000..e1d99ae --- /dev/null +++ b/SOURCES/linuxptp-warnings.patch @@ -0,0 +1,89 @@ +commit 9ddd2a60249eaf2b2a48bf9af3843fa06cf0a095 +Author: Miroslav Lichvar +Date: Wed Sep 17 11:11:15 2014 +0200 + + Fix copying of device name to ifreq. + + Don't overwrite the last NUL with strncpy() and also replace strcpy() + with strncpy(). + + Signed-off-by: Miroslav Lichvar + +diff --git a/hwstamp_ctl.c b/hwstamp_ctl.c +index ec5dd6d..0d21843 100644 +--- a/hwstamp_ctl.c ++++ b/hwstamp_ctl.c +@@ -131,7 +131,7 @@ int main(int argc, char *argv[]) + memset(&ifreq, 0, sizeof(ifreq)); + memset(&cfg, 0, sizeof(cfg)); + +- strncpy(ifreq.ifr_name, device, sizeof(ifreq.ifr_name)); ++ strncpy(ifreq.ifr_name, device, sizeof(ifreq.ifr_name) - 1); + + ifreq.ifr_data = (void *) &cfg; + +diff --git a/sk.c b/sk.c +index f694bbd..a9133fd 100644 +--- a/sk.c ++++ b/sk.c +@@ -51,7 +51,7 @@ static int hwts_init(int fd, const char *device, int rx_filter, int one_step) + memset(&ifreq, 0, sizeof(ifreq)); + memset(&cfg, 0, sizeof(cfg)); + +- strncpy(ifreq.ifr_name, device, sizeof(ifreq.ifr_name)); ++ strncpy(ifreq.ifr_name, device, sizeof(ifreq.ifr_name) - 1); + + ifreq.ifr_data = (void *) &cfg; + cfg.tx_type = one_step ? HWTSTAMP_TX_ONESTEP_SYNC : HWTSTAMP_TX_ON; +@@ -85,7 +85,7 @@ int sk_interface_index(int fd, const char *name) + int err; + + memset(&ifreq, 0, sizeof(ifreq)); +- strcpy(ifreq.ifr_name, name); ++ strncpy(ifreq.ifr_name, name, sizeof(ifreq.ifr_name) - 1); + err = ioctl(fd, SIOCGIFINDEX, &ifreq); + if (err < 0) { + pr_err("ioctl SIOCGIFINDEX failed: %m"); +@@ -154,7 +154,7 @@ int sk_interface_macaddr(const char *name, struct address *mac) + int err, fd; + + memset(&ifreq, 0, sizeof(ifreq)); +- strcpy(ifreq.ifr_name, name); ++ strncpy(ifreq.ifr_name, name, sizeof(ifreq.ifr_name) - 1); + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) { + +commit 6b459abc8c54189ec3315404057923c039c1796a +Author: Miroslav Lichvar +Date: Wed Sep 17 11:11:16 2014 +0200 + + Fix Coverity warning in sk_interface_addr(). + + Copy the address directly to struct sockaddr_in or sockaddr_in6 instead + of sockaddr as Coverity doesn't seem to understand the union and reports + a buffer overflow. + + Signed-off-by: Miroslav Lichvar + +diff --git a/sk.c b/sk.c +index a9133fd..c48cf45 100644 +--- a/sk.c ++++ b/sk.c +@@ -191,14 +191,15 @@ int sk_interface_addr(const char *name, int family, struct address *addr) + switch (family) { + case AF_INET: + addr->len = sizeof(addr->sin); ++ memcpy(&addr->sin, i->ifa_addr, addr->len); + break; + case AF_INET6: + addr->len = sizeof(addr->sin6); ++ memcpy(&addr->sin6, i->ifa_addr, addr->len); + break; + default: + continue; + } +- memcpy(&addr->sa, i->ifa_addr, addr->len); + result = 0; + break; + } diff --git a/SOURCES/timemaster.conf b/SOURCES/timemaster.conf new file mode 100644 index 0000000..d47a305 --- /dev/null +++ b/SOURCES/timemaster.conf @@ -0,0 +1,33 @@ +# Configuration file for timemaster + +#[ntp_server ntp-server.local] +#minpoll 4 +#maxpoll 4 + +#[ptp_domain 0] +#interfaces eth0 + +[timemaster] +ntp_program chronyd + +[chrony.conf] +include /etc/chrony.conf + +[ntp.conf] +includefile /etc/ntp.conf + +[ptp4l.conf] + +[chronyd] +path /usr/sbin/chronyd +options -u chrony + +[ntpd] +path /usr/sbin/ntpd +options -u ntp:ntp -g + +[phc2sys] +path /usr/sbin/phc2sys + +[ptp4l] +path /usr/sbin/ptp4l diff --git a/SOURCES/timemaster.service b/SOURCES/timemaster.service new file mode 100644 index 0000000..7505387 --- /dev/null +++ b/SOURCES/timemaster.service @@ -0,0 +1,11 @@ +[Unit] +Description=Synchronize system clock to NTP and PTP time sources +After=chronyd.service ntpd.service ntpdate.service sntp.service +Conflicts=chronyd.service ntpd.service phc2sys.service ptp4l.service + +[Service] +Type=simple +ExecStart=/usr/sbin/timemaster -f /etc/timemaster.conf + +[Install] +WantedBy=multi-user.target diff --git a/SPECS/linuxptp.spec b/SPECS/linuxptp.spec index d1cd048..89a64c2 100644 --- a/SPECS/linuxptp.spec +++ b/SPECS/linuxptp.spec @@ -1,15 +1,36 @@ +%global _hardened_build 1 +%global gitdate 20140718 +%global gitrev bdb6a3 Name: linuxptp -Version: 1.3 -Release: 3%{?dist} +Version: 1.4 +Release: 3.%{gitdate}git%{gitrev}%{?dist} Summary: PTP implementation for Linux Group: System Environment/Base License: GPLv2+ URL: http://linuxptp.sourceforge.net/ -Source0: http://downloads.sourceforge.net/%{name}/%{name}-%{version}.tgz +#Source0: http://downloads.sourceforge.net/%{name}/%{name}-%{version}.tgz +# git clone git://git.code.sf.net/p/linuxptp/code linuxptp; cd linuxptp +# git archive --prefix=linuxptp-%{version}/ %{gitrev} | \ +# gzip > linuxptp-%{gitdate}git%{gitrev}.tar.gz +Source0: %{name}-%{gitdate}git%{gitrev}.tar.gz Source1: phc2sys.service Source2: ptp4l.service +Source3: timemaster.service +Source4: timemaster.conf +# test suite from https://github.com/mlichvar/linuxptp-testsuite.git +Source10: testsuite-7c523f.tar.gz +# simulator for test suite from https://github.com/mlichvar/clknetsim.git +Source11: clknetsim-e63178.tar.gz + +Patch1: linuxptp-warnings.patch +Patch2: linuxptp-uds.patch +Patch3: linuxptp-shm.patch +Patch4: linuxptp-timemaster.patch +Patch5: linuxptp-peeraddress.patch +Patch6: linuxptp-linreg_reset.patch +Patch7: linuxptp-phc2sys_state.patch BuildRequires: systemd-units @@ -25,50 +46,86 @@ Application Programming Interfaces (API) offered by the Linux kernel. Supporting legacy APIs and other platforms is not a goal. %prep -%setup -q +%setup -q -a 10 -a 11 +%patch1 -p1 -b .warnings +%patch2 -p1 -b .uds +%patch3 -p1 -b .shm +%patch4 -p1 -b .timemaster +%patch5 -p1 -b .peeraddress +%patch6 -p1 -b .linreg_reset +%patch7 -p1 -b .phc2sys_state +mv clknetsim testsuite %build make %{?_smp_mflags} \ - EXTRA_CFLAGS="$RPM_OPT_FLAGS -pie -fpie" \ - EXTRA_LDFLAGS="-Wl,-z,relro,-z,now" + EXTRA_CFLAGS="$RPM_OPT_FLAGS" \ + EXTRA_LDFLAGS="$RPM_LD_FLAGS" %install %makeinstall mkdir -p $RPM_BUILD_ROOT{%{_sysconfdir}/sysconfig,%{_unitdir},%{_mandir}/man5} install -m 644 -p default.cfg $RPM_BUILD_ROOT%{_sysconfdir}/ptp4l.conf -install -m 644 -p %{SOURCE1} %{SOURCE2} $RPM_BUILD_ROOT%{_unitdir} +install -m 644 -p %{SOURCE1} %{SOURCE2} %{SOURCE3} $RPM_BUILD_ROOT%{_unitdir} +install -m 644 -p %{SOURCE4} $RPM_BUILD_ROOT%{_sysconfdir} echo 'OPTIONS="-f /etc/ptp4l.conf -i eth0"' > \ $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/ptp4l -echo 'OPTIONS="-w -s eth0"' > $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/phc2sys +echo 'OPTIONS="-a -r"' > $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/phc2sys echo '.so man8/ptp4l.8' > $RPM_BUILD_ROOT%{_mandir}/man5/ptp4l.conf.5 +echo '.so man8/timemaster.8' > $RPM_BUILD_ROOT%{_mandir}/man5/timemaster.conf.5 + +%check +cd testsuite +make %{?_smp_mflags} -C clknetsim +PATH=..:$PATH ./run %post -%systemd_post ptp4l.service phc2sys.service +%systemd_post phc2sys.service ptp4l.service timemaster.service %preun -%systemd_preun phc2sys.service ptp4l.service +%systemd_preun phc2sys.service ptp4l.service timemaster.service %postun -%systemd_postun_with_restart ptp4l.service phc2sys.service +%systemd_postun_with_restart phc2sys.service ptp4l.service timemaster.service %files %doc COPYING README.org default.cfg gPTP.cfg %config(noreplace) %{_sysconfdir}/ptp4l.conf %config(noreplace) %{_sysconfdir}/sysconfig/phc2sys %config(noreplace) %{_sysconfdir}/sysconfig/ptp4l +%config(noreplace) %{_sysconfdir}/timemaster.conf %{_unitdir}/phc2sys.service %{_unitdir}/ptp4l.service +%{_unitdir}/timemaster.service %{_sbindir}/hwstamp_ctl %{_sbindir}/phc2sys +%{_sbindir}/phc_ctl %{_sbindir}/pmc %{_sbindir}/ptp4l +%{_sbindir}/timemaster %{_mandir}/man5/*.5* %{_mandir}/man8/*.8* %changelog +* Tue Nov 25 2014 Miroslav Lichvar 1.4-3.20140718gitbdb6a3 +- fix resetting of linreg servo (#1165045) +- fix phc2sys automatic mode with multiple interfaces (#1108795) + +* Tue Oct 14 2014 Miroslav Lichvar 1.4-2.20140718gitbdb6a3 +- add timemaster (#1085580) +- send peer messages to correct address +- make NTP SHM segment number configurable +- update UDS handling to allow running multiple ptp4l/phc2sys instances +- fix warnings from static analysis + +* Wed Sep 03 2014 Miroslav Lichvar 1.4-1.20140718gitbdb6a3 +- update to 20140718gitbdb6a3 (#1108795, #1059039) +- fix PIE linking (#1092537) +- replace hardening build flags with _hardened_build +- include simulation test suite + * Fri Jan 24 2014 Daniel Mach - 1.3-3 - Mass rebuild 2014-01-24