Merge pull request #1417 from kernelkit/gps

Gps refactor
This commit is contained in:
Mattias Walström
2026-03-05 11:00:17 +01:00
committed by GitHub
35 changed files with 755 additions and 558 deletions
@@ -8,7 +8,22 @@
};
};
};
/*
From 236317dd4e55512933dcdaec3d0b4a0824f5e5bd Mon Sep 17 00:00:00 2001
From: Shiji Yang <yangshiji66@outlook.com>
Date: Sun, 10 Aug 2025 18:11:01 +0800
Subject: [PATCH] arm64: dts: mediatek: mt7986: increase ATF reserved memory to
256 kiB
The latest Mediatek open-source Trusted Firmware-A project has
reserved 256 KiB for BL2 and BL31. It is better to increase the
reserved memory region in the Linux kernel to protect the data.
https://github.com/openwrt/openwrt/blob/main/target/linux/mediatek/patches-6.12/199-arm64-dts-mediatek-mt7986-increase-ATF-reserved-memo.patch
*/
&secmon_reserved {
reg = <0 0x43000000 0 0x40000>;
};
&cpu_thermal {
/delete-node/ trips;
/delete-node/ cooling-maps;
-3
View File
@@ -41,9 +41,6 @@ BR2_PACKAGE_DBUS_CXX=y
BR2_PACKAGE_DBUS_GLIB=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
-4
View File
@@ -41,9 +41,6 @@ BR2_PACKAGE_DBUS_CXX=y
BR2_PACKAGE_DBUS_GLIB=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
@@ -126,7 +123,6 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu
INFIX_HOME="https://github.com/kernelkit/infix/"
INFIX_DOC="https://www.kernelkit.org/infix/"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_FEATURE_GPS=y
BR2_PACKAGE_CONFD=y
BR2_PACKAGE_NETD=y
BR2_PACKAGE_CONFD_TEST_MODE=y
-3
View File
@@ -43,9 +43,6 @@ BR2_PACKAGE_DBUS_CXX=y
BR2_PACKAGE_DBUS_GLIB=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
-4
View File
@@ -43,9 +43,6 @@ BR2_PACKAGE_DBUS_CXX=y
BR2_PACKAGE_DBUS_GLIB=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
@@ -126,7 +123,6 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu
INFIX_HOME="https://github.com/kernelkit/infix/"
INFIX_DOC="https://www.kernelkit.org/infix/"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_FEATURE_GPS=y
BR2_PACKAGE_CONFD=y
BR2_PACKAGE_NETD=y
BR2_PACKAGE_CONFD_TEST_MODE=y
-3
View File
@@ -52,9 +52,6 @@ BR2_PACKAGE_DBUS_CXX=y
BR2_PACKAGE_DBUS_GLIB=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
-3
View File
@@ -41,9 +41,6 @@ BR2_PACKAGE_DBUS_CXX=y
BR2_PACKAGE_DBUS_GLIB=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
-4
View File
@@ -41,9 +41,6 @@ BR2_PACKAGE_DBUS_CXX=y
BR2_PACKAGE_DBUS_GLIB=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
@@ -125,7 +122,6 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu
INFIX_HOME="https://github.com/kernelkit/infix/"
INFIX_DOC="https://www.kernelkit.org/infix/"
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
BR2_PACKAGE_FEATURE_GPS=y
BR2_PACKAGE_CONFD=y
BR2_PACKAGE_NETD=y
BR2_PACKAGE_CONFD_TEST_MODE=y
+12
View File
@@ -30,6 +30,11 @@ CONFD_CONF_OPTS += --enable-wifi
else
CONFD_CONF_OPTS += --disable-wifi
endif
ifeq ($(BR2_PACKAGE_FEATURE_GPS),y)
CONFD_CONF_OPTS += --enable-gps
else
CONFD_CONF_OPTS += --disable-gps
endif
define CONFD_INSTALL_EXTRA
for fn in confd.conf resolvconf.conf; do \
cp $(CONFD_PKGDIR)/$$fn $(FINIT_D)/available/; \
@@ -80,6 +85,12 @@ define CONFD_INSTALL_YANG_MODULES_WIFI
$(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/wifi.inc
endef
endif
ifeq ($(BR2_PACKAGE_FEATURE_GPS),y)
define CONFD_INSTALL_YANG_MODULES_GPS
$(COMMON_SYSREPO_ENV) \
$(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/gps.inc
endef
endif
# PER_PACKAGE_DIR
# Since the last package in the dependency chain that runs sysrepoctl is confd, we need to
@@ -109,6 +120,7 @@ CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_EXTRA
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_CONTAINERS
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_WIFI
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES_GPS
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_IN_ROMFS
CONFD_TARGET_FINALIZE_HOOKS += CONFD_CLEANUP
+22 -20
View File
@@ -7,59 +7,61 @@ 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"
# ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="gps%n"
# 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"
ATTRS{idVendor}=="0557", ATTRS{idProduct}=="2008", SYMLINK+="gps%n"
# 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"
ATTRS{idVendor}=="067b", ATTRS{idProduct}=="aaa0", SYMLINK+="gps%n"
# 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"
# ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="gps%n"
# 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"
ATTRS{idVendor}=="1163", ATTRS{idProduct}=="0100", SYMLINK+="gps%n"
# Cypress M8/CY7C64013 (DeLorme LT-40)
ATTRS{idVendor}=="1163", ATTRS{idProduct}=="0200", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
ATTRS{idVendor}=="1163", ATTRS{idProduct}=="0200", SYMLINK+="gps%n"
# 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"
ATTRS{idVendor}=="091e", ATTRS{idProduct}=="0003", SYMLINK+="gps%n"
# 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"
#ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="gps%n"
# 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"
#ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea71", SYMLINK+="gps%n"
# 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"
ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a5", SYMLINK+="gps%n"
# 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"
ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a6", SYMLINK+="gps%n"
# 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"
ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a7", SYMLINK+="gps%n"
# 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"
ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a8", SYMLINK+="gps%n"
# 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"
ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a9", SYMLINK+="gps%n"
# 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"
ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="3329", SYMLINK+="gps%n"
# 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"
ATTRS{interface}=="Telit Wireless Module Port", ATTRS{bInterfaceNumber}=="06", SYMLINK+="gps%n"
# u-blox AG, u-blox 8 (tested with u-blox8 GNSS Mouse Receiver / GR-801) [linux module: cdc_acm]
# 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"
#ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="gps%n"
LABEL="gpsd_rules_end"
# Virtio serial GPS ports (QEMU emulated GPS for testing)
SUBSYSTEM=="virtio-ports", ATTR{name}=="gps0", SYMLINK+="gps0"
SUBSYSTEM=="virtio-ports", ATTR{name}=="gps1", SYMLINK+="gps1"
-3
View File
@@ -1,9 +1,6 @@
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
+7
View File
@@ -6,13 +6,20 @@
FEATURE_GPS_PACKAGE_VERSION = 1.0
FEATURE_GPS_PACKAGE_LICENSE = MIT
FEATURE_GPS_DEPENDENCIES += gpsd
define FEATURE_GPS_INSTALL_TARGET_CMDS
$(INSTALL) -D -m 0644 $(FEATURE_GPS_PKGDIR)/gpsd.default \
$(TARGET_DIR)/etc/default/gpsd
endef
# Install custom udev rules as a gpsd post-install hook to ensure
# they are not overwritten by gpsd during parallel or incremental builds.
define FEATURE_GPS_INSTALL_UDEV_RULES
$(INSTALL) -D -m 0644 $(FEATURE_GPS_PKGDIR)/25-gpsd.rules \
$(TARGET_DIR)/usr/lib/udev/rules.d/25-gpsd.rules
endef
GPSD_POST_INSTALL_TARGET_HOOKS += FEATURE_GPS_INSTALL_UDEV_RULES
define FEATURE_GPS_LINUX_CONFIG_FIXUPS
$(call KCONFIG_SET_OPT,CONFIG_USB_ACM,m)
@@ -0,0 +1,41 @@
From 9ba3bc688c45a0c9e761c6c8bd0be83d6570c9dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
Date: Fri, 27 Feb 2026 16:58:12 +0100
Subject: [PATCH] Always log to syslog
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
gpsd/libgpsd_core.c | 13 ++-----------
1 file changed, 2 insertions(+), 11 deletions(-)
diff --git a/gpsd/libgpsd_core.c b/gpsd/libgpsd_core.c
index 982f822b8..c53c64838 100644
--- a/gpsd/libgpsd_core.c
+++ b/gpsd/libgpsd_core.c
@@ -180,17 +180,8 @@ static void gpsd_vlog(const int errlevel,
// this was crazy expensive, just fix the bad log calls
// gps_visibilize(outbuf, outlen, buf, strlen(buf));
- if (getpid() == getsid(getpid())) {
- // I think this calls syslog() only when daemonized
- syslog(level, "%s", outbuf);
- } else if (NULL != errout->report) {
- // we are a thread, use report()?
- // FIXME: is POSIX syslog() thread safe?
- errout->report(outbuf);
- } else {
- // foreground, use stderr?
- (void)fputs(outbuf, stderr);
- }
+ // always log to syslog, even with -N
+ syslog(level, "%s", outbuf);
gpsd_release_reporting_lock();
#endif // !SQUELCH_ENABLE
}
--
2.43.0
+8
View File
@@ -46,6 +46,10 @@ AC_ARG_ENABLE(wifi,
AS_HELP_STRING([--enable-wifi], [Enable support for Wi-Fi]),,[
enable_wifi=no])
AC_ARG_ENABLE(gps,
AS_HELP_STRING([--enable-gps], [Enable support for GPS receivers]),,[
enable_gps=no])
AC_ARG_WITH(login-shell,
AS_HELP_STRING([--with-login-shell=shell], [Login shell for new users, default: /bin/false]),
[login_shell=$withval], [login_shell=yes])
@@ -60,6 +64,9 @@ AS_IF([test "x$enable_containers" = "xyes"], [
AS_IF([test "x$enable_wifi" = "xyes"], [
AC_DEFINE(HAVE_WIFI, 1, [Built with Wi-Fi support])])
AS_IF([test "x$enable_gps" = "xyes"], [
AC_DEFINE(HAVE_GPS, 1, [Built with GPS receiver support])])
AS_IF([test "x$with_login_shell" != "xno"], [
AS_IF([test "x$login_shell" = "xyes"], [login_shell=/bin/false])
AC_DEFINE_UNQUOTED(LOGIN_SHELL, "$login_shell", [Default: /bin/false])],[
@@ -131,6 +138,7 @@ cat <<EOF
Optional features:
Container support ....: $enable_containers
Wi-Fi support ........: $enable_wifi
GPS support ..........: $enable_gps
Login shell ..........: $login_shell
Default crypt algo ...: $crypt
+71 -4
View File
@@ -17,6 +17,9 @@
#define XPATH_BASE_ "/ietf-hardware:hardware"
#define HOSTAPD_CONF "/etc/hostapd-%s.conf"
#define HOSTAPD_CONF_NEXT HOSTAPD_CONF"+"
#define GPSD_CONF "/etc/finit.d/available/gpsd.conf"
#define GPSD_CONF_NEXT GPSD_CONF"+"
#define GPSD_MAX_DEVICES 4
static int dir_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
{
@@ -593,6 +596,7 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
{
struct lyd_node *difs = NULL, *dif = NULL;
int rc = SR_ERR_OK;
int gps_changed = 0;
if (!lydx_find_xpathf(diff, XPATH_BASE_))
return SR_ERR_OK;
@@ -707,13 +711,76 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
wifi_iface_count = 0;
}
} else if (!strcmp(class, "infix-hardware:gps")) {
if (event != SR_EV_DONE)
continue;
systemf("initctl -nbq touch statd");
gps_changed = 1;
}
}
if (gps_changed) {
const char *gps_names[GPSD_MAX_DEVICES] = { NULL };
struct lyd_node *cifs, *comp;
int num_gps = 0;
switch (event) {
case SR_EV_UPDATE:
case SR_EV_ENABLED:
case SR_EV_RPC:
break;
case SR_EV_ABORT:
unlink(GPSD_CONF_NEXT);
break;
case SR_EV_CHANGE:
cifs = lydx_get_descendant(config, "hardware", "component", NULL);
LYX_LIST_FOR_EACH(cifs, comp, "component") {
const char *cls = lydx_get_cattr(comp, "class");
if (strcmp(cls, "infix-hardware:gps"))
continue;
if (num_gps < GPSD_MAX_DEVICES)
gps_names[num_gps] = lydx_get_cattr(comp, "name");
else
return SR_ERR_INTERNAL;
num_gps++;
}
if (num_gps > 0) {
FILE *fp;
fp = fopen(GPSD_CONF_NEXT, "w");
if (!fp) {
ERROR("Could not open " GPSD_CONF_NEXT);
return SR_ERR_INTERNAL;
}
int i;
fprintf(fp, "# Generated by confd, do not edit.\n");
fprintf(fp, "service");
fprintf(fp, " <!");
for (i = 0; i < num_gps && i < GPSD_MAX_DEVICES; i++)
fprintf(fp, "%sdev/%s", i ? "," : "", gps_names[i]);
fprintf(fp, ">");
fprintf(fp, " pid:!/run/gpsd.pid");
fprintf(fp, " env:-/etc/default/gpsd \\\n");
fprintf(fp, "\t[2345] gpsd -n");
for (i = 0; i < num_gps && i < GPSD_MAX_DEVICES; i++)
fprintf(fp, " /dev/%s", gps_names[i]);
fprintf(fp, " -P /run/gpsd.pid -- GPS Daemon\n");
fclose(fp);
}
break;
case SR_EV_DONE:
if (fexist(GPSD_CONF_NEXT)) {
unlink(GPSD_CONF);
rename(GPSD_CONF_NEXT, GPSD_CONF);
systemf("initctl -nbq enable gpsd");
} else {
unlink(GPSD_CONF);
systemf("initctl -nbq disable gpsd");
}
break;
}
}
err:
return rc;
+11
View File
@@ -45,6 +45,13 @@ module infix-hardware {
description "Initial";
reference "internal";
}
feature wifi {
description "WiFi support is an optional build-time feature in Infix.";
}
feature gps {
description "GPS support is an optional build-time feature in Infix.";
}
typedef country-code {
type string {
@@ -104,11 +111,13 @@ module infix-hardware {
description "This identity is used to a VPD memory on the device.";
}
identity wifi {
if-feature wifi;
base iahw:hardware-class;
description "This identity is used to describe a WiFi radio/PHY";
}
identity gps {
if-feature gps;
base iahw:hardware-class;
description "GPS/GNSS receiver for time synchronization";
}
@@ -211,6 +220,7 @@ module infix-hardware {
*/
container wifi-radio {
if-feature wifi;
when "derived-from-or-self(../iehw:class, 'ih:wifi')";
presence "WiFi radio configuration";
description
@@ -534,6 +544,7 @@ module infix-hardware {
*/
container gps-receiver {
if-feature gps;
when "derived-from-or-self(../iehw:class, 'ih:gps')";
presence "GPS receiver configuration";
description
+3
View File
@@ -0,0 +1,3 @@
MODULES=(
"infix-hardware -e gps"
)
+1
View File
@@ -1,4 +1,5 @@
MODULES=(
"infix-interfaces -e wifi"
"infix-hardware -e wifi"
"infix-if-type -e wifi"
)
+1 -1
View File
@@ -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 gpsd.c gpsd.h
statd_SOURCES = statd.c shared.c shared.h journal.c journal_retention.c journal.h
statd_CPPFLAGS = -D_DEFAULT_SOURCE -D_GNU_SOURCE
statd_CFLAGS = -W -Wall -Wextra
statd_CFLAGS += $(jansson_CFLAGS) $(libyang_CFLAGS) $(sysrepo_CFLAGS)
-415
View File
@@ -1,415 +0,0 @@
/* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ev.h>
#include <jansson.h>
#include <srx/common.h>
#include "gpsd.h"
static int gps_device_present(void)
{
struct stat st;
int i;
for (i = 0; i < 4; i++) {
char path[32];
snprintf(path, sizeof(path), "/dev/gps%d", i);
if (stat(path, &st) == 0)
return 1;
}
return 0;
}
static void cache_write(struct gpsd_ctx *ctx)
{
char tmp[] = GPSD_CACHE_FILE ".XXXXXX";
int fd;
if (!ctx->cache)
return;
fd = mkstemp(tmp);
if (fd < 0) {
ERROR("gpsd: failed to create temp file: %s", strerror(errno));
return;
}
if (json_dumpfd(ctx->cache, fd, JSON_INDENT(2)) < 0) {
ERROR("gpsd: failed to write cache");
close(fd);
unlink(tmp);
return;
}
close(fd);
if (rename(tmp, GPSD_CACHE_FILE) < 0) {
ERROR("gpsd: failed to rename cache: %s", strerror(errno));
unlink(tmp);
}
}
static void handle_devices(struct gpsd_ctx *ctx, json_t *msg)
{
json_t *devices = json_object_get(msg, "devices");
size_t i;
if (!json_is_array(devices))
return;
for (i = 0; i < json_array_size(devices); i++) {
json_t *dev = json_array_get(devices, i);
const char *path = json_string_value(json_object_get(dev, "path"));
json_t *entry, *driver, *activated;
if (!path)
continue;
entry = json_object_get(ctx->cache, path);
if (!entry) {
entry = json_object();
json_object_set_new(ctx->cache, path, entry);
}
driver = json_object_get(dev, "driver");
if (json_is_string(driver))
json_object_set(entry, "driver", driver);
/* activated is a timestamp string when active, absent when not */
activated = json_object_get(dev, "activated");
if (activated && json_is_string(activated) &&
strlen(json_string_value(activated)) > 0)
json_object_set_new(entry, "activated", json_true());
else
json_object_set_new(entry, "activated", json_false());
}
}
static void handle_tpv(struct gpsd_ctx *ctx, json_t *msg)
{
const char *path = json_string_value(json_object_get(msg, "device"));
json_t *entry, *val;
if (!path)
return;
entry = json_object_get(ctx->cache, path);
if (!entry) {
entry = json_object();
json_object_set_new(ctx->cache, path, entry);
}
/* Fix mode: 0=unknown, 1=none, 2=2D, 3=3D */
val = json_object_get(msg, "mode");
if (json_is_integer(val))
json_object_set(entry, "mode", val);
val = json_object_get(msg, "lat");
if (json_is_number(val))
json_object_set(entry, "lat", val);
val = json_object_get(msg, "lon");
if (json_is_number(val))
json_object_set(entry, "lon", val);
val = json_object_get(msg, "altHAE");
if (json_is_number(val))
json_object_set(entry, "altHAE", val);
}
static void handle_sky(struct gpsd_ctx *ctx, json_t *msg)
{
const char *path = json_string_value(json_object_get(msg, "device"));
json_t *entry, *sats;
size_t i, visible, used;
if (!path)
return;
entry = json_object_get(ctx->cache, path);
if (!entry) {
entry = json_object();
json_object_set_new(ctx->cache, path, entry);
}
sats = json_object_get(msg, "satellites");
if (!json_is_array(sats))
return;
visible = json_array_size(sats);
used = 0;
for (i = 0; i < visible; i++) {
json_t *sat = json_array_get(sats, i);
if (json_is_true(json_object_get(sat, "used")))
used++;
}
json_object_set_new(entry, "satellites_visible", json_integer(visible));
json_object_set_new(entry, "satellites_used", json_integer(used));
}
static void process_line(struct gpsd_ctx *ctx, const char *line)
{
json_error_t err;
json_t *msg;
const char *cls;
msg = json_loads(line, 0, &err);
if (!msg)
return;
cls = json_string_value(json_object_get(msg, "class"));
if (!cls)
goto out;
if (strcmp(cls, "DEVICES") == 0)
handle_devices(ctx, msg);
else if (strcmp(cls, "TPV") == 0)
handle_tpv(ctx, msg);
else if (strcmp(cls, "SKY") == 0)
handle_sky(ctx, msg);
cache_write(ctx);
out:
json_decref(msg);
}
static void gpsd_disconnect(struct gpsd_ctx *ctx)
{
if (!ctx->connected)
return;
ev_io_stop(ctx->loop, &ctx->sock_watcher);
close(ctx->sock_fd);
ctx->sock_fd = -1;
ctx->connected = 0;
ctx->buf_used = 0;
DEBUG("gpsd: disconnected");
}
static void sock_read_cb(struct ev_loop *, ev_io *w, int)
{
struct gpsd_ctx *ctx = (struct gpsd_ctx *)w->data;
char *start, *nl;
ssize_t n;
n = read(ctx->sock_fd, ctx->buf + ctx->buf_used,
sizeof(ctx->buf) - ctx->buf_used - 1);
if (n <= 0) {
if (n < 0 && (errno == EAGAIN || errno == EINTR))
return;
DEBUG("gpsd: connection lost (%s)", n == 0 ? "EOF" : strerror(errno));
gpsd_disconnect(ctx);
return;
}
ctx->buf_used += n;
ctx->buf[ctx->buf_used] = '\0';
/* Process complete lines (gpsd sends one JSON object per line) */
start = ctx->buf;
while ((nl = strchr(start, '\n')) != NULL) {
*nl = '\0';
if (nl > start)
process_line(ctx, start);
start = nl + 1;
}
/* Shift remaining partial line to beginning of buffer */
if (start != ctx->buf) {
ctx->buf_used -= (start - ctx->buf);
memmove(ctx->buf, start, ctx->buf_used);
}
/* Buffer overflow protection */
if (ctx->buf_used >= sizeof(ctx->buf) - 1) {
ERROR("gpsd: read buffer overflow, resetting");
ctx->buf_used = 0;
}
}
static int gpsd_connect(struct gpsd_ctx *ctx)
{
static const char watch_cmd[] = "?WATCH={\"enable\":true,\"json\":true};\n";
struct sockaddr_in addr;
int fd, flags;
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
DEBUG("gpsd: socket(): %s", strerror(errno));
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(GPSD_PORT);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
DEBUG("gpsd: connect(): %s", strerror(errno));
close(fd);
return -1;
}
/* Enable JSON watch mode (socket still blocking, localhost = instant) */
if (write(fd, watch_cmd, strlen(watch_cmd)) < 0) {
ERROR("gpsd: failed to send WATCH command: %s", strerror(errno));
close(fd);
return -1;
}
/* Switch to non-blocking for ev_io */
flags = fcntl(fd, F_GETFL, 0);
if (flags >= 0)
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
/* Clear stale cache data from previous connection */
json_object_clear(ctx->cache);
ctx->sock_fd = fd;
ctx->connected = 1;
ctx->buf_used = 0;
ev_io_init(&ctx->sock_watcher, sock_read_cb, fd, EV_READ);
ctx->sock_watcher.data = ctx;
ev_io_start(ctx->loop, &ctx->sock_watcher);
INFO("gpsd: connected");
return 0;
}
static void check_timer_cb(struct ev_loop *, ev_timer *w, int)
{
struct gpsd_ctx *ctx = (struct gpsd_ctx *)w->data;
if (ctx->connected)
return;
if (!gps_device_present()) {
unlink(GPSD_CACHE_FILE);
return;
}
gpsd_connect(ctx);
}
/*
* Check sysrepo running config for hardware components with class
* infix-hardware:gps. Returns 1 if at least one is found.
*/
static int has_gps_component(sr_conn_ctx_t *conn)
{
sr_session_ctx_t *ses;
sr_val_t *vals = NULL;
size_t cnt = 0;
int found;
if (sr_session_start(conn, SR_DS_RUNNING, &ses))
return 0;
sr_get_items(ses,
"/ietf-hardware:hardware/component[class='infix-hardware:gps']/name",
0, 0, &vals, &cnt);
found = cnt > 0;
sr_free_values(vals, cnt);
sr_session_stop(ses);
return found;
}
static void gpsd_activate(struct gpsd_ctx *ctx)
{
if (ctx->active)
return;
ctx->active = 1;
ev_timer_start(ctx->loop, &ctx->check_timer);
if (gps_device_present())
gpsd_connect(ctx);
INFO("gpsd: GPS monitoring activated");
}
static void gpsd_deactivate(struct gpsd_ctx *ctx)
{
if (!ctx->active)
return;
ctx->active = 0;
gpsd_disconnect(ctx);
ev_timer_stop(ctx->loop, &ctx->check_timer);
json_object_clear(ctx->cache);
unlink(GPSD_CACHE_FILE);
INFO("gpsd: GPS monitoring deactivated");
}
void gpsd_reload(struct gpsd_ctx *ctx)
{
if (has_gps_component(ctx->sr_conn))
gpsd_activate(ctx);
else
gpsd_deactivate(ctx);
}
int gpsd_init(struct gpsd_ctx *ctx, struct ev_loop *loop, sr_conn_ctx_t *conn)
{
memset(ctx, 0, sizeof(*ctx));
ctx->loop = loop;
ctx->sock_fd = -1;
ctx->sr_conn = conn;
ctx->cache = json_object();
if (!ctx->cache) {
ERROR("gpsd: failed to create cache object");
return -1;
}
ev_timer_init(&ctx->check_timer, check_timer_cb,
GPSD_CHECK_INTERVAL, GPSD_CHECK_INTERVAL);
ctx->check_timer.data = ctx;
/* Timer not started here -- gpsd_reload() activates when needed */
INFO("gpsd: GPS monitor initialized");
return 0;
}
void gpsd_exit(struct gpsd_ctx *ctx)
{
gpsd_deactivate(ctx);
if (ctx->cache) {
json_decref(ctx->cache);
ctx->cache = NULL;
}
INFO("gpsd: GPS monitor stopped");
}
-32
View File
@@ -1,32 +0,0 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#ifndef STATD_GPSD_H_
#define STATD_GPSD_H_
#include <ev.h>
#include <jansson.h>
#include <sysrepo.h>
#define GPSD_CACHE_FILE "/run/gps-status.json"
#define GPSD_PORT 2947
#define GPSD_CHECK_INTERVAL 10.0 /* Seconds between device/connection checks */
#define GPSD_READ_BUFSZ 4096
struct gpsd_ctx {
struct ev_loop *loop;
ev_timer check_timer; /* Periodic check for GPS devices / reconnect */
ev_io sock_watcher; /* Read watcher on gpsd socket */
int sock_fd; /* TCP socket to gpsd */
char buf[GPSD_READ_BUFSZ]; /* Line accumulation buffer */
size_t buf_used;
json_t *cache; /* Accumulated GPS data, keyed by device path */
int connected;
sr_conn_ctx_t *sr_conn; /* Sysrepo connection for config queries */
int active; /* GPS monitoring enabled (config has gps component) */
};
int gpsd_init(struct gpsd_ctx *ctx, struct ev_loop *loop, sr_conn_ctx_t *conn);
void gpsd_reload(struct gpsd_ctx *ctx);
void gpsd_exit(struct gpsd_ctx *ctx);
#endif
+19 -13
View File
@@ -2207,24 +2207,27 @@ def show_hardware(json):
if gps_receivers:
Decore.title("GPS/GNSS Receivers", width)
gps_table = SimpleTable([
Column('NAME'),
Column('DEVICE'),
Column('STATUS'),
Column('FIX'),
Column('SATELLITES'),
Column('POSITION', flexible=True),
Column('PPS')
], min_width=width)
for component in gps_receivers:
gps = component.get("infix-hardware:gps-receiver", {})
name = component.get("name", "unknown")
device = gps.get("device", "N/A")
driver = gps.get("driver", "Unknown")
fix = gps.get("fix-mode", "none")
activated = gps.get("activated", False)
print(f"{'Name':<20}: {name}")
print(f"{'Device':<20}: {device}")
print(f"{'Driver':<20}: {driver}")
print(f"{'Status':<20}: {'Active' if activated else 'Inactive'}")
print(f"{'Fix':<20}: {fix.upper()}")
status = "Active" if activated else "Inactive"
fix = gps.get("fix-mode", "none").upper()
sat_vis = gps.get("satellites-visible")
sat_used = gps.get("satellites-used")
if sat_vis is not None:
print(f"{'Satellites':<20}: {sat_used}/{sat_vis} (used/visible)")
satellites = f"{sat_used}/{sat_vis}" if sat_vis is not None else "N/A"
lat = gps.get("latitude")
lon = gps.get("longitude")
@@ -2237,11 +2240,14 @@ def show_hardware(json):
pos = f"{abs(lat_f):.6f}{lat_dir} {abs(lon_f):.6f}{lon_dir}"
if alt is not None:
pos += f" {alt}m"
print(f"{'Position':<20}: {pos}")
else:
pos = "N/A"
pps = gps.get("pps-available", False)
print(f"{'PPS':<20}: {'Available' if pps else 'Not available'}")
pps = "Yes" if gps.get("pps-available", False) else "No"
gps_table.row(name, device, status, fix, satellites, pos, pps)
gps_table.print()
if usb_ports:
Decore.title("USB Ports", width)
+104 -24
View File
@@ -667,13 +667,53 @@ def wifi_radio_components():
return components
def _gpsd_poll():
"""Query gpsd for current state via ?POLL command.
Returns a dict keyed by device path with tpv/sky data merged,
or empty dict on failure.
"""
import json
import socket
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(0.5)
sock.connect(("127.0.0.1", 2947))
# Read VERSION banner
sock.recv(4096)
# Send POLL request
sock.sendall(b'?WATCH={"enable":true,"json":true};\n?POLL;\n')
# Read until we get the POLL response
buf = b""
for _ in range(5):
chunk = sock.recv(4096)
if not chunk:
break
buf += chunk
for line in buf.decode(errors="replace").splitlines():
try:
msg = json.loads(line)
except (json.JSONDecodeError, ValueError):
continue
if msg.get("class") == "POLL":
sock.close()
return msg
sock.close()
except (OSError, socket.error):
pass
return {}
def gps_receiver_components():
"""Discover GPS/GNSS receivers and populate operational state.
GPS devices are discovered via /dev/gps* symlinks (created by udev rules).
Status is read from /run/gps-status.json, a cache maintained by statd's
background GPS monitor (gpsd.c) which streams data from gpsd without
blocking the operational datastore.
GPS devices are discovered via /dev/gps* symlinks (created by udev
rules). Status is queried live from gpsd via the ?POLL command.
"""
components = []
@@ -683,7 +723,6 @@ def gps_receiver_components():
dev_path = f"/dev/gps{i}"
if not HOST.exists(dev_path):
continue
# Resolve symlink to actual device (for matching gpsd cache keys)
actual = HOST.run(("readlink", "-f", dev_path), default="").strip()
gps_devices[actual] = {
"name": f"gps{i}",
@@ -693,12 +732,24 @@ def gps_receiver_components():
if not gps_devices:
return components
# Read cached GPS status from statd background monitor
cache = HOST.read_json("/run/gps-status.json", {})
# Query gpsd directly for current state
poll = _gpsd_poll()
active = poll.get("active", 0)
# Index TPV and SKY responses by device path
tpv_by_dev = {}
for tpv in poll.get("tpv", []):
dev = tpv.get("device", "")
tpv_by_dev[dev] = tpv
sky_by_dev = {}
for sky in poll.get("sky", []):
dev = sky.get("device", "")
sky_by_dev[dev] = sky
# Build hardware components for each discovered GPS device
for actual_path, dev in gps_devices.items():
name = dev["name"]
symlink = dev["symlink"]
component = {
"name": name,
"class": "infix-hardware:gps",
@@ -706,16 +757,22 @@ def gps_receiver_components():
}
gps_data = {}
gps_data["device"] = dev["symlink"]
gps_data["device"] = symlink
# Look up cached status by actual device path
info = cache.get(actual_path, {})
# Match by resolved path or symlink (fallback to single report)
tpv = tpv_by_dev.get(actual_path, tpv_by_dev.get(symlink, {}))
sky = sky_by_dev.get(actual_path, sky_by_dev.get(symlink, {}))
if not tpv and len(tpv_by_dev) == 1:
tpv = next(iter(tpv_by_dev.values()))
if not sky and len(sky_by_dev) == 1:
sky = next(iter(sky_by_dev.values()))
if info.get("driver"):
gps_data["driver"] = info["driver"]
gps_data["activated"] = bool(info.get("activated"))
gps_data["activated"] = active > 0 and bool(tpv)
mode = info.get("mode", 0)
if tpv.get("driver"):
gps_data["driver"] = tpv["driver"]
mode = tpv.get("mode", 0)
if mode == 2:
gps_data["fix-mode"] = "2d"
elif mode == 3:
@@ -723,16 +780,39 @@ def gps_receiver_components():
else:
gps_data["fix-mode"] = "none"
if "lat" in info:
gps_data["latitude"] = f"{float(info['lat']):.6f}"
if "lon" in info:
gps_data["longitude"] = f"{float(info['lon']):.6f}"
if "altHAE" in info:
gps_data["altitude"] = f"{float(info['altHAE']):.1f}"
if "lat" in tpv:
gps_data["latitude"] = f"{float(tpv['lat']):.6f}"
if "lon" in tpv:
gps_data["longitude"] = f"{float(tpv['lon']):.6f}"
if "altHAE" in tpv:
gps_data["altitude"] = f"{float(tpv['altHAE']):.1f}"
if "satellites_visible" in info:
gps_data["satellites-visible"] = int(info["satellites_visible"])
gps_data["satellites-used"] = int(info.get("satellites_used", 0))
sat_vis = 0
sat_used = 0
sats = sky.get("satellites", [])
if isinstance(sats, list):
sat_vis = len(sats)
sat_used = sum(1 for s in sats if s.get("used"))
# Fallback for gpsd variants that report aggregated counters only
# (common in POLL output for some setups).
if not sat_vis:
sat_vis = int(sky.get("nSat", sky.get("satellites_visible", 0)) or 0)
if not sat_used:
sat_used = int(sky.get("uSat", sky.get("satellites_used", 0)) or 0)
# Last fallback to TPV keys when SKY is absent.
if not sat_vis:
sat_vis = int(tpv.get("nSat", tpv.get("satellites_visible", 0)) or 0)
if not sat_used:
sat_used = int(tpv.get("uSat", tpv.get("satellites_used", 0)) or 0)
if sat_used > sat_vis:
sat_vis = sat_used
gps_data["satellites-visible"] = sat_vis
gps_data["satellites-used"] = sat_used
# Check for PPS device availability
pps_path = f"/dev/pps{name.replace('gps', '')}"
+1 -19
View File
@@ -30,7 +30,6 @@
#include "shared.h"
#include "journal.h"
#include "gpsd.h"
/* New kernel feature, not in sys/mman.h yet */
#ifndef MFD_NOEXEC_SEAL
@@ -70,7 +69,6 @@ struct statd {
sr_conn_ctx_t *sr_conn; /* Connection (owns YANG context) */
struct ev_loop *ev_loop;
struct journal_ctx journal; /* Journal thread context */
struct gpsd_ctx gpsd; /* GPS monitor context */
};
static int ly_add_yanger_data(const struct ly_ctx *ctx, struct lyd_node **parent,
@@ -352,13 +350,6 @@ static void sigusr1_cb(struct ev_loop *, struct ev_signal *, int)
debug ^= 1;
}
static void sighup_cb(struct ev_loop *, struct ev_signal *w, int)
{
struct statd *statd = (struct statd *)w->data;
INFO("SIGHUP received, reloading GPS config");
gpsd_reload(&statd->gpsd);
}
static void sr_event_cb(struct ev_loop *, struct ev_io *w, int)
{
@@ -462,7 +453,7 @@ static int subscribe_to_all(struct statd *statd)
int main(int argc, char *argv[])
{
struct ev_signal sigint_watcher, sigusr1_watcher, sighup_watcher;
struct ev_signal sigint_watcher, sigusr1_watcher;
int log_opts = LOG_PID | LOG_NDELAY;
struct statd statd = {};
const char *env;
@@ -523,10 +514,6 @@ int main(int argc, char *argv[])
sigusr1_watcher.data = &statd;
ev_signal_start(statd.ev_loop, &sigusr1_watcher);
ev_signal_init(&sighup_watcher, sighup_cb, SIGHUP);
sighup_watcher.data = &statd;
ev_signal_start(statd.ev_loop, &sighup_watcher);
err = journal_start(&statd.journal, statd.sr_query_ses);
if (err) {
sr_session_stop(statd.sr_query_ses);
@@ -535,10 +522,6 @@ int main(int argc, char *argv[])
return EXIT_FAILURE;
}
if (gpsd_init(&statd.gpsd, statd.ev_loop, statd.sr_conn))
INFO("GPS monitoring not available");
gpsd_reload(&statd.gpsd);
/* Signal readiness to Finit */
pidfile(NULL);
@@ -548,7 +531,6 @@ int main(int argc, char *argv[])
/* We should never get here during normal operation */
INFO("Status daemon shutting down");
gpsd_exit(&statd.gpsd);
journal_stop(&statd.journal);
unsub_to_all(&statd);
+3
View File
@@ -7,3 +7,6 @@
- name: Watchdog reset on system lockup
case: watchdog/test.py
- name: GPS receiver basic test
case: gps_simple/test.py
+1
View File
@@ -0,0 +1 @@
test.adoc
+29
View File
@@ -0,0 +1,29 @@
=== GPS receiver basic test
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/hardware/gps_simple]
==== Description
Verify that a simulated GPS receiver is detected and reports a valid
fix via the ietf-hardware operational datastore.
The test injects NMEA sentences through a QEMU pipe chardev FIFO,
which appears as a virtio serial port inside the guest.
==== Topology
image::topology.svg[GPS receiver basic test topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to target DUT
. Configure GPS hardware component
. Verify GPS is activated
. Verify GPS has a fix
. Verify the position is near the coordinates you test with
. Save the configuration to startup configuration and reboot
. Verify GPS is activated
. Verify GPS has a fix
. Verify the position is near the coordinates you test with
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""GPS receiver basic test
Verify that two simulated GPS receivers are detected and report valid
fixes via the ietf-hardware operational datastore.
The test injects NMEA sentences through QEMU pipe chardev FIFOs,
which appear as virtio serial ports inside the guest.
"""
import infamy
import infamy.gps as gps
from infamy.util import until, wait_boot
# Fun facts: The top of mount everest
test_lat = 27.9881
test_lon = 86.9250
test_alt = 8848.86
def _near(a, b, tol):
return abs(a - b) <= tol
def verify_position(target, name="gps0"):
state = gps.get_gps_state(target, name)
try:
lat = float(state["latitude"])
lon = float(state["longitude"])
alt = float(state["altitude"])
except (KeyError, TypeError, ValueError):
test.fail()
if not _near(lat, test_lat, 0.01):
test.fail()
if not _near(lon, test_lon, 0.01):
test.fail()
if not _near(alt, test_alt, 100):
test.fail()
try:
sat_used = int(state["satellites-used"])
except (KeyError, TypeError, ValueError):
test.fail()
if sat_used != 8:
test.fail()
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUT"):
env = infamy.Env()
target = env.attach("target", "mgmt")
phys_name = env.ltop.mapping["target"][None]
if not target.has_feature("infix-hardware", "gps"):
test.skip()
# These are hacks, will only work on virtual devices
pipe0 = f"/tmp/{phys_name}-gps"
pipe1 = f"/tmp/{phys_name}-gps1"
with gps.NMEAGenerator(pipe0, lat=test_lat, lon=test_lon, alt=test_alt), \
gps.NMEAGenerator(pipe1, lat=test_lat, lon=test_lon, alt=test_alt):
with test.step("Configure GPS hardware components"):
target.put_config_dicts({"ietf-hardware": {
"hardware": {
"component": [
{
"name": "gps0",
"class": "infix-hardware:gps",
"infix-hardware:gps-receiver": {}
},
{
"name": "gps1",
"class": "infix-hardware:gps",
"infix-hardware:gps-receiver": {}
}
]
}
}})
with test.step("Verify both GPS receivers are activated"):
until(lambda: gps.is_activated(target, "gps0"), attempts=500)
until(lambda: gps.is_activated(target, "gps1"), attempts=500)
with test.step("Verify both GPS receivers have a fix"):
until(lambda: gps.has_fix(target, "gps0"), attempts=60)
until(lambda: gps.has_fix(target, "gps1"), attempts=60)
with test.step("Verify gps0 position is near the coordinates"):
verify_position(target, "gps0")
with test.step("Verify gps1 position is near the coordinates"):
verify_position(target, "gps1")
with test.step("Save the configuration to startup configuration and reboot"):
target.startup_override()
target.copy("running", "startup")
target.reboot()
if not wait_boot(target, env):
test.fail()
target = env.attach("target", "mgmt", test_reset=False)
with test.step("Verify both GPS receivers are activated"):
until(lambda: gps.is_activated(target, "gps0"), attempts=500)
until(lambda: gps.is_activated(target, "gps1"), attempts=500)
with test.step("Verify both GPS receivers have a fix"):
until(lambda: gps.has_fix(target, "gps0"), attempts=60)
until(lambda: gps.has_fix(target, "gps1"), attempts=60)
with test.step("Verify gps0 position is near the coordinates"):
verify_position(target, "gps0")
with test.step("Verify gps1 position is near the coordinates"):
verify_position(target, "gps1")
test.succeed()
@@ -0,0 +1,22 @@
graph "1x1" {
layout="neato";
overlap="false";
esep="+80";
node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
host [
label="host | { <mgmt> mgmt }",
pos="0,12!",
requires="controller",
];
target [
label="{ <mgmt> mgmt } | target",
pos="10,12!",
requires="infix gps",
];
host:mgmt -- target:mgmt [requires="mgmt", color="lightgray"]
}
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: 1x1 Pages: 1 -->
<svg width="424pt" height="45pt"
viewBox="0.00 0.00 424.03 45.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 41)">
<title>1x1</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-41 420.03,-41 420.03,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-0.5 0,-36.5 100,-36.5 100,-0.5 0,-0.5"/>
<text text-anchor="middle" x="25" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="50,-0.5 50,-36.5 "/>
<text text-anchor="middle" x="75" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
</g>
<!-- target -->
<g id="node2" class="node">
<title>target</title>
<polygon fill="none" stroke="black" points="300.03,-0.5 300.03,-36.5 416.03,-36.5 416.03,-0.5 300.03,-0.5"/>
<text text-anchor="middle" x="325.03" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="350.03,-0.5 350.03,-36.5 "/>
<text text-anchor="middle" x="383.03" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
</g>
<!-- host&#45;&#45;target -->
<g id="edge1" class="edge">
<title>host:mgmt&#45;&#45;target:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M100,-18.5C100,-18.5 300.03,-18.5 300.03,-18.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+210
View File
@@ -0,0 +1,210 @@
"""GPS/NMEA test helpers
Provides an NMEAGenerator class that writes NMEA sentences to a QEMU
pipe chardev FIFO, simulating a GPS receiver. Also provides helpers
for querying GPS operational state via YANG.
"""
import os
import errno
import threading
import time
import pynmea2
class NMEAGenerator:
"""Write NMEA sentences to a QEMU pipe chardev FIFO.
Sends a full cycle of NMEA sentences (like a real u-blox receiver)
continuously at 1 Hz in a background thread.
The pipe_path should be the base path (without .in/.out suffix),
matching the QEMU ``-chardev pipe,path=...`` argument.
Usage::
with NMEAGenerator("/tmp/node-gps") as nmea:
# NMEA data is being sent in background
time.sleep(10)
"""
def __init__(self, pipe_path, lat=48.1173, lon=11.5167, alt=545.4):
self.pipe_path = pipe_path
self._fd = -1
self.lat = lat
self.lon = lon
self.alt = alt
self._thread = None
self._stop = threading.Event()
def __enter__(self):
self.start()
return self
def __exit__(self, _, __, ___):
self.close()
def start(self):
self._stop.clear()
self._thread = threading.Thread(target=self._send_loop, daemon=True)
self._thread.start()
print(f"NMEA: started sender thread for {self.pipe_path}")
def close(self):
self._stop.set()
if self._thread:
self._thread.join(timeout=5)
self._thread = None
if self._fd >= 0:
try:
os.close(self._fd)
except OSError:
pass
self._fd = -1
def _send_loop(self):
print(f"NMEA: send_loop entered for {self.pipe_path}")
while not self._stop.is_set():
try:
self._fd = os.open(f"{self.pipe_path}.in",
os.O_WRONLY | os.O_NONBLOCK)
print(f"NMEA: opened {self.pipe_path}.in (fd={self._fd})")
except OSError as e:
if e.errno in (errno.ENXIO, errno.ENOENT):
print(f"NMEA: {self.pipe_path}.in not ready ({e}), retrying ...")
self._stop.wait(0.5)
continue
print(f"NMEA: {self.pipe_path}.in open failed: {e}")
raise
cycles = 0
while not self._stop.is_set():
try:
self._send_cycle()
cycles += 1
if cycles <= 3 or cycles % 10 == 0:
print(f"NMEA: {self.pipe_path} sent cycle {cycles}")
except BlockingIOError:
print(f"NMEA: {self.pipe_path} write blocked (cycle {cycles})")
except OSError as e:
print(f"NMEA: {self.pipe_path} write error: {e}, reconnecting")
break
self._stop.wait(1)
try:
os.close(self._fd)
except OSError:
pass
self._fd = -1
def _send(self, sentence):
data = (str(sentence) + "\r\n").encode()
try:
os.write(self._fd, data)
except BlockingIOError:
pass
def _send_cycle(self):
"""Send a full NMEA cycle matching real u-blox GPS output."""
now = time.gmtime()
utc = time.strftime("%H%M%S.00", now)
date = time.strftime("%d%m%y", now)
lat_deg = int(abs(self.lat))
lat_min = (abs(self.lat) - lat_deg) * 60
lat_str = f"{lat_deg:02d}{lat_min:07.4f}"
lat_ns = "N" if self.lat >= 0 else "S"
lon_deg = int(abs(self.lon))
lon_min = (abs(self.lon) - lon_deg) * 60
lon_str = f"{lon_deg:03d}{lon_min:07.4f}"
lon_ew = "E" if self.lon >= 0 else "W"
# RMC - Recommended Minimum
self._send(pynmea2.RMC("GP", "RMC", (
utc, "A",
lat_str, lat_ns,
lon_str, lon_ew,
"0.0", "0.0",
date,
"0.0", "E",
"A",
)))
# VTG - Track Made Good and Ground Speed
self._send(pynmea2.VTG("GP", "VTG", (
"0.0", "T",
"", "M",
"0.0", "N",
"0.0", "K",
"A",
)))
# GGA - Fix Data
self._send(pynmea2.GGA("GP", "GGA", (
utc,
lat_str, lat_ns,
lon_str, lon_ew,
"1", "08", "0.9",
f"{self.alt:.1f}", "M",
"47.0", "M",
"", "",
)))
# GSA - DOP and Active Satellites (3D fix, 8 sats)
self._send(pynmea2.GSA("GP", "GSA", (
"A", "3",
"01", "02", "03", "04", "05", "06", "07", "08",
"", "", "", "",
"1.5", "0.9", "1.2",
)))
# GSV - Satellites in View (4 sats per message, 2 messages)
self._send(pynmea2.GSV("GP", "GSV", (
"2", "1", "08",
"01", "45", "045", "40",
"02", "30", "090", "38",
"03", "60", "135", "42",
"04", "15", "180", "35",
)))
self._send(pynmea2.GSV("GP", "GSV", (
"2", "2", "08",
"05", "50", "225", "41",
"06", "25", "270", "36",
"07", "70", "315", "44",
"08", "20", "000", "33",
)))
# GLL - Geographic Position
self._send(pynmea2.GLL("GP", "GLL", (
lat_str, lat_ns,
lon_str, lon_ew,
utc,
"A",
"A",
)))
def _get_hardware(target):
data = target.get_data("/ietf-hardware:hardware")
if not data or "hardware" not in data:
return {}
return data["hardware"]
def get_gps_state(target, name="gps0"):
"""Get GPS receiver operational state for a named component."""
hardware = _get_hardware(target)
for component in hardware.get("component", []):
if component.get("name") == name:
return component.get("infix-hardware:gps-receiver",
component.get("gps-receiver"))
return None
def is_activated(target, name="gps0"):
"""Check if gpsd has activated the GPS device."""
state = get_gps_state(target, name)
return state.get("activated", False) if state else False
def has_fix(target, name="gps0"):
"""Check if GPS reports a fix (2D or 3D)."""
state = get_gps_state(target, name)
if not state:
return False
return state.get("fix-mode") in ("2d", "3d")
+7
View File
@@ -79,6 +79,13 @@ class Transport(ABC):
"""Check if the device has the given YANG model loaded."""
return model_name in self.modules
def has_feature(self, model_name, feature_name):
"""Check if a specific feature is enabled on the device for a given YANG model."""
if model_name not in self.modules:
return False
features = self.modules[model_name].get("feature", [])
return feature_name in features
def reachable(self):
"""Check if the device reachable on ll6"""
neigh = ll6ping(self.location.interface, flags=["-w1", "-c1", "-L", "-n"])
+11
View File
@@ -15,6 +15,11 @@ truncate -s 0 $imgdir/{{name}}.mactab
echo "{{qn_name}} {{qn_mac}}" >>$imgdir/{{name}}.mactab
{{/links}}
{{#qn_gps}}
mkfifo /tmp/{{name}}-gps.in /tmp/{{name}}-gps.out 2>/dev/null
mkfifo /tmp/{{name}}-gps1.in /tmp/{{name}}-gps1.out 2>/dev/null
{{/qn_gps}}
exec qemu-system-x86_64 -M pc,accel=kvm:tcg -cpu max \
-m {{#qn_mem}}{{qn_mem}}{{/qn_mem}}{{^qn_mem}}256M{{/qn_mem}} \
{{> ../qeneth/templates/inc/qemu-links}}
@@ -24,3 +29,9 @@ exec qemu-system-x86_64 -M pc,accel=kvm:tcg -cpu max \
$usb_cmd \
{{> ../qeneth/templates/inc/infix-common}}
{{> ../qeneth/templates/inc/qemu-console}}
{{#qn_gps}}
-chardev pipe,id=gps0,path=/tmp/{{name}}-gps \
-device virtserialport,chardev=gps0,name=gps0 \
-chardev pipe,id=gps1,path=/tmp/{{name}}-gps1 \
-device virtserialport,chardev=gps1,name=gps1 \
{{/qn_gps}}
+3 -2
View File
@@ -23,11 +23,12 @@ graph "quad" {
dut1 [
label="{ <e1> e1 | <e2> e2 | <e3> e3 | <e4> e4 } | dut1 | { <e5> e5 | <e6> e6 | <e7> e7 | <e8> e8}",
pos="10,30!",
provides="infix watchdog",
provides="infix watchdog gps",
expected_boot="primary",
qn_console=9001,
qn_mem="384M",
qn_usb="dut1.usb"
qn_usb="dut1.usb",
qn_gps="true"
];
dut2 [
label="{ <e1> e1 | <e2> e2 | <e3> e3 | <e4> e4 } | dut2 | { <e5> e5 | <e6> e6 | <e7> e7 | <e8> e8}",