diff --git a/configs/aarch64_defconfig b/configs/aarch64_defconfig index ccc59f03d..0d9ff6920 100644 --- a/configs/aarch64_defconfig +++ b/configs/aarch64_defconfig @@ -42,6 +42,9 @@ BR2_PACKAGE_DBUS_GLIB=y BR2_PACKAGE_DBUS_TRIGGERD=y BR2_PACKAGE_EUDEV_RULES_GEN=y # BR2_PACKAGE_EUDEV_ENABLE_HWDB is not set +BR2_PACKAGE_GPSD_DEVICES="/dev/gps0" +BR2_PACKAGE_GPSD_MAX_CLIENT_VALUE=2 +BR2_PACKAGE_GPSD_MAX_DEV_VALUE=1 BR2_PACKAGE_GPTFDISK=y BR2_PACKAGE_GPTFDISK_SGDISK=y BR2_PACKAGE_MDIO_TOOLS=y @@ -146,6 +149,7 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu INFIX_HOME="https://github.com/kernelkit/infix/" INFIX_DOC="https://kernelkit.org/infix/" INFIX_SUPPORT="mailto:kernelkit@googlegroups.com" +BR2_PACKAGE_FEATURE_GPS=y BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y BR2_PACKAGE_FEATURE_WIFI_REALTEK=y BR2_PACKAGE_CONFD=y diff --git a/configs/aarch64_minimal_defconfig b/configs/aarch64_minimal_defconfig index c8f5f3211..af01486f6 100644 --- a/configs/aarch64_minimal_defconfig +++ b/configs/aarch64_minimal_defconfig @@ -42,6 +42,9 @@ BR2_PACKAGE_DBUS_GLIB=y BR2_PACKAGE_DBUS_TRIGGERD=y BR2_PACKAGE_EUDEV_RULES_GEN=y # BR2_PACKAGE_EUDEV_ENABLE_HWDB is not set +BR2_PACKAGE_GPSD_DEVICES="/dev/gps0" +BR2_PACKAGE_GPSD_MAX_CLIENT_VALUE=2 +BR2_PACKAGE_GPSD_MAX_DEV_VALUE=1 BR2_PACKAGE_GPTFDISK=y BR2_PACKAGE_GPTFDISK_SGDISK=y BR2_PACKAGE_MDIO_TOOLS=y @@ -124,6 +127,7 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu INFIX_HOME="https://github.com/kernelkit/infix/" INFIX_DOC="https://kernelkit.org/infix/" INFIX_SUPPORT="mailto:kernelkit@googlegroups.com" +BR2_PACKAGE_FEATURE_GPS=y BR2_PACKAGE_CONFD=y BR2_PACKAGE_CONFD_TEST_MODE=y BR2_PACKAGE_GENCERT=y diff --git a/configs/arm_defconfig b/configs/arm_defconfig index 20e286d07..d0a8db975 100644 --- a/configs/arm_defconfig +++ b/configs/arm_defconfig @@ -44,6 +44,9 @@ BR2_PACKAGE_DBUS_GLIB=y BR2_PACKAGE_DBUS_TRIGGERD=y BR2_PACKAGE_EUDEV_RULES_GEN=y # BR2_PACKAGE_EUDEV_ENABLE_HWDB is not set +BR2_PACKAGE_GPSD_DEVICES="/dev/gps0" +BR2_PACKAGE_GPSD_MAX_CLIENT_VALUE=2 +BR2_PACKAGE_GPSD_MAX_DEV_VALUE=1 BR2_PACKAGE_GPTFDISK=y BR2_PACKAGE_GPTFDISK_SGDISK=y BR2_PACKAGE_MDIO_TOOLS=y @@ -142,6 +145,7 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu INFIX_HOME="https://github.com/kernelkit/infix/" INFIX_DOC="https://kernelkit.org/infix/" INFIX_SUPPORT="mailto:kernelkit@googlegroups.com" +BR2_PACKAGE_FEATURE_GPS=y BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y BR2_PACKAGE_FEATURE_WIFI_REALTEK=y BR2_PACKAGE_CONFD=y diff --git a/configs/arm_minimal_defconfig b/configs/arm_minimal_defconfig index 98a211e98..0eac09603 100644 --- a/configs/arm_minimal_defconfig +++ b/configs/arm_minimal_defconfig @@ -44,6 +44,9 @@ BR2_PACKAGE_DBUS_GLIB=y BR2_PACKAGE_DBUS_TRIGGERD=y BR2_PACKAGE_EUDEV_RULES_GEN=y # BR2_PACKAGE_EUDEV_ENABLE_HWDB is not set +BR2_PACKAGE_GPSD_DEVICES="/dev/gps0" +BR2_PACKAGE_GPSD_MAX_CLIENT_VALUE=2 +BR2_PACKAGE_GPSD_MAX_DEV_VALUE=1 BR2_PACKAGE_GPTFDISK=y BR2_PACKAGE_GPTFDISK_SGDISK=y BR2_PACKAGE_MDIO_TOOLS=y @@ -124,6 +127,7 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu INFIX_HOME="https://github.com/kernelkit/infix/" INFIX_DOC="https://kernelkit.org/infix/" INFIX_SUPPORT="mailto:kernelkit@googlegroups.com" +BR2_PACKAGE_FEATURE_GPS=y BR2_PACKAGE_CONFD=y BR2_PACKAGE_CONFD_TEST_MODE=y BR2_PACKAGE_GENCERT=y diff --git a/configs/riscv64_defconfig b/configs/riscv64_defconfig index f496f4b46..ef5f05dc2 100644 --- a/configs/riscv64_defconfig +++ b/configs/riscv64_defconfig @@ -53,6 +53,9 @@ BR2_PACKAGE_DBUS_GLIB=y BR2_PACKAGE_DBUS_TRIGGERD=y BR2_PACKAGE_EUDEV_RULES_GEN=y # BR2_PACKAGE_EUDEV_ENABLE_HWDB is not set +BR2_PACKAGE_GPSD_DEVICES="/dev/gps0" +BR2_PACKAGE_GPSD_MAX_CLIENT_VALUE=2 +BR2_PACKAGE_GPSD_MAX_DEV_VALUE=1 BR2_PACKAGE_GPTFDISK=y BR2_PACKAGE_GPTFDISK_SGDISK=y BR2_PACKAGE_MDIO_TOOLS=y @@ -173,6 +176,7 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu INFIX_HOME="https://github.com/kernelkit/infix/" INFIX_DOC="https://kernelkit.org/infix/" INFIX_SUPPORT="mailto:kernelkit@googlegroups.com" +BR2_PACKAGE_FEATURE_GPS=y BR2_PACKAGE_FEATURE_WIFI=y BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y BR2_PACKAGE_FEATURE_WIFI_REALTEK=y diff --git a/configs/x86_64_defconfig b/configs/x86_64_defconfig index 431b28397..c64156302 100644 --- a/configs/x86_64_defconfig +++ b/configs/x86_64_defconfig @@ -42,6 +42,9 @@ BR2_PACKAGE_DBUS_GLIB=y BR2_PACKAGE_DBUS_TRIGGERD=y BR2_PACKAGE_EUDEV_RULES_GEN=y # BR2_PACKAGE_EUDEV_ENABLE_HWDB is not set +BR2_PACKAGE_GPSD_DEVICES="/dev/gps0" +BR2_PACKAGE_GPSD_MAX_CLIENT_VALUE=2 +BR2_PACKAGE_GPSD_MAX_DEV_VALUE=1 BR2_PACKAGE_GPTFDISK=y BR2_PACKAGE_GPTFDISK_SGDISK=y BR2_PACKAGE_RNG_TOOLS=y @@ -145,6 +148,7 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu INFIX_HOME="https://github.com/kernelkit/infix/" INFIX_DOC="https://kernelkit.org/infix/" INFIX_SUPPORT="mailto:kernelkit@googlegroups.com" +BR2_PACKAGE_FEATURE_GPS=y BR2_PACKAGE_FEATURE_WIFI=y BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y BR2_PACKAGE_FEATURE_WIFI_REALTEK=y diff --git a/configs/x86_64_minimal_defconfig b/configs/x86_64_minimal_defconfig index 3398654c3..2fa921ecf 100644 --- a/configs/x86_64_minimal_defconfig +++ b/configs/x86_64_minimal_defconfig @@ -42,6 +42,9 @@ BR2_PACKAGE_DBUS_GLIB=y BR2_PACKAGE_DBUS_TRIGGERD=y BR2_PACKAGE_EUDEV_RULES_GEN=y # BR2_PACKAGE_EUDEV_ENABLE_HWDB is not set +BR2_PACKAGE_GPSD_DEVICES="/dev/gps0" +BR2_PACKAGE_GPSD_MAX_CLIENT_VALUE=2 +BR2_PACKAGE_GPSD_MAX_DEV_VALUE=1 BR2_PACKAGE_GPTFDISK=y BR2_PACKAGE_GPTFDISK_SGDISK=y BR2_PACKAGE_RNG_TOOLS=y @@ -123,6 +126,7 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu INFIX_HOME="https://github.com/kernelkit/infix/" INFIX_DOC="https://kernelkit.org/infix/" INFIX_SUPPORT="mailto:kernelkit@googlegroups.com" +BR2_PACKAGE_FEATURE_GPS=y BR2_PACKAGE_CONFD=y BR2_PACKAGE_CONFD_TEST_MODE=y BR2_PACKAGE_GENCERT=y diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index bdfbab2f9..7cc4529b8 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -10,6 +10,7 @@ All notable changes to the project are documented in this file. - Upgrade Linux kernel to 6.18.9 (LTS) - Add support for Microchip SAMA7G54-EK Evaluation Kit, Arm Cortex-A7 +- Add GPS/GNSS receiver support with NTP reference clock integration ### Fixes diff --git a/doc/hardware.md b/doc/hardware.md index 814fec296..f794e20eb 100644 --- a/doc/hardware.md +++ b/doc/hardware.md @@ -3,6 +3,56 @@ The hardware infomation and status is handled by the YANG model [IETF hardware][1], with deviations and augmentations in _infix-hardware_. +## GPS/GNSS Receivers + +Infix supports GPS/GNSS receivers for hardware status monitoring and NTP +time synchronization. USB GPS receivers using the USB ACM interface are +supported (e.g., u-blox). When connected, devices are automatically +discovered and named `gps0`, `gps1`, etc. + +### Current status + +
admin@example:/> show hardware
+HARDWARE COMPONENTS
+──────────────────────────────────────────────────────────────
+GPS/GNSS Receivers
+Name : gps0
+Device : /dev/gps0
+Driver : u-blox
+Status : Active
+Fix : 3D
+Satellites : 10/15 (used/visible)
+Position : 59.334567N 18.063456E 42.3m
+PPS : Available
+
+
+Available information per receiver:
+
+| Field | Description |
+|------------|---------------------------------------------------|
+| Name | Component name (`gps0`, `gps1`, ...) |
+| Device | Device path (`/dev/gps0`) |
+| Driver | Protocol driver (e.g., `u-blox`, `NMEA`, `SiRF`) |
+| Status | `Active` or `Inactive` |
+| Fix | `NONE`, `2D`, or `3D` |
+| Satellites | Used/visible count |
+| Position | Latitude, longitude, altitude (when fix acquired) |
+| PPS | Pulse Per Second signal availability |
+
+### Configure GPS receiver
+
+GPS receivers are hardware components with class `gps`. The class is
+auto-inferred from the component name, similar to WiFi radios (`radioN`):
+
+admin@example:/> configure
+admin@example:/config/> set hardware component gps0
+admin@example:/config/> leave
+
+
+To use a GPS receiver as an NTP reference clock source, see
+[NTP — GPS Reference Clock](ntp.md#gps-reference-clock).
+
+
## USB Ports
For Infix to be able to control USB port(s), a device tree modification
diff --git a/doc/ntp.md b/doc/ntp.md
index 67ea2bbc8..100e955e6 100644
--- a/doc/ntp.md
+++ b/doc/ntp.md
@@ -28,6 +28,70 @@ admin@example:/config/ntp/> set refclock-master master-stratum 10
admin@example:/config/ntp/> leave
+## GPS Reference Clock
+
+A GPS/GNSS receiver can be used as an NTP reference clock source,
+providing stratum 1 time derived from the GPS satellite constellation.
+This requires a GPS hardware component to be configured first, see
+[Hardware — GPS/GNSS Receivers](hardware.md#gpsgnss-receivers).
+
+### Basic setup
+
+Add a GPS receiver as a reference clock source:
+
+admin@example:/config/> edit ntp
+admin@example:/config/ntp/> edit refclock-master source gps0
+admin@example:/config/ntp/refclock-master/source/gps0/> set poll 2
+admin@example:/config/ntp/refclock-master/source/gps0/> set precision 0.1
+admin@example:/config/ntp/refclock-master/source/gps0/> end
+admin@example:/config/ntp/> leave
+
+
+Tunable parameters:
+
+| Parameter | Default | Description |
+|-------------|--------:|----------------------------------------------------|
+| `poll` | `2` | Polling interval in log2 seconds (2 = 4s) |
+| `precision` | `0.1` | Assumed precision in seconds (0.1 = 100ms) |
+| `refid` | `"GPS"`| Reference identifier (e.g., `GPS`, `GNSS`, `GLO`) |
+| `prefer` | `false` | Prefer this source over other reference clocks |
+| `pps` | `false` | Enable PPS for microsecond-level accuracy |
+| `offset` | `0.0` | Constant offset correction in seconds |
+| `delay` | `0.0` | Assumed maximum delay from the receiver |
+
+### PPS (Pulse Per Second)
+
+When the GPS receiver provides a PPS signal, enable the `pps` option for
+microsecond-level accuracy. With PPS, the GPS time provides the initial
+lock and the PPS edges discipline the clock:
+
+admin@example:/config/ntp/> edit refclock-master source gps0
+admin@example:/config/ntp/refclock-master/source/gps0/> set pps true
+admin@example:/config/ntp/refclock-master/source/gps0/> set precision 0.000001
+admin@example:/config/ntp/refclock-master/source/gps0/> end
+admin@example:/config/ntp/> leave
+
+
+### Monitoring
+
+The `show ntp` command shows the GPS receiver as the reference clock source:
+
+admin@example:/> show ntp
+Mode : Server (GPS reference clock: gps0)
+Port : 123
+Stratum : 1
+Ref time (UTC) : Sun Feb 08 19:44:36 2026
+
+
+Use `show ntp source` to see GPS reference clock details:
+
+admin@example:/> show ntp source
+Reference Clock : gps0 (u-blox)
+Status : selected
+Fix Mode : 3D
+Satellites : 9/17 (used/visible)
+
+
## Server Mode
Synchronize from upstream NTP servers while serving time to clients:
diff --git a/package/Config.in b/package/Config.in
index 71cf73cc7..3ccb35d03 100644
--- a/package/Config.in
+++ b/package/Config.in
@@ -1,6 +1,7 @@
menu "Packages"
comment "Hardware Support"
+source "$BR2_EXTERNAL_INFIX_PATH/package/feature-gps/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/feature-wifi/Config.in"
comment "Software Packages"
diff --git a/package/feature-gps/25-gpsd.rules b/package/feature-gps/25-gpsd.rules
new file mode 100644
index 000000000..906936b46
--- /dev/null
+++ b/package/feature-gps/25-gpsd.rules
@@ -0,0 +1,65 @@
+# Override udev rules for gpsd
+#
+# This file is Copyright 2010 by the GPSD project
+# SPDX-License-Identifier: BSD-2-clause
+
+SUBSYSTEM!="tty", GOTO="gpsd_rules_end"
+
+# Prolific Technology, Inc. PL2303 Serial Port [linux module: pl2303]
+# !!! rule disabled in Debian as it matches too many other devices
+# ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+# ATEN International Co., Ltd UC-232A Serial Port [linux module: pl2303]
+ATTRS{idVendor}=="0557", ATTRS{idProduct}=="2008", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+# PS-360 OEM (GPS sold with MS Street and Trips 2005) [linux module: pl2303]
+ATTRS{idVendor}=="067b", ATTRS{idProduct}=="aaa0", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+# FTDI 8U232AM / FT232 [linux module: ftdi_sio]
+# !!! rule disabled in Debian as it matches too many other devices
+# ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+# Cypress M8/CY7C64013 (Delorme uses these) [linux module: cypress_m8]
+ATTRS{idVendor}=="1163", ATTRS{idProduct}=="0100", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+# Cypress M8/CY7C64013 (DeLorme LT-40)
+ATTRS{idVendor}=="1163", ATTRS{idProduct}=="0200", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+# Garmin International GPSmap, various models (tested with Garmin GPS 18 USB) [linux module: garmin_gps]
+ATTRS{idVendor}=="091e", ATTRS{idProduct}=="0003", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+# Cygnal Integrated Products, Inc. CP210x Composite Device (Used by Holux m241 and Wintec grays2 wbt-201) [linux module: cp210x]
+# !!! rule disabled in Debian as it matches too many other devices
+#ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+# Cygnal Integrated Products, Inc. [linux module: cp210x]
+# !!! rule disabled in Debian as it matches too many other devices
+#ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea71", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+# u-blox AG, u-blox 5 (tested with Navilock NL-402U) [linux module: cdc_acm]
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a5", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+# u-blox AG, u-blox 6 (tested with GNSS Evaluation Kit TCXO) [linux module: cdc_acm]
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a6", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+# u-blox AG, u-blox 7 [linux module: cdc_acm]
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a7", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+# u-blox AG, u-blox 8 (tested with GNSS Evaluation Kit EKV-M8N) [linux module: cdc_acm]
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a8", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+# u-blox AG, u-blox 9 (tested with GNSS Evaluation Kit C099-F9P) [linux module: cdc_acm]
+ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a9", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+# MediaTek (tested with HOLUX M-1200E) [linux module: cdc_acm]
+ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="3329", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+# Telit wireless solutions (tested with HE910G) [linux module: cdc_acm]
+ATTRS{interface}=="Telit Wireless Module Port", ATTRS{bInterfaceNumber}=="06", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+# u-blox AG, u-blox 8 (tested with u-blox8 GNSS Mouse Receiver / GR-801) [linux module: cdc_acm]
+# !!! rule disabled in Debian as it matches too many other devices
+#ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+ACTION=="remove", RUN+="/usr/lib/udev/gpsd.hotplug"
+
+LABEL="gpsd_rules_end"
diff --git a/package/feature-gps/Config.in b/package/feature-gps/Config.in
new file mode 100644
index 000000000..a222e5218
--- /dev/null
+++ b/package/feature-gps/Config.in
@@ -0,0 +1,34 @@
+config BR2_PACKAGE_FEATURE_GPS
+ bool "Feature GPS/GNSS"
+ select BR2_PACKAGE_GPSD
+ select BR2_PACKAGE_GPSD_MAX_CLIENT
+ select BR2_PACKAGE_GPSD_MAX_DEV
+ select BR2_PACKAGE_GPSD_SQUELCH
+ select BR2_PACKAGE_GPSD_ASHTECH
+ select BR2_PACKAGE_GPSD_EARTHMATE
+ select BR2_PACKAGE_GPSD_EVERMORE
+ select BR2_PACKAGE_GPSD_FURY
+ select BR2_PACKAGE_GPSD_FV18
+ select BR2_PACKAGE_GPSD_GARMIN
+ select BR2_PACKAGE_GPSD_GARMIN_SIMPLE_TXT
+ select BR2_PACKAGE_GPSD_GEOSTAR
+ select BR2_PACKAGE_GPSD_GPSCLOCK
+ select BR2_PACKAGE_GPSD_GREIS
+ select BR2_PACKAGE_GPSD_ISYNC
+ select BR2_PACKAGE_GPSD_ITRAX
+ select BR2_PACKAGE_GPSD_NMEA2000
+ select BR2_PACKAGE_GPSD_OCEANSERVER
+ select BR2_PACKAGE_GPSD_ONCORE
+ select BR2_PACKAGE_GPSD_RTCM104V2
+ select BR2_PACKAGE_GPSD_RTCM104V3
+ select BR2_PACKAGE_GPSD_SIRF
+ select BR2_PACKAGE_GPSD_SKYTRAQ
+ select BR2_PACKAGE_GPSD_SUPERSTAR2
+ select BR2_PACKAGE_GPSD_TRIMBLE_TSIP
+ select BR2_PACKAGE_GPSD_TRIPMATE
+ select BR2_PACKAGE_GPSD_TRUE_NORTH
+ select BR2_PACKAGE_GPSD_UBX
+ help
+ Enables GPS/GNSS support in Infix. Includes gpsd with all
+ receiver protocol drivers and kernel USB ACM support for
+ common USB GPS receivers.
diff --git a/package/feature-gps/feature-gps.mk b/package/feature-gps/feature-gps.mk
new file mode 100644
index 000000000..6da640c97
--- /dev/null
+++ b/package/feature-gps/feature-gps.mk
@@ -0,0 +1,21 @@
+################################################################################
+#
+# GPS/GNSS support
+#
+################################################################################
+
+FEATURE_GPS_PACKAGE_VERSION = 1.0
+FEATURE_GPS_PACKAGE_LICENSE = MIT
+
+define FEATURE_GPS_INSTALL_TARGET_CMDS
+ $(INSTALL) -D -m 0644 $(FEATURE_GPS_PKGDIR)/gpsd.default \
+ $(TARGET_DIR)/etc/default/gpsd
+ $(INSTALL) -D -m 0644 $(FEATURE_GPS_PKGDIR)/25-gpsd.rules \
+ $(TARGET_DIR)/usr/lib/udev/rules.d/25-gpsd.rules
+endef
+
+define FEATURE_GPS_LINUX_CONFIG_FIXUPS
+ $(call KCONFIG_SET_OPT,CONFIG_USB_ACM,m)
+endef
+
+$(eval $(generic-package))
diff --git a/package/feature-gps/gpsd.default b/package/feature-gps/gpsd.default
new file mode 100644
index 000000000..2fba46864
--- /dev/null
+++ b/package/feature-gps/gpsd.default
@@ -0,0 +1 @@
+GPSD_OPTIONS="-n"
diff --git a/src/bin/show/__init__.py b/src/bin/show/__init__.py
index f86a164aa..85846731c 100755
--- a/src/bin/show/__init__.py
+++ b/src/bin/show/__init__.py
@@ -106,6 +106,7 @@ def ntp(args: List[str]) -> None:
# Fetch both client and server operational data
client_data = get_json("/system-state/ntp")
server_data = get_json("/ietf-ntp:ntp")
+ hw_data = get_json("/ietf-hardware:hardware")
# Merge into single data structure
data = {}
@@ -113,6 +114,8 @@ def ntp(args: List[str]) -> None:
data.update(client_data)
if server_data:
data.update(server_data)
+ if hw_data:
+ data.update(hw_data)
if RAW_OUTPUT:
if not data:
@@ -147,6 +150,11 @@ def ntp_source(args: List[str]) -> None:
print("No ntp server data retrieved.")
return
+ # Also fetch hardware data for GPS receiver info
+ hw_data = get_json("/ietf-hardware:hardware")
+ if hw_data:
+ data.update(hw_data)
+
if RAW_OUTPUT:
print(json.dumps(data, indent=2))
return
diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c
index 6d441fe7f..5857ceb12 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:
@@ -701,6 +707,11 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
free(wifi_iface_list);
wifi_iface_list = NULL;
wifi_iface_count = 0;
+ } else if (!strcmp(class, "infix-hardware:gps")) {
+ if (event != SR_EV_DONE)
+ continue;
+
+ systemf("initctl -nbq touch statd");
}
}
diff --git a/src/confd/src/ntp.c b/src/confd/src/ntp.c
index 9813a6f2e..82b98591b 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;
+
+ /*
+ * 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");
- /* Only configure local clock if stratum is valid (1-15) */
+ /* 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 10ef69d5a..8efe9e3e1 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 09b7b2a2b..b28ada610 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 d3576b85b..089f4e615 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
diff --git a/src/statd/Makefile.am b/src/statd/Makefile.am
index 9dd4fa4d7..160495b20 100644
--- a/src/statd/Makefile.am
+++ b/src/statd/Makefile.am
@@ -2,7 +2,7 @@ DISTCLEANFILES = *~ *.d
ACLOCAL_AMFLAGS = -I m4
sbin_PROGRAMS = statd
-statd_SOURCES = statd.c shared.c shared.h journal.c journal_retention.c journal.h
+statd_SOURCES = statd.c shared.c shared.h journal.c journal_retention.c journal.h gpsd.c gpsd.h
statd_CPPFLAGS = -D_DEFAULT_SOURCE -D_GNU_SOURCE
statd_CFLAGS = -W -Wall -Wextra
statd_CFLAGS += $(jansson_CFLAGS) $(libyang_CFLAGS) $(sysrepo_CFLAGS)
diff --git a/src/statd/gpsd.c b/src/statd/gpsd.c
new file mode 100644
index 000000000..770b4dbed
--- /dev/null
+++ b/src/statd/gpsd.c
@@ -0,0 +1,415 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+
+/*
+ * Background GPS monitor for statd.
+ *
+ * Maintains a persistent connection to gpsd (localhost:2947) and caches
+ * GPS device status to /run/gps-status.json. The yanger ietf_hardware
+ * module reads this cache instead of spawning gpspipe, avoiding blocking
+ * the operational datastore.
+ *
+ * Activated on SIGHUP when sysrepo running config contains a hardware
+ * component with class infix-hardware:gps, reconnects automatically
+ * if gpsd restarts.
+ */
+
+#include