From eb1763d49fab073371d11fb8f04d67a0039c706d Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 8 Feb 2026 10:01:10 +0100 Subject: [PATCH] confd: add gps support to ietf-hardware and ietf-ntp Signed-off-by: Joachim Wiberg --- src/confd/src/hardware.c | 6 + src/confd/src/ntp.c | 77 +++++++- src/confd/yang/confd.inc | 4 +- src/confd/yang/confd/infix-hardware.yang | 139 +++++++++++++++ ...19.yang => infix-hardware@2026-02-08.yang} | 0 src/confd/yang/confd/infix-ntp.yang | 164 ++++++++++++++++++ ...5-12-03.yang => infix-ntp@2026-02-08.yang} | 0 7 files changed, 386 insertions(+), 4 deletions(-) rename src/confd/yang/confd/{infix-hardware@2026-01-19.yang => infix-hardware@2026-02-08.yang} (100%) rename src/confd/yang/confd/{infix-ntp@2025-12-03.yang => infix-ntp@2026-02-08.yang} (100%) diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index 6d441fe7..e7c30450 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -150,6 +150,12 @@ static int hardware_cand_infer_class(json_t *root, sr_session_ctx_t *session, co inferred.data.string_val = "infix-hardware:wifi"; err = srx_set_item(session, &inferred, 0, "%s/class", xpath); } + + if (!fnmatch("gps+([0-9])", name, FNM_EXTMATCH)) { + inferred.data.string_val = "infix-hardware:gps"; + err = srx_set_item(session, &inferred, 0, "%s/class", xpath); + } + out_free_name: free(name); out_free_xpath: diff --git a/src/confd/src/ntp.c b/src/confd/src/ntp.c index 9813a6f2..82b98591 100644 --- a/src/confd/src/ntp.c +++ b/src/confd/src/ntp.c @@ -134,12 +134,85 @@ static int change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd } fprintf(fp, "\n"); - /* Reference clock (local stratum) - fallback time source */ + /* Reference clock configuration */ refclock = lydx_get_child(ntp, "refclock-master"); if (refclock) { + struct lyd_node *source; int stratum = atoi(lydx_get_cattr(refclock, "master-stratum")); + bool has_gps_sources = false; - /* Only configure local clock if stratum is valid (1-15) */ + /* + * GPS/GNSS reference clock sources via gpsd SHM + * + * Each GPS hardware component (gps0, gps1, ...) maps to: + * - SHM unit 2*N for GPS time (coarse, ~100ms) + * - SHM unit 2*N+1 for PPS time (precise, ~1us) if enabled + * + * The mapping is based on gpsd's convention. + */ + LYX_LIST_FOR_EACH(lyd_child(refclock), source, "source") { + const char *receiver, *refid, *poll_str, *precision_str; + const char *offset_str, *delay_str; + bool prefer, pps; + int unit; + + receiver = lydx_get_cattr(source, "receiver"); + if (!receiver) + continue; + + /* Extract unit number from receiver name (gps0 -> 0, gps1 -> 1, ...) */ + if (sscanf(receiver, "gps%d", &unit) != 1) { + ERROR("Invalid GPS receiver name: %s (expected gpsN)", receiver); + continue; + } + + refid = lydx_get_cattr(source, "refid"); + poll_str = lydx_get_cattr(source, "poll"); + precision_str = lydx_get_cattr(source, "precision"); + offset_str = lydx_get_cattr(source, "offset"); + delay_str = lydx_get_cattr(source, "delay"); + prefer = lydx_get_bool(source, "prefer"); + pps = lydx_get_bool(source, "pps"); + + if (!has_gps_sources) { + fprintf(fp, "# GPS/GNSS reference clock sources (via gpsd SHM)\n"); + has_gps_sources = true; + } + + /* GPS time source: SHM unit 2*N */ + fprintf(fp, "refclock SHM %d", unit * 2); + if (poll_str) + fprintf(fp, " poll %s", poll_str); + if (refid) + fprintf(fp, " refid %s", refid); + if (precision_str) + fprintf(fp, " precision %s", precision_str); + if (offset_str && atof(offset_str) != 0.0) + fprintf(fp, " offset %s", offset_str); + if (delay_str && atof(delay_str) != 0.0) + fprintf(fp, " delay %s", delay_str); + if (prefer) + fprintf(fp, " prefer"); + fprintf(fp, "\n"); + + /* PPS time source: SHM unit 2*N+1 (if enabled) */ + if (pps) { + fprintf(fp, "refclock SHM %d", unit * 2 + 1); + fprintf(fp, " poll %s", poll_str ? poll_str : "-6"); + fprintf(fp, " refid PPS"); + fprintf(fp, " precision 0.000001"); + if (prefer) + fprintf(fp, " prefer"); + /* PPS needs a time source for initial lock */ + fprintf(fp, " lock %s", refid ? refid : "GPS"); + fprintf(fp, "\n"); + } + } + + if (has_gps_sources) + fprintf(fp, "\n"); + + /* Local clock fallback - only if stratum is valid (1-15) */ if (stratum >= 1 && stratum <= 15) { fprintf(fp, "# Local reference clock - fallback stratum %d source\n", stratum); fprintf(fp, "local stratum %d orphan\n\n", stratum); diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 10ef69d5..8efe9e3e 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -27,7 +27,7 @@ MODULES=( "infix-syslog@2025-11-17.yang" "iana-hardware@2018-03-13.yang" "ietf-hardware@2018-03-13.yang -e hardware-state -e hardware-sensor" - "infix-hardware@2026-01-19.yang" + "infix-hardware@2026-02-08.yang" "ieee802-dot1q-types@2022-10-29.yang" "infix-ip@2025-11-02.yang" "infix-if-type@2026-01-07.yang" @@ -51,6 +51,6 @@ MODULES=( "ietf-crypto-types -e cleartext-symmetric-keys" "infix-crypto-types@2025-11-09.yang" "ietf-keystore -e symmetric-keys" - "infix-ntp@2025-12-03.yang" + "infix-ntp@2026-02-08.yang" "infix-keystore@2025-12-17.yang" ) diff --git a/src/confd/yang/confd/infix-hardware.yang b/src/confd/yang/confd/infix-hardware.yang index 09b7b2a2..b28ada61 100644 --- a/src/confd/yang/confd/infix-hardware.yang +++ b/src/confd/yang/confd/infix-hardware.yang @@ -21,6 +21,10 @@ module infix-hardware { contact "kernelkit@googlegroups.com"; description "Vital Product Data augmentation of ieee-hardware and deviations."; + revision 2026-02-08 { + description "Add GPS/GNSS receiver hardware class and container for time sync."; + reference "internal"; + } revision 2026-01-19 { description "Add probe-timeout for slow USB WiFi dongles."; reference "internal"; @@ -63,6 +67,15 @@ module infix-hardware { WiFi radios are hardware components with class 'ih:wifi'."; } + typedef gps-receiver-ref { + type leafref { + path "/iehw:hardware/iehw:component/iehw:name"; + } + description + "Reference to a GPS/GNSS receiver hardware component. + GPS receivers are hardware components with class 'ih:gps'."; + } + typedef wifi-band { type enumeration { enum "2.4GHz" { @@ -95,6 +108,11 @@ module infix-hardware { description "This identity is used to describe a WiFi radio/PHY"; } + identity gps { + base iahw:hardware-class; + description "GPS/GNSS receiver for time synchronization"; + } + deviation "/iehw:hardware/iehw:component/iehw:state/iehw:admin-state" { deviate add { must ". = 'locked' or . = 'unlocked'" { @@ -510,5 +528,126 @@ module infix-hardware { } } } + + /* + * GPS/GNSS Receiver configuration (when class = 'ih:gps') + */ + + container gps-receiver { + when "derived-from-or-self(../iehw:class, 'ih:gps')"; + presence "GPS receiver configuration"; + description + "GPS/GNSS receiver configuration and operational data. + + This container is present when the hardware component represents + a GPS/GNSS receiver (class 'ih:gps'). GPS receivers provide + precision time via gpsd, which chronyd can use as a reference + clock through shared memory (SHM). + + The hardware component name (e.g., 'gps0') determines: + - Device path: /dev/gps0 (symlink managed by udev) + - SHM unit: 0 for GPS time, 1 for PPS (derived from gpsN index) + + Supported GNSS constellations depend on the hardware: + - GPS (US) + - GLONASS (Russia) + - Galileo (EU) + - BeiDou (China)"; + + /* + * Operational state from gpsd + */ + + leaf device { + config false; + type string; + description + "Actual device path being used (e.g., /dev/ttyUSB0, /dev/ttyACM0). + This is the physical device that /dev/gpsN symlinks to."; + } + + leaf driver { + config false; + type string; + description + "GPS driver/protocol in use (e.g., 'u-blox', 'NMEA', 'SiRF')."; + } + + leaf activated { + config false; + type boolean; + description + "Whether gpsd has successfully activated this GPS device."; + } + + leaf satellites-visible { + config false; + type uint8; + description + "Number of satellites currently visible to the receiver."; + } + + leaf satellites-used { + config false; + type uint8; + description + "Number of satellites being used for the fix."; + } + + leaf fix-mode { + config false; + type enumeration { + enum "none" { + description "No fix available"; + } + enum "2d" { + description "2D fix (latitude/longitude only)"; + } + enum "3d" { + description "3D fix (latitude/longitude/altitude)"; + } + } + description + "Current GPS fix mode. A 3D fix is required for accurate time."; + } + + leaf latitude { + config false; + type decimal64 { + fraction-digits 6; + } + units "degrees"; + description + "Current latitude in decimal degrees. Negative values are south."; + } + + leaf longitude { + config false; + type decimal64 { + fraction-digits 6; + } + units "degrees"; + description + "Current longitude in decimal degrees. Negative values are west."; + } + + leaf altitude { + config false; + type decimal64 { + fraction-digits 1; + } + units "meters"; + description + "Current altitude above mean sea level in meters."; + } + + leaf pps-available { + config false; + type boolean; + description + "Whether this GPS device provides a PPS (Pulse Per Second) signal. + PPS provides microsecond-level timing accuracy."; + } + } } } diff --git a/src/confd/yang/confd/infix-hardware@2026-01-19.yang b/src/confd/yang/confd/infix-hardware@2026-02-08.yang similarity index 100% rename from src/confd/yang/confd/infix-hardware@2026-01-19.yang rename to src/confd/yang/confd/infix-hardware@2026-02-08.yang diff --git a/src/confd/yang/confd/infix-ntp.yang b/src/confd/yang/confd/infix-ntp.yang index d3576b85..089f4e61 100644 --- a/src/confd/yang/confd/infix-ntp.yang +++ b/src/confd/yang/confd/infix-ntp.yang @@ -15,10 +15,30 @@ module infix-ntp { "RFC 6991: Common YANG Data Types"; } + import ietf-hardware { + prefix iehw; + reference + "RFC 8348: A YANG Data Model for Hardware Management"; + } + + import infix-hardware { + prefix ih; + } + organization "KernelKit"; contact "kernelkit@googlegroups.com"; description "Infix deviations and augments to ietf-ntp."; + revision 2026-02-08 { + description + "Add GPS/GNSS reference clock support. + + Augments refclock-master with a list of hardware GPS sources that + chronyd can use via gpsd's shared memory (SHM) interface. Supports + multiple GNSS receivers for redundancy (GPS, GLONASS, Galileo, BeiDou)."; + reference "internal"; + } + revision 2025-12-03 { description "Initial release - NTP server support. @@ -70,6 +90,150 @@ module infix-ntp { } } + /* + * GPS/GNSS reference clock sources + * + * Augments refclock-master with hardware GPS sources that chronyd + * can use via gpsd's shared memory (SHM) interface. + */ + augment "/ntp:ntp/ntp:refclock-master" { + list source { + key "receiver"; + max-elements 2; + description + "GPS/GNSS hardware reference clock sources. + + Each source references a GPS receiver hardware component and + configures how chronyd should use it. The hardware component + name (e.g., 'gps0') determines the SHM unit number: + - gps0 -> SHM 0 (GPS time), SHM 1 (PPS if available) + - gps1 -> SHM 2 (GPS time), SHM 3 (PPS if available) + + Two sources can be configured for redundancy against jamming + or hardware failure. The NTP server will evaluate all sources + and select the best one."; + + leaf receiver { + type leafref { + path "/iehw:hardware/iehw:component/iehw:name"; + } + must "derived-from-or-self(/iehw:hardware/iehw:component[iehw:name=current()]/iehw:class, 'ih:gps')" { + error-message "Referenced hardware component must be a GPS receiver (class 'ih:gps')"; + } + description + "Reference to a GPS receiver hardware component. + + The component name determines the device path (/dev/gpsN) + and the SHM unit number for chronyd."; + } + + leaf poll { + type int8 { + range "-6..10"; + } + default "2"; + description + "Polling interval in log2 seconds. + + Common values: + - -6 = 1/64 second (for hardware with PPS) + - 0 = 1 second + - 2 = 4 seconds (default, good for GPS without PPS) + - 4 = 16 seconds + + Lower values provide faster response but higher CPU usage."; + } + + leaf precision { + type decimal64 { + fraction-digits 9; + range "0.000000001..1.0"; + } + default "0.1"; + units "seconds"; + description + "Assumed precision of the reference clock in seconds. + + Typical values: + - 0.1 (100ms) - GPS without PPS, default + - 0.001 (1ms) - GPS with software PPS processing + - 0.000001 (1us) - GPS with hardware PPS"; + } + + leaf offset { + type decimal64 { + fraction-digits 9; + } + default "0.0"; + units "seconds"; + description + "Constant offset correction in seconds. + + Used to compensate for known systematic delays in the + GPS receiver or signal path. Positive values mean the + GPS time is ahead of true time."; + } + + leaf delay { + type decimal64 { + fraction-digits 9; + range "0.0..1.0"; + } + default "0.0"; + units "seconds"; + description + "Assumed maximum delay from the GPS receiver in seconds. + + This accounts for processing delays in gpsd and the + GPS receiver. Default is 0 (let chronyd estimate)."; + } + + leaf refid { + type string { + length "1..4"; + pattern "[A-Z0-9]+"; + } + default "GPS"; + description + "Reference identifier (refid) shown in chronyc tracking. + + Common values: + - GPS - Generic GPS + - GLO - GLONASS + - GAL - Galileo + - BDS - BeiDou + - PPS - Pulse Per Second (for PPS-only sources) + - GNSS - Multi-constellation receiver"; + } + + leaf prefer { + type boolean; + default false; + description + "Prefer this source over others when multiple are available. + + When multiple reference clocks have similar quality, the + preferred source will be selected. Useful for designating + a primary GPS receiver."; + } + + leaf pps { + type boolean; + default false; + description + "Use PPS (Pulse Per Second) signal from this source. + + When enabled, chronyd will also configure the PPS SHM + unit (2*N+1) for microsecond-level accuracy. The GPS + receiver must provide PPS and gpsd must be configured + to expose it. + + Note: PPS requires the GPS source for initial time lock, + then provides precise time edges."; + } + } + } + /* * Additional operational state fields from chronyd * that are not part of the standard ietf-ntp model diff --git a/src/confd/yang/confd/infix-ntp@2025-12-03.yang b/src/confd/yang/confd/infix-ntp@2026-02-08.yang similarity index 100% rename from src/confd/yang/confd/infix-ntp@2025-12-03.yang rename to src/confd/yang/confd/infix-ntp@2026-02-08.yang