Add support for synchronization between clocks controlled by different ptp4l instances in the automatic mode (-a option) to allow switching to a backup source on failures.
The -z and -n options can be used multiple times to specify sockets and domain numbers of different ptp4l instances (up to 16 instances). Clocks from one ptp4l instance are considered a domain. If the domain doesn't have a source clock (i.e. a port in the slave state), the clocks in the domain can be synchronized to a source clock from a different domain. If multiple domains have a source clock, the first one in the order of specified sockets is selected. The selection can be later extended to compare some specific clock attributes. The real-time clock is a separate domain, which is created only with the -r option and can be a source for other domains if the -r option is specified twice. In the non-automatic modes only one domain is created for all clocks. Signed-off-by: Miroslav Lichvar <mlich...@redhat.com> --- phc2sys.8 | 12 +- phc2sys.c | 337 +++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 241 insertions(+), 108 deletions(-) diff --git a/phc2sys.8 b/phc2sys.8 index 6c6c7e8..9a37778 100644 --- a/phc2sys.8 +++ b/phc2sys.8 @@ -199,7 +199,12 @@ the direction of the clock synchronization. Not compatible with the option. .TP .BI \-n " domain-number" -Specify the domain number used by ptp4l. The default is 0. +Specify the domain number used by ptp4l. This option can be used up to 16 times +to specify different domain numbers for different sockets specified by the +.B \-z +option. The domain numbers are assigned according to the order of the options +on the command line. +The default is 0. .TP .B \-x When a leap second is announced, don't apply it in the kernel by stepping the @@ -209,7 +214,10 @@ clock frequency (unless the option is used). .TP .BI \-z " uds-address" -Specifies the address of the server's UNIX domain socket. +Specifies the address of the server's UNIX domain socket. This option can be +used up to 16 times in the automatic mode to synchronize clocks between multiple +.B ptp4l +instances. The default is /var/run/ptp4l. .TP .BI \-l " print-level" diff --git a/phc2sys.c b/phc2sys.c index c1a1c6c..2750691 100644 --- a/phc2sys.c +++ b/phc2sys.c @@ -66,6 +66,8 @@ #define MAX_DST_CLOCKS 128 +#define MAX_DOMAINS 16 + struct clock { LIST_ENTRY(clock) list; LIST_ENTRY(clock) dst_list; @@ -107,10 +109,13 @@ struct domain { int state_changed; int free_running; struct pmc_agent *agent; + int agent_subscribed; LIST_HEAD(port_head, port) ports; LIST_HEAD(clock_head, clock) clocks; LIST_HEAD(dst_clock_head, clock) dst_clocks; struct clock *src_clock; + struct domain *src_domain; + int src_priority; }; static struct config *phc2sys_config; @@ -372,13 +377,13 @@ static struct clock *find_dst_clock(struct domain *domain, return c; } -static void reconfigure(struct domain *domain) +static int reconfigure_domain(struct domain *domain) { - struct clock *c, *rt = NULL, *src = NULL, *last = NULL, *dup = NULL; + struct clock *c, *src = NULL, *dup = NULL; int src_cnt = 0, dst_cnt = 0; - pr_info("reconfiguring after port state change"); domain->state_changed = 0; + domain->src_domain = domain; while (domain->dst_clocks.lh_first != NULL) { LIST_REMOVE(LIST_FIRST(&domain->dst_clocks), dst_list); @@ -386,8 +391,10 @@ static void reconfigure(struct domain *domain) LIST_FOREACH(c, &domain->clocks, list) { if (c->clkid == CLOCK_REALTIME) { - rt = c; - continue; + /* If present, it can always be a sink clock */ + LIST_INSERT_HEAD(&domain->dst_clocks, c, dst_list); + domain->src_clock = c->dest_only ? NULL : c; + return 0; } if (c->new_state) { @@ -424,57 +431,81 @@ static void reconfigure(struct domain *domain) src_cnt++; break; } - last = c; } if (src_cnt > 1) { pr_info("multiple source clocks available, postponing sync..."); domain->src_clock = NULL; - return; + return -1; } if (src_cnt > 0 && !src) { pr_info("source clock not ready, waiting..."); domain->src_clock = NULL; - return; + return -1; } if (!src_cnt && !dst_cnt) { pr_info("no PHC ready, waiting..."); domain->src_clock = NULL; - return; + return -1; + } + + if (!src) { + domain->src_clock = NULL; + return 0; + } + + domain->src_clock = src; + pr_info("selecting %s as domain source clock", src->device); + return 0; +} + +static int compare_domains(struct domain *a, struct domain *b) +{ + if (!a || !b) { + if (a && a->src_clock) + return -1; + if (b && b->src_clock) + return 1; + return 0; } - if (dst_cnt > 1 && !src) { - if (!rt || rt->dest_only) { - domain->src_clock = last; - /* Reset to original state in next reconfiguration. */ - domain->src_clock->new_state = domain->src_clock->state; - domain->src_clock->state = PS_SLAVE; - if (rt) - rt->state = PS_SLAVE; - pr_info("no source, selecting %s as the default clock", - last->device); + if (!a->src_clock != !b->src_clock) + return !!b->src_clock - !!a->src_clock; + + return b->src_priority - a->src_priority; +} + +static void reconfigure(struct domain *domains, int n_domains) +{ + struct domain *src_domain = NULL; + int i; + + pr_info("reconfiguring after port state change"); + + for (i = 0; i < n_domains; i++) { + while (!LIST_EMPTY(&domains[i].dst_clocks)) + LIST_REMOVE(LIST_FIRST(&domains[i].dst_clocks), dst_list); + + if (reconfigure_domain(&domains[i])) return; + + if (compare_domains(src_domain, &domains[i]) > 0) { + src_domain = &domains[i]; } } - if ((!src_cnt && (!rt || rt->dest_only)) || - (!dst_cnt && !rt)) { - pr_info("nothing to synchronize"); - domain->src_clock = NULL; + if (n_domains <= 1 || !src_domain) { return; } - if (!src_cnt) { - src = rt; - rt->state = PS_SLAVE; - } else if (rt) { - if (rt->state != PS_MASTER) { - rt->state = PS_MASTER; - clock_reinit(domain, rt, rt->state); - } - LIST_INSERT_HEAD(&domain->dst_clocks, rt, dst_list); - pr_info("selecting %s for synchronization", rt->device); + + pr_info("selecting %s as out-of-domain source clock", + src_domain->src_clock->device); + + for (i = 0; i < n_domains; i++) { + if (domains[i].src_clock && domains[i].src_priority > 0) + continue; + domains[i].src_clock = src_domain->src_clock; + domains[i].src_domain = src_domain; } - domain->src_clock = src; - pr_info("selecting %s as the source clock", src->device); } static int64_t get_sync_offset(struct domain *domain, struct clock *dst) @@ -688,6 +719,9 @@ static int do_pps_loop(struct domain *domain, struct clock *clock, static int update_needed(struct clock *c) { + if (c->clkid == CLOCK_REALTIME) + return 1; + switch (c->state) { case PS_FAULTY: case PS_DISABLED: @@ -756,32 +790,53 @@ static int update_domain_clocks(struct domain *domain) return 0; } -static int do_loop(struct domain *domain) +static int do_loop(struct domain *domains, int n_domains) { struct timespec interval; + struct domain *domain; + int i, state_changed; - interval.tv_sec = domain->phc_interval; - interval.tv_nsec = (domain->phc_interval - interval.tv_sec) * 1e9; + /* All domains have the same interval */ + interval.tv_sec = domains[0].phc_interval; + interval.tv_nsec = (domains[0].phc_interval - interval.tv_sec) * 1e9; while (is_running()) { clock_nanosleep(CLOCK_MONOTONIC, 0, &interval, NULL); - if (pmc_agent_update(domain->agent) < 0) { - continue; - } - if (domain->state_changed) { - /* force getting offset, as it may have - * changed after the port state change */ - if (pmc_agent_query_utc_offset(domain->agent, 1000)) { - pr_err("failed to get UTC offset"); + state_changed = 0; + for (i = 0; i < n_domains; i++) { + domain = &domains[i]; + if (pmc_agent_update(domain->agent) < 0) { continue; } - reconfigure(domain); + + if (domain->state_changed) { + state_changed = 1; + + /* force getting offset, as it may have + * changed after the port state change */ + if (pmc_agent_query_utc_offset(domain->agent, + 1000)) { + pr_err("failed to get UTC offset"); + continue; + } + } + } + + if (state_changed) { + reconfigure(domains, n_domains); + state_changed = 0; + } + + for (i = 0; i < n_domains; i++) { + domain = &domains[i]; + + if (!domain->src_clock) + continue; + + if (update_domain_clocks(domain)) + return -1; } - if (!domain->src_clock) - continue; - if (update_domain_clocks(domain)) - return -1; } return 0; } @@ -814,6 +869,8 @@ static int phc2sys_recv_subscribed(void *context, struct ptp_message *msg, struct port *port; struct clock *clock; + domain->agent_subscribed = 1; + mgt_id = management_tlv_id(msg); if (mgt_id == excluded) return 0; @@ -843,7 +900,7 @@ static int phc2sys_recv_subscribed(void *context, struct ptp_message *msg, return 0; } -static int auto_init_ports(struct domain *domain, int add_rt) +static int auto_init_ports(struct domain *domain) { int err, number_ports, phc_index, timestamping; enum port_state state; @@ -900,6 +957,7 @@ static int auto_init_ports(struct domain *domain, int add_rt) return -1; port->state = port_state_normalize(state); } + if (LIST_EMPTY(&domain->clocks)) { pr_err("no suitable ports available"); return -1; @@ -908,14 +966,7 @@ static int auto_init_ports(struct domain *domain, int add_rt) clock->new_state = clock_compute_state(domain, clock); } domain->state_changed = 1; - - if (add_rt) { - clock = clock_add(domain, "CLOCK_REALTIME", -1); - if (!clock) - return -1; - if (add_rt == 1) - clock->dest_only = 1; - } + domain->src_priority = 1; /* get initial offset */ if (pmc_agent_query_utc_offset(domain->agent, 1000)) { @@ -925,13 +976,30 @@ static int auto_init_ports(struct domain *domain, int add_rt) return 0; } +static int auto_init_rt(struct domain *domain, int dest_only) +{ + struct clock *clock; + + clock = clock_add(domain, "CLOCK_REALTIME", -1); + if (!clock) + return -1; + clock->dest_only = dest_only; + domain->src_priority = 0; + return 0; +} + /* Returns: non-zero to skip clock update */ static int clock_handle_leap(struct domain *domain, struct clock *clock, int64_t offset, uint64_t ts) { - int clock_leap, node_leap = pmc_agent_get_leap(domain->agent); + int clock_leap, node_leap; + struct pmc_agent *agent; - clock->sync_offset = pmc_agent_get_sync_offset(domain->agent); + /* The system clock's domain doesn't have a subscribed agent */ + agent = domain->agent_subscribed ? domain->agent : domain->src_domain->agent; + + node_leap = pmc_agent_get_leap(agent); + clock->sync_offset = pmc_agent_get_sync_offset(agent); if ((node_leap || clock->leap_set) && clock->is_utc != domain->src_clock->is_utc) { @@ -972,7 +1040,7 @@ static int clock_handle_leap(struct domain *domain, struct clock *clock, } } - if (pmc_agent_utc_offset_traceable(domain->agent) && + if (pmc_agent_utc_offset_traceable(agent) && clock->utc_offset_set != clock->sync_offset) { if (clock->clkid == CLOCK_REALTIME) sysclk_set_tai_offset(clock->sync_offset); @@ -999,6 +1067,7 @@ static int phc2sys_static_src_configuration(struct domain *domain, } src->state = PS_SLAVE; domain->src_clock = src; + domain->src_domain = domain; return 0; } @@ -1075,19 +1144,23 @@ static void usage(char *progname) int main(int argc, char *argv[]) { char *config = NULL, *progname, *src_name = NULL; - const char *dst_names[MAX_DST_CLOCKS]; + const char *dst_names[MAX_DST_CLOCKS], *uds_remotes[MAX_DOMAINS]; char uds_local[MAX_IFNAME_SIZE + 1]; - int i, autocfg = 0, c, domain_number = 0, index, ntpshm_segment, offset; + int domain_numbers[MAX_DOMAINS], domain_number_cnt = 0; + int i, autocfg = 0, c, index, ntpshm_segment, offset = 0; int pps_fd = -1, print_level = LOG_INFO, r = -1, rt = 0; - int wait_sync = 0, dst_cnt = 0; + int wait_sync = 0, dst_cnt = 0, uds_remote_cnt = 0; struct config *cfg; struct option *opts; double phc_rate, tmp; - struct domain domain; + struct domain domains[MAX_DOMAINS]; struct domain settings = { .phc_readings = 5, .phc_interval = 1.0, }; + int n_domains = 0; + + memset(domains, 0, sizeof (domains)); handle_term_signals(); @@ -1218,8 +1291,13 @@ int main(int argc, char *argv[]) wait_sync = 1; break; case 'n': - if (get_arg_val_i(c, optarg, &domain_number, 0, 255) || - config_set_int(cfg, "domainNumber", domain_number)) { + if (domain_number_cnt == MAX_DOMAINS) { + fprintf(stderr, "too many domains\n"); + goto end; + } + if (get_arg_val_i(c, optarg, + &domain_numbers[domain_number_cnt++], + 0, 255)) { goto end; } break; @@ -1234,9 +1312,12 @@ int main(int argc, char *argv[]) optarg, MAX_IFNAME_SIZE); goto end; } - if (config_set_string(cfg, "uds_address", optarg)) { + if (uds_remote_cnt == MAX_DOMAINS) { + fprintf(stderr, "too many domains\n"); goto end; } + uds_remotes[uds_remote_cnt++] = optarg; + n_domains++; break; case 'l': if (get_arg_val_i(c, optarg, &print_level, @@ -1287,6 +1368,12 @@ int main(int argc, char *argv[]) "autoconfiguration cannot be mixed with manual config options.\n"); goto bad_usage; } + if (!autocfg && n_domains > 1) { + fprintf(stderr, + "autoconfiguration needed with multiple domains.\n"); + goto bad_usage; + } + if (!autocfg && !hardpps_configured(pps_fd) && !src_name) { fprintf(stderr, "autoconfiguration or valid source clock must be selected.\n"); @@ -1321,36 +1408,67 @@ int main(int argc, char *argv[]) settings.kernel_leap = config_get_int(cfg, NULL, "kernel_leap"); settings.sanity_freq_limit = config_get_int(cfg, NULL, "sanity_freq_limit"); - domain = settings; - domain.agent = pmc_agent_create(); - if (!domain.agent) { - return -1; + if (autocfg) { + if (n_domains == 0) + n_domains = 1; + if (rt) + n_domains += 1; + if (n_domains > MAX_DOMAINS) { + fprintf(stderr, "too many domains\n"); + goto bad_usage; + } + } else { + n_domains = 1; } - if (settings.forced_sync_offset) - pmc_agent_set_sync_offset(domain.agent, offset); - - for (i = 0; i < dst_cnt; i++) { - r = phc2sys_static_dst_configuration(&domain, dst_names[i]); - if (r) { - goto end; + for (i = 0; i < n_domains; i++) { + domains[i] = settings; + domains[i].agent = pmc_agent_create(); + if (!domains[i].agent) { + return -1; } - } - snprintf(uds_local, sizeof(uds_local), "/var/run/phc2sys.%d", - getpid()); + if (settings.forced_sync_offset) + pmc_agent_set_sync_offset(domains[i].agent, offset); + } if (autocfg) { - if (init_pmc_node(cfg, domain.agent, uds_local, - phc2sys_recv_subscribed, &domain)) - goto end; - if (auto_init_ports(&domain, rt) < 0) - goto end; - r = do_loop(&domain); + for (i = 0; i < n_domains; i++) { + if (rt && i + 1 == n_domains) { + if (auto_init_rt(&domains[n_domains - 1], + rt == 1) < 0) + goto end; + continue; + } + + snprintf(uds_local, sizeof(uds_local), + "/var/run/phc2sys.%d.%d", getpid(), i); + + if (uds_remote_cnt > i) + config_set_string(cfg, "uds_address", + uds_remotes[i]); + if (domain_number_cnt > i) + config_set_int(cfg, "domainNumber", + domain_numbers[i]); + + if (init_pmc_node(cfg, domains[i].agent, uds_local, + phc2sys_recv_subscribed, &domains[i])) + goto end; + if (auto_init_ports(&domains[i]) < 0) + goto end; + } + r = do_loop(domains, n_domains); goto end; } - r = phc2sys_static_src_configuration(&domain, src_name); + for (i = 0; i < dst_cnt; i++) { + r = phc2sys_static_dst_configuration(&domains[0], dst_names[i]); + if (r) { + goto end; + } + } + + r = phc2sys_static_src_configuration(&domains[0], src_name); if (r) { goto end; } @@ -1358,12 +1476,16 @@ int main(int argc, char *argv[]) r = -1; if (wait_sync) { - if (init_pmc_node(cfg, domain.agent, uds_local, - phc2sys_recv_subscribed, &domain)) + snprintf(uds_local, sizeof(uds_local), + "/var/run/phc2sys.%d", getpid()); + config_set_string(cfg, "uds_address", uds_remotes[0]); + + if (init_pmc_node(cfg, domains[0].agent, uds_local, + phc2sys_recv_subscribed, &domains[0])) goto end; while (is_running()) { - r = run_pmc_wait_sync(domain.agent, 1000); + r = run_pmc_wait_sync(domains[0].agent, 1000); if (r < 0) goto end; if (r > 0) @@ -1372,37 +1494,40 @@ int main(int argc, char *argv[]) pr_notice("Waiting for ptp4l..."); } - if (!domain.forced_sync_offset) { - r = pmc_agent_query_utc_offset(domain.agent, 1000); + if (!domains[0].forced_sync_offset) { + r = pmc_agent_query_utc_offset(domains[0].agent, 1000); if (r) { pr_err("failed to get UTC offset"); goto end; } } - if (domain.forced_sync_offset || - !phc2sys_using_systemclock(&domain) || + if (domains[0].forced_sync_offset || + !phc2sys_using_systemclock(&domains[0]) || hardpps_configured(pps_fd)) { - pmc_agent_disable(domain.agent); + pmc_agent_disable(domains[0].agent); } } if (hardpps_configured(pps_fd)) { - struct clock *dst = LIST_FIRST(&domain.dst_clocks); + struct clock *dst = LIST_FIRST(&domains[0].dst_clocks); /* only one destination clock allowed with PPS until we * implement a mean to specify PTP port to PPS mapping */ - dst->servo = servo_add(&domain, dst); + dst->servo = servo_add(&domains[0], dst); servo_sync_interval(dst->servo, 1.0); - r = do_pps_loop(&domain, dst, pps_fd); + r = do_pps_loop(&domains[0], dst, pps_fd); } else { - r = do_loop(&domain); + r = do_loop(&domains[0], 1); } end: - pmc_agent_destroy(domain.agent); - clock_cleanup(&domain); - port_cleanup(&domain); + for (i = 0; i < n_domains; i++) { + if (domains[i].agent) + pmc_agent_destroy(domains[i].agent); + clock_cleanup(&domains[i]); + port_cleanup(&domains[i]); + } config_destroy(cfg); msg_cleanup(); return r; -- 2.40.1 _______________________________________________ Linuxptp-devel mailing list Linuxptp-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/linuxptp-devel