mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
@@ -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;
|
||||
|
||||
+1
-1
Submodule buildroot updated: 66450a898a...7323ef2c85
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
MODULES=(
|
||||
"infix-hardware -e gps"
|
||||
)
|
||||
@@ -1,4 +1,5 @@
|
||||
MODULES=(
|
||||
"infix-interfaces -e wifi"
|
||||
"infix-hardware -e wifi"
|
||||
"infix-if-type -e wifi"
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -7,3 +7,6 @@
|
||||
|
||||
- name: Watchdog reset on system lockup
|
||||
case: watchdog/test.py
|
||||
|
||||
- name: GPS receiver basic test
|
||||
case: gps_simple/test.py
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
test.adoc
|
||||
@@ -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
|
||||
|
||||
|
||||
Executable
+119
@@ -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--target -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt--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 |
@@ -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")
|
||||
@@ -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"])
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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}",
|
||||
|
||||
Reference in New Issue
Block a user