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

Reply via email to