mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
Add cellular modem support via modemd daemon
Integrates the modem management subsystem, donated by Avvero Pty Ltd,
adapted to work on any platform without proprietary hardware (simctrl,
modules.json, /sys/class/sim/).
Components:
- package/feature-modem: Buildroot feature package pulling in modemd,
ModemManager, libmbim, libqmi, and USB serial/MBIM/QMI kernel drivers
- package/modemd: Buildroot package for the modemd daemon and helpers
- src/modemd: modem management daemon — detects modems via ModemManager,
connects bearers, configures IP addresses, handles SMS and location
- src/confd/src/modem.c: confd plugin — enables/disables modemd and
modem-manager via sysrepo, forwards RPCs (restart/reset/send-sms)
- src/confd/src/interfaces.{c,h}: IFT_MODEM type for wwan* interfaces;
confd manages address/route assignment, modem daemon owns link state
- src/confd/yang/confd/infix-modem.yang: YANG model for modem config and
operational state (bearers, APN, bands, modes, location, SMS)
- src/statd/python/yanger/infix_modem.py: operational state collector
- patches/modem-manager: upstream patches for udev install path,
multiplexed interface naming, and MBIM port trust from udev hint
(the last fixes Dell DW5811e / Sierra EM7455 on cold boot)
- patches/libqmi: ignore sysfs in download mode
Platform portability fixes for boards without simctrl hardware:
- modem-info: fall back to /run/modems.json when /run/modules.json is
absent; discover wwan interfaces via sysfs scan; provide synthetic SIM
entry when /sys/class/sim/ is not present; fix dbg() to respect flag
- modemd: make --set-allowed-modes=any best-effort (some modems only
support one mode combination and reject any change); skip mode set
when current mode already matches requested mode
- sim-setup: guard simctrl open, return None when modules.json absent
to skip SIM slot switching entirely on non-simctrl hardware
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -149,6 +149,7 @@ 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_FEATURE_MODEM=y
|
||||
BR2_PACKAGE_FEATURE_WIFI=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_QUALCOMM=y
|
||||
|
||||
@@ -2,6 +2,7 @@ menu "Packages"
|
||||
|
||||
comment "Hardware Support"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/feature-gps/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/feature-modem/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/feature-wifi/Config.in"
|
||||
|
||||
comment "Software Packages"
|
||||
@@ -30,6 +31,7 @@ source "$BR2_EXTERNAL_INFIX_PATH/package/landing/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/libsrx/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/lowdown/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/mcd/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/modemd/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/mdns-alias/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/netbrowse/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/onieprom/Config.in"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
config BR2_PACKAGE_FEATURE_MODEM
|
||||
bool "Feature Modem"
|
||||
select BR2_PACKAGE_MODEMD
|
||||
select BR2_PACKAGE_MODEM_MANAGER
|
||||
select BR2_PACKAGE_MODEM_MANAGER_LIBMBIM
|
||||
select BR2_PACKAGE_MODEM_MANAGER_LIBQMI
|
||||
select BR2_PACKAGE_MODEM_MANAGER_LIBQRTR
|
||||
help
|
||||
Enables cellular modem support in Infix via ModemManager and
|
||||
the modemd management daemon. Includes drivers for common
|
||||
USB option modems and QMI/MBIM-based devices.
|
||||
|
||||
config BR2_PACKAGE_FEATURE_MODEM_QUALCOMM
|
||||
bool "Qualcomm-based modems (QMI/QRTR/MHI)"
|
||||
depends on BR2_PACKAGE_FEATURE_MODEM
|
||||
help
|
||||
Adds kernel support for Qualcomm-based cellular modems that use
|
||||
the MHI bus and QRTR IPC router (e.g. Sierra Wireless EM7xxx,
|
||||
Quectel EM/RMxxx, Telit LN9xx).
|
||||
@@ -0,0 +1,25 @@
|
||||
################################################################################
|
||||
#
|
||||
# Cellular modem support
|
||||
#
|
||||
################################################################################
|
||||
|
||||
FEATURE_MODEM_PACKAGE_VERSION = 1.0
|
||||
FEATURE_MODEM_PACKAGE_LICENSE = MIT
|
||||
|
||||
define FEATURE_MODEM_LINUX_CONFIG_FIXUPS
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_USB_SERIAL)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_USB_SERIAL_WWAN)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_USB_SERIAL_OPTION)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_USB_WDM)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_USB_NET_QMI_WWAN)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_USB_CDC_MBIM)
|
||||
|
||||
$(if $(filter y,$(BR2_PACKAGE_FEATURE_MODEM_QUALCOMM)),
|
||||
$(call KCONFIG_SET_OPT,CONFIG_QRTR,m)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_MHI_BUS,m)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_QRTR_MHI)
|
||||
)
|
||||
endef
|
||||
|
||||
$(eval $(generic-package))
|
||||
@@ -0,0 +1,6 @@
|
||||
config BR2_PACKAGE_MODEMD
|
||||
bool "modemd"
|
||||
select BR2_PACKAGE_MODEM_MANAGER
|
||||
select BR2_PACKAGE_PYTHON3
|
||||
help
|
||||
Daemon which manages modems.
|
||||
@@ -0,0 +1,2 @@
|
||||
# Locally calculated
|
||||
sha256 25b33026a661c4c550374cfcba6890a4363bf19db0c1c31a6e65b5edd113ecf0 LICENSE
|
||||
@@ -0,0 +1,44 @@
|
||||
################################################################################
|
||||
#
|
||||
# modemd
|
||||
#
|
||||
################################################################################
|
||||
|
||||
MODEMD_VERSION = 1.0
|
||||
MODEMD_SITE_METHOD = local
|
||||
MODEMD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/modemd
|
||||
MODEMD_LICENSE = BSD-3-Clause
|
||||
MODEMD_LICENSE_FILES = LICENSE
|
||||
MODEMD_REDISTRIBUTE = NO
|
||||
MODEMD_DEPENDENCIES = modem-manager jansson python3
|
||||
|
||||
define MODEMD_BUILD_CMDS
|
||||
$(TARGET_CC) $(TARGET_CFLAGS) $(TARGET_LDFLAGS) \
|
||||
$(MODEMD_DIR)/modem-command.c -o $(MODEMD_DIR)/modem-command -ljansson
|
||||
endef
|
||||
|
||||
define MODEMD_INSTALL_TARGET_CMDS
|
||||
mkdir -p $(TARGET_DIR)/usr/libexec/modemd
|
||||
mkdir -p $(TARGET_DIR)/lib/udev/rules.d
|
||||
mkdir -p $(FINIT_D)/available/
|
||||
mkdir -p $(TARGET_DIR)/sbin
|
||||
$(INSTALL) -D -m 0644 $(MODEMD_DIR)/finit.conf $(FINIT_D)/available/modemd.conf
|
||||
install -m 644 $(MODEMD_DIR)/modemd.rules $(TARGET_DIR)/lib/udev/rules.d/90-modemd.rules
|
||||
install -m 644 $(MODEMD_DIR)/qmi-wwan-ids.rules $(TARGET_DIR)/lib/udev/rules.d/91-qmi-wwan-ids.rules
|
||||
install -m 644 $(MODEMD_DIR)/77-mm-dell-port-types.rules $(TARGET_DIR)/etc/udev/rules.d/77-mm-dell-port-types.rules
|
||||
install -D -m 644 $(MODEMD_DIR)/modemd.modules-load $(TARGET_DIR)/etc/modules-load.d/modemd.conf
|
||||
install -m 755 $(MODEMD_DIR)/modem-udev $(TARGET_DIR)/usr/libexec/modemd/
|
||||
install -m 755 $(MODEMD_DIR)/modemd $(TARGET_DIR)/sbin/modemd
|
||||
install -m 755 $(MODEMD_DIR)/modem-command $(TARGET_DIR)/sbin/modem-command
|
||||
install -m 755 $(MODEMD_DIR)/modem-info $(TARGET_DIR)/usr/libexec/modemd/
|
||||
install -m 755 $(MODEMD_DIR)/modem-rpc $(TARGET_DIR)/usr/libexec/modemd/
|
||||
install -m 755 $(MODEMD_DIR)/modem-sms $(TARGET_DIR)/usr/libexec/modemd/
|
||||
install -m 755 $(MODEMD_DIR)/modem-scan-networks $(TARGET_DIR)/usr/libexec/modemd/
|
||||
install -m 755 $(MODEMD_DIR)/modem-update-firmware $(TARGET_DIR)/usr/libexec/modemd/
|
||||
install -m 755 $(MODEMD_DIR)/modem-ussd $(TARGET_DIR)/usr/libexec/modemd/
|
||||
install -m 755 $(MODEMD_DIR)/modem-carrier $(TARGET_DIR)/usr/libexec/modemd/
|
||||
install -m 755 $(MODEMD_DIR)/modem-power $(TARGET_DIR)/usr/libexec/modemd/
|
||||
install -m 755 $(MODEMD_DIR)/sim-setup $(TARGET_DIR)/usr/libexec/modemd/
|
||||
endef
|
||||
|
||||
$(eval $(generic-package))
|
||||
@@ -0,0 +1 @@
|
||||
service [2345789] ModemManager -- ModemManager daemon
|
||||
@@ -0,0 +1,21 @@
|
||||
diff --git a/src/qmi-firmware-update/qfu-helpers-udev.c b/src/qmi-firmware-update/qfu-helpers-udev.c
|
||||
index bda9106..40d648f 100644
|
||||
--- a/src/qmi-firmware-update/qfu-helpers-udev.c
|
||||
+++ b/src/qmi-firmware-update/qfu-helpers-udev.c
|
||||
@@ -364,8 +364,14 @@ device_matches (GUdevDevice *device,
|
||||
if (!device_sysfs_path)
|
||||
goto out;
|
||||
|
||||
- if (g_strcmp0 (device_sysfs_path, sysfs_path) != 0)
|
||||
- goto out;
|
||||
+ /*
|
||||
+ * don't compare sysfs path for download mode as it
|
||||
+ * changes from USB4 to USB3 and thus may use a different host controller
|
||||
+ */
|
||||
+ if (mode != QFU_HELPERS_DEVICE_MODE_DOWNLOAD) {
|
||||
+ if (g_strcmp0 (device_sysfs_path, sysfs_path) != 0)
|
||||
+ goto out;
|
||||
+ }
|
||||
|
||||
if (device_mode != mode)
|
||||
return NULL;
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/meson.build b/meson.build
|
||||
index 8cddf77..e97f862 100644
|
||||
--- a/meson.build
|
||||
+++ b/meson.build
|
||||
@@ -188,7 +188,7 @@ endif
|
||||
config_h.set('WITH_UDEV', enable_udev)
|
||||
|
||||
# udev base directory (required to install rules even when udev support is disabled)
|
||||
-udev_udevdir = get_option('udevdir')
|
||||
+udev_udevdir = '/lib/udev'
|
||||
if udev_udevdir == ''
|
||||
assert(enable_udev, 'udevdir must be explicitly given if udev support is disabled')
|
||||
udev_udevdir = dependency('udev').get_pkgconfig_variable('udevdir')
|
||||
@@ -0,0 +1,17 @@
|
||||
diff --git a/src/mm-bearer-qmi.c b/src/mm-bearer-qmi.c
|
||||
index 54f2e93..3c4df7b 100644
|
||||
--- a/src/mm-bearer-qmi.c
|
||||
+++ b/src/mm-bearer-qmi.c
|
||||
@@ -2494,7 +2494,11 @@ load_settings_from_bearer (MMBearerQmi *self,
|
||||
}
|
||||
|
||||
/* The link prefix hint given must be modem-specific */
|
||||
- ctx->link_prefix_hint = g_strdup_printf ("qmapmux%u.", mm_base_modem_get_dbus_id (MM_BASE_MODEM (modem)));
|
||||
+ const gchar *name = mm_port_get_device (MM_PORT (ctx->data));
|
||||
+ if (name)
|
||||
+ ctx->link_prefix_hint = g_strdup_printf ("%s.", name);
|
||||
+ else
|
||||
+ ctx->link_prefix_hint = g_strdup_printf ("qmapmux%u.", mm_base_modem_get_dbus_id (MM_BASE_MODEM (modem)));
|
||||
}
|
||||
|
||||
/* If profile id is given, we'll launch the connection specifying the profile id in use
|
||||
@@ -0,0 +1,29 @@
|
||||
When a udev rule explicitly sets ID_MM_PORT_TYPE_MBIM=1 for a port,
|
||||
treat it as definitive and skip the active MBIM open/probe. This
|
||||
mirrors the way ID_MM_PORT_TYPE_GPS is handled (set via is_gps, a
|
||||
direct result field rather than a "maybe" hint).
|
||||
|
||||
The active MBIM probe attempts to open the WDM device and send an
|
||||
MBIM OPEN message. Some modems (e.g. Dell DW5811e / Sierra Wireless
|
||||
EM7455 in USB composition 8) persistently send QMI/QMUX-framed
|
||||
notifications on the MBIM control channel before ever responding to
|
||||
MBIM OPEN, causing the probe to time out (~50 s). The probe failure
|
||||
causes mm_port_probe_list_has_mbim_port() to return FALSE, so the
|
||||
Dell plugin creates a non-MBIM modem object that cannot handle the
|
||||
usbmisc/MBIM port, resulting in "unhandled port type".
|
||||
|
||||
Since the udev rule already encodes the vendor/product/interface
|
||||
specificity required to identify the port, the active probe adds no
|
||||
information in this case.
|
||||
|
||||
diff --git a/src/mm-port-probe.c b/src/mm-port-probe.c
|
||||
--- a/src/mm-port-probe.c
|
||||
+++ b/src/mm-port-probe.c
|
||||
@@ -1529,6 +1529,7 @@ mm_port_probe_run (MMPortProbe *self,
|
||||
/* If this is a port flagged as being a MBIM port, don't do any other probing */
|
||||
if (self->priv->maybe_mbim) {
|
||||
mm_obj_dbg (self, "no AT/QCDM/QMI probing in possible MBIM port");
|
||||
+ mm_port_probe_set_result_mbim (self, TRUE);
|
||||
mm_port_probe_set_result_at (self, FALSE);
|
||||
mm_port_probe_set_result_qcdm (self, FALSE);
|
||||
mm_port_probe_set_result_qmi (self, FALSE);
|
||||
@@ -48,6 +48,7 @@ confd_plugin_la_SOURCES = \
|
||||
if-vxlan.c \
|
||||
if-wifi.c \
|
||||
if-wireguard.c \
|
||||
modem.c \
|
||||
keystore.c \
|
||||
system.c \
|
||||
ntp.c \
|
||||
|
||||
@@ -858,7 +858,9 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv)
|
||||
rc = ntp_candidate_init(&confd);
|
||||
if (rc)
|
||||
goto err;
|
||||
/* YOUR_INIT GOES HERE */
|
||||
rc = modem_init(&confd);
|
||||
if (rc)
|
||||
goto err;
|
||||
|
||||
return SR_ERR_OK;
|
||||
err:
|
||||
|
||||
@@ -133,6 +133,10 @@ typedef enum {
|
||||
if ((rc = register_rpc(s, x, c, a, u))) \
|
||||
goto fail
|
||||
|
||||
#define REGISTER_NOTIF(s,m,x,c,a,u) \
|
||||
if ((rc = register_notif(s, m, x, c, a, u))) \
|
||||
goto fail
|
||||
|
||||
struct confd {
|
||||
sr_session_ctx_t *session; /* running datastore */
|
||||
sr_session_ctx_t *startup; /* startup datastore */
|
||||
@@ -192,6 +196,17 @@ static inline int register_rpc(sr_session_ctx_t *session, const char *xpath,
|
||||
return rc;
|
||||
}
|
||||
|
||||
static inline int register_notif(sr_session_ctx_t *session, const char *module, const char *xpath,
|
||||
sr_event_notif_cb cb, void *arg, sr_subscription_ctx_t **sub)
|
||||
{
|
||||
int rc = sr_notif_subscribe(session, module, xpath,
|
||||
NULL, NULL, /* forever */
|
||||
cb, arg, SR_SUBSCR_NO_THREAD, sub);
|
||||
if (rc)
|
||||
ERROR("failed subscribing to %s notification: %s", xpath, sr_strerror(rc));
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/* core.c */
|
||||
int finit_enable(const char *svc);
|
||||
@@ -274,4 +289,6 @@ int ntp_candidate_init(struct confd *confd);
|
||||
/* ptp.c */
|
||||
int ptp_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd);
|
||||
|
||||
int modem_init(struct confd *confd);
|
||||
|
||||
#endif /* CONFD_CORE_H_ */
|
||||
|
||||
@@ -80,6 +80,8 @@ static int ifchange_cand_infer_type(sr_session_ctx_t *session, const char *path)
|
||||
|
||||
if (!fnmatch("wifi+([0-9])*", ifname, FNM_EXTMATCH))
|
||||
inferred.data.string_val = "infix-if-type:wifi";
|
||||
else if (!fnmatch("wwan+([0-9-])", ifname, FNM_EXTMATCH))
|
||||
inferred.data.string_val = "infix-if-type:modem";
|
||||
else if (iface_is_phys(ifname))
|
||||
inferred.data.string_val = "infix-if-type:ethernet";
|
||||
else if (!fnmatch("br+([0-9])", ifname, FNM_EXTMATCH))
|
||||
@@ -390,6 +392,8 @@ static int netdag_gen_afspec_add(sr_session_ctx_t *session, struct dagger *net,
|
||||
return vlan_gen(NULL, cif, ip);
|
||||
case IFT_VXLAN:
|
||||
return vxlan_gen(NULL, cif, ip);
|
||||
case IFT_MODEM:
|
||||
return modem_gen(NULL, cif, net);
|
||||
case IFT_WIFI:
|
||||
return wifi_validate_secret(session, cif)
|
||||
? : wifi_add_iface(cif, net);
|
||||
@@ -434,6 +438,7 @@ static int netdag_gen_afspec_set(sr_session_ctx_t *session, struct dagger *net,
|
||||
case IFT_DUMMY:
|
||||
case IFT_GRE:
|
||||
case IFT_GRETAP:
|
||||
case IFT_MODEM:
|
||||
case IFT_VETH:
|
||||
case IFT_VXLAN:
|
||||
case IFT_WIREGUARD:
|
||||
@@ -474,6 +479,8 @@ static bool netdag_must_del(struct lyd_node *dif, struct lyd_node *cif)
|
||||
return lydx_get_descendant(lyd_child(dif), "veth", NULL);
|
||||
case IFT_VXLAN:
|
||||
return lydx_get_descendant(lyd_child(dif), "vxlan", NULL);
|
||||
case IFT_MODEM:
|
||||
return false;
|
||||
case IFT_WIREGUARD:
|
||||
return lydx_get_descendant(lyd_child(dif), "wireguard", NULL);
|
||||
case IFT_UNKNOWN:
|
||||
@@ -551,6 +558,9 @@ static int netdag_gen_iface_del(struct dagger *net, struct lyd_node *dif,
|
||||
case IFT_VETH:
|
||||
veth_gen_del(dif, ip);
|
||||
break;
|
||||
case IFT_MODEM:
|
||||
modem_gen_del(dif, net);
|
||||
break;
|
||||
case IFT_WIFI:
|
||||
wifi_del_iface(dif, net);
|
||||
break;
|
||||
@@ -573,7 +583,8 @@ static int netdag_gen_iface_del(struct dagger *net, struct lyd_node *dif,
|
||||
|
||||
static sr_error_t netdag_gen_iface_timeout(struct dagger *net, const char *ifname, const char *iftype)
|
||||
{
|
||||
if (!strcmp(iftype, "infix-if-type:ethernet")) {
|
||||
if (!strcmp(iftype, "infix-if-type:ethernet") ||
|
||||
!strcmp(iftype, "infix-if-type:modem")) {
|
||||
FILE *wait;
|
||||
|
||||
wait = dagger_fopen_net_init(net, ifname, NETDAG_INIT_TIMEOUT, "wait-interface.sh");
|
||||
@@ -693,8 +704,9 @@ static sr_error_t netdag_gen_iface(sr_session_ctx_t *session, struct dagger *net
|
||||
fprintf(ip, "link set alias \"%s\" dev %s\n", attr ?: "", ifname);
|
||||
|
||||
/* Bring interface back up, if enabled */
|
||||
if (lydx_is_enabled(cif, "enabled"))
|
||||
fprintf(ip, "link set dev %s up state up\n", ifname);
|
||||
if (strcmp(iftype, "infix-if-type:modem") != 0)
|
||||
if (lydx_is_enabled(cif, "enabled"))
|
||||
fprintf(ip, "link set dev %s up state up\n", ifname);
|
||||
|
||||
err = err ? : netdag_gen_sysctl(net, cif, dif);
|
||||
|
||||
@@ -724,6 +736,7 @@ static int netdag_init_iface(struct lyd_node *cif)
|
||||
return vlan_add_deps(cif);
|
||||
case IFT_VETH:
|
||||
return veth_add_deps(cif);
|
||||
case IFT_MODEM:
|
||||
case IFT_WIFI:
|
||||
case IFT_DUMMY:
|
||||
case IFT_ETH:
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
_map(IFT_GRETAP, "infix-if-type:gretap") \
|
||||
_map(IFT_LAG, "infix-if-type:lag") \
|
||||
_map(IFT_LO, "infix-if-type:loopback") \
|
||||
_map(IFT_MODEM, "infix-if-type:modem") \
|
||||
_map(IFT_VETH, "infix-if-type:veth") \
|
||||
_map(IFT_VLAN, "infix-if-type:vlan") \
|
||||
_map(IFT_VXLAN, "infix-if-type:vxlan") \
|
||||
@@ -167,6 +168,10 @@ int ifchange_cand_infer_dhcp(sr_session_ctx_t *session, const char *path);
|
||||
/* if-vxlan.c */
|
||||
int vxlan_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip);
|
||||
|
||||
/* modem.c */
|
||||
int modem_gen(struct lyd_node *dif, struct lyd_node *cif, struct dagger *net);
|
||||
int modem_gen_del(struct lyd_node *dif, struct dagger *net);
|
||||
|
||||
/* infix-if-wireguard */
|
||||
int wireguard_validate_peers(sr_session_ctx_t *session, struct lyd_node *cif);
|
||||
int wireguard_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip, struct dagger *net);
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <regex.h>
|
||||
|
||||
#include <srx/common.h>
|
||||
#include <srx/lyx.h>
|
||||
#include <srx/srx_val.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#define MAX_MODEMS 8
|
||||
#define RUN_DIR "/run/modemd"
|
||||
#define SOCK RUN_DIR "/modemd.sock"
|
||||
|
||||
#define MODULE "infix-modem"
|
||||
#define ROOT_XPATH "/infix-modem:"
|
||||
#define CFG_XPATH ROOT_XPATH "modems"
|
||||
|
||||
|
||||
static int xpath_get_index(const char *xpath)
|
||||
{
|
||||
regmatch_t pmatch[2];
|
||||
regex_t regex;
|
||||
char buf[32];
|
||||
int ret, len, i;
|
||||
int index = -1;
|
||||
|
||||
if (regcomp(®ex, "index='([0-9]+)'", REG_EXTENDED))
|
||||
return -1;
|
||||
|
||||
ret = regexec(®ex, xpath, 2, pmatch, 0);
|
||||
if (!ret) {
|
||||
len = (pmatch[1].rm_eo - pmatch[1].rm_so);
|
||||
if (len < (int)(sizeof(buf)-1)) {
|
||||
for (i = 0; i < len; i++)
|
||||
buf[i] = xpath[pmatch[1].rm_so + i];
|
||||
|
||||
buf[i] = '\0';
|
||||
index = (int) strtoul(buf, NULL, 10);
|
||||
}
|
||||
}
|
||||
regfree(®ex);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
static int node_index(struct lyd_node *node)
|
||||
{
|
||||
const char *s;
|
||||
|
||||
s = lydx_get_cattr(node, "index");
|
||||
if (!s || !s[0])
|
||||
return -1;
|
||||
|
||||
return (int) strtoul(s, NULL, 10);
|
||||
}
|
||||
|
||||
static int disable(void)
|
||||
{
|
||||
NOTE("Disabling modemd");
|
||||
|
||||
/* disable modem-manager */
|
||||
systemf("initctl -bfqn stop modem-manager");
|
||||
systemf("initctl -bfqn disable modem-manager");
|
||||
|
||||
/* disable modemd */
|
||||
systemf("initctl -bnq stop modemd");
|
||||
systemf("initctl -bnq disable modemd");
|
||||
|
||||
return SR_ERR_OK;
|
||||
}
|
||||
|
||||
static int enable(void)
|
||||
{
|
||||
int enabled, reload = 0;
|
||||
|
||||
NOTE("Enabling modemd");
|
||||
|
||||
/* enable modem-manager */
|
||||
enabled = !systemf("initctl -bfq status modem-manager");
|
||||
if (!enabled) {
|
||||
systemf("initctl -bfqn enable modem-manager");
|
||||
reload = 1;
|
||||
}
|
||||
/* enable modemd */
|
||||
enabled = !systemf("initctl -bfq status modemd");
|
||||
if (!enabled) {
|
||||
systemf("initctl -bfqn enable modemd");
|
||||
reload = 1;
|
||||
}
|
||||
/* reload if required */
|
||||
if (reload)
|
||||
systemf("initctl -b reload");
|
||||
|
||||
/* restart modem-manager */
|
||||
systemf("initctl -bfqn restart modem-manager");
|
||||
|
||||
/* restart modemd */
|
||||
systemf("initctl -bfqn restart modemd");
|
||||
|
||||
return SR_ERR_OK;
|
||||
}
|
||||
|
||||
static int genconf(sr_data_t *cfg, struct lyd_node *diff)
|
||||
{
|
||||
struct lyd_node *node, *tree;
|
||||
uint8_t enabled[MAX_MODEMS];
|
||||
int index;
|
||||
|
||||
memset(enabled, 0, sizeof(enabled));
|
||||
|
||||
tree = lydx_get_descendant(cfg->tree, "modems", "modem", NULL);
|
||||
LYX_LIST_FOR_EACH(tree, node, "modem") {
|
||||
index = node_index(node);
|
||||
if (index < MAX_MODEMS)
|
||||
enabled[index] = lydx_get_bool(node, "enabled") ? 1 : 0;
|
||||
}
|
||||
|
||||
tree = lydx_get_descendant(diff, "modems", "modem", NULL);
|
||||
LYX_LIST_FOR_EACH(tree, node, "modem") {
|
||||
index = node_index(node);
|
||||
if (index < MAX_MODEMS && lydx_get_op(node) == LYDX_OP_DELETE)
|
||||
enabled[index] = 0;
|
||||
}
|
||||
|
||||
for (index = 0; index < MAX_MODEMS; index++) {
|
||||
if (enabled[index]) {
|
||||
if (enable() == SR_ERR_OK) {
|
||||
return SR_ERR_OK;
|
||||
} else {
|
||||
ERROR("Cannot enable modem%d", index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return disable();
|
||||
}
|
||||
|
||||
static int infix_modem_change(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
|
||||
const char *xpath, sr_event_t event, unsigned request_id, void *confd)
|
||||
{
|
||||
sr_data_t *cfg = NULL;
|
||||
struct lyd_node *diff = NULL;
|
||||
sr_error_t err;
|
||||
|
||||
if (event != SR_EV_DONE) {
|
||||
return SR_ERR_OK;
|
||||
}
|
||||
err = sr_get_data(session, CFG_XPATH "//.", 0, 0, 0, &cfg);
|
||||
if (err) {
|
||||
ERROR("Can't get data");
|
||||
goto out;
|
||||
}
|
||||
err = srx_get_diff(session, &diff);
|
||||
if (err) {
|
||||
ERROR("Can't get diff");
|
||||
goto out;
|
||||
}
|
||||
err = genconf(cfg, diff);
|
||||
if (err) {
|
||||
ERROR("Can't gen conf");
|
||||
goto out;
|
||||
}
|
||||
out:
|
||||
if (diff) lyd_free_tree(diff);
|
||||
if (cfg) sr_release_data(cfg);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int infix_modem_rpcsend(char *msg, int len)
|
||||
{
|
||||
struct sockaddr_un addr;
|
||||
struct timeval tv;
|
||||
fd_set wfds;
|
||||
int sock, ret = -1;
|
||||
|
||||
sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sock < 0)
|
||||
return -1;
|
||||
|
||||
addr.sun_family = AF_UNIX;
|
||||
strcpy(addr.sun_path, SOCK);
|
||||
|
||||
if (connect(sock, &addr, sizeof(addr)) == 0) {
|
||||
tv.tv_sec = 5;
|
||||
tv.tv_usec = 0;
|
||||
FD_ZERO(&wfds);
|
||||
FD_SET(sock, &wfds);
|
||||
|
||||
if (select(sock + 1, NULL, &wfds, NULL, &tv) > 0) {
|
||||
if (write(sock, msg, len) == len) {
|
||||
ret = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
close(sock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int infix_modem_rpc(const char *xpath, const char *rpc, const char *data)
|
||||
{
|
||||
char msg[1024];
|
||||
int len;
|
||||
|
||||
NOTE("Sending rpc %s to modemd", rpc);
|
||||
|
||||
len = snprintf(msg, sizeof(msg),
|
||||
"{ \"rpc\" : \"%s\", \"data\" : %s }",
|
||||
rpc, data ? data : "null");
|
||||
|
||||
if (infix_modem_rpcsend(msg, len) < 0) {
|
||||
ERROR("Unable to send rpc");
|
||||
return SR_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
return SR_ERR_OK;
|
||||
}
|
||||
|
||||
static int infix_modem_sendsms(sr_session_ctx_t *session, uint32_t sub_id, const char *xpath,
|
||||
const sr_val_t *input, const size_t input_cnt, sr_event_t event,
|
||||
unsigned request_id, sr_val_t **output, size_t *output_cnt, void *priv)
|
||||
{
|
||||
char data[1024];
|
||||
|
||||
if (input_cnt < 3) {
|
||||
ERROR("Not enough input parameters");
|
||||
return SR_ERR_SYS;
|
||||
}
|
||||
|
||||
snprintf(data, sizeof(data)-1,
|
||||
"{ \"index\" : %s, \"number\" : \"%s\", \"text\" : \"%s\" }",
|
||||
input[0].data.string_val,
|
||||
input[1].data.string_val,
|
||||
input[2].data.string_val);
|
||||
|
||||
return infix_modem_rpc(xpath, "send-sms", data);
|
||||
}
|
||||
|
||||
static int infix_modem_restart(sr_session_ctx_t *session, uint32_t sub_id, const char *xpath,
|
||||
const sr_val_t *input, const size_t input_cnt, sr_event_t event,
|
||||
unsigned request_id, sr_val_t **output, size_t *output_cnt, void *priv)
|
||||
{
|
||||
char data[1024];
|
||||
|
||||
if (input_cnt < 1) {
|
||||
ERROR("Not enough input parameters");
|
||||
return SR_ERR_SYS;
|
||||
}
|
||||
|
||||
snprintf(data, sizeof(data)-1,
|
||||
"{ \"index\" : %s }", input[0].data.string_val);
|
||||
|
||||
return infix_modem_rpc(xpath, "restart", data);
|
||||
}
|
||||
|
||||
static int infix_modem_reset(sr_session_ctx_t *session, uint32_t sub_id, const char *xpath,
|
||||
const sr_val_t *input, const size_t input_cnt, sr_event_t event,
|
||||
unsigned request_id, sr_val_t **output, size_t *output_cnt, void *priv)
|
||||
{
|
||||
char data[1024];
|
||||
|
||||
if (input_cnt < 1) {
|
||||
ERROR("Not enough input parameters");
|
||||
return SR_ERR_SYS;
|
||||
}
|
||||
|
||||
snprintf(data, sizeof(data)-1,
|
||||
"{ \"index\" : %s }", input[0].data.string_val);
|
||||
|
||||
return infix_modem_rpc(xpath, "reset", data);
|
||||
}
|
||||
|
||||
static void infix_modem_notif (sr_session_ctx_t *session, uint32_t sub_id,
|
||||
const sr_ev_notif_type_t notif_type, const char *xpath,
|
||||
const sr_val_t *values, const size_t values_cnt,
|
||||
struct timespec *timestamp, void *confd)
|
||||
{
|
||||
int index;
|
||||
|
||||
index = xpath_get_index(xpath);
|
||||
if (index < 0) {
|
||||
ERROR("No index");
|
||||
return;
|
||||
}
|
||||
if (values_cnt < 1) {
|
||||
ERROR("No values");
|
||||
return;
|
||||
}
|
||||
|
||||
NOTE("Notification from modem%d: %s",
|
||||
index, values[0].data.string_val);
|
||||
}
|
||||
|
||||
int modem_init(struct confd *confd)
|
||||
{
|
||||
int rc;
|
||||
|
||||
REGISTER_CHANGE(confd->session, MODULE, CFG_XPATH, 0, infix_modem_change, confd, &confd->sub);
|
||||
REGISTER_NOTIF(confd->session, MODULE, CFG_XPATH "/modem/status-update", infix_modem_notif, confd, &confd->sub);
|
||||
REGISTER_RPC(confd->session, ROOT_XPATH "restart", infix_modem_restart, NULL, &confd->sub);
|
||||
REGISTER_RPC(confd->session, ROOT_XPATH "reset", infix_modem_reset, NULL, &confd->sub);
|
||||
REGISTER_RPC(confd->session, ROOT_XPATH "send-sms", infix_modem_sendsms, NULL, &confd->sub);
|
||||
|
||||
return SR_ERR_OK;
|
||||
fail:
|
||||
ERROR("init failed: %s", sr_strerror(rc));
|
||||
return rc;
|
||||
}
|
||||
|
||||
int modem_gen(struct lyd_node *dif, struct lyd_node *cif, struct dagger *net)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int modem_gen_del(struct lyd_node *dif, struct dagger *net)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -57,4 +57,5 @@ MODULES=(
|
||||
"ieee1588-ptp-tt@2023-08-14.yang -e timestamp-correction"
|
||||
"ieee802-dot1as-gptp@2025-12-10.yang"
|
||||
"infix-ptp@2026-04-07.yang"
|
||||
"infix-modem@2024-03-15.yang"
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
infix-modem.yang
|
||||
@@ -0,0 +1,39 @@
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION!="add|change|move|bind", GOTO="mm_dell_port_types_end"
|
||||
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="413c", GOTO="mm_dell_vendorcheck"
|
||||
GOTO="mm_dell_port_types_end"
|
||||
|
||||
LABEL="mm_dell_vendorcheck"
|
||||
SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
|
||||
|
||||
# Dell DW5821e (default 0x81d7, with esim support 0x81e0)
|
||||
# if 02: primary port
|
||||
# if 03: secondary port
|
||||
# if 04: raw NMEA port
|
||||
# if 05: diag/qcdm port
|
||||
ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d7", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
|
||||
ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d7", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
|
||||
ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d7", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
|
||||
ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d7", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
|
||||
ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81e0", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
|
||||
ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81e0", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
|
||||
ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81e0", ENV{.MM_USBIFNUM}=="04", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_GPS}="1"
|
||||
ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81e0", ENV{.MM_USBIFNUM}=="05", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_QCDM}="1"
|
||||
|
||||
# Dell DW5820e
|
||||
# if 02: AT port
|
||||
# if 04: debug port (ignore)
|
||||
# if 06: AT port
|
||||
ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81d9", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Dell DW5811e (Sierra Wireless EM7455 rebadge)
|
||||
# USB composition 8: MBIM-only (bInterfaceClass=02 bInterfaceSubClass=0e)
|
||||
# Bound by cdc_mbim driver; no AT ports in this composition.
|
||||
# if 0c: MBIM control port
|
||||
# if 0d: data port (handled by cdc_mbim / cdc_ncm driver)
|
||||
ATTRS{idVendor}=="413c", ATTRS{idProduct}=="81b6", ENV{.MM_USBIFNUM}=="0c", SUBSYSTEM=="usbmisc", ENV{ID_MM_PORT_TYPE_MBIM}="1"
|
||||
|
||||
GOTO="mm_dell_port_types_end"
|
||||
LABEL="mm_dell_port_types_end"
|
||||
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2024 Avvero Pty Ltd
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of copyright holders nor the names of
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1 @@
|
||||
service [2345] name:modemd /sbin/modemd -- Modem daemon
|
||||
Executable
+314
@@ -0,0 +1,314 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import argparse
|
||||
import json
|
||||
import syslog
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
syslog.openlog(logoption=syslog.LOG_PID, facility=syslog.LOG_SYSLOG)
|
||||
|
||||
|
||||
def log(msg):
|
||||
syslog.syslog(syslog.LOG_INFO, msg)
|
||||
|
||||
|
||||
def err(msg):
|
||||
syslog.syslog(syslog.LOG_ERR, msg)
|
||||
|
||||
|
||||
def fatal(msg):
|
||||
syslog.syslog(syslog.LOG_ALERT, msg)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def runcmd(cmd):
|
||||
ret = None
|
||||
try:
|
||||
res = subprocess.run(cmd, check=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, text=True)
|
||||
if res.returncode == 0:
|
||||
if res.stdout:
|
||||
ret = res.stdout.strip()
|
||||
else:
|
||||
ret = True
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
finally:
|
||||
return ret
|
||||
|
||||
|
||||
def runcmdj(cmd):
|
||||
output = runcmd(cmd)
|
||||
if not output:
|
||||
return None
|
||||
try:
|
||||
data = json.loads(output)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
finally:
|
||||
return data
|
||||
|
||||
|
||||
def read_file(path):
|
||||
if os.path.exists(path):
|
||||
with open(path, "r") as fd:
|
||||
output = str(fd.read())
|
||||
if output:
|
||||
return output.strip()
|
||||
return None
|
||||
|
||||
|
||||
# Telit LE910
|
||||
le910_carriers = [
|
||||
{"num": 0, "name": "AT&T"},
|
||||
{"num": 1, "name": "Verizon"},
|
||||
{"num": 2, "name": "T-Mobile"},
|
||||
{"num": 3, "name": "Bell"},
|
||||
{"num": 4, "name": "Telus"},
|
||||
{"num": 10, "name": "NTT-Docomo"},
|
||||
{"num": 11, "name": "Telstra"},
|
||||
{"num": 12, "name": "KDDI"},
|
||||
{"num": 13, "name": "Softbank"},
|
||||
{"num": 14, "name": "Vodafone New Zealand"},
|
||||
{"num": 15, "name": "Spark New Zealand"},
|
||||
{"num": 20, "name": "China Mobile"},
|
||||
{"num": 21, "name": "China Unicom"},
|
||||
{"num": 22, "name": "China Telecom"},
|
||||
{"num": 30, "name": "Sprint"},
|
||||
{"num": 31, "name": "SouthernLINC"},
|
||||
{"num": 40, "name": "Global"},
|
||||
{"num": 101, "name": "T-Mobile Germany"},
|
||||
{"num": 102, "name": "AT&T Mexico"},
|
||||
{"num": 103, "name": "Orange-WW"},
|
||||
{"num": 104, "name": "Southern Linc USA"},
|
||||
{"num": 105, "name": "Vodafone DE"}
|
||||
]
|
||||
|
||||
|
||||
def le910_carrier_bynum(num):
|
||||
for carrier in le910_carriers:
|
||||
if carrier["num"] == num:
|
||||
return carrier
|
||||
return None
|
||||
|
||||
|
||||
def le910_carrier_byname(name):
|
||||
for carrier in le910_carriers:
|
||||
if carrier["name"] == name:
|
||||
return carrier
|
||||
return None
|
||||
|
||||
|
||||
def le910_is_supported(variant):
|
||||
if variant == "NS":
|
||||
return True
|
||||
elif variant == "AP":
|
||||
return True
|
||||
elif variant == "NF" or variant == "NFD":
|
||||
return True
|
||||
elif variant == "CN":
|
||||
return True
|
||||
elif variant == "APX":
|
||||
return True
|
||||
elif variant == "WWX" or variant == "WWXD":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def le910_carrier_default(variant):
|
||||
if variant == "NF" or variant == "WWX" or variant == "WWXD":
|
||||
num = 0
|
||||
elif variant == "AP" or variant == "APX":
|
||||
num = 10
|
||||
elif variant == "CN":
|
||||
num = 20
|
||||
elif variant == "NS":
|
||||
num = 30
|
||||
elif variant == "EU" or variant == "EUX":
|
||||
num = 40
|
||||
else:
|
||||
return None
|
||||
|
||||
return le910_carrier_bynum(num)
|
||||
|
||||
|
||||
def le910_command(index, command):
|
||||
cmd = ['/sbin/modem-command',
|
||||
'--index', str(index),
|
||||
'--mode', '115200 8N1',
|
||||
'--secondary',
|
||||
'--timeout', '5',
|
||||
command]
|
||||
|
||||
for attempt in range(1, 3):
|
||||
output = runcmd(cmd)
|
||||
if output:
|
||||
return output
|
||||
|
||||
err("Command '%s' failed" % " ".join(cmd))
|
||||
return None
|
||||
|
||||
|
||||
def le910_list_carriers(index):
|
||||
output = le910_command(index, 'AT#FWSWITCH=?')
|
||||
if output is None:
|
||||
return None
|
||||
|
||||
r = re.search(r"#FWSWITCH: \(([^\)]*)\)", output)
|
||||
if not r:
|
||||
err("Unable to list carriers on modem%d" % index)
|
||||
return None
|
||||
|
||||
rlist = r.group(1)
|
||||
nums = []
|
||||
for rl in rlist.split(","):
|
||||
r = re.search(r"(\d+)-(\d+)", rl)
|
||||
if r:
|
||||
start = int(r.group(1))
|
||||
end = int(r.group(2))
|
||||
for i in range(start, end + 1):
|
||||
nums.append(i)
|
||||
else:
|
||||
nums.append(int(rl))
|
||||
|
||||
carriers = []
|
||||
for num in nums:
|
||||
carrier = le910_carrier_bynum(num)
|
||||
if carrier:
|
||||
carriers.append(carrier)
|
||||
else:
|
||||
err("Unknown carrier %d" % num)
|
||||
|
||||
return carriers
|
||||
|
||||
|
||||
def le910_set_carrier(index, carrier):
|
||||
output = le910_command(index, 'AT#FWSWITCH=%d' % carrier["num"])
|
||||
if output is None:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def le910_get_carrier(index):
|
||||
output = le910_command(index, 'AT#FWSWITCH?')
|
||||
if output is None:
|
||||
return -1
|
||||
|
||||
r = re.search(r"#FWSWITCH: (\d+)", output)
|
||||
if not r:
|
||||
err("Unable to query carrier on modem%d" % index)
|
||||
return -1
|
||||
num = int(r.group(1))
|
||||
|
||||
return le910_carrier_bynum(num)
|
||||
|
||||
|
||||
def runcheck(manf, model):
|
||||
if manf == "Telit" and model[:5] == "LE910":
|
||||
return le910_is_supported(model.split("-")[1])
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def runset(index, manf, model, name):
|
||||
if not runcheck(manf, model):
|
||||
fatal("Unsupported modem")
|
||||
|
||||
if name == "default":
|
||||
carrier = le910_carrier_default(model.split("-")[1])
|
||||
else:
|
||||
carrier = le910_carrier_byname(name)
|
||||
if not carrier:
|
||||
fatal("Invalid carrier '%s'" % name)
|
||||
|
||||
current = le910_get_carrier(index)
|
||||
if not current:
|
||||
fatal("Cannot get current carrier")
|
||||
|
||||
if current["name"] == carrier["name"]:
|
||||
log("Carrier '%s' already set on modem%d" % (carrier["name"], index))
|
||||
print(json.dumps({"action": "none"}))
|
||||
sys.exit(0)
|
||||
|
||||
log("Setting carrier '%s' on modem%d" % (carrier["name"], index))
|
||||
if not le910_set_carrier(index, carrier):
|
||||
fatal("Unable to set carrier on modem%d" % index)
|
||||
|
||||
print(json.dumps({"action": "reset"}))
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def runget(index, manf, model):
|
||||
if not runcheck(manf, model):
|
||||
fatal("Unsupported modem")
|
||||
|
||||
carrier = le910_get_carrier(index)
|
||||
if not carrier:
|
||||
fatal("Unable to get carrier")
|
||||
|
||||
print(json.dumps(carrier))
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def runlist(index, manf, model):
|
||||
if not runcheck(manf, model):
|
||||
print(json.dumps([]))
|
||||
sys.exit(0)
|
||||
|
||||
carriers = le910_list_carriers(index)
|
||||
if not carriers:
|
||||
fatal("No carriers")
|
||||
|
||||
names = []
|
||||
for carrier in sorted(carriers, key=lambda c: c["name"]):
|
||||
names.append(carrier["name"])
|
||||
|
||||
print(json.dumps(names))
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(prog='modem-carrier')
|
||||
parser.add_argument("-i", "--index", default=0, help="Modem index")
|
||||
parser.add_argument("-m", "--manf", default=0, help="Modem manufacturer")
|
||||
parser.add_argument("-M", "--model", default=0, help="Modem model")
|
||||
parser.add_argument("-s", "--set", help="Set carrier")
|
||||
parser.add_argument("-g", "--get", help="Get carrier",
|
||||
action="store_true")
|
||||
parser.add_argument("-l", "--list", help="List supported carriers",
|
||||
action="store_true")
|
||||
parser.add_argument("-c", "--check", help="Check support for carriers",
|
||||
action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.manf:
|
||||
fatal("No manufacturer given")
|
||||
else:
|
||||
manf = args.manf
|
||||
|
||||
if not args.model:
|
||||
fatal("No model given")
|
||||
else:
|
||||
model = args.model
|
||||
|
||||
if args.index:
|
||||
index = int(args.index)
|
||||
else:
|
||||
index = 0
|
||||
|
||||
if args.check:
|
||||
if not runcheck(manf, model):
|
||||
sys.exit(1)
|
||||
elif args.list:
|
||||
runlist(index, manf, model)
|
||||
elif args.set:
|
||||
runset(index, manf, model, args.set)
|
||||
elif args.get:
|
||||
runget(index, manf, model)
|
||||
|
||||
sys.exit(0)
|
||||
@@ -0,0 +1,617 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
#include <getopt.h>
|
||||
#include <fcntl.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/file.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <jansson.h>
|
||||
|
||||
static int verbose = 0;
|
||||
|
||||
#define MODEMS_LOCK "/var/lock/modems.lock"
|
||||
#define MODEMS_INFO "/run/modems.json"
|
||||
#define TIMEOUT 3
|
||||
|
||||
#define DBG(fmt, a...) print(LOG_DEBUG, fmt, ##a)
|
||||
#define SAY(fmt, a...) print(LOG_INFO, fmt, ##a)
|
||||
#define ERR(fmt, a...) print(LOG_ERR, fmt, ##a)
|
||||
#define FATAL(fmt, a...) print(LOG_ALERT, fmt, ##a)
|
||||
|
||||
static void print (int level, const char *fmt, ...)
|
||||
{
|
||||
char msg[1024];
|
||||
va_list ap;
|
||||
|
||||
if (!fmt) return;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(msg, sizeof(msg) - 1, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
switch (level) {
|
||||
case LOG_ERR:
|
||||
fprintf(stderr, "ERROR: %s\n", msg);
|
||||
break;
|
||||
case LOG_ALERT:
|
||||
fprintf(stderr, "ERROR: %s\n", msg);
|
||||
unlink(MODEMS_LOCK);
|
||||
exit(1);
|
||||
break;
|
||||
case LOG_DEBUG:
|
||||
if (verbose) {
|
||||
printf("DEBUG: %s\n", msg);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
printf("%s\n", msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void printser (int dir, char *ser)
|
||||
{
|
||||
char quoted[1024];
|
||||
char *p;
|
||||
int len = 0;
|
||||
|
||||
for (p = ser; *p; p++) {
|
||||
if (*p == '\r') {
|
||||
len += snprintf(quoted + len, sizeof(quoted) - len, "<CR>");
|
||||
} else if (*p == '\n') {
|
||||
len += snprintf(quoted + len, sizeof(quoted) - len, "<NL>");
|
||||
} else {
|
||||
len += snprintf(quoted + len, sizeof(quoted) - len, "%c", *p);
|
||||
}
|
||||
}
|
||||
if (dir == 0) {
|
||||
DBG("Received: '%s'", quoted);
|
||||
} else {
|
||||
DBG("Sending '%s'", quoted);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ttymode {
|
||||
int set;
|
||||
int speed;
|
||||
int parity;
|
||||
int bits;
|
||||
int stopbits;
|
||||
};
|
||||
|
||||
static int parse_mode (const char *str, struct ttymode *mode)
|
||||
{
|
||||
char buf[256];
|
||||
char *t1, *t2;
|
||||
|
||||
/* parse string */
|
||||
if (strlen(str) >= sizeof(buf)) {
|
||||
return -1;
|
||||
}
|
||||
strncpy(buf, str, sizeof(buf));
|
||||
t1 = strtok(buf, " ");
|
||||
if (!t1) {
|
||||
ERR("Invalid format");
|
||||
return -1;
|
||||
}
|
||||
t2 = strtok(NULL, " ");
|
||||
if (!t2 || strlen(t2) != 3) {
|
||||
ERR("Invalid format");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* parse speed */
|
||||
switch (atoi(t1)) {
|
||||
case 9600:
|
||||
mode->speed = B9600;
|
||||
break;
|
||||
case 19200:
|
||||
mode->speed = B19200;
|
||||
break;
|
||||
case 38400:
|
||||
mode->speed = B38400;
|
||||
break;
|
||||
case 57600:
|
||||
mode->speed = B57600;
|
||||
break;
|
||||
case 115200:
|
||||
mode->speed = B115200;
|
||||
break;
|
||||
default:
|
||||
ERR("Invalid baudrate");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* parse bits */
|
||||
switch (t2[0]) {
|
||||
case '5':
|
||||
mode->bits = CS5;
|
||||
break;
|
||||
case '6':
|
||||
mode->bits = CS6;
|
||||
break;
|
||||
case '7':
|
||||
mode->bits = CS7;
|
||||
break;
|
||||
case '8':
|
||||
mode->bits = CS8;
|
||||
break;
|
||||
default:
|
||||
ERR("Invalid bits");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* parse parity */
|
||||
switch (t2[1]) {
|
||||
case 'N':
|
||||
mode->parity = 0;
|
||||
break;
|
||||
case 'E':
|
||||
mode->parity = PARENB;
|
||||
break;
|
||||
case 'O':
|
||||
mode->parity = PARENB | PARODD;
|
||||
break;
|
||||
default:
|
||||
ERR("Invalid parity");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* parse stopbits */
|
||||
switch (t2[2]) {
|
||||
case '1':
|
||||
mode->stopbits = 0;
|
||||
break;
|
||||
case '2':
|
||||
mode->stopbits = CSTOPB;
|
||||
break;
|
||||
default:
|
||||
ERR("Invalid stop bits %c");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int setup (const char *tty, int timeout, struct ttymode *mode)
|
||||
{
|
||||
struct termios stbuf;
|
||||
struct stat st;
|
||||
char dev[32];
|
||||
int i, fd;
|
||||
|
||||
if (strncmp(tty, "/dev", 4) == 0) {
|
||||
strncpy(dev, tty, sizeof(dev));
|
||||
} else {
|
||||
snprintf(dev, sizeof(dev), "/dev/%s", tty);
|
||||
}
|
||||
|
||||
/* wait for device */
|
||||
for (i = 0; i < timeout; i++) {
|
||||
if (lstat(dev, &st) == 0) break;
|
||||
sleep(1);
|
||||
}
|
||||
if (i >= timeout) {
|
||||
ERR("Timeout waiting for %s", dev);
|
||||
return -1;
|
||||
}
|
||||
|
||||
DBG("Opening %s", tty);
|
||||
fd = open(dev, O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY);
|
||||
if (fd < 0) {
|
||||
ERR("Unable to open '%s'", dev);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!mode->set) return fd;
|
||||
|
||||
memset(&stbuf, 0, sizeof (struct termios));
|
||||
if (tcgetattr(fd, &stbuf) != 0) {
|
||||
ERR("Unable to get serial attributes");
|
||||
goto error;
|
||||
}
|
||||
/* default settings */
|
||||
stbuf.c_cflag &= ~(CBAUD | CSIZE | CSTOPB | PARENB | PARODD | CRTSCTS);
|
||||
stbuf.c_iflag &= ~(IGNCR | ICRNL | IUCLC | INPCK | IXON | IXOFF | IXANY );
|
||||
stbuf.c_oflag &= ~(OPOST | OLCUC | OCRNL | ONLCR | ONLRET);
|
||||
stbuf.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL);
|
||||
stbuf.c_cc[VMIN] = 1;
|
||||
stbuf.c_cc[VTIME] = 0;
|
||||
stbuf.c_cc[VEOF] = 1;
|
||||
|
||||
/* ignore parity */
|
||||
stbuf.c_iflag |= IGNPAR;
|
||||
|
||||
/* configure serial attributes */
|
||||
stbuf.c_cflag |= (mode->bits | mode->parity | mode->stopbits | CLOCAL | CREAD);
|
||||
|
||||
/* disable XON/XOFF flow control */
|
||||
stbuf.c_iflag &= ~(IXON | IXOFF | IXANY);
|
||||
|
||||
/* disable RTS/CTS flow control */
|
||||
stbuf.c_cflag &= ~(CRTSCTS);
|
||||
|
||||
if (cfsetispeed(&stbuf, mode->speed) != 0 ||
|
||||
cfsetospeed(&stbuf, mode->speed) != 0) {
|
||||
ERR("Unable to set port speed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (tcsetattr(fd, TCSANOW, &stbuf) != 0) {
|
||||
ERR("Unable to set serial attributes");
|
||||
goto error;
|
||||
}
|
||||
|
||||
return fd;
|
||||
error:
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int serial_write (int fd, int timeout, char *buf, int len)
|
||||
{
|
||||
struct timeval tv;
|
||||
fd_set fds;
|
||||
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(fd, &fds);
|
||||
tv.tv_sec = timeout;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
if (select(fd + 1, NULL, &fds, NULL, &tv) <= 0) {
|
||||
DBG("Cannot write to serial device");
|
||||
return -1;
|
||||
}
|
||||
alarm(timeout);
|
||||
tcflush(fd, TCIOFLUSH);
|
||||
|
||||
printser(1, buf);
|
||||
if (write(fd, buf, len) != len) {
|
||||
return -1;
|
||||
}
|
||||
tcdrain(fd);
|
||||
alarm(0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int serial_read (int fd, int timeout, char *buf, int size)
|
||||
{
|
||||
struct timeval tv;
|
||||
fd_set fds;
|
||||
int rc, len = 0;
|
||||
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(fd, &fds);
|
||||
tv.tv_sec = timeout;
|
||||
tv.tv_usec = 0;
|
||||
memset(buf, 0, size);
|
||||
|
||||
rc = select(fd + 1, &fds, NULL, NULL, &tv);
|
||||
if (rc <= 0) {
|
||||
DBG("Cannot read from serial device (%d)", rc);
|
||||
return -1;
|
||||
}
|
||||
do {
|
||||
errno = 0;
|
||||
alarm(timeout);
|
||||
rc = read(fd, buf+len, size-len-1);
|
||||
alarm(0);
|
||||
|
||||
if (rc > 0) {
|
||||
len += rc;
|
||||
} else {
|
||||
if (rc < 0 && errno == -EAGAIN) {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (1);
|
||||
|
||||
if (len > 0) buf[len] = '\0';
|
||||
|
||||
printser(0, buf);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int execute (int fd, int timeout, const char *cmd, const char *expect)
|
||||
{
|
||||
char buf[1024];
|
||||
time_t start;
|
||||
long elapsed;
|
||||
int len, ret = -1;
|
||||
|
||||
if (cmd) {
|
||||
start = time(NULL);
|
||||
len = snprintf(buf, sizeof(buf), "%s\r\n", cmd);
|
||||
if (serial_write(fd, timeout, buf, len) < 0) {
|
||||
DBG("Cannot write to serial device");
|
||||
return -1;
|
||||
}
|
||||
elapsed = time(NULL) - start;
|
||||
timeout -= elapsed;
|
||||
}
|
||||
|
||||
DBG("Waiting for response");
|
||||
|
||||
while (timeout > 0) {
|
||||
start = time(NULL);
|
||||
len = serial_read(fd, timeout, buf, sizeof(buf));
|
||||
if (len > 0) {
|
||||
buf[len] = '\0';
|
||||
SAY("%s", buf);
|
||||
|
||||
if (strstr(buf, expect)) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
elapsed = time(NULL) - start;
|
||||
timeout -= elapsed;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int probe (int fd)
|
||||
{
|
||||
char buf[256];
|
||||
int i, len;
|
||||
|
||||
DBG("Probing serial port");
|
||||
|
||||
/* try 3 times */
|
||||
for (i = 3; i > 0; i--) {
|
||||
len = snprintf(buf, sizeof(buf), "AT\r\n");
|
||||
if (serial_write(fd, 1, buf, len) < 0) {
|
||||
DBG("Cannot write to tty");
|
||||
continue;
|
||||
}
|
||||
len = serial_read(fd, 1, buf, sizeof(buf));
|
||||
if (len <= 0) {
|
||||
DBG("Cannot read from tty");
|
||||
continue;
|
||||
}
|
||||
if (strstr(buf, "OK")) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
DBG("Probing failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int modemidx (const char *devpath)
|
||||
{
|
||||
json_t *json, *modules, *module, *mod, *paths, *obj;
|
||||
const char *p;
|
||||
int i, j, index = -1;
|
||||
|
||||
json = json_load_file("/run/modules.json", 0, NULL);
|
||||
if (!json) {
|
||||
return -1;
|
||||
}
|
||||
modules = json_object_get(json, "modules");
|
||||
if (!modules || !json_is_array(modules)) {
|
||||
goto out;
|
||||
}
|
||||
for (i = 0; i < json_array_size(modules); i++) {
|
||||
module = json_array_get(modules, i);
|
||||
if (!module) continue;
|
||||
|
||||
obj = json_object_get(module, "type");
|
||||
if (!obj || !json_is_string(obj)) continue;
|
||||
p = json_string_value(obj);
|
||||
if (strcmp(p, "modem") != 0) continue;
|
||||
|
||||
obj = json_object_get(module, "index");
|
||||
if (!obj || !json_is_integer(obj)) continue;
|
||||
index = json_integer_value(obj);
|
||||
|
||||
paths = json_object_get(module, "paths");
|
||||
if (!paths || !json_is_array(paths)) continue;
|
||||
|
||||
for (j = 0; j < json_array_size(paths); j++) {
|
||||
obj = json_array_get(paths, j);
|
||||
if (!obj) continue;
|
||||
p = json_string_value(obj);
|
||||
|
||||
if (strstr(devpath, p)) {
|
||||
/* found */
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
index = -1;
|
||||
out:
|
||||
json_decref(json);
|
||||
return index;
|
||||
}
|
||||
|
||||
static char *modemdev (int index, int port)
|
||||
{
|
||||
json_t *json, *modems, *modem, *ports, *obj;
|
||||
char *dev = NULL;
|
||||
int i, p, fd;
|
||||
|
||||
fd = open(MODEMS_LOCK,
|
||||
O_WRONLY | O_CREAT | O_EXCL,
|
||||
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
||||
if (fd < 0) {
|
||||
ERR("Cannot open lock");
|
||||
return NULL;
|
||||
}
|
||||
if (flock(fd, LOCK_EX) < 0) {
|
||||
ERR("Cannot to lock");
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
json = json_load_file(MODEMS_INFO, 0, NULL);
|
||||
if (!json) {
|
||||
goto out;
|
||||
}
|
||||
modems = json_object_get(json, "modems");
|
||||
if (!modems || !json_is_array(modems)) {
|
||||
goto out;
|
||||
}
|
||||
for (i = 0; i < json_array_size(modems); i++) {
|
||||
modem = json_array_get(modems, i);
|
||||
if (!modem)
|
||||
continue;
|
||||
|
||||
obj = json_object_get(modem, "devpath");
|
||||
if (!obj || !json_is_string(obj))
|
||||
continue;
|
||||
|
||||
if (modemidx(json_string_value(obj)) != index)
|
||||
continue;
|
||||
|
||||
ports = json_object_get(modem, "atports");
|
||||
if (!ports || !json_is_array(ports) || json_array_size(ports) == 0)
|
||||
goto out;
|
||||
|
||||
if (json_array_size(ports) >= port)
|
||||
port = 0;
|
||||
|
||||
obj = json_array_get(ports, port);
|
||||
if (!obj || !json_is_string(obj))
|
||||
goto out;
|
||||
|
||||
/* found */
|
||||
dev = strdup(json_string_value(obj));
|
||||
goto out;
|
||||
}
|
||||
dev = NULL;
|
||||
|
||||
out:
|
||||
if (json) json_decref(json);
|
||||
|
||||
unlink(MODEMS_LOCK);
|
||||
flock(fd, LOCK_UN);
|
||||
close(fd);
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
static struct option options[] = {
|
||||
{ "index", required_argument, 0, 'i' },
|
||||
{ "device", required_argument, 0, 'd' },
|
||||
{ "mode", required_argument, 0, 'm' },
|
||||
{ "command", required_argument, 0, 'c' },
|
||||
{ "expect", required_argument, 0, 'e' },
|
||||
{ "timeout", required_argument, 0, 't' },
|
||||
{ "primary", no_argument, 0, 'P' },
|
||||
{ "secondary", no_argument, 0, 'S' },
|
||||
{ "verbose", no_argument, 0, 'v' },
|
||||
{ "help", no_argument, 0, 'h' },
|
||||
{ 0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
static void usage (void)
|
||||
{
|
||||
fprintf(stderr, "Usage: modem-command [OPTIONS] [command]\n\n");
|
||||
fprintf(stderr, " --index <idx> Index of modem\n");
|
||||
fprintf(stderr, " --device <tty> TTY device of modem\n");
|
||||
fprintf(stderr, " --mode <mode> Serial mode for TTY (e.g. '115200 8N1')\n");
|
||||
fprintf(stderr, " --expect <str> String to expect\n");
|
||||
fprintf(stderr, " --timeout <to> Timeout for operation\n");
|
||||
fprintf(stderr, " --primary Use primary atport\n");
|
||||
fprintf(stderr, " --secondary Use secondary atport\n");
|
||||
fprintf(stderr, " --verbose Be verbose\n");
|
||||
fprintf(stderr, " --help print help\n\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[])
|
||||
{
|
||||
const char *cmd = NULL, *expect = "OK";
|
||||
char *dev = NULL;
|
||||
struct ttymode mode;
|
||||
int timeout = TIMEOUT;
|
||||
int index = 0, port = 0;
|
||||
int opt, c, fd;
|
||||
int ret = 1;
|
||||
|
||||
memset(&mode, 0, sizeof(struct ttymode));
|
||||
|
||||
while (1) {
|
||||
c = getopt_long(argc, argv, "i:d:m:e:t:PSvh", options, &opt);
|
||||
if (c < 0) break;
|
||||
|
||||
switch (c) {
|
||||
case 'i':
|
||||
index = atoi(optarg);
|
||||
break;
|
||||
case 'd':
|
||||
dev = strdup(optarg);
|
||||
break;
|
||||
case 'm':
|
||||
if (parse_mode(optarg, &mode) < 0) {
|
||||
FATAL("Invalid serial mode specified");
|
||||
}
|
||||
mode.set = 1;
|
||||
break;
|
||||
case 'c':
|
||||
cmd = optarg;
|
||||
break;
|
||||
case 'e':
|
||||
expect = optarg;
|
||||
break;
|
||||
case 't':
|
||||
timeout = atoi(optarg);
|
||||
break;
|
||||
case 'P':
|
||||
port = 0;
|
||||
break;
|
||||
case 'S':
|
||||
port = 1;
|
||||
break;
|
||||
case 'v':
|
||||
verbose = 1;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind < argc) {
|
||||
cmd = argv[optind];
|
||||
if (strlen(cmd) == 0)
|
||||
FATAL("Invalid command");
|
||||
}
|
||||
dev = modemdev(index, port);
|
||||
if (!dev || strlen(dev) == 0) {
|
||||
FATAL("No device found");
|
||||
}
|
||||
fd = setup(dev, timeout, &mode);
|
||||
if (fd < 0) {
|
||||
FATAL("Unable to setup device");
|
||||
}
|
||||
|
||||
if (probe(fd) < 0) {
|
||||
ERR("Unable to probe device");
|
||||
goto abort;
|
||||
}
|
||||
if (execute(fd, timeout, cmd, expect) < 0) {
|
||||
ERR("Command failed");
|
||||
goto abort;
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
abort:
|
||||
close(fd);
|
||||
if (dev) free(dev);
|
||||
return ret;
|
||||
}
|
||||
Executable
+432
@@ -0,0 +1,432 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import argparse
|
||||
import syslog
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
debug = False
|
||||
|
||||
syslog.openlog(logoption=syslog.LOG_PID, facility=syslog.LOG_SYSLOG)
|
||||
|
||||
|
||||
def dbg(msg):
|
||||
global debug
|
||||
if debug:
|
||||
syslog.syslog(syslog.LOG_INFO, msg)
|
||||
|
||||
|
||||
def runcmd(cmd):
|
||||
ret = None
|
||||
try:
|
||||
res = subprocess.run(cmd, check=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, text=True)
|
||||
if res.returncode == 0:
|
||||
if res.stdout:
|
||||
ret = res.stdout.strip()
|
||||
else:
|
||||
ret = True
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
finally:
|
||||
return ret
|
||||
|
||||
|
||||
def runcmdj(cmd):
|
||||
output = runcmd(cmd)
|
||||
if not output:
|
||||
return None
|
||||
try:
|
||||
data = json.loads(output)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
finally:
|
||||
return data
|
||||
|
||||
|
||||
def vbool(val):
|
||||
if val == "yes" or val == "true":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def vnum(val):
|
||||
if val != "--":
|
||||
return int(val)
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
def vnumstr(val):
|
||||
if val != "" and val != "--":
|
||||
return val
|
||||
else:
|
||||
return "0"
|
||||
|
||||
|
||||
def vstr(val):
|
||||
if val != "--":
|
||||
return val
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def venum(val, enums, default):
|
||||
enval = vstr(val)
|
||||
for en in enums:
|
||||
if enval == en:
|
||||
return en
|
||||
return default
|
||||
|
||||
|
||||
def vip4(val):
|
||||
val = vstr(val)
|
||||
if val == "":
|
||||
return "0.0.0.0"
|
||||
else:
|
||||
return val
|
||||
|
||||
|
||||
def vip6(val):
|
||||
val = vstr(val)
|
||||
if val == "":
|
||||
return "::"
|
||||
else:
|
||||
return val
|
||||
|
||||
|
||||
def fread(path):
|
||||
if os.path.exists(path):
|
||||
with open(path, "r") as fd:
|
||||
output = str(fd.read())
|
||||
if output:
|
||||
return output.strip()
|
||||
return None
|
||||
|
||||
|
||||
def freadj(path):
|
||||
output = fread(path)
|
||||
if not output:
|
||||
return None
|
||||
try:
|
||||
data = json.loads(output)
|
||||
except json.JSONDecodeError:
|
||||
dbg("Unable to parse json output")
|
||||
return None
|
||||
finally:
|
||||
return data
|
||||
|
||||
|
||||
def get_manufacturer(index, gen):
|
||||
path = "/run/modemd/modem%d/manufacturer" % index
|
||||
manf = fread(path)
|
||||
if manf:
|
||||
return manf
|
||||
|
||||
manf = vstr(gen.get("manufacturer", ""))
|
||||
return manf
|
||||
|
||||
|
||||
def get_model(index, gen):
|
||||
path = "/run/modemd/modem%d/model" % index
|
||||
model = fread(path)
|
||||
if model:
|
||||
return model
|
||||
|
||||
model = vstr(gen.get("model", ""))
|
||||
return model
|
||||
|
||||
|
||||
def get_supported_carriers(index, manf, model):
|
||||
path = "/run/modemd/modem%d/supported-carriers" % index
|
||||
output = freadj(path)
|
||||
if output:
|
||||
return output
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def get_current_carrier(index, manf, model):
|
||||
path = "/run/modemd/modem%d/current-carrier" % index
|
||||
output = freadj(path)
|
||||
if output:
|
||||
return output
|
||||
else:
|
||||
return "default"
|
||||
|
||||
|
||||
def device_devpath(devpath):
|
||||
path = devpath
|
||||
while path != "/sys":
|
||||
path = os.path.realpath("%s/.." % path)
|
||||
subdir = os.path.basename(path)
|
||||
r = re.search(r"usb\d", subdir)
|
||||
if r:
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
def modem_get_module(devpath):
|
||||
if not os.path.exists("/run/modules.json"):
|
||||
if os.path.exists("/run/modems.json"):
|
||||
with open("/run/modems.json", "r") as fd:
|
||||
data = json.load(fd)
|
||||
for index, modem in enumerate(data.get("modems", [])):
|
||||
if devpath in modem["devpath"]:
|
||||
return {"index": index, "slot": index, "paths": [modem["devpath"]], "type": "modem"}
|
||||
return None
|
||||
|
||||
with open("/run/modules.json", "r") as fd:
|
||||
data = json.load(fd)
|
||||
|
||||
for module in data["modules"]:
|
||||
if module["type"] != "modem":
|
||||
continue
|
||||
for path in module["paths"]:
|
||||
if path in devpath:
|
||||
return module
|
||||
return None
|
||||
|
||||
|
||||
def modem_get_sim(module):
|
||||
path = "/sys/class/sim"
|
||||
for index in range(0, 5):
|
||||
name = "sim%d" % index
|
||||
if not os.path.exists("%s/%s" % (path, name)):
|
||||
continue
|
||||
|
||||
slot = int(fread("%s/%s/slot" % (path, name)))
|
||||
if slot != module["slot"]:
|
||||
continue
|
||||
|
||||
present = int(fread("%s/%s/present" % (path, name)))
|
||||
|
||||
return {
|
||||
"index": index,
|
||||
"name": name,
|
||||
"slot": slot,
|
||||
"present": bool(present)
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def sysfs_interfaces(devpath):
|
||||
ifaces = []
|
||||
try:
|
||||
for iface in os.listdir("/sys/class/net"):
|
||||
p = os.path.realpath("/sys/class/net/%s" % iface)
|
||||
if p.startswith(devpath):
|
||||
ifaces.append(iface)
|
||||
except OSError:
|
||||
pass
|
||||
return ifaces
|
||||
|
||||
|
||||
def print_modem(index):
|
||||
with open("/run/modems.json", "r") as fd:
|
||||
data = json.load(fd)
|
||||
|
||||
for modem in data["modems"]:
|
||||
module = modem_get_module(modem["devpath"])
|
||||
if module["index"] == index:
|
||||
modem["slot"] = module["slot"]
|
||||
modem["sim"] = modem_get_sim(module) or {"index": 0, "name": "sim0", "slot": 0, "present": True}
|
||||
if not modem.get("interfaces"):
|
||||
modem["interfaces"] = sysfs_interfaces(modem["devpath"])
|
||||
print(json.dumps(modem))
|
||||
sys.exit(0)
|
||||
|
||||
print(json.dumps({}))
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def print_all():
|
||||
modems = []
|
||||
|
||||
output = runcmdj(['mmcli', '-J', '-L'])
|
||||
if not output:
|
||||
dbg("mmcli command failed")
|
||||
print(json.dumps(modems))
|
||||
sys.exit(0)
|
||||
|
||||
for mpath in output['modem-list']:
|
||||
output = runcmdj(['mmcli', '-J', '-m', mpath])
|
||||
if not output:
|
||||
dbg("no output for %s" % mpath)
|
||||
continue
|
||||
modem = output.get("modem")
|
||||
if modem is None:
|
||||
dbg("no modem for %s" % mpath)
|
||||
continue
|
||||
gen = modem.get("generic")
|
||||
if gen is None:
|
||||
dbg("no generic for %s" % mpath)
|
||||
continue
|
||||
gpp = modem.get("3gpp")
|
||||
if gpp is None:
|
||||
dbg("no 3gpp for %s" % mpath)
|
||||
continue
|
||||
|
||||
devpath = device_devpath(gen["device"])
|
||||
if devpath is None:
|
||||
dbg("no devpath for %s" % mpath)
|
||||
continue
|
||||
|
||||
module = modem_get_module(devpath)
|
||||
if module is None:
|
||||
dbg("no modem module for %s" % devpath)
|
||||
continue
|
||||
|
||||
index = module["index"]
|
||||
|
||||
manf = get_manufacturer(index, gen)
|
||||
if manf is None:
|
||||
dbg("no manufacturer for modem%d" % index)
|
||||
continue
|
||||
|
||||
model = get_model(index, gen)
|
||||
if model is None:
|
||||
dbg("no model for modem%d" % index)
|
||||
continue
|
||||
|
||||
modem = {
|
||||
"index": index,
|
||||
"path": mpath
|
||||
}
|
||||
|
||||
modem["info"] = {
|
||||
"manufacturer": manf,
|
||||
"model": model,
|
||||
"supported-carrier": get_supported_carriers(index, manf, model),
|
||||
"hardware-revision": vstr(gen["hardware-revision"]),
|
||||
"firmware-version": vstr(gen["revision"]),
|
||||
"serial-number": vstr(gen["equipment-identifier"]),
|
||||
"phone-number": gen.get("own-numbers", []),
|
||||
}
|
||||
|
||||
modem["status"] = {
|
||||
"state": vstr(gen["state"]),
|
||||
"selected-carrier": get_current_carrier(index, manf, model),
|
||||
"signal-quality": vnum(gen["signal-quality"]["value"])
|
||||
}
|
||||
|
||||
if gen["state"] == "failed":
|
||||
reason = vstr(gen["state-failed-reason"])
|
||||
if len(reason) > 0:
|
||||
modem["status"]["state-failed-reason"] = reason
|
||||
|
||||
sim = modem_get_sim(module)
|
||||
if sim is not None:
|
||||
modem["status"]["sim-active"] = sim["index"]
|
||||
modem["status"]["sim-present"] = sim["present"]
|
||||
|
||||
refresh = 0
|
||||
output = runcmdj(['mmcli', '-J', '-m', mpath, '--signal-get'])
|
||||
if output:
|
||||
refresh = int(output["modem"]["signal"]["refresh"]["rate"])
|
||||
if refresh == 0:
|
||||
# enable extended signal info
|
||||
runcmd(['mmcli', '-J', '-m', mpath, '--signal-setup=5'])
|
||||
else:
|
||||
signals = ["rssi", "rsrp", "rsrq", "rscp", "snr", "sinr"]
|
||||
found = False
|
||||
for tech in output["modem"]["signal"].keys():
|
||||
for sig in signals:
|
||||
v = output["modem"]["signal"][tech].get(sig, "--")
|
||||
if v != "--":
|
||||
modem["status"]["signal-%s" % sig] = v
|
||||
found = True
|
||||
if found:
|
||||
break
|
||||
|
||||
modem["status"]["cellular"] = {
|
||||
"registration-state":
|
||||
venum(gpp["registration-state"],
|
||||
["idle", "home", "searching", "denied", "roaming"],
|
||||
"unknown"),
|
||||
"operator-name": vstr(gpp["operator-name"]),
|
||||
"operator-id": vstr(gpp["operator-code"]),
|
||||
"network-type": ",".join(vstr(gen["access-technologies"])).upper(),
|
||||
"service-state": vstr(gpp["packet-service-state"])
|
||||
}
|
||||
|
||||
bearers = []
|
||||
for bpath in gen["bearers"]:
|
||||
output = runcmdj(['mmcli', '-J', '-m', mpath, '-b', bpath])
|
||||
if not output:
|
||||
continue
|
||||
|
||||
b = output["bearer"]
|
||||
|
||||
bearer = {
|
||||
"path": vstr(bpath),
|
||||
"connected": vbool(b["status"]["connected"]),
|
||||
"ipv4-address": vip4(b["ipv4-config"]["address"]),
|
||||
"ipv4-prefix": vnum(b["ipv4-config"]["prefix"]),
|
||||
"ipv6-address": vip6(b["ipv6-config"]["address"]),
|
||||
"ipv6-prefix": vnum(b["ipv6-config"]["prefix"]),
|
||||
"in-bytes": vnumstr(b["stats"]["bytes-rx"]),
|
||||
"out-bytes": vnumstr(b["stats"]["bytes-tx"]),
|
||||
"total-in-bytes": vnumstr(b["stats"]["total-bytes-rx"]),
|
||||
"total-out-bytes": vnumstr(b["stats"]["total-bytes-tx"]),
|
||||
"total-duration": vnumstr(b["stats"]["total-duration"])
|
||||
}
|
||||
if b["status"]["connected"] != "yes":
|
||||
reason = vstr(b["status"]["connection-error"]["message"])
|
||||
if len(reason) > 0:
|
||||
bearer["connection-failed-reason"] = reason
|
||||
|
||||
iface = vstr(b["status"]["interface"])
|
||||
if len(iface) > 0:
|
||||
bearer["interface"] = iface
|
||||
|
||||
bearers.append(bearer)
|
||||
|
||||
modem["status"]["bearer"] = bearers
|
||||
|
||||
output = runcmdj(['mmcli', '-J', '-m', mpath, '-i', gen["sim"]])
|
||||
if output:
|
||||
modem["info"]["imsi"] = output["sim"]["properties"]["imsi"]
|
||||
modem["info"]["iccid"] = output["sim"]["properties"]["iccid"]
|
||||
|
||||
output = runcmdj(['mmcli', '-J', '-m', mpath, '--location-get'])
|
||||
if output:
|
||||
loc = output["modem"]["location"]
|
||||
modem["status"]["location"] = {
|
||||
"latitude": vstr(loc["gps"]["latitude"]),
|
||||
"longitude": vstr(loc["gps"]["longitude"]),
|
||||
"altitude": vstr(loc["gps"]["altitude"]),
|
||||
"cid": vstr(loc["3gpp"]["cid"]),
|
||||
"lac": vstr(loc["3gpp"]["lac"]),
|
||||
"mcc": vstr(loc["3gpp"]["mcc"]),
|
||||
"mnc": vstr(loc["3gpp"]["mnc"]),
|
||||
"tac": vstr(loc["3gpp"]["tac"])
|
||||
}
|
||||
|
||||
modems.append(modem)
|
||||
|
||||
modems = sorted(modems, key=lambda m: m['index'])
|
||||
data = json.dumps(modems)
|
||||
|
||||
print(data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(prog='modem-info')
|
||||
parser.add_argument("-i", "--index", default=0, help="Modem index")
|
||||
args = parser.parse_args()
|
||||
|
||||
with open("/proc/cmdline", 'r') as fd:
|
||||
cmdline = fd.read()
|
||||
if "debug" in cmdline:
|
||||
debug = True
|
||||
|
||||
if args.index:
|
||||
print_modem(int(args.index))
|
||||
else:
|
||||
print_all()
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import argparse
|
||||
import json
|
||||
import time
|
||||
import syslog
|
||||
import sys
|
||||
|
||||
syslog.openlog(logoption=syslog.LOG_PID, facility=syslog.LOG_SYSLOG)
|
||||
|
||||
|
||||
def log(msg):
|
||||
syslog.syslog(syslog.LOG_INFO, msg)
|
||||
|
||||
|
||||
def err(msg):
|
||||
syslog.syslog(syslog.LOG_ERR, msg)
|
||||
|
||||
|
||||
def fatal(msg):
|
||||
syslog.syslog(syslog.LOG_ALERT, msg)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def runcmd(cmd):
|
||||
ret = None
|
||||
try:
|
||||
res = subprocess.run(cmd, check=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, text=True)
|
||||
if res.returncode == 0:
|
||||
if res.stdout:
|
||||
ret = res.stdout.strip()
|
||||
else:
|
||||
ret = True
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
finally:
|
||||
return ret
|
||||
|
||||
|
||||
def runcmdj(cmd):
|
||||
output = runcmd(cmd)
|
||||
if not output:
|
||||
return None
|
||||
try:
|
||||
data = json.loads(output)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
finally:
|
||||
return data
|
||||
|
||||
|
||||
def reset(index, info):
|
||||
log("Power-cycling modem %d" % index)
|
||||
|
||||
with open("%s/authorized" % info["devpath"], "w") as fd:
|
||||
fd.write("0\n")
|
||||
time.sleep(0.5)
|
||||
|
||||
path = "/sys/class/pcie/slot%d/pwrctl" % info["slot"]
|
||||
with open(path, "w") as fd:
|
||||
fd.write("cycle\n")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(prog='modem-power')
|
||||
parser.add_argument("-i", "--index", default=0, help="Modem index")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.index:
|
||||
index = int(args.index)
|
||||
else:
|
||||
index = 0
|
||||
|
||||
info = runcmdj(['/usr/libexec/modemd/modem-info',
|
||||
'-i', str(index)])
|
||||
if info is None:
|
||||
fatal("Unable to obtain modem info")
|
||||
|
||||
if not reset(index, info):
|
||||
fatal("Unable to power-cycle modem")
|
||||
|
||||
sys.exit(0)
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
def sendrpc(index, rpc):
|
||||
path = "/run/modemd/modem%d/rpc" % index
|
||||
if os.path.exists(path):
|
||||
sys.stderr.write("Error: another rpc is running\n")
|
||||
return
|
||||
|
||||
with open(path, "w") as fd:
|
||||
json.dump(rpc, fd)
|
||||
|
||||
if not os.system(['sysrepocfg', '-f', 'json', '-R', path]):
|
||||
sys.stderr.write("Error: unable to restart\n")
|
||||
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(prog='modem-rpc')
|
||||
parser.add_argument("-i", "--index", default=0, help="Modem index")
|
||||
parser.add_argument("-r", "--rpc", default=0, help="RPC command")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.index:
|
||||
index = int(args.index)
|
||||
else:
|
||||
index = 0
|
||||
|
||||
if not args.rpc:
|
||||
sys.stderr.write("Error: no rpc command\n")
|
||||
sys.exit(1)
|
||||
|
||||
print("Sending '%s' rpc to modem%d" % (args.rpc, index))
|
||||
|
||||
rpc = {"infix-modem:%s" % args.rpc: {"index": str(index)}}
|
||||
|
||||
sendrpc(index, rpc)
|
||||
Executable
+79
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import json
|
||||
import sys
|
||||
|
||||
index = 0
|
||||
|
||||
|
||||
def runcmd(cmd):
|
||||
ret = None
|
||||
try:
|
||||
res = subprocess.run(cmd, check=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, text=True)
|
||||
if res.returncode == 0:
|
||||
if res.stdout:
|
||||
ret = res.stdout.strip()
|
||||
else:
|
||||
ret = True
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
finally:
|
||||
return ret
|
||||
|
||||
|
||||
def runcmdj(cmd):
|
||||
output = runcmd(cmd)
|
||||
if not output:
|
||||
return None
|
||||
try:
|
||||
data = json.loads(output)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
finally:
|
||||
return data
|
||||
|
||||
|
||||
def scan(mpath):
|
||||
output = runcmdj(['mmcli', '-J', '-m', mpath,
|
||||
'--3gpp-scan', '--timeout', '60'])
|
||||
if not output:
|
||||
return False
|
||||
|
||||
networks = []
|
||||
for entry in output["modem"]["3gpp"]["scan-networks"]:
|
||||
net = {}
|
||||
for attr in entry.split(", "):
|
||||
(key, value) = attr.split(": ")
|
||||
net[key] = value
|
||||
|
||||
if "operator-name" in net:
|
||||
networks.append(net)
|
||||
|
||||
if len(networks) == 0:
|
||||
print("No networks found")
|
||||
return True
|
||||
|
||||
for net in networks:
|
||||
print("Network '%s' %s (%s, %s)" % (
|
||||
net.get("operator-name"),
|
||||
net.get("operator-code"),
|
||||
net.get("access-technologies"),
|
||||
net.get("availability")))
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(prog='modem-scan-networks')
|
||||
parser.add_argument("-i", "--index", default=0, help="Modem index")
|
||||
args = parser.parse_args()
|
||||
if args.index:
|
||||
index = int(args.index)
|
||||
|
||||
for m in runcmdj(['/usr/libexec/modemd/modem-info']):
|
||||
if m["index"] == index:
|
||||
print("Scanning networks on modem%d, please stand by ..." % index)
|
||||
if not scan(m["path"]):
|
||||
print("Operation failed.", file=sys.stderr)
|
||||
Executable
+136
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from datetime import datetime
|
||||
import argparse
|
||||
import locale
|
||||
import subprocess
|
||||
import json
|
||||
import glob
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
|
||||
LOCALEPATH = "/usr/lib/locale"
|
||||
|
||||
|
||||
def run_cmd(cmd):
|
||||
try:
|
||||
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||
return True if res.returncode == 0 else False
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def send(index, number, text):
|
||||
if index < 0:
|
||||
# use first modem by default
|
||||
index = 0
|
||||
|
||||
rpc = {
|
||||
"infix-modem:send-sms": {
|
||||
"index": str(index),
|
||||
"phone-number": number,
|
||||
"message-text": text
|
||||
}
|
||||
}
|
||||
print("Sending SMS to modem%d" % index)
|
||||
|
||||
path = "/run/modemd/modem%d/rpc" % index
|
||||
if os.path.exists(path):
|
||||
sys.stderr.write("Error: another rpc is running\n")
|
||||
return
|
||||
|
||||
with open(path, "w") as fd:
|
||||
json.dump(rpc, fd)
|
||||
|
||||
if not run_cmd(['sysrepocfg', '-f', 'json', '-R', path]):
|
||||
sys.stderr.write("Error: Unable to send SMS\n")
|
||||
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
def listsms():
|
||||
smslist = []
|
||||
files = glob.glob('/var/sms/*')
|
||||
for f in files:
|
||||
with open(f, "r") as fd:
|
||||
sms = json.load(fd)
|
||||
sms["path"] = f
|
||||
|
||||
t = sms["payload"]["properties"]["timestamp"]
|
||||
t = re.sub("[+-][\\d:]+$", "", t)
|
||||
sms["time"] = datetime.strptime(t, "%Y-%m-%dT%H:%M:%S")
|
||||
|
||||
smslist.append(sms)
|
||||
|
||||
return sorted(smslist, key=lambda sms: sms["time"])
|
||||
|
||||
|
||||
def delete(index=-1):
|
||||
count = 0
|
||||
for sms in listsms():
|
||||
if index > -1 and sms["modem"] == index:
|
||||
os.remove(sms["path"])
|
||||
count += 1
|
||||
print("Deleted %d SMS files" % count)
|
||||
|
||||
|
||||
def show(index=-1):
|
||||
for sms in listsms():
|
||||
if index > -1 and sms["modem"] != index:
|
||||
continue
|
||||
|
||||
content = sms["payload"]["content"]
|
||||
prop = sms["payload"]["properties"]
|
||||
|
||||
print("--- %s ---\n" % os.path.basename(sms["path"]))
|
||||
print("From: %s" % content["number"])
|
||||
print("SMSC: %s" % prop["smsc"])
|
||||
print("Modem: %d" % sms["modem"])
|
||||
print("Date: %s" % prop["timestamp"])
|
||||
print("State: %s" % prop["state"])
|
||||
print("\n%s\n" % content["text"])
|
||||
|
||||
|
||||
def listlocale():
|
||||
locales = []
|
||||
for f in os.listdir(LOCALEPATH):
|
||||
if os.path.isdir(os.path.join(LOCALEPATH, f)):
|
||||
locales.append(f)
|
||||
return ", ".join(locales)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(prog='modem-sms')
|
||||
parser.add_argument("-d", action="store_true", help="Delete SMS")
|
||||
parser.add_argument("-s", action="store_true", help="Send SMS")
|
||||
|
||||
parser.add_argument("-i", "--index", default=0, help="Modem index")
|
||||
parser.add_argument("-n", "--number", default=None, help="Phone number")
|
||||
parser.add_argument("-t", "--text", default=None, help="Message text")
|
||||
|
||||
parser.add_argument("-l", "--locale", default=None, help="Set locale")
|
||||
args = parser.parse_args()
|
||||
|
||||
index = -1
|
||||
if args.index:
|
||||
index = int(args.index)
|
||||
|
||||
if args.locale:
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, args.locale)
|
||||
except locale.Error:
|
||||
print("Error: Invalid locale (available: %s)" % listlocale())
|
||||
sys.exit(1)
|
||||
|
||||
if args.s:
|
||||
if not args.number:
|
||||
sys.stderr.write("Error: need number\n")
|
||||
elif not args.text:
|
||||
sys.stderr.write("Error: need text\n")
|
||||
else:
|
||||
send(index, args.number, args.text)
|
||||
elif args.d:
|
||||
delete(index)
|
||||
else:
|
||||
show(index)
|
||||
Executable
+294
@@ -0,0 +1,294 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import shutil
|
||||
import errno
|
||||
import time
|
||||
import json
|
||||
import fcntl
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
MODEMS = "/run/modems.json"
|
||||
LOCK = "/var/lock/modems.lock"
|
||||
LOGFILE = "/run/modemd/modem-udev.log"
|
||||
debug = False
|
||||
|
||||
|
||||
def log(msg):
|
||||
ts = '{:010.3f}'.format(time.monotonic())
|
||||
with open(LOGFILE, 'a') as fd:
|
||||
fd.write("[%s] modem-udev (pid %d) %s\n" % (ts, os.getpid(), msg))
|
||||
|
||||
|
||||
def dbg(msg):
|
||||
global debug
|
||||
if debug:
|
||||
log(msg)
|
||||
|
||||
|
||||
def info(msg):
|
||||
log(msg)
|
||||
|
||||
|
||||
def err(msg):
|
||||
log("Error: %s" % msg)
|
||||
|
||||
|
||||
def lock():
|
||||
start = time.time()
|
||||
while True:
|
||||
elapsed = time.time() - start
|
||||
if elapsed > 10:
|
||||
err("Timeout waiting for lock")
|
||||
break
|
||||
try:
|
||||
fd = open(LOCK, "a")
|
||||
fcntl.flock(fd.fileno(), fcntl.LOCK_EX)
|
||||
dbg("Acquired lock")
|
||||
return fd
|
||||
except IOError as e:
|
||||
dbg("Unable to lock (%d)" % e.errno)
|
||||
if e.errno != errno.EAGAIN:
|
||||
err("Unable to lock")
|
||||
return None
|
||||
else:
|
||||
time.sleep(1)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def unlock(fd):
|
||||
try:
|
||||
fcntl.flock(fd.fileno(), fcntl.LOCK_UN)
|
||||
fd.close()
|
||||
dbg("Released lock")
|
||||
return True
|
||||
except IOError as e:
|
||||
err("Unlock failed with %d" % e.errno)
|
||||
return False
|
||||
|
||||
|
||||
def mkdir(path):
|
||||
if os.path.isdir(path):
|
||||
return True
|
||||
try:
|
||||
os.mkdir(path, mode=0o755)
|
||||
shutil.chown(path, user="root", group="wheel")
|
||||
except OSError:
|
||||
err("Unable to mkdir %s" % path)
|
||||
return False
|
||||
finally:
|
||||
return True
|
||||
|
||||
|
||||
def read_modem_data():
|
||||
data = {}
|
||||
if os.path.exists(MODEMS):
|
||||
with open(MODEMS, "r") as fd:
|
||||
data = json.load(fd)
|
||||
return data
|
||||
|
||||
|
||||
def write_modem_data(data):
|
||||
with open(MODEMS + ".bak", 'w') as fd:
|
||||
json.dump(data, fd)
|
||||
|
||||
os.rename(MODEMS + ".bak", MODEMS)
|
||||
return True
|
||||
|
||||
|
||||
def get_vendor(devpath):
|
||||
path = "%s/idVendor" % devpath
|
||||
if os.path.exists(path):
|
||||
with open(path, "r") as fd:
|
||||
return fd.readline().strip()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_product(devpath):
|
||||
path = "%s/idProduct" % devpath
|
||||
if os.path.exists(path):
|
||||
with open(path, "r") as fd:
|
||||
return fd.readline().strip()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def update(d):
|
||||
modem = None
|
||||
devpath = d.get("devpath")
|
||||
|
||||
data = read_modem_data()
|
||||
if "modems" not in data:
|
||||
data = {"modems": []}
|
||||
else:
|
||||
for m in data["modems"]:
|
||||
if devpath == m.get("devpath"):
|
||||
data["modems"].remove(m)
|
||||
modem = m
|
||||
break
|
||||
|
||||
if not modem:
|
||||
modem = {"devpath": devpath}
|
||||
|
||||
vendor = get_vendor(devpath)
|
||||
if vendor:
|
||||
modem["vendor"] = vendor
|
||||
|
||||
product = get_product(devpath)
|
||||
if product:
|
||||
modem["product"] = product
|
||||
|
||||
atport = d.get("atport")
|
||||
atports = modem.get("atports", [])
|
||||
if atport and atport not in atports:
|
||||
ptype = d.get("ptype", "default")
|
||||
if ptype == "primary":
|
||||
atports.insert(0, atport)
|
||||
else:
|
||||
atports.append(atport)
|
||||
modem["atports"] = atports
|
||||
|
||||
qmiport = d.get("qmiport")
|
||||
qmiports = modem.get("qmiports", [])
|
||||
if qmiport and qmiport not in qmiports:
|
||||
qmiports.append(qmiport)
|
||||
modem["qmiports"] = qmiports
|
||||
|
||||
iface = d.get("iface")
|
||||
ifaces = modem.get("interfaces", [])
|
||||
if iface and iface not in ifaces:
|
||||
ifaces.append(iface)
|
||||
modem["interfaces"] = ifaces
|
||||
|
||||
data["modems"].append(modem)
|
||||
write_modem_data(data)
|
||||
|
||||
info("Updated modem %s" % devpath)
|
||||
|
||||
|
||||
def add_tty(devpath):
|
||||
devpath = os.path.realpath("/sys/%s/../../../../" % devpath)
|
||||
|
||||
info("Adding tty device %s" % devpath)
|
||||
|
||||
atport = os.getenv("DEVNAME")
|
||||
if not atport:
|
||||
err("No DEVNAME")
|
||||
return False
|
||||
|
||||
if os.getenv("ID_MM_PORT_TYPE_AT_PRIMARY"):
|
||||
ptype = "primary"
|
||||
elif os.getenv("ID_MM_PORT_TYPE_AT_SECONDARY"):
|
||||
ptype = "secondary"
|
||||
else:
|
||||
ptype = "default"
|
||||
|
||||
info("Adding %s atport %s for %s" % (ptype, atport, devpath))
|
||||
|
||||
update({"devpath": devpath, "atport": atport, "ptype": ptype})
|
||||
|
||||
|
||||
def add_net(devpath):
|
||||
devpath = os.path.realpath("/sys/%s/../../../" % devpath)
|
||||
|
||||
info("Adding net device %s" % devpath)
|
||||
|
||||
iface = os.getenv("INTERFACE", None)
|
||||
if not iface:
|
||||
err("No INTERFACE")
|
||||
return False
|
||||
|
||||
r = re.search(r"wwan(\d+)\.(\d+)", iface)
|
||||
if r and r.group(2):
|
||||
info("Skipping interface %s" % iface)
|
||||
return False
|
||||
|
||||
info("Adding interface %s for %s" % (iface, devpath))
|
||||
update({"devpath": devpath, "iface": iface})
|
||||
|
||||
try:
|
||||
with open("/sys/class/net/%s/qmi/raw_ip" % iface, "w") as fd:
|
||||
fd.write("Y\n")
|
||||
except (IOError, OSError):
|
||||
err("Unable to set raw-ip")
|
||||
return False
|
||||
|
||||
info("Interface %s has been set to rawip" % iface)
|
||||
|
||||
|
||||
def add_usbmisc(devpath):
|
||||
devpath = os.path.realpath("/sys/%s/../../../" % devpath)
|
||||
|
||||
info("Adding usbmisc device %s" % devpath)
|
||||
|
||||
qmiport = os.getenv("DEVNAME", None)
|
||||
if not qmiport:
|
||||
err("No DEVNAME")
|
||||
return False
|
||||
|
||||
info("Adding qmiport %s for %s" % (qmiport, devpath))
|
||||
update({"devpath": devpath, "qmiport": qmiport})
|
||||
|
||||
|
||||
def apply():
|
||||
subsystem = os.getenv("SUBSYSTEM")
|
||||
if not subsystem:
|
||||
err("No SUBSYSTEM")
|
||||
return False
|
||||
|
||||
devpath = os.getenv("DEVPATH")
|
||||
if not devpath:
|
||||
err("No DEVPATH")
|
||||
return False
|
||||
|
||||
if "usb" not in devpath:
|
||||
info("Skipping %s" % devpath)
|
||||
return False
|
||||
|
||||
if subsystem == "net":
|
||||
add_net(devpath)
|
||||
elif subsystem == "tty":
|
||||
add_tty(devpath)
|
||||
elif subsystem == "usbmisc":
|
||||
add_usbmisc(devpath)
|
||||
else:
|
||||
err("Subsystem %s unhandled" % subsystem)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("/proc/cmdline", 'r') as fd:
|
||||
cmdline = fd.read()
|
||||
if "debug" in cmdline:
|
||||
debug = True
|
||||
|
||||
mkdir("/run/modemd")
|
||||
dbg("started")
|
||||
|
||||
if debug:
|
||||
for i, j in os.environ.items():
|
||||
dbg("ENV: %s=%s" % (i, j))
|
||||
else:
|
||||
if os.path.exists(LOGFILE):
|
||||
os.remove(LOGFILE)
|
||||
|
||||
fd = lock()
|
||||
if fd is None:
|
||||
sys.exit(2)
|
||||
|
||||
if apply():
|
||||
rc = 0
|
||||
else:
|
||||
err("Unable to add modem device")
|
||||
rc = 1
|
||||
|
||||
unlock(fd)
|
||||
|
||||
dbg("ended")
|
||||
|
||||
sys.exit(rc)
|
||||
Executable
+389
@@ -0,0 +1,389 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import argparse
|
||||
import urllib.parse
|
||||
import hashlib
|
||||
import syslog
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
|
||||
tempdir = "/tmp/modem-fwupdate"
|
||||
debug = False
|
||||
index = 0
|
||||
|
||||
syslog.openlog(logoption=syslog.LOG_PID, facility=syslog.LOG_SYSLOG)
|
||||
|
||||
|
||||
def dbg(msg):
|
||||
global debug
|
||||
if debug:
|
||||
syslog.syslog(syslog.LOG_INFO, msg)
|
||||
|
||||
|
||||
def info(msg):
|
||||
syslog.syslog(syslog.LOG_INFO, msg)
|
||||
|
||||
|
||||
def err(msg):
|
||||
syslog.syslog(syslog.LOG_ERR, msg)
|
||||
|
||||
|
||||
def fatal(msg):
|
||||
syslog.syslog(syslog.LOG_ALERT, msg)
|
||||
rmdir(tempdir)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def runcmd(cmd):
|
||||
ret = None
|
||||
try:
|
||||
res = subprocess.run(cmd, check=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, text=True)
|
||||
if res.returncode == 0:
|
||||
if res.stdout:
|
||||
ret = res.stdout.strip()
|
||||
else:
|
||||
ret = True
|
||||
except subprocess.CalledProcessError:
|
||||
err("Command failed")
|
||||
dbg(res.stderr)
|
||||
return None
|
||||
finally:
|
||||
return ret
|
||||
|
||||
|
||||
def runcmdj(cmd):
|
||||
output = runcmd(cmd)
|
||||
if not output:
|
||||
return None
|
||||
try:
|
||||
data = json.loads(output)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
finally:
|
||||
return data
|
||||
|
||||
|
||||
def exists(path):
|
||||
try:
|
||||
os.stat(path)
|
||||
except OSError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def rm(path):
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
|
||||
|
||||
def rmdir(path):
|
||||
if os.path.isdir(path):
|
||||
for f in os.listdir(path):
|
||||
p = os.path.join(path, f)
|
||||
if os.path.isdir(p):
|
||||
rmdir(p)
|
||||
else:
|
||||
os.remove(p)
|
||||
|
||||
|
||||
def mkdir(path):
|
||||
if os.path.isdir(path):
|
||||
return True
|
||||
try:
|
||||
os.mkdir(path, mode=0o755)
|
||||
except OSError:
|
||||
fatal("Unable to mkdir %s" % path)
|
||||
|
||||
|
||||
def initctl(cmd, svc):
|
||||
output = runcmdj(['initctl', '-j', 'status'])
|
||||
for ctl in output:
|
||||
if ctl["identity"] == svc:
|
||||
return runcmd(['initctl', cmd, svc])
|
||||
return False
|
||||
|
||||
|
||||
def get_path(index):
|
||||
for modem in runcmdj(['/usr/libexec/modemd/modem-info']):
|
||||
if modem["index"] == index:
|
||||
return modem["path"]
|
||||
|
||||
|
||||
def get_device_info(index):
|
||||
path = get_path(index)
|
||||
if path is None:
|
||||
return None
|
||||
|
||||
output = runcmdj(['mmcli', '-J', '-m', path])
|
||||
if not output:
|
||||
return None
|
||||
|
||||
gen = output["modem"]["generic"]
|
||||
info = {
|
||||
"index": index,
|
||||
"vendor": gen["manufacturer"],
|
||||
"model": gen["model"],
|
||||
"state": gen["state"],
|
||||
"qmidev": [],
|
||||
"atdev": []
|
||||
}
|
||||
for port in gen["ports"]:
|
||||
if "(qmi)" in port:
|
||||
info["qmidev"].append(port.split()[0])
|
||||
elif "(at)" in port:
|
||||
info["atdev"].append(port.split()[0])
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def wait_for_device(dev, appear):
|
||||
if appear:
|
||||
mode = "appear"
|
||||
else:
|
||||
mode = "disappear"
|
||||
|
||||
info("Waiting for modem%d to %s" % (dev["index"], mode))
|
||||
|
||||
path = "/dev/%s" % dev["qmidev"][0]
|
||||
timeout = 30
|
||||
while timeout > 0:
|
||||
if mode == "appear":
|
||||
if exists(path):
|
||||
return True
|
||||
elif mode == "disappear":
|
||||
if not exists(path):
|
||||
return True
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
return False
|
||||
|
||||
|
||||
def qmiupdate(dev):
|
||||
if len(dev["qmidev"]) == 0:
|
||||
err("No qmi device found")
|
||||
return False
|
||||
|
||||
qmidev = "/dev/%s" % dev["qmidev"][0]
|
||||
cwefile = None
|
||||
nvufile = None
|
||||
|
||||
for f in os.scandir(tempdir):
|
||||
if f.name.endswith('cwe'):
|
||||
cwefile = "%s/%s" % (tempdir, f.name)
|
||||
if f.name.endswith('nvu'):
|
||||
nvufile = "%s/%s" % (tempdir, f.name)
|
||||
|
||||
if not cwefile:
|
||||
err("No CWE file found")
|
||||
return False
|
||||
if not nvufile:
|
||||
err("No NVU file found")
|
||||
return False
|
||||
|
||||
info("Resetting modem")
|
||||
if not runcmd(['qmicli', '-d', qmidev,
|
||||
'--dms-set-operating-mode=offline']):
|
||||
err("Unable to set modem offline")
|
||||
return False
|
||||
if not runcmd(['qmicli', '-d', qmidev,
|
||||
'--dms-set-operating-mode=reset']):
|
||||
err("Unable to reset modem")
|
||||
return False
|
||||
|
||||
if not wait_for_device(dev, 0):
|
||||
err("Device has not disappeared")
|
||||
return False
|
||||
if not wait_for_device(dev, 1):
|
||||
err("Device has not appeared")
|
||||
return False
|
||||
|
||||
info("Running qmi-firmware-update, please stand by...")
|
||||
|
||||
cmd = ['qmi-firmware-update', '-v', '--override-download',
|
||||
'-w', qmidev, '-u', cwefile, nvufile]
|
||||
return runcmd(cmd)
|
||||
|
||||
|
||||
def firmware_update_sierra(dev, url, checksum):
|
||||
if not download(url, checksum):
|
||||
err("Unable to prepare firmware")
|
||||
return False
|
||||
|
||||
info("Stopping modem services")
|
||||
initctl("stop", "modemd")
|
||||
initctl("stop", "modem-manager")
|
||||
|
||||
time.sleep(3)
|
||||
ret = qmiupdate(dev)
|
||||
|
||||
info("Starting modem services")
|
||||
initctl("restart", "modem-manager")
|
||||
initctl("restart", "modemd")
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def atcmd(dev, command, expect=None, timeout=None):
|
||||
device = "/dev/%s" % dev["atdev"][0]
|
||||
|
||||
cmd = ['modem-command', '-d', device, command]
|
||||
if expect:
|
||||
cmd += ['-e', expect]
|
||||
if timeout:
|
||||
cmd += ['-t', str(timeout)]
|
||||
|
||||
return runcmd(cmd)
|
||||
|
||||
|
||||
def firmware_update_telit(dev, url, checksum):
|
||||
if 'LN920' not in dev["model"]:
|
||||
err("Not supported")
|
||||
return False
|
||||
|
||||
if len(dev["atdev"]) == 0:
|
||||
err("No AT device found")
|
||||
return False
|
||||
|
||||
if dev["state"] != "connected":
|
||||
err("Modem is not connected")
|
||||
return False
|
||||
|
||||
u = urllib.parse.urlparse(url)
|
||||
if not u:
|
||||
err("Unable to parse URL")
|
||||
return False
|
||||
if u.scheme != "ftp":
|
||||
err("Not an FTP URL")
|
||||
return False
|
||||
|
||||
hostname = ""
|
||||
username = ""
|
||||
password = ""
|
||||
port = 21
|
||||
|
||||
if u.hostname:
|
||||
hostname = u.hostname
|
||||
if u.username:
|
||||
username = u.username
|
||||
if u.password:
|
||||
password = u.password
|
||||
if u.port:
|
||||
port = int(u.port)
|
||||
if u.path:
|
||||
path = u.path
|
||||
|
||||
if hostname == "":
|
||||
err("No hostname in URL")
|
||||
return False
|
||||
if path == "":
|
||||
err("No path in URL")
|
||||
return False
|
||||
|
||||
parms = (hostname, port, path, username, password)
|
||||
cmd = 'AT#FTPGETOTAENH=%s,%d,%s,%s,%s' % parms
|
||||
exp = '#DREL'
|
||||
if not atcmd(dev, cmd, exp, 60):
|
||||
err("Unable to download firmware")
|
||||
return False
|
||||
|
||||
if not atcmd(dev, 'AT#OTAUP=2'):
|
||||
err("Unable to update firmware")
|
||||
return False
|
||||
|
||||
if not atcmd(dev, 'AT#ENHRST=1,3'):
|
||||
err("Unable to reboot modem")
|
||||
return False
|
||||
|
||||
time.sleep(5)
|
||||
info("Restarting modem services")
|
||||
initctl("restart", "modem-manager")
|
||||
initctl("restart", "modemd")
|
||||
return True
|
||||
|
||||
|
||||
def verify(path, checksum):
|
||||
if not checksum or checksum == "any":
|
||||
return True
|
||||
|
||||
sha256 = hashlib.sha256()
|
||||
with open(path, 'rb') as f:
|
||||
while True:
|
||||
data = f.read(1024)
|
||||
if not data:
|
||||
break
|
||||
sha256.update(data)
|
||||
|
||||
calculated = sha256.hexdigest()
|
||||
|
||||
if checksum != calculated:
|
||||
err("Checksum mismatch (%s vs. %s)" % (calculated, checksum))
|
||||
return False
|
||||
else:
|
||||
info("Verified checksum")
|
||||
return True
|
||||
|
||||
|
||||
def download(url, checksum):
|
||||
path = "%s/firmware.zip" % tempdir
|
||||
|
||||
info("Downloading %s" % url)
|
||||
|
||||
if not runcmd(['curl', '-s', '-L', url, '-o', path]):
|
||||
err("Unable to download firmware")
|
||||
return False
|
||||
|
||||
if not verify(path, checksum):
|
||||
err("Checksum verification failed")
|
||||
return False
|
||||
|
||||
if not runcmd(['unzip', '-d', tempdir, path]):
|
||||
err("Unable to unpack firmware")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
syslog.openlog(logoption=syslog.LOG_PID, facility=syslog.LOG_DAEMON)
|
||||
|
||||
parser = argparse.ArgumentParser(prog='modem-firmware-update')
|
||||
parser.add_argument("-i", "--index", default=None, help="Modem index")
|
||||
parser.add_argument("-u", "--url", default=None, help="URL to firmware")
|
||||
parser.add_argument("-c", "--checksum", default=None,
|
||||
help="SHA256 checksum of firmware")
|
||||
parser.add_argument('-d', action='store_true')
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.d:
|
||||
debug = True
|
||||
if args.index:
|
||||
index = int(args.index)
|
||||
if not args.url:
|
||||
fatal("No firmware URL specified")
|
||||
|
||||
rmdir(tempdir)
|
||||
mkdir(tempdir)
|
||||
|
||||
dev = get_device_info(index)
|
||||
if dev is None:
|
||||
fatal("No device found for modem%d" % index)
|
||||
|
||||
info("Starting firmware update for %s" % dev["model"])
|
||||
|
||||
if "Sierra" in dev["vendor"]:
|
||||
ret = firmware_update_sierra(dev, args.url, args.checksum)
|
||||
elif "Telit" in dev["vendor"]:
|
||||
ret = firmware_update_telit(dev, args.url, args.checksum)
|
||||
else:
|
||||
fatal("Unsupported vendor")
|
||||
|
||||
if ret:
|
||||
info("Firmware update succeeded")
|
||||
else:
|
||||
fatal("Firmware update failed")
|
||||
|
||||
rmdir(tempdir)
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
verbose = False
|
||||
|
||||
|
||||
def runcmd(cmd):
|
||||
if verbose:
|
||||
print("Running: %s" % " ".join(cmd))
|
||||
|
||||
ret = None
|
||||
try:
|
||||
res = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if verbose:
|
||||
print(res.stdout)
|
||||
if res.returncode == 0:
|
||||
if res.stdout:
|
||||
ret = res.stdout.strip()
|
||||
else:
|
||||
ret = True
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
finally:
|
||||
return ret
|
||||
|
||||
|
||||
def ussd(index, code):
|
||||
cmd = ['/sbin/modem-command',
|
||||
'--index', str(index),
|
||||
'--expect', '+CUSD',
|
||||
'--timeout', '60',
|
||||
'AT+CUSD=1,"%s",15' % code]
|
||||
|
||||
if verbose:
|
||||
cmd.append('--verbose')
|
||||
|
||||
output = runcmd(cmd)
|
||||
if output is None:
|
||||
sys.stderr.write("ERROR: Command failed\n")
|
||||
return False
|
||||
|
||||
resp = output.split('"')
|
||||
if len(resp) == 3:
|
||||
print(resp[1])
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(prog='modem-ussd')
|
||||
parser.add_argument("-i", "--index", default=0, help="Modem index")
|
||||
parser.add_argument("-c", "--code", default=None, help="USSD codec")
|
||||
parser.add_argument("-v", "--verbose", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
verbose = True
|
||||
|
||||
index = 0
|
||||
if args.index:
|
||||
index = int(args.index)
|
||||
|
||||
if not args.code:
|
||||
sys.stderr.write("Error: need USSD code\n")
|
||||
else:
|
||||
if ussd(index, args.code):
|
||||
sys.exit(0)
|
||||
|
||||
sys.exit(1)
|
||||
Executable
+1618
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
# Placeholder — add module names here if any modem drivers need explicit
|
||||
# loading (e.g. when device IDs are not yet upstream in the driver table).
|
||||
@@ -0,0 +1,8 @@
|
||||
ACTION!="add", GOTO="modemd_end"
|
||||
|
||||
SUBSYSTEM=="net", ENV{INTERFACE}=="wwan[0-9]*", RUN+="/usr/libexec/modemd/modem-udev"
|
||||
SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}=="1", RUN+="/usr/libexec/modemd/modem-udev"
|
||||
SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}=="1", RUN+="/usr/libexec/modemd/modem-udev"
|
||||
SUBSYSTEM=="usbmisc", KERNEL=="cdc-wdm[0-9]*",, RUN+="/usr/libexec/modemd/modem-udev"
|
||||
|
||||
LABEL="modemd_end"
|
||||
@@ -0,0 +1,7 @@
|
||||
# Bind USB modem devices whose IDs are not yet upstream in qmi_wwan.
|
||||
# Required until the IDs are accepted into the kernel driver.
|
||||
ACTION!="add", GOTO="qmi_wwan_ids_end"
|
||||
SUBSYSTEM!="usb", GOTO="qmi_wwan_ids_end"
|
||||
ENV{DEVTYPE}!="usb_device", GOTO="qmi_wwan_ids_end"
|
||||
|
||||
LABEL="qmi_wwan_ids_end"
|
||||
Executable
+240
@@ -0,0 +1,240 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import json
|
||||
import fcntl
|
||||
import struct
|
||||
import time
|
||||
import syslog
|
||||
import sys
|
||||
import os
|
||||
|
||||
syslog.openlog(logoption=syslog.LOG_PID, facility=syslog.LOG_SYSLOG)
|
||||
|
||||
def log(msg):
|
||||
syslog.syslog(syslog.LOG_INFO, msg)
|
||||
|
||||
|
||||
def err(msg):
|
||||
syslog.syslog(syslog.LOG_ERR, msg)
|
||||
|
||||
|
||||
def fatal(msg):
|
||||
syslog.syslog(syslog.LOG_ALERT, msg)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def runcmd(cmd):
|
||||
ret = None
|
||||
try:
|
||||
res = subprocess.run(cmd, check=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, text=True)
|
||||
if res.returncode == 0:
|
||||
if res.stdout:
|
||||
ret = res.stdout.strip()
|
||||
else:
|
||||
ret = True
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
finally:
|
||||
return ret
|
||||
|
||||
|
||||
def runcmdj(cmd):
|
||||
output = runcmd(cmd)
|
||||
if not output:
|
||||
return None
|
||||
try:
|
||||
data = json.loads(output)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
finally:
|
||||
return data
|
||||
|
||||
|
||||
def fread(path):
|
||||
if os.path.exists(path):
|
||||
with open(path, "r") as fd:
|
||||
output = str(fd.read())
|
||||
if output:
|
||||
return output.strip()
|
||||
return None
|
||||
|
||||
|
||||
def freadj(path):
|
||||
output = fread(path)
|
||||
if not output:
|
||||
return None
|
||||
try:
|
||||
data = json.loads(output)
|
||||
except json.JSONDecodeError:
|
||||
err("Unable to parse json output")
|
||||
return None
|
||||
finally:
|
||||
return data
|
||||
|
||||
|
||||
def find_modem(setup, index):
|
||||
for modem in setup["modems"]:
|
||||
if modem["index"] == index:
|
||||
return modem
|
||||
return None
|
||||
|
||||
|
||||
def find_sim(setup, index):
|
||||
for sim in setup["sims"]:
|
||||
if sim["index"] == index:
|
||||
return sim
|
||||
return None
|
||||
|
||||
|
||||
def pwrctl(setup, state):
|
||||
for modem in setup["modems"]:
|
||||
path = "/sys/class/pcie/slot%d/pwrctl" % modem["slot"]
|
||||
if not os.path.exists(path):
|
||||
continue
|
||||
with open(path, "w") as fd:
|
||||
fd.write("%s\n" % state)
|
||||
|
||||
|
||||
def sim_ctrl(cmd, slots):
|
||||
req = struct.pack('2i', *slots)
|
||||
|
||||
if not os.path.exists("/dev/simctrl"):
|
||||
return None
|
||||
|
||||
try:
|
||||
fd = os.open("/dev/simctrl", os.O_RDWR)
|
||||
except OSError:
|
||||
fatal("Cannot open sim ctrl")
|
||||
|
||||
try:
|
||||
resp = fcntl.ioctl(fd, cmd, req)
|
||||
except OSError:
|
||||
fatal("Cannot run sim ctrl ioctl")
|
||||
|
||||
if not resp:
|
||||
return None
|
||||
else:
|
||||
return struct.unpack('2i', resp)
|
||||
|
||||
|
||||
def sim_get_slots():
|
||||
# NM_SIMIOC_SLOT_GET is 0x80085300 (see linux/nm-sim.h)
|
||||
return sim_ctrl(0x80085300, (0, 0))
|
||||
|
||||
|
||||
def sim_set_slots(slots):
|
||||
# NM_SIMIOC_SLOT_SET is 0x40085301 (see linux/nm-sim.h)
|
||||
return sim_ctrl(0x40085301, slots)
|
||||
|
||||
|
||||
def slot_setup(setup):
|
||||
slots = []
|
||||
for sim in setup["sims"]:
|
||||
log("Connecting sim%d to slot %d" % (sim["index"], sim["newslot"]))
|
||||
slots.append(sim["newslot"])
|
||||
|
||||
if sim_set_slots(tuple(slots)) is None:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def change_setup(setup):
|
||||
log("Changing SIM setup")
|
||||
|
||||
runcmd(["initctl", "stop", "modem-manager"])
|
||||
time.sleep(1)
|
||||
|
||||
pwrctl(setup, "down")
|
||||
time.sleep(1)
|
||||
|
||||
if slot_setup(setup) is False:
|
||||
err("Unable to set up sim slots")
|
||||
|
||||
pwrctl(setup, "up")
|
||||
|
||||
time.sleep(1)
|
||||
runcmd(["initctl", "restart", "modem-manager"])
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def swapslot(slot):
|
||||
if slot == 0:
|
||||
return 1
|
||||
elif slot == 1:
|
||||
return 0
|
||||
else:
|
||||
return -1
|
||||
|
||||
|
||||
def sim_setup(setup):
|
||||
changed = False
|
||||
|
||||
config = runcmdj(["sysrepocfg", "-f", "json", "-X", "-m", "infix-modem"])
|
||||
if not config or "infix-modem:modems" not in config:
|
||||
err("Cannot read config")
|
||||
return False
|
||||
|
||||
for cfg in config["infix-modem:modems"]["modem"]:
|
||||
if not cfg.get("enabled", False) or cfg.get("index", -1) < 0:
|
||||
continue
|
||||
|
||||
modem = find_modem(setup, cfg["index"])
|
||||
sim = find_sim(setup, cfg.get("sim", -1))
|
||||
if modem and sim:
|
||||
sim["newslot"] = modem["slot"]
|
||||
if sim["newslot"] != sim["slot"]:
|
||||
changed = True
|
||||
|
||||
if not changed:
|
||||
log("SIMs already set up")
|
||||
return True
|
||||
|
||||
if len(setup["modems"]) == 2 and len(setup["sims"]) == 2:
|
||||
for i in range(0, 2):
|
||||
o = 1 if i == 0 else 0
|
||||
if setup["sims"][i]["newslot"] == -1:
|
||||
log("Swapping slot of sim%d" % setup["sims"][i]["index"])
|
||||
setup["sims"][i]["newslot"] = swapslot(setup["sims"][o]["newslot"])
|
||||
|
||||
return change_setup(setup)
|
||||
|
||||
|
||||
def read_setup():
|
||||
setup = {
|
||||
"modems": [],
|
||||
"sims": []
|
||||
}
|
||||
|
||||
modules = freadj("/run/modules.json")
|
||||
if not modules:
|
||||
log("No modules.json found, skipping SIM setup")
|
||||
return None
|
||||
|
||||
for module in modules.get("modules", []):
|
||||
if module["slot"] > -1 and module["type"] == "modem":
|
||||
setup["modems"].append({"index": module["index"],
|
||||
"slot": module["slot"]})
|
||||
|
||||
slots = sim_get_slots()
|
||||
if slots:
|
||||
for index in range(0, len(slots)):
|
||||
setup["sims"].append({"index": index,
|
||||
"slot": slots[index],
|
||||
"newslot": -1})
|
||||
|
||||
return setup
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
setup = read_setup()
|
||||
if setup is None:
|
||||
sys.exit(0)
|
||||
|
||||
if sim_setup(setup) is False:
|
||||
fatal("Unable set up SIMs")
|
||||
|
||||
sys.exit(0)
|
||||
@@ -126,6 +126,9 @@ def main():
|
||||
elif model == 'ieee1588-ptp-tt':
|
||||
from . import ieee1588_ptp
|
||||
yang_data = ieee1588_ptp.operational()
|
||||
elif model == 'infix-modem':
|
||||
from . import infix_modem
|
||||
yang_data = infix_modem.operational()
|
||||
else:
|
||||
common.LOG.warning("Unsupported model %s", model)
|
||||
sys.exit(1)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
from .common import LOG
|
||||
from .host import HOST
|
||||
|
||||
|
||||
def operational():
|
||||
modems = HOST.run_json(['/usr/libexec/modemd/modem-info'], [])
|
||||
|
||||
return {
|
||||
"infix-modem:modems": {
|
||||
"modem": [m for m in modems]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user