Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/uplink-runtime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
sh application/qmodem_uplink_manager/tests/test_health.sh
sh application/qmodem/tests/test_bridge_passthrough.sh
sh application/qmodem/tests/test_modem_netdev.sh
sh application/qmodem/tests/test_modem_presence.sh
cc -pthread application/modem_scan/tests/test_scheduler.c -ljson-c -o /tmp/test-scheduler
/tmp/test-scheduler
cc -pthread application/quectel_CM_5G_M/tests/test_managed_ipv4.c -o /tmp/test-managed
Expand Down
2 changes: 2 additions & 0 deletions application/modem_scan/src/modem_scan_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
#define QMODEM_MODEM_SCAN_COMMON_H

#define QMODEM_SCAND_SOCKET "/var/run/qmodem/modem_scand.sock"
#ifndef QMODEM_RUN_DIR
#define QMODEM_RUN_DIR "/var/run/qmodem"
#endif
#define QMODEM_SUPPORT_JSON "/usr/share/qmodem/modem_support.json"
#define QMODEM_PORT_RULE_JSON "/usr/share/qmodem/modem_port_rule.json"

Expand Down
5 changes: 3 additions & 2 deletions application/modem_scan/src/modem_scanc.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ static void usage(FILE *out)
" modem_scanc disable <slot> [delay_seconds]\n"
" modem_scanc scan [usb|pcie|all] [delay_seconds]\n"
" modem_scanc set-log-level <debug|info|notice|warn|err>\n"
" modem_scanc check <section>\n"
" modem_scanc status\n");
}

Expand Down Expand Up @@ -78,12 +79,12 @@ int main(int argc, char **argv)
return 1;
}
snprintf(line, sizeof(line), "add %s %s %s", argv[2], argv[3], argc == 5 ? argv[4] : "0");
} else if (!strcmp(argv[1], "remove")) {
} else if (!strcmp(argv[1], "remove") || !strcmp(argv[1], "check")) {
if (argc != 3 && argc != 4) {
usage(stderr);
return 1;
}
snprintf(line, sizeof(line), "remove %s %s", argv[2], argc == 4 ? argv[3] : "0");
snprintf(line, sizeof(line), "%s %s %s", argv[1], argv[2], argc == 4 ? argv[3] : "0");
} else if (!strcmp(argv[1], "disable")) {
if (argc != 3 && argc != 4) {
usage(stderr);
Expand Down
276 changes: 212 additions & 64 deletions application/modem_scan/src/modem_scand.c

Large diffs are not rendered by default.

113 changes: 113 additions & 0 deletions application/modem_scan/tests/test_presence.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#define main qmodem_daemon_main
#include "../src/modem_scand.c"
#undef main
#include <assert.h>

static void expect_state(const char *expected)
{
char state[32];
assert(!uci_get("qmodem.1_1.state", state, sizeof(state)));
assert(!strcmp(state, expected));
}

int main(void)
{
char control[512], original[512], other[512], fail_commit[512];
const char *sys = getenv("QMODEM_SYSFS"), *fail = getenv("QMODEM_TEST_FAIL");
struct event *old, *event;
pthread_mutex_init(&g.queue_lock, NULL);
pthread_cond_init(&g.queue_cond, NULL);
g.log_level = LOG_L_ERR;
g.add_retry_max = 2;
g.add_retry_delay = 0;
snprintf(control, sizeof(control), "%s/dev/char/1:3", sys);
snprintf(original, sizeof(original), "%s/devices/usb/1-1/control", sys);
snprintf(other, sizeof(other), "%s/devices/usb/1-10/control", sys);
snprintf(fail_commit, sizeof(fail_commit), "%s.commit", fail);

assert(enqueue_event(EV_CHECK, "../escape", NULL, 0) == -1);
revoke_ready("1_1");
assert(!check_modem("1_1"));
assert(!ready_command("check", "1_1"));
expect_state("enabled");
/* A running scan loses authority immediately on remove arrival. */
assert(!enqueue_event(EV_ADD, "1-1", "usb", 0));
old = dequeue_event(); current_event = old;
assert(!enqueue_event(EV_DISABLE, "1-1", NULL, 0));
assert(ready_command("check", "1_1"));
assert(!event_current());
assert(!confirm_section("1_1"));
assert(ready_command("check", "1_1"));
finish_event_key(old); free(old); current_event = NULL;
event = dequeue_event(); current_event = event;
process_event(event);
expect_state("disabled");
finish_event_key(event); free(event); current_event = NULL;

/* Manual mode validates existing settings and never discovers a new section. */
assert(!add_modem("1-1", "usb"));
expect_state("enabled");
assert(!ready_command("check", "1_1"));
assert(!add_modem("2-2", "usb"));
char value[32];
assert(uci_get("qmodem.2_2", value, sizeof(value)));
assert(!unlink(control)); assert(!symlink(other, control));
assert(check_modem("1_1"));
expect_state("disabled");
assert(ready_command("check", "1_1"));

/* Even if the modem returns, failed host cleanup must not enable it. */
assert(!unlink(control)); assert(!symlink(original, control));
int fd = open(fail, O_CREAT | O_WRONLY, 0600); assert(fd >= 0); close(fd);
assert(check_modem("1_1"));
expect_state("disabled");
assert(ready_command("check", "1_1"));
assert(!enqueue_event(EV_DISABLE, "1-1", NULL, 0));
event = dequeue_event(); current_event = event; process_event(event);
assert(g.queue_len == 1); /* failed disable queued a bounded retry */
finish_event_key(event); free(event); current_event = NULL;
unlink(fail);
event = dequeue_event(); current_event = event; process_event(event);
finish_event_key(event); free(event); current_event = NULL;
assert(g.queue_len == 0);

/* Failed UCI commit revokes proof, even if the set was visible in memory. */
fd = open(fail_commit, O_CREAT | O_WRONLY, 0600); assert(fd >= 0); close(fd);
assert(confirm_section("1_1"));
assert(ready_command("check", "1_1"));
unlink(fail_commit);
assert(!check_modem("1_1"));
expect_state("enabled");

/* Full scans reconcile old sections even with automatic discovery blocked. */
assert(!unlink(control)); assert(!symlink(other, control));
assert(!enqueue_event(EV_SCAN, "usb", NULL, 0));
event = dequeue_event(); current_event = event; process_event(event);
finish_event_key(event); free(event); current_event = NULL;
while (g.queue_len) {
event = dequeue_event(); current_event = event; process_event(event);
finish_event_key(event); free(event); current_event = NULL;
}
expect_state("disabled");
assert(ready_command("check", "1_1"));
/* Fixed configurations are checked but their user settings are not probed. */
assert(!unlink(control)); assert(!symlink(original, control));
assert(!uci_set("qmodem.main.block_auto_probe", "0"));
assert(!uci_set("qmodem.1_1.fixed_device", "1"));
assert(!add_modem("1-1", "usb"));
expect_state("enabled");
assert(!ready_command("check", "1_1"));
assert(!uci_set("qmodem.1_1.fixed_device", "0"));
assert(!unlink(control)); assert(!symlink(other, control));
/* Automatic mode can rediscover ports within the same configured slot. */
assert(!uci_set("qmodem.main.block_auto_probe", "0"));
assert(!enqueue_event(EV_CHECK, "1_1", NULL, 0));
event = dequeue_event(); current_event = event; process_event(event);
finish_event_key(event); free(event); current_event = NULL;
assert(g.queue_len == 1);
event = dequeue_event();
assert(event->type == EV_ADD && !strcmp(event->slot, "1-1"));
finish_event_key(event); free(event);
puts("PASS: scanner confirmation, stale events, manual mode, disable retries, commit failure, stale-section reconciliation");
return 0;
}
2 changes: 0 additions & 2 deletions application/qmodem/files/etc/hotplug.d/net/20-modem-net
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#!/bin/sh
# Copyright (C) 2024 Tom <fjrcn@outlook.com>
manual=$(uci -q get qmodem.main.block_auto_probe)
[ "${manual}" -eq 1 ] && exit

logger -t modem_hotplug "net slot: ${DEVPATH} action: ${ACTION}"
hotplug_add_delay=$(uci -q get qmodem.main.hotplug_add_delay || echo 8)
Expand Down
2 changes: 0 additions & 2 deletions application/qmodem/files/etc/hotplug.d/usb/20-modem-usb
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#!/bin/sh
# Copyright (C) 2024 Tom <fjrcn@outlook.com>
manual=$(uci -q get qmodem.main.block_auto_probe)
[ "${manual}" -eq 1 ] && exit
logger -t modem_hotplug "usb_event slot: ${DEVPATH} action: ${ACTION}"
hotplug_add_delay=$(uci -q get qmodem.main.hotplug_add_delay || echo 8)
[ -z "${DEVNUM}" ] && exit
Expand Down
39 changes: 1 addition & 38 deletions application/qmodem/files/etc/init.d/qmodem_init
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ start_service() {
config_get enable_usb_scan main enable_usb_scan 0
config_get try_preset_usb main try_preset_usb 1
config_get try_preset_pcie main try_preset_pcie 1
#once enabled, skip all check or scan
# scand always validates configured devices; this only blocks discovery.
[ "$block_auto_probe" -eq 1 ] && return

#enabled by default
Expand Down Expand Up @@ -45,13 +45,11 @@ boot() {

_try_vendor_preset_usb()
{
config_foreach _try_usb_device modem-device
config_foreach _try_usb_slot modem-slot
}

_try_vendor_preset_pcie()
{
config_foreach _try_pcie_device modem-device
config_foreach _try_pcie_slot modem-slot
}

Expand All @@ -67,18 +65,6 @@ _try_usb_slot()
[ "$type" == "usb" ] && _try_slot $1
}

_try_usb_device()
{
config_get data_interface $1 data_interface
[ "$data_interface" == "usb" ] && _try_device $1
}

_try_pcie_device()
{
config_get data_interface $1 data_interface
[ "$data_interface" == "pcie" ] && _try_device $1
}

_scan_usb()
{
( sleep 15; /usr/bin/modem_scanc scan usb >/dev/null 2>&1 ) &
Expand Down Expand Up @@ -127,26 +113,3 @@ _try_slot()
( sleep 1; /usr/share/qmodem/modem_scan.sh disable "$slot" >/dev/null 2>&1 ) &
fi
}

_try_device()
{
config_get path "$1" path
config_get network "$1" network
config_get slot "$1" slot
[ -n "$slot" ] || slot=$(basename "$path")
config_get data_interface "$1" data_interface
if [ ! -d "$path" ]; then
( sleep 1; /usr/share/qmodem/modem_scan.sh disable "$slot" >/dev/null 2>&1 ) &
return
fi
# if device_path is not parent of netdevice_path, disable device path and scan again
network=$(/usr/share/qmodem/modem_netdev.sh "$1")
netdevice_path=$(readlink -f "/sys/class/net/$network/device/")
device_path=`readlink -f $path`
echo "$netdevice_path" | grep -q "^$device_path"
is_parent=$?
if [ "$is_parent" -ne 0 ] || [ ! -d "$netdevice_path" ]; then
( sleep 1; /usr/share/qmodem/modem_scan.sh disable "$slot" >/dev/null 2>&1 ) &
( sleep 1; /usr/share/qmodem/modem_scan.sh add "$(basename "$path")" "$data_interface" >/dev/null 2>&1 ) &
fi
}
36 changes: 32 additions & 4 deletions application/qmodem/files/etc/init.d/qmodem_network
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ MODEM_RUNDIR="/var/run/qmodem"

extra_command "reconfigure" "<modem_cfg> apply settings while respecting dial enable state"
extra_command "dial" "<modem_cfg> run dial script"
extra_command "offline" "<modem_cfg> stop and clean host state without modem IO"
extra_command "hang" "<modem_cfg> run hang script"
extra_command "redial" "<modem_cfg> stop previous dial script. run hang script. and run dial script"
extra_command "modem_status" "<modem_cfg> Check dial script status"
Expand Down Expand Up @@ -60,6 +61,12 @@ hang()
_hang $1
}

offline()
{
qmodem_valid_instance "$1" || return 2
_hang "$1" offline
}

modem_status()
{
qmodem_valid_instance "$1" || return 2
Expand All @@ -85,24 +92,35 @@ reconfigure()

_update_modem()
{
local target
local target present=1
target=1
config_get enabled $1 enable_dial
config_get state $1 state
if [ "$state" = "disabled" ] || [ "$enabled" = "0" ] || [ "$g_enabled" = "0" ] || [ "$force_stop" = "1" ];then
if [ "$state" != "enabled" ] || [ "$enabled" = "0" ] || [ "$g_enabled" = "0" ] || [ "$force_stop" = "1" ];then
target=0
fi
"$SCRIPT_DIR/modem_ready.sh" check "$1" || { target=0; present=0; }
data=$(_procd_ubus_call list | jq -r ".$service.instances.modem_$1")
[ "$data" == "null" ] && current_status=0 || current_status=1
logger -t qmodem_network "cfg:$1 current:$current_status;target:$target "
if [ "$current_status" != "$target" ];then
[ "$target" -eq 1 ] && dial $1 || hang $1
if [ "$target" -eq 1 ]; then
dial "$1"
elif [ "$present" -eq 1 ]; then
hang "$1"
else
offline "$1"
fi
logger -t qmodem_network "update modem cfg: $1 dial:$target"
fi
}

_dial()
{
[ "$(uci -q get "qmodem.$1.state")" = enabled ] || return 1
[ "$(uci -q get "qmodem.$1.enable_dial")" = 1 ] || return 1
[ "$(uci -q get qmodem.main.enable_dial)" != 0 ] || return 1
"$SCRIPT_DIR/modem_ready.sh" check "$1" || return 1
procd_open_instance "modem_$1"
procd_set_param command "/usr/share/qmodem/modem_dial.sh" "$1" dial
procd_set_param respawn 120 15 10
Expand All @@ -118,7 +136,17 @@ _hang()
rm -f "$MODEM_RUNDIR/${1}_dir/generation" "$MODEM_RUNDIR/${1}_dir/ipv4.json" "$MODEM_RUNDIR/${1}_dir/backend.json"
)
procd_kill $service "modem_$1"
/usr/share/qmodem/modem_dial.sh "$1" hang || return $?
/usr/share/qmodem/modem_dial.sh "$1" "${2:-hang}" || return $?
# procd termination is asynchronous; descendants may still own dial.lock.
local attempt=0
while ! ( exec 7>"$MODEM_RUNDIR/${1}_dir/dial.lock"; flock -n 7 ); do
attempt=$((attempt + 1))
if [ "$attempt" -ge 15 ]; then
logger -t qmodem_network "Modem $1 stop incomplete: dial process still alive"
return 1
fi
sleep 1
done
logger -t qmodem_network "Modem $1 Stop Dial and Hang"
}

Expand Down
Loading
Loading