integ/base/linuxptp/debian/patches/0038-Best-source-selection-algorithm.patch
Andre Mauricio Zelak 600ad549b5 Use time traceable flag in HA clock selection
A new time traceable flag was added to pmc agent to store the current
time traceable status.

This flag replaces the utc_offset_traceable flag in the HA clock
selection algorithm and status command.

Test plan: HA clock selection algorithm
PASS: Verify the clock source which time isn't traceable is discarded
by the algorithm if ha_gm_timeTraceable is enabled.
PASS: Verify the clock source which time is traceable isn't discarded
by the algorithm if ha_gm_timeTraceable is enabled.

Regression: status command
PASS: Verify the response of status command shows the correct GM time
traceable.

The 'valid sources' command is used to get a list of interfaces which
the clock is matching the requirements. The response contains a space
separated list of interfaces, or "None" when not a single clock is
matching all the requirements.

Test plan: valid sources command
PASS: Verify that a space separated list of interface is returned when
one or more clocks match the requirements.
PASS: Verify that the string "None" is returned when not a single clock
match the requirements.

Now the GM time traceable check is enabled by default as it is an
important check for both T-GM and T-BC scenarios.

The GM time traceable check is controlled in configuration by using
the ha_gm_timeTraceable setting, and it can be disabled using the
value 0 (ha_gm_timeTraceable 0).

Test plan: default value
PASS Verify the check is performed by default.
PASS Verify the user can disable the check by configuration.

Bonus:

Fixed the behavior when none clock is matching the requirements and the
active clock source is disabled using the 'disable source <interface>'
command. The interface is must be disabled and a new clock source is
selected.

Test plan: none clock is matching the requirements
PASS: Verify that the active source can be disabled and a new one is
selected.
PASS: Verify that an attempt to disable the last active interface
fails and an appopriated message is given as response.
PASS: Verify that the interface with higher priority is selected after
re-enabling it.
PASS: Verify the active clock source doesn't change if another
interface is disabled.
PASS: Verify the active clock source doesn't change if another
interface is re-enabled.

Story: 2010723
Task: 48702

Change-Id: I64193575a995e520d36460c0ebb8dd452fa8c2b8
Signed-off-by: Andre Mauricio Zelak <andre.zelak@windriver.com>
2023-09-06 15:09:14 -03:00

442 lines
14 KiB
Diff

From 142b30b1f996a5bd48f0edc9b5fb0f51af0b97fd Mon Sep 17 00:00:00 2001
From: Andre Mauricio Zelak <andre.zelak@windriver.com>
Date: Tue, 4 Jul 2023 17:27:50 -0300
Subject: [PATCH 38/54] Best source selection algorithm
An algorithm to select the best available clock and use it
as clock source.
A new set of configuration options was introduced to control
the clock requirements. The clock which fails to match the
requirements is discarded.
If a single clock matches the requirements, it is selected
as source.
If one or more clock match requirements, the clock with the highest
priority is selected. In case of tie, the 1st configured clock is
selected.
And if no clock match requirements, the clock with the best local
clock class is selected.
The ha_priority option is an interface setting used to configure
the clock priority. The lowest priority is 0 and the highest is 254,
and the default value is 0.
The ha_min_local_clockClass option is a global setting for the minimal
local clock class requirement. It ranges from 6 to 255 and its default
is 135.
The ha_min_clockAccuracy option is a global setting for the minimal
clock accuracy requirement. It ranges from 0x00 to 0xff and its default
is 0xfe.
The ha_min_offsetScaledLogVariance is a global setting for the minimal
offset scaled log variance. It ranges from 0 to 65535 and its default
is 65535.
The ha_timeTraceable is a global setting to enable or disable
the time traceable verification. When it's set the clock
which time is not traceable is discarded.
The ha_frequencyTraceable is a global setting to enable or disable
the frequency traceable verification. When it's set the clock
which frequency is not traceable is discarded.
The ha_min_gm_ClockClass is a global setting for the minimal
GM clock class requirement. It ranges from 6 to 255 and its
default is 135.
Test Plan: clock selection algorithm
PASS: Verify clock is discarded when local clock class doesn't match
requirements.
PASS: Verify clock is discarded when local clock accuracy doesn't match
requirements.
PASS: Verify clock is discarded when local offset scaled log variance doesn't
match requirements.
PASS: Verify clock is discarded when time traceable verification is set 1 and
the clock hasn't time traceable flag set.
PASS: Verify clock is discarded when frequency traceable verification is set 1
and the clock hasn't frequency traceable flag set.
PASS: Verify clock is discarded when GM clock class doesn't match requirements.
PASS: Verify clock is accepted when time traceable verification is set 0 even
when clock hasn't time traceable flag set.
PASS: Verify clock is accepted when frequency traceable verification is set 0
even when clock hasn't frequency traceable flag set.
PASS: Verify the highest priority clock is selected when one or more clock
match the requirements.
PASS: Verify the 1st configured clock is selected when one or more clock of the
same priority match the requirements.
PASS: Verify the clock with highest local clock class is selected when no clock
match the requirements.
Reviewed-by: Cole Walker <cole.walker@windriver.com>
Reviewed-by: Andre Fernando Zanella Kantek <andrefernandozanella.kantek@windriver.com>
[commit 1c10dd42b32388c2e708ad249dd1f193e7208155 upstream]
[commit 373c4fd50aaf52540d3eeb8f38f3e07307dea3a3 upstream]
[commit 279d5b6e7f88876ce00f1e87faba65c7cd6a90b0 upstream]
[commit 00d9ad798b1f700faefa0b5d4074c46f8ae87ef4 upstream]
[commit 1407a51d8000ca7df18ba67d611a761abb6f77f8 upstream]
[commit e0c1c7b64f7af8002092c01e023f524bfcc39f8b upstream]
Signed-off-by: Andre Mauricio Zelak <andre.zelak@windriver.com>
---
config.c | 7 ++
phc2sys.c | 231 ++++++++++++++++++++++++++++++++++++++++++++++++++++
pmc_agent.c | 20 +++++
pmc_agent.h | 13 +++
4 files changed, 271 insertions(+)
diff --git a/config.c b/config.c
index b97e5d7..8ce5f6c 100644
--- a/config.c
+++ b/config.c
@@ -250,7 +250,14 @@ struct config_item config_tab[] = {
GLOB_ITEM_INT("G.8275.defaultDS.localPriority", 128, 1, UINT8_MAX),
PORT_ITEM_INT("G.8275.portDS.localPriority", 128, 1, UINT8_MAX),
GLOB_ITEM_INT("gmCapable", 1, 0, 1),
+ GLOB_ITEM_INT("ha_frequencyTraceable", 0, 0, 1),
GLOB_ITEM_INT("ha_enabled", 0, 0, 1),
+ GLOB_ITEM_INT("ha_min_clockAccuracy", 0xfe, 0, 0xff),
+ GLOB_ITEM_INT("ha_min_gm_ClockClass", 135, 6, 255),
+ GLOB_ITEM_INT("ha_min_local_clockClass", 135, 6, 255),
+ GLOB_ITEM_INT("ha_min_offsetScaledLogVariance", 65535, 0, 65535),
+ PORT_ITEM_INT("ha_priority", 0, 0, 255),
+ GLOB_ITEM_INT("ha_timeTraceable", 0, 0, 1),
PORT_ITEM_STR("ha_uds_address", "/var/run/ptp4l"),
GLOB_ITEM_ENU("hwts_filter", HWTS_FILTER_NORMAL, hwts_filter_enu),
PORT_ITEM_INT("hybrid_e2e", 0, 0, 1),
diff --git a/phc2sys.c b/phc2sys.c
index a4afe01..d148d62 100644
--- a/phc2sys.c
+++ b/phc2sys.c
@@ -73,6 +73,7 @@
struct clock {
LIST_ENTRY(clock) list;
LIST_ENTRY(clock) dst_list;
+ LIST_ENTRY(clock) good_list;
clockid_t clkid;
int phc_index;
int sysoff_method;
@@ -1073,6 +1074,232 @@ static int clock_handle_leap(struct phc2sys_private *priv, struct clock *clock,
return 0;
}
+static struct clock* startup_select_clock(struct phc2sys_private *priv, struct config *cfg)
+{
+ struct clock *clock = NULL, *best = NULL;
+ LIST_HEAD(head, clock) good_clocks;
+ int clock_priority, highest_priority;
+ int min_local_clock_class, min_gm_clock_class, clock_class, lowest_clock_class;
+ int err;
+ unsigned int min_clock_accuracy, min_offset_scaled_log_variance, retries;
+ bool check_time_traceable, check_freq_traceable;
+
+ LIST_INIT(&good_clocks);
+
+ /* get requirements */
+ min_local_clock_class = config_get_int(cfg, NULL, "ha_min_local_clockClass");
+ min_clock_accuracy = config_get_int(cfg, NULL, "ha_min_clockAccuracy");
+ min_offset_scaled_log_variance = config_get_int(cfg, NULL, "ha_min_offsetScaledLogVariance");
+ check_time_traceable = config_get_int(cfg, NULL, "ha_timeTraceable");
+ check_freq_traceable = config_get_int(cfg, NULL, "ha_frequencyTraceable");
+ min_gm_clock_class = config_get_int(cfg, NULL, "ha_min_gm_ClockClass");
+
+ /* save a list of available source clocks that matches requirements */
+ LIST_FOREACH(clock, &priv->clocks, list) {
+ /* check matching parameters */
+ pr_debug("clock %s state %d", clock->device, clock->state);
+
+ /* ignore the dst clock */
+ if (clock->state == PS_MASTER) {
+ pr_debug("clock %s discarded because state is PS_MASTER", clock->device);
+ continue;
+ }
+
+ /* sanity check */
+ if (clock->node == NULL) {
+ pr_debug("clock %s discarded because node is (null)", clock->device);
+ continue;
+ }
+
+ /* get Default Data Set */
+ retries = 0;
+ while(retries < 10) {
+ if (!is_running()) {
+ return NULL;
+ }
+ err = pmc_agent_query_dds(clock->node, 1000);
+ if (!err) {
+ break;
+ }
+ if (err == -ETIMEDOUT) {
+ pr_notice("Waiting for ptp4l...");
+ retries++;
+ } else {
+ return NULL;
+ }
+ }
+
+ if (!clock->node->dds_valid) {
+ pr_debug("clock %s discarded because dds is invalid", clock->device);
+ continue;
+ }
+
+ /* min clockClass
+ as lower clock class is better, accept sources which clock class
+ is lower then or equal to min local clock class and discard
+ the sources which clock class is higher than min local clock class.
+ */
+ clock_class = clock->node->dds.clockQuality.clockClass;
+ pr_debug("clock %s local clockClass %d", clock->device, clock_class);
+ if (clock_class > min_local_clock_class) {
+ pr_debug("clock %s discarded because local clock class %d > min clock class %d",
+ clock->device, clock_class, min_local_clock_class);
+ continue;
+ }
+
+ /* min clockAccuracy (lower is better) */
+ pr_debug("clock %s clockAccuracy 0x%x", clock->device,
+ clock->node->dds.clockQuality.clockAccuracy);
+ if (clock->node->dds.clockQuality.clockAccuracy > min_clock_accuracy) {
+ pr_debug("clock %s discarded because clock accuracy %d > min clock accuracy %d",
+ clock->device, clock->node->dds.clockQuality.clockAccuracy,
+ min_clock_accuracy);
+ continue;
+ }
+
+ /* min offset scaled log variance */
+ pr_debug("clock %s offsetScaledLogVariance 0x%x", clock->device,
+ clock->node->dds.clockQuality.offsetScaledLogVariance);
+ if (clock->node->dds.clockQuality.offsetScaledLogVariance > min_offset_scaled_log_variance) {
+ pr_debug("clock %s discarded because offset scaled log variance 0x%x > min offset 0x%x",
+ clock->device, clock->node->dds.clockQuality.offsetScaledLogVariance,
+ min_offset_scaled_log_variance);
+ continue;
+ }
+
+ if (check_time_traceable || check_freq_traceable) {
+ /* get Time Properties Data Set */
+ retries = 0;
+ while(retries < 10) {
+ if (!is_running()) {
+ return NULL;
+ }
+ err = pmc_agent_query_utc_offset(clock->node, 1000);
+ if (!err) {
+ break;
+ }
+ if (err == -ETIMEDOUT) {
+ pr_notice("Waiting for ptp4l...");
+ retries++;
+ } else {
+ return NULL;
+ }
+ }
+
+ if (err != 0) {
+ pr_debug("clock %s discarded because tds is invalid", clock->device);
+ continue;
+ }
+ }
+
+ /* is time traceable */
+ pr_debug("clock %s is time traceable %s", clock->device,
+ clock->node->utc_offset_traceable ? "yes" : "no");
+ if (check_time_traceable && !clock->node->utc_offset_traceable) {
+ pr_debug("clock %s discarded because time is not traceable", clock->device);
+ continue;
+ }
+
+ /* is frequency traceable */
+ pr_debug("clock %s is frequency traceable %s", clock->device,
+ clock->node->freq_traceable ? "yes" : "no");
+ if (check_freq_traceable && !clock->node->freq_traceable) {
+ pr_debug("clock %s discarded because frequency is not traceable", clock->device);
+ continue;
+ }
+
+ retries = 0;
+ while (retries < 10) {
+ if (!is_running()) {
+ return NULL;
+ }
+ err = pmc_agent_query_pds(clock->node, 1000);
+ if (!err) {
+ break;
+ }
+ if (err == -ETIMEDOUT) {
+ pr_notice("Waiting for ptp4l...");
+ retries++;
+ } else {
+ return NULL;
+ }
+ }
+
+ if (!clock->node->pds_valid) {
+ pr_debug("clock %s discarded because pds is invalid", clock->device);
+ continue;
+ }
+
+ /* min gm clock class - lower is better */
+ clock_class = clock->node->pds.grandmasterClockQuality.clockClass;
+ pr_debug("clock %s GM clockClass %d", clock->device, clock_class);
+ if (clock_class > min_gm_clock_class) {
+ pr_debug("clock %s discarded because GM clock class %d > min clock class %d",
+ clock->device, clock_class, min_gm_clock_class);
+ continue;
+ }
+
+ pr_notice("clock %s matched requirements", clock->device);
+
+ clock->good_list.le_next = NULL;
+ clock->good_list.le_prev = NULL;
+ LIST_INSERT_HEAD(&good_clocks, clock, good_list);
+ }
+
+ /* one or more sources match requirements, select highest priority */
+ highest_priority = 0;
+ LIST_FOREACH(clock, &good_clocks, good_list) {
+ clock_priority = config_get_int(cfg, clock->device, "ha_priority");
+
+ /* select highest priority clock
+ more than one clock with same priority, select first
+ don't select clocks with ha_priority 0 */
+ if (clock_priority > highest_priority) {
+ pr_notice("new highest ha priority clock %s ha_priority %d",
+ clock->device, clock_priority);
+ best = clock;
+ highest_priority = clock_priority;
+ }
+ }
+
+ /* no sources match requirements, choose best available clockClass */
+ if (!best) {
+ lowest_clock_class = 256;
+ LIST_FOREACH(clock, &priv->clocks, list) {
+ /* ignore the dst clock */
+ if (clock->state == PS_MASTER) {
+ continue;
+ }
+
+ /* get clock class */
+ clock_class = clock->node->dds.clockQuality.clockClass;
+ if (clock_class <= lowest_clock_class) {
+ pr_notice("new better clock class clock %s clock class %d",
+ clock->device, clock_class);
+ best = clock;
+ lowest_clock_class = clock_class;
+ }
+ }
+ }
+
+ /* no clock selected, select first clock configured (last in list) */
+ if (!best) {
+ LIST_FOREACH(clock, &priv->clocks, list) {
+ /* ignore the dst clock */
+ if (clock->state == PS_MASTER) {
+ continue;
+ }
+
+ best = clock;
+ }
+ }
+
+ if (best)
+ pr_notice("Best clock selected %s", best->device);
+
+ return best;
+};
+
static void usage(char *progname)
{
fprintf(stderr,
@@ -1486,6 +1713,10 @@ int main(int argc, char *argv[])
++i;
}
+
+ if (ha_enabled) {
+ startup_select_clock(&priv, cfg);
+ }
}
if (pps_fd >= 0) {
diff --git a/pmc_agent.c b/pmc_agent.c
index d13f569..534f483 100644
--- a/pmc_agent.c
+++ b/pmc_agent.c
@@ -351,15 +351,35 @@ int pmc_agent_query_utc_offset(struct pmc_agent *node, int timeout)
node->leap = 0;
node->utc_offset_traceable = tds->flags & UTC_OFF_VALID &&
tds->flags & TIME_TRACEABLE;
+ node->freq_traceable = tds->flags & FREQ_TRACEABLE;
} else {
node->sync_offset = 0;
node->leap = 0;
node->utc_offset_traceable = 0;
+ node->freq_traceable = 0;
}
msg_put(msg);
return 0;
}
+int pmc_agent_query_pds(struct pmc_agent *node, int timeout)
+{
+ struct parentDS *pds;
+ struct ptp_message *msg;
+ int res;
+
+ res = run_pmc(node, timeout, MID_PARENT_DATA_SET, &msg);
+ if (is_run_pmc_error(res)) {
+ return run_pmc_err2errno(res);
+ }
+
+ pds = (struct parentDS *) management_tlv_data(msg);
+ memcpy(&node->pds, pds, sizeof(node->pds));
+ node->pds_valid = true;
+ msg_put(msg);
+ return 0;
+}
+
void pmc_agent_set_sync_offset(struct pmc_agent *agent, int offset)
{
agent->sync_offset = offset;
diff --git a/pmc_agent.h b/pmc_agent.h
index 5f25984..2bd7f02 100644
--- a/pmc_agent.h
+++ b/pmc_agent.h
@@ -41,7 +41,10 @@ struct pmc_agent {
bool stay_subscribed;
int sync_offset;
int utc_offset_traceable;
+ int freq_traceable;
unsigned int index;
+ struct parentDS pds;
+ bool pds_valid;
/* Callback on message reception */
pmc_node_recv_subscribed_t *recv_subscribed;
@@ -142,6 +145,16 @@ int pmc_agent_query_port_properties(struct pmc_agent *agent, int timeout,
*/
int pmc_agent_query_utc_offset(struct pmc_agent *agent, int timeout);
+/**
+ * Queries the parent data set from the ptp4l service.
+ * The result of the query will be cached inside of the agent.
+ *
+ * @param agent Pointer to a PMC instance obtained via @ref pmc_agent_create().
+ * @param timeout Transmit and receive timeout in milliseconds.
+ * @return Zero on success, negative error code otherwise.
+ */
+int pmc_agent_query_pds(struct pmc_agent *agent, int timeout);
+
/**
* Sets the TAI-UTC offset.
* @param agent Pointer to a PMC instance obtained via @ref pmc_agent_create().
--
2.25.1