2026-10-24 18:59:48 +03:00
#!/bin/sh
# Bananarchy Dual-SIM & QMI UIM Provisioning Helper for Qualcomm MSM8905/MSM8909
set -eu
CONF_FILE = "/etc/conf.d/msm-modem-uim-selection"
if [ -r " $CONF_FILE " ] ; then
# shellcheck disable=SC1090
. " $CONF_FILE "
fi
2026-10-25 12:32:46 +02:00
SIM_WAIT_TIME = " ${ SIM_WAIT_TIME :- ${ sim_wait_time :- 4 }} "
2026-10-24 18:59:48 +03:00
PREFERRED_SIM_SLOT = " ${ PREFERRED_SIM_SLOT :- ${ preferred_sim_slot :- 1 }} "
QMICLI_MODEM = ""
2026-10-25 12:32:46 +02:00
ensure_usb_network_gadget() {
CONFIGFS = "/sys/kernel/config/usb_gadget"
if ! [ -d " $CONFIGFS " ] ; then
modprobe libcomposite 2>/dev/null || true
mount -t configfs configfs /sys/kernel/config 2>/dev/null || true
fi
udc_dev = " $( ls /sys/class/udc 2>/dev/null | head -n 1 || true ) "
if [ -z " $udc_dev " ] ; then
modprobe pm8916-lbc 2>/dev/null || true
w = 0
while [ -z " $udc_dev " ] && [ " $w " -lt 20 ] ; do
sleep 0.1
udc_dev = " $( ls /sys/class/udc 2>/dev/null | head -n 1 || true ) "
w = $(( w + 1 ))
done
fi
if [ -d " $CONFIGFS " ] && [ -n " $udc_dev " ] ; then
if ! [ -d " $CONFIGFS /g1" ] ; then
mkdir -p " $CONFIGFS /g1" 2>/dev/null || true
echo "0x18D1" > " $CONFIGFS /g1/idVendor" 2>/dev/null || true
echo "0xD001" > " $CONFIGFS /g1/idProduct" 2>/dev/null || true
mkdir -p " $CONFIGFS /g1/strings/0x409" 2>/dev/null || true
echo "Nokia" > " $CONFIGFS /g1/strings/0x409/manufacturer" 2>/dev/null || true
echo "Bananarchy" > " $CONFIGFS /g1/strings/0x409/serialnumber" 2>/dev/null || true
echo "Nokia 8110 4G" > " $CONFIGFS /g1/strings/0x409/product" 2>/dev/null || true
func = "ncm.usb0"
if ! mkdir -p " $CONFIGFS /g1/functions/ $func " 2>/dev/null; then
func = "rndis.usb0"
mkdir -p " $CONFIGFS /g1/functions/ $func " 2>/dev/null || true
fi
mkdir -p " $CONFIGFS /g1/configs/c.1/strings/0x409" 2>/dev/null || true
echo "USB network" > " $CONFIGFS /g1/configs/c.1/strings/0x409/configuration" 2>/dev/null || true
ln -sf " $CONFIGFS /g1/functions/ $func " " $CONFIGFS /g1/configs/c.1/" 2>/dev/null || true
fi
cur_udc = " $( cat " $CONFIGFS /g1/UDC" 2>/dev/null | tr -d '[:space:]' || true ) "
if [ -z " $cur_udc " ] ; then
echo " $udc_dev " > " $CONFIGFS /g1/UDC" 2>/dev/null || true
fi
fi
if ip link show dev usb0 >/dev/null 2>& 1; then
ip link set usb0 up 2>/dev/null || true
ip -4 addr show dev usb0 2>/dev/null | grep -q "172.16.42.1" || \
ip addr add 172.16.42.1/16 dev usb0 2>/dev/null || true
if ! pidof unudhcpd >/dev/null 2>& 1; then
( unudhcpd -i usb0 -s 172.16.42.1 -c 172.16.42.2 >/dev/null 2>& 1) &
fi
fi
}
2026-10-24 18:59:48 +03:00
detect_qmicli_modem() {
if ! command -v qmicli >/dev/null 2>& 1; then
return 1
fi
2026-10-25 12:32:46 +02:00
if timeout 3 qmicli --silent -pd qrtr://0 --uim-noop >/dev/null 2>& 1; then
QMICLI_MODEM = "timeout 4 qmicli --silent -pd qrtr://0"
2026-10-24 18:59:48 +03:00
return 0
2026-10-25 11:31:41 +02:00
elif [ -e "/dev/wwan0qmi0" ] ; then
2026-10-25 12:32:46 +02:00
if timeout 3 qmicli --silent -pd /dev/wwan0qmi0 --device-open-qmi --uim-noop >/dev/null 2>& 1; then
QMICLI_MODEM = "timeout 4 qmicli --silent -pd /dev/wwan0qmi0 --device-open-qmi"
2026-10-25 11:31:41 +02:00
return 0
2026-10-25 12:32:46 +02:00
elif timeout 3 qmicli --silent -d /dev/wwan0qmi0 --device-open-qmi --uim-noop >/dev/null 2>& 1; then
QMICLI_MODEM = "timeout 4 qmicli --silent -d /dev/wwan0qmi0 --device-open-qmi"
2026-10-25 11:31:41 +02:00
return 0
fi
elif [ -e "/dev/modem" ] ; then
2026-10-25 12:32:46 +02:00
if timeout 3 qmicli --silent -pd /dev/modem --uim-noop >/dev/null 2>& 1; then
QMICLI_MODEM = "timeout 4 qmicli --silent -pd /dev/modem"
2026-10-25 11:31:41 +02:00
return 0
2026-10-25 12:32:46 +02:00
elif timeout 3 qmicli --silent -d /dev/modem --uim-noop >/dev/null 2>& 1; then
QMICLI_MODEM = "timeout 4 qmicli --silent -d /dev/modem"
2026-10-25 11:31:41 +02:00
return 0
fi
2026-10-24 18:59:48 +03:00
fi
return 1
}
wait_for_qmicli_modem() {
2026-10-25 12:32:46 +02:00
max_wait = " ${ 1 :- 12 } "
2026-10-24 18:59:48 +03:00
count = 0
while [ " $count " -lt " $max_wait " ] ; do
if detect_qmicli_modem; then
return 0
fi
sleep 1
count = $(( count + 1 ))
done
return 1
}
# Parse `qmicli --uim-get-card-status` into shell-safe KEY=VALUE lines
parse_uim_card_status() {
raw_status = " $1 "
pref_slot = " $2 "
printf "%s\n" " $raw_status " | awk -v pref = " $pref_slot " '
BEGIN {
prim_slot = 0
prim_app = 0
cur_slot = 0
in_app = 0
for (s = 1; s <= 2; s++) {
cstate[s] = "absent"
rawstate[s] = "absent"
upin_state[s] = "not-initialized"
upin_ret[s] = 0
upuk_ret[s] = 0
app_type[s] = "none"
app_state[s] = "none"
aid[s] = ""
pin1_state[s] = "not-initialized"
pin1_ret[s] = 0
puk1_ret[s] = 0
2026-10-25 11:31:41 +02:00
want_aid[s] = 0
need_aid_line[s] = 0
2026-10-24 18:59:48 +03:00
}
}
/Primary GW:/ {
if ($0 ~ /slot \x27[0-9]+\x27/) {
line = $0
sub(/.*slot \x27/, "", line)
sub(/\x27.*/, "", line)
prim_slot = line + 0
}
if ($0 ~ /application \x27[0-9]+\x27/) {
line = $0
sub(/.*application \x27/, "", line)
sub(/\x27.*/, "", line)
prim_app = line + 0
}
}
/^[[:space:]]*Slot \[[0-9]+\]:/ {
line = $0
sub(/.*Slot \[/, "", line)
sub(/\].*/, "", line)
cur_slot = line + 0
in_app = 0
next
}
cur_slot >= 1 && cur_slot <= 2 {
if ($0 ~ /^[[:space:]]*Application \[[0-9]+\]:/) {
in_app = 1
next
}
if (!in_app && $0 ~ /Card state:/) {
line = $0
2026-10-25 11:31:41 +02:00
sub(/.*Card state:[[:space:]]*\x27/, "", line)
2026-10-24 18:59:48 +03:00
sub(/\x27.*/, "", line)
rawstate[cur_slot] = line
if (line == "present") cstate[cur_slot] = "present"
else if (line == "absent" || line ~ /no-atr-received/) cstate[cur_slot] = "absent"
else cstate[cur_slot] = "error"
}
if (!in_app && $0 ~ /UPIN state:/) {
line = $0
2026-10-25 11:31:41 +02:00
sub(/.*UPIN state:[[:space:]]*\x27/, "", line)
2026-10-24 18:59:48 +03:00
sub(/\x27.*/, "", line)
upin_state[cur_slot] = line
}
if (!in_app && $0 ~ /UPIN retries:/) {
line = $0
2026-10-25 11:31:41 +02:00
sub(/.*UPIN retries:[[:space:]]*\x27/, "", line)
2026-10-24 18:59:48 +03:00
sub(/\x27.*/, "", line)
upin_ret[cur_slot] = line + 0
}
if (!in_app && $0 ~ /UPUK retries:/) {
line = $0
2026-10-25 11:31:41 +02:00
sub(/.*UPUK retries:[[:space:]]*\x27/, "", line)
2026-10-24 18:59:48 +03:00
sub(/\x27.*/, "", line)
upuk_ret[cur_slot] = line + 0
}
if (in_app && $0 ~ /Application type:/) {
line = $0
2026-10-25 11:31:41 +02:00
sub(/.*Application type:[[:space:]]*\x27/, "", line)
2026-10-24 18:59:48 +03:00
sub(/\x27.*/, "", line)
if (line ~ /^usim/) {
app_type[cur_slot] = "usim"
want_aid[cur_slot] = 2
} else if (line ~ /^sim/ && app_type[cur_slot] != "usim") {
app_type[cur_slot] = "sim"
want_aid[cur_slot] = 1
2026-10-25 11:31:41 +02:00
} else if (app_type[cur_slot] == "none") {
app_type[cur_slot] = "sim"
want_aid[cur_slot] = 1
2026-10-24 18:59:48 +03:00
} else {
want_aid[cur_slot] = 0
}
}
if (in_app && $0 ~ /Application state:/ && want_aid[cur_slot] > 0) {
line = $0
2026-10-25 11:31:41 +02:00
sub(/.*Application state:[[:space:]]*\x27/, "", line)
2026-10-24 18:59:48 +03:00
sub(/\x27.*/, "", line)
app_state[cur_slot] = line
}
if (in_app && $0 ~ /Application ID:/ && want_aid[cur_slot] > 0) {
need_aid_line[cur_slot] = 1
next
}
if (in_app && need_aid_line[cur_slot] == 1) {
line = $0
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
2026-10-25 11:31:41 +02:00
if (line != "" && line !~ /:.*\x27/) {
2026-10-24 18:59:48 +03:00
aid[cur_slot] = line
}
need_aid_line[cur_slot] = 0
}
if (in_app && $0 ~ /PIN1 state:/ && want_aid[cur_slot] > 0) {
line = $0
2026-10-25 11:31:41 +02:00
sub(/.*PIN1 state:[[:space:]]*\x27/, "", line)
2026-10-24 18:59:48 +03:00
sub(/\x27.*/, "", line)
pin1_state[cur_slot] = line
}
if (in_app && $0 ~ /PIN1 retries:/ && want_aid[cur_slot] > 0) {
line = $0
2026-10-25 11:31:41 +02:00
sub(/.*PIN1 retries:[[:space:]]*\x27/, "", line)
2026-10-24 18:59:48 +03:00
sub(/\x27.*/, "", line)
pin1_ret[cur_slot] = line + 0
}
if (in_app && $0 ~ /PUK1 retries:/ && want_aid[cur_slot] > 0) {
line = $0
2026-10-25 11:31:41 +02:00
sub(/.*PUK1 retries:[[:space:]]*\x27/, "", line)
2026-10-24 18:59:48 +03:00
sub(/\x27.*/, "", line)
puk1_ret[cur_slot] = line + 0
}
}
END {
printf "PREFERRED_SLOT=%d\n", (pref == 2 ? 2 : 1)
printf "PRIMARY_GW_SLOT=%d\n", prim_slot
printf "PRIMARY_GW_APP=%d\n", prim_app
for (s = 1; s <= 2; s++) {
printf "SLOT%d_STATE=%s\n", s, cstate[s]
printf "SLOT%d_RAW_STATE=%s\n", s, rawstate[s]
printf "SLOT%d_APP_TYPE=%s\n", s, app_type[s]
printf "SLOT%d_APP_STATE=%s\n", s, app_state[s]
printf "SLOT%d_AID=%s\n", s, aid[s]
printf "SLOT%d_PIN1_STATE=%s\n", s, pin1_state[s]
printf "SLOT%d_PIN1_RETRIES=%d\n", s, pin1_ret[s]
printf "SLOT%d_PUK1_RETRIES=%d\n", s, puk1_ret[s]
printf "SLOT%d_UPIN_STATE=%s\n", s, upin_state[s]
printf "SLOT%d_UPIN_RETRIES=%d\n", s, upin_ret[s]
printf "SLOT%d_UPUK_RETRIES=%d\n", s, upuk_ret[s]
}
}
'
}
2026-10-25 11:31:41 +02:00
parse_nas_serving_system() {
raw_nas = " $1 "
printf "%s\n" " $raw_nas " | awk '
BEGIN {
reg_state = "unknown"
cs_state = "unknown"
ps_state = "unknown"
rat = ""
mcc = ""
mnc = ""
op_desc = ""
roam = "off"
in_rat = 0
in_plmn = 0
in_roam = 0
}
/^[[:space:]]*Registration state:/ {
line = $0
sub(/.*Registration state:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
reg_state = line
in_rat = 0; in_plmn = 0; in_roam = 0
next
}
/^[[:space:]]*CS:/ {
line = $0
sub(/.*CS:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
cs_state = line
next
}
/^[[:space:]]*PS:/ {
line = $0
sub(/.*PS:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
ps_state = line
next
}
/^[[:space:]]*Radio interfaces:/ {
in_rat = 1; in_plmn = 0; in_roam = 0
next
}
/^[[:space:]]*Data service capabilities:/ {
in_rat = 0
next
}
/^[[:space:]]*Current PLMN:/ {
in_plmn = 1; in_rat = 0; in_roam = 0
next
}
/^[[:space:]]*Roaming indicators:/ {
in_roam = 1; in_plmn = 0; in_rat = 0
next
}
/^[[:space:]]*Default roaming status:/ {
line = $0
sub(/.*Default roaming status:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
roam = line
in_roam = 0
next
}
in_rat && /^[[:space:]]*\[[0-9]+\]:/ {
line = $0
sub(/.*\[[0-9]+\]:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
if (line != "" && line != "none" && rat == "") rat = line
}
in_plmn && /MCC:/ {
line = $0
sub(/.*MCC:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
mcc = line
}
in_plmn && /MNC:/ {
line = $0
sub(/.*MNC:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
mnc = line
}
in_plmn && /Description:/ {
line = $0
sub(/.*Description:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
gsub(/[\r\n\t]/, " ", line)
op_desc = line
in_plmn = 0
}
in_roam && /^[[:space:]]*\[[0-9]+\]:/ {
line = $0
sub(/.*\[[0-9]+\]:[[:space:]]*\x27/, "", line)
sub(/\x27.*/, "", line)
if (line == "on") roam = "on"
}
END {
printf "NAS_REG_STATE=%s\n", reg_state
printf "NAS_CS_STATE=%s\n", cs_state
printf "NAS_PS_STATE=%s\n", ps_state
printf "NAS_RAT=%s\n", rat
printf "NAS_MCC=%s\n", mcc
printf "NAS_MNC=%s\n", mnc
printf "NAS_OPERATOR=%s\n", op_desc
printf "NAS_ROAMING=%s\n", roam
}
'
}
2026-10-24 18:59:48 +03:00
save_preferred_slot() {
target_slot = " $1 "
2026-10-25 11:31:41 +02:00
PREFERRED_SIM_SLOT = " $target_slot "
2026-10-24 18:59:48 +03:00
if [ -f " $CONF_FILE " ] ; then
if grep -q "^preferred_sim_slot=" " $CONF_FILE " ; then
sed -i "s/^preferred_sim_slot=.*/preferred_sim_slot= $target_slot /" " $CONF_FILE "
else
printf "\npreferred_sim_slot=%s\n" " $target_slot " >> " $CONF_FILE "
fi
else
2026-10-25 11:31:41 +02:00
printf "sim_wait_time=10\npreferred_sim_slot=%s\n" " $target_slot " > " $CONF_FILE "
2026-10-24 18:59:48 +03:00
fi
}
provision_slot() {
req_slot = " $1 "
wait_secs = " $2 "
if ! wait_for_qmicli_modem " $wait_secs " ; then
echo "No modem available." >& 2
return 2
fi
QMI_CARDS = " $( $QMICLI_MODEM --uim-get-card-status 2>/dev/null || true ) "
count = 0
2026-10-25 11:31:41 +02:00
while : ; do
if [ -n " $QMI_CARDS " ] ; then
eval " $( parse_uim_card_status " $QMI_CARDS " " $req_slot " ) "
if [ " $req_slot " = "2" ] && [ " $SLOT2_STATE " = "present" ] && [ " $SLOT2_APP_TYPE " != "none" ] ; then
break
elif [ " $req_slot " != "2" ] && [ " $SLOT1_STATE " = "present" ] && [ " $SLOT1_APP_TYPE " != "none" ] ; then
break
elif [ " $count " -ge 3 ] && { { [ " $SLOT1_STATE " = "present" ] && [ " $SLOT1_APP_TYPE " != "none" ] ; } || { [ " $SLOT2_STATE " = "present" ] && [ " $SLOT2_APP_TYPE " != "none" ] ; } ; } ; then
break
fi
fi
2026-10-24 18:59:48 +03:00
if [ " $count " -ge " $SIM_WAIT_TIME " ] ; then
2026-10-25 11:31:41 +02:00
break
2026-10-24 18:59:48 +03:00
fi
sleep 1
count = $(( count + 1 ))
QMI_CARDS = " $( $QMICLI_MODEM --uim-get-card-status 2>/dev/null || true ) "
done
2026-10-25 11:31:41 +02:00
if [ -z " $QMI_CARDS " ] ; then
echo "No SIM card status returned."
return 0
fi
2026-10-24 18:59:48 +03:00
eval " $( parse_uim_card_status " $QMI_CARDS " " $req_slot " ) "
chosen_slot = ""
chosen_aid = ""
if [ " $req_slot " = "2" ] ; then
2026-10-25 11:31:41 +02:00
if [ " $SLOT2_STATE " = "present" ] ; then
2026-10-24 18:59:48 +03:00
chosen_slot = "2"
chosen_aid = " $SLOT2_AID "
2026-10-25 11:31:41 +02:00
elif [ " $SLOT1_STATE " = "present" ] ; then
2026-10-24 18:59:48 +03:00
chosen_slot = "1"
chosen_aid = " $SLOT1_AID "
fi
else
2026-10-25 11:31:41 +02:00
if [ " $SLOT1_STATE " = "present" ] ; then
2026-10-24 18:59:48 +03:00
chosen_slot = "1"
chosen_aid = " $SLOT1_AID "
2026-10-25 11:31:41 +02:00
elif [ " $SLOT2_STATE " = "present" ] ; then
2026-10-24 18:59:48 +03:00
chosen_slot = "2"
chosen_aid = " $SLOT2_AID "
fi
fi
2026-10-25 11:31:41 +02:00
if [ -z " $chosen_slot " ] ; then
echo "No SIM detected after $SIM_WAIT_TIME seconds."
2026-10-24 18:59:48 +03:00
return 0
fi
2026-10-25 11:31:41 +02:00
if [ " $chosen_slot " != " $PREFERRED_SIM_SLOT " ] ; then
save_preferred_slot " $chosen_slot "
fi
2026-10-25 11:50:57 +02:00
# If already provisioned on the chosen slot and application is ready or waiting for PIN, keep session intact
2026-10-25 11:31:41 +02:00
eval "chosen_app_state=\$SLOT ${ chosen_slot } _APP_STATE"
2026-10-25 11:50:57 +02:00
if [ " $PRIMARY_GW_SLOT " = " $chosen_slot " ] && { [ " $chosen_app_state " = "ready" ] || [ " $chosen_app_state " = "pin1-or-upin-pin-required" ] || [ " $chosen_app_state " = "puk1-or-upin-puk-required" ] ; } ; then
echo "Slot $chosen_slot already bound to primary-gw-provisioning ( $chosen_app_state )."
2026-10-25 11:31:41 +02:00
return 0
fi
2026-10-25 11:50:57 +02:00
if [ " $PRIMARY_GW_SLOT " != "0" ] ; then
2026-10-24 18:59:48 +03:00
$QMICLI_MODEM --uim-change-provisioning-session= 'activate=no,session-type=primary-gw-provisioning' >/dev/null 2>& 1 || true
fi
2026-10-25 11:31:41 +02:00
if [ -n " $chosen_aid " ] ; then
echo "Selecting AID $chosen_aid on slot $chosen_slot "
if ! $QMICLI_MODEM --uim-change-provisioning-session= "slot= $chosen_slot ,activate=yes,session-type=primary-gw-provisioning,aid= $chosen_aid " >/dev/null 2>& 1; then
$QMICLI_MODEM --uim-change-provisioning-session= "slot= $chosen_slot ,activate=yes,session-type=primary-gw-provisioning" >/dev/null 2>& 1 || true
fi
else
echo "Selecting slot $chosen_slot "
$QMICLI_MODEM --uim-change-provisioning-session= "slot= $chosen_slot ,activate=yes,session-type=primary-gw-provisioning" >/dev/null 2>& 1 || true
fi
return 0
}
2026-10-25 11:50:57 +02:00
prepare_slot_for_pin_op() {
req_slot = " ${ 1 :- 1 } "
slot = " $req_slot "
target_aid = ""
if detect_qmicli_modem; then
QMI_CARDS = " $( $QMICLI_MODEM --uim-get-card-status 2>/dev/null || true ) "
if [ -n " $QMI_CARDS " ] ; then
eval " $( parse_uim_card_status " $QMI_CARDS " " $PREFERRED_SIM_SLOT " ) "
if [ " $slot " = "1" ] && [ " $SLOT1_STATE " != "present" ] && [ " $SLOT2_STATE " = "present" ] ; then
slot = "2"
elif [ " $slot " = "2" ] && [ " $SLOT2_STATE " != "present" ] && [ " $SLOT1_STATE " = "present" ] ; then
slot = "1"
fi
eval "target_aid=\$SLOT ${ slot } _AID"
if [ " $PRIMARY_GW_SLOT " != " $slot " ] ; then
if [ " $PRIMARY_GW_SLOT " != "0" ] ; then
$QMICLI_MODEM --uim-change-provisioning-session= 'activate=no,session-type=primary-gw-provisioning' >/dev/null 2>& 1 || true
fi
if [ -n " $target_aid " ] ; then
$QMICLI_MODEM --uim-change-provisioning-session= "slot= $slot ,activate=yes,session-type=primary-gw-provisioning,aid= $target_aid " >/dev/null 2>& 1 || \
$QMICLI_MODEM --uim-change-provisioning-session= "slot= $slot ,activate=yes,session-type=primary-gw-provisioning" >/dev/null 2>& 1 || true
else
$QMICLI_MODEM --uim-change-provisioning-session= "slot= $slot ,activate=yes,session-type=primary-gw-provisioning" >/dev/null 2>& 1 || true
fi
sleep 0.6
fi
fi
fi
}
2026-10-25 11:31:41 +02:00
enable_and_register_modem() {
target_slot = " ${ 1 :- $PREFERRED_SIM_SLOT } "
2026-10-25 11:50:57 +02:00
force_unlock = " ${ 2 :- 0 } "
2026-10-25 11:31:41 +02:00
if detect_qmicli_modem; then
$QMICLI_MODEM --dms-set-operating-mode= online >/dev/null 2>& 1 || true
$QMICLI_MODEM --nas-initiate-network-register >/dev/null 2>& 1 || true
fi
i = 0
mm_state = ""
2026-10-25 12:32:46 +02:00
while [ " $i " -lt 10 ] ; do
mm_state = " $( timeout 3 mmcli -m any -K 2>/dev/null | awk -F': ' '/^modem\.generic\.state / {print $2; exit}' || true ) "
2026-10-25 11:31:41 +02:00
if [ -n " $mm_state " ] ; then
break
fi
sleep 1
i = $(( i + 1 ))
done
2026-10-25 11:50:57 +02:00
if [ " $mm_state " = "failed" ] || { [ " $force_unlock " = "1" ] && [ " $mm_state " = "locked" ] ; } ; then
2026-10-25 11:31:41 +02:00
if command -v rc-service >/dev/null 2>& 1; then
2026-10-25 12:32:46 +02:00
timeout 6 rc-service modemmanager restart >/dev/null 2>& 1 || true
2026-10-25 11:31:41 +02:00
elif command -v systemctl >/dev/null 2>& 1; then
2026-10-25 12:32:46 +02:00
timeout 6 systemctl restart ModemManager >/dev/null 2>& 1 || true
2026-10-25 11:31:41 +02:00
fi
i = 0
mm_state = ""
2026-10-25 12:32:46 +02:00
while [ " $i " -lt 10 ] ; do
mm_state = " $( timeout 3 mmcli -m any -K 2>/dev/null | awk -F': ' '/^modem\.generic\.state / {print $2; exit}' || true ) "
2026-10-25 11:31:41 +02:00
if [ -n " $mm_state " ] && [ " $mm_state " != "failed" ] ; then
break
fi
sleep 1
i = $(( i + 1 ))
done
fi
if [ -z " $mm_state " ] || [ " $mm_state " = "locked" ] ; then
return 0
fi
i = 0
2026-10-25 12:32:46 +02:00
while [ " $i " -lt 8 ] ; do
mm_state = " $( timeout 3 mmcli -m any -K 2>/dev/null | awk -F': ' '/^modem\.generic\.state / {print $2; exit}' || true ) "
2026-10-25 11:31:41 +02:00
case " $mm_state " in
disabled)
2026-10-25 12:32:46 +02:00
timeout 4 mmcli -m any -e >/dev/null 2>& 1 || true
2026-10-25 11:31:41 +02:00
;;
enabled)
2026-10-25 12:32:46 +02:00
timeout 4 mmcli -m any --3gpp-register-home >/dev/null 2>& 1 || true
2026-10-25 11:31:41 +02:00
break
;;
searching| registered| connected| connecting)
break
;;
locked)
break
;;
esac
sleep 1
i = $(( i + 1 ))
done
2026-10-25 12:32:46 +02:00
timeout 4 mmcli -m any --3gpp-register-home >/dev/null 2>& 1 || true
2026-10-25 11:31:41 +02:00
if command -v sxmo_hook_statusbar.sh >/dev/null 2>& 1; then
2026-10-25 12:32:46 +02:00
DISPLAY = " ${ DISPLAY :- : 0 } " timeout 3 sxmo_hook_statusbar.sh modem >/dev/null 2>& 1 || true
2026-10-25 11:31:41 +02:00
fi
2026-10-24 18:59:48 +03:00
}
2026-10-25 12:32:46 +02:00
spawn_enable_and_register() {
s_slot = " ${ 1 :- $PREFERRED_SIM_SLOT } "
s_force = " ${ 2 :- 0 } "
(
exec >/dev/null 2>& 1 </dev/null 3>& - 4>& - 5>& - 6>& - 7>& - 8>& - 9>& - 2>/dev/null || true
enable_and_register_modem " $s_slot " " $s_force "
) &
}
2026-10-24 18:59:48 +03:00
cmd = " ${ 1 :- boot } "
case " $cmd " in
boot)
2026-10-25 12:32:46 +02:00
ensure_usb_network_gadget
2026-10-24 18:59:48 +03:00
if command -v systemd-notify >/dev/null 2>& 1; then
trap '[ $? -eq 0 ] && systemd-notify --ready' EXIT
fi
case " $( cat /sys/devices/soc0/machine 2>/dev/null || true ) " in
APQ*)
echo "Skipping SIM configuration on APQ SoC."
exit 0
;;
esac
2026-10-25 12:32:46 +02:00
provision_slot " $PREFERRED_SIM_SLOT " 10 || true
2026-10-25 11:31:41 +02:00
exit 0
2026-10-24 18:59:48 +03:00
;;
status)
if ! detect_qmicli_modem; then
echo "MODEM_AVAILABLE=0"
echo "PREFERRED_SLOT= $PREFERRED_SIM_SLOT "
exit 0
fi
QMI_CARDS = " $( $QMICLI_MODEM --uim-get-card-status 2>/dev/null || true ) "
if [ -z " $QMI_CARDS " ] ; then
echo "MODEM_AVAILABLE=0"
echo "PREFERRED_SLOT= $PREFERRED_SIM_SLOT "
exit 0
fi
echo "MODEM_AVAILABLE=1"
parse_uim_card_status " $QMI_CARDS " " $PREFERRED_SIM_SLOT "
2026-10-25 11:31:41 +02:00
QMI_NAS = " $( $QMICLI_MODEM --nas-get-serving-system 2>/dev/null || true ) "
if [ -n " $QMI_NAS " ] ; then
parse_nas_serving_system " $QMI_NAS "
fi
2026-10-24 18:59:48 +03:00
exit 0
;;
switch)
target = " ${ 2 :- 1 } "
if [ " $target " != "1" ] && [ " $target " != "2" ] ; then
echo "Invalid slot: $target (must be 1 or 2)" >& 2
exit 1
fi
save_preferred_slot " $target "
2026-10-25 11:31:41 +02:00
SIM_WAIT_TIME = 3
2026-10-24 18:59:48 +03:00
provision_slot " $target " 5
if command -v rc-service >/dev/null 2>& 1; then
2026-10-25 12:32:46 +02:00
timeout 6 rc-service modemmanager restart >/dev/null 2>& 1 || true
2026-10-24 18:59:48 +03:00
elif command -v systemctl >/dev/null 2>& 1; then
2026-10-25 12:32:46 +02:00
timeout 6 systemctl restart ModemManager >/dev/null 2>& 1 || true
2026-10-24 18:59:48 +03:00
fi
2026-10-25 12:32:46 +02:00
spawn_enable_and_register " $target " 0
2026-10-24 18:59:48 +03:00
exit 0
;;
verify-pin)
slot = " ${ 2 :- 1 } "
pin = " ${ 3 :- } "
if [ -z " $pin " ] ; then
echo "Missing PIN" >& 2
exit 1
fi
2026-10-25 11:50:57 +02:00
prepare_slot_for_pin_op " $slot "
2026-10-25 11:31:41 +02:00
verified = 0
2026-10-25 12:32:46 +02:00
sim_id = " $( timeout 3 mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true ) "
if [ -n " $sim_id " ] && timeout 4 mmcli -i " $sim_id " --pin= " $pin " >/dev/null 2>& 1; then
2026-10-25 11:50:57 +02:00
verified = 1
fi
if [ " $verified " = "0" ] && [ -n " $QMICLI_MODEM " ] ; then
if $QMICLI_MODEM --uim-verify-pin= "PIN1, $pin ,session-type=primary-gw-provisioning" >/dev/null 2>& 1; then
2026-10-25 11:31:41 +02:00
verified = 1
2026-10-25 11:50:57 +02:00
elif $QMICLI_MODEM --uim-verify-pin= "PIN1, $pin ,session-type=card-slot- $slot " >/dev/null 2>& 1; then
verified = 1
elif $QMICLI_MODEM --uim-verify-pin= "PIN1, $pin " >/dev/null 2>& 1; then
verified = 1
elif $QMICLI_MODEM --uim-verify-pin= "UPIN, $pin ,session-type=primary-gw-provisioning" >/dev/null 2>& 1; then
verified = 1
elif $QMICLI_MODEM --uim-verify-pin= "UPIN, $pin ,session-type=card-slot- $slot " >/dev/null 2>& 1; then
verified = 1
elif $QMICLI_MODEM --dms-uim-verify-pin= "PIN, $pin " >/dev/null 2>& 1; then
2026-10-25 11:31:41 +02:00
verified = 1
2026-10-24 18:59:48 +03:00
fi
fi
2026-10-25 11:31:41 +02:00
if [ " $verified " = "0" ] ; then
2026-10-25 11:50:57 +02:00
at_cpin = " $( " $0 " at auto "AT+CPIN=\" $pin \"" 2>/dev/null || true ) "
if printf "%s\n" " $at_cpin " | grep -qE "(^|[[:space:]])OK([[:space:]]| $)" ; then
2026-10-25 11:31:41 +02:00
verified = 1
fi
fi
2026-10-25 11:50:57 +02:00
if [ " $verified " = "0" ] && [ -n " $QMICLI_MODEM " ] ; then
QMI_CARDS_AFTER = " $( $QMICLI_MODEM --uim-get-card-status 2>/dev/null || true ) "
if [ -n " $QMI_CARDS_AFTER " ] ; then
eval " $( parse_uim_card_status " $QMI_CARDS_AFTER " " $slot " ) "
eval "after_pin1=\$SLOT ${ slot } _PIN1_STATE"
eval "after_app=\$SLOT ${ slot } _APP_STATE"
if [ " $after_pin1 " = "enabled-verified" ] || [ " $after_pin1 " = "disabled" ] || [ " $after_app " = "ready" ] ; then
verified = 1
fi
fi
fi
2026-10-25 11:31:41 +02:00
if [ " $verified " = "1" ] ; then
save_preferred_slot " $slot "
SIM_WAIT_TIME = 2
provision_slot " $slot " 3 >/dev/null 2>& 1 || true
2026-10-25 11:50:57 +02:00
if [ -n " $sim_id " ] ; then
2026-10-25 12:32:46 +02:00
timeout 4 mmcli -i " $sim_id " --pin= " $pin " >/dev/null 2>& 1 || true
2026-10-25 11:50:57 +02:00
fi
2026-10-25 12:32:46 +02:00
timeout 4 mmcli -m any -e >/dev/null 2>& 1 || true
spawn_enable_and_register " $slot " 1
2026-10-25 11:31:41 +02:00
exit 0
fi
2026-10-24 18:59:48 +03:00
exit 1
;;
unblock-puk)
slot = " ${ 2 :- 1 } "
puk = " ${ 3 :- } "
newpin = " ${ 4 :- } "
if [ -z " $puk " ] || [ -z " $newpin " ] ; then
echo "Missing PUK or new PIN" >& 2
exit 1
fi
2026-10-25 11:50:57 +02:00
prepare_slot_for_pin_op " $slot "
2026-10-25 11:31:41 +02:00
unblocked = 0
2026-10-25 12:32:46 +02:00
sim_id = " $( timeout 3 mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true ) "
if [ -n " $sim_id " ] && timeout 4 mmcli -i " $sim_id " --puk= " $puk " --pin= " $newpin " >/dev/null 2>& 1; then
2026-10-25 11:50:57 +02:00
unblocked = 1
fi
if [ " $unblocked " = "0" ] && [ -n " $QMICLI_MODEM " ] ; then
if $QMICLI_MODEM --uim-unblock-pin= "PIN1, $puk , $newpin ,session-type=primary-gw-provisioning" >/dev/null 2>& 1; then
2026-10-25 11:31:41 +02:00
unblocked = 1
2026-10-25 11:50:57 +02:00
elif $QMICLI_MODEM --uim-unblock-pin= "PIN1, $puk , $newpin ,session-type=card-slot- $slot " >/dev/null 2>& 1; then
unblocked = 1
elif $QMICLI_MODEM --uim-unblock-pin= "PIN1, $puk , $newpin " >/dev/null 2>& 1; then
unblocked = 1
elif $QMICLI_MODEM --dms-uim-unblock-pin= "PIN, $puk , $newpin " >/dev/null 2>& 1; then
2026-10-25 11:31:41 +02:00
unblocked = 1
2026-10-24 18:59:48 +03:00
fi
fi
2026-10-25 11:31:41 +02:00
if [ " $unblocked " = "0" ] ; then
2026-10-25 11:50:57 +02:00
at_cpin = " $( " $0 " at auto "AT+CPIN=\" $puk \",\" $newpin \"" 2>/dev/null || true ) "
if printf "%s\n" " $at_cpin " | grep -qE "(^|[[:space:]])OK([[:space:]]| $)" ; then
2026-10-25 11:31:41 +02:00
unblocked = 1
fi
fi
if [ " $unblocked " = "1" ] ; then
save_preferred_slot " $slot "
SIM_WAIT_TIME = 2
provision_slot " $slot " 3 >/dev/null 2>& 1 || true
2026-10-25 12:32:46 +02:00
timeout 4 mmcli -m any -e >/dev/null 2>& 1 || true
spawn_enable_and_register " $slot " 1
2026-10-25 11:31:41 +02:00
exit 0
fi
2026-10-24 18:59:48 +03:00
exit 1
;;
set-pin-lock)
slot = " ${ 2 :- 1 } "
mode = " ${ 3 :- enable } "
pin = " ${ 4 :- } "
if [ -z " $pin " ] || { [ " $mode " != "enable" ] && [ " $mode " != "disable" ] ; } ; then
echo "Usage: $0 set-pin-lock <1|2> <enable|disable> <pin>" >& 2
exit 1
fi
2026-10-25 11:50:57 +02:00
prepare_slot_for_pin_op " $slot "
if [ -n " $QMICLI_MODEM " ] ; then
if $QMICLI_MODEM --uim-set-pin-protection= "PIN1, $mode , $pin ,session-type=primary-gw-provisioning" >/dev/null 2>& 1; then
exit 0
fi
2026-10-25 11:31:41 +02:00
if $QMICLI_MODEM --uim-set-pin-protection= "PIN1, $mode , $pin ,session-type=card-slot- $slot " >/dev/null 2>& 1; then
exit 0
fi
2026-10-25 11:50:57 +02:00
if $QMICLI_MODEM --dms-uim-set-pin-protection= "PIN, $mode , $pin " >/dev/null 2>& 1; then
2026-10-25 11:31:41 +02:00
exit 0
fi
fi
2026-10-24 18:59:48 +03:00
sim_id = " $( mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true ) "
if [ -n " $sim_id " ] ; then
if [ " $mode " = "enable" ] && mmcli -i " $sim_id " --enable-pin --pin= " $pin " >/dev/null 2>& 1; then
exit 0
elif [ " $mode " = "disable" ] && mmcli -i " $sim_id " --disable-pin --pin= " $pin " >/dev/null 2>& 1; then
exit 0
fi
fi
exit 1
;;
change-pin)
slot = " ${ 2 :- 1 } "
oldpin = " ${ 3 :- } "
newpin = " ${ 4 :- } "
if [ -z " $oldpin " ] || [ -z " $newpin " ] ; then
echo "Usage: $0 change-pin <1|2> <oldpin> <newpin>" >& 2
exit 1
fi
2026-10-25 11:50:57 +02:00
prepare_slot_for_pin_op " $slot "
if [ -n " $QMICLI_MODEM " ] ; then
if $QMICLI_MODEM --uim-change-pin= "PIN1, $oldpin , $newpin ,session-type=primary-gw-provisioning" >/dev/null 2>& 1; then
exit 0
fi
2026-10-24 18:59:48 +03:00
if $QMICLI_MODEM --uim-change-pin= "PIN1, $oldpin , $newpin ,session-type=card-slot- $slot " >/dev/null 2>& 1; then
exit 0
fi
2026-10-25 11:50:57 +02:00
if $QMICLI_MODEM --dms-uim-change-pin= "PIN, $oldpin , $newpin " >/dev/null 2>& 1; then
2026-10-25 11:31:41 +02:00
exit 0
fi
fi
sim_id = " $( mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true ) "
if [ -n " $sim_id " ] && mmcli -i " $sim_id " --change-pin= " $newpin " --pin= " $oldpin " >/dev/null 2>& 1; then
exit 0
2026-10-24 18:59:48 +03:00
fi
exit 1
;;
2026-10-24 19:58:11 +03:00
imei)
imei1 = ""
imei2 = ""
imei_sv = ""
if detect_qmicli_modem; then
dms_ids = " $( $QMICLI_MODEM --dms-get-ids 2>/dev/null || true ) "
imei1 = " $( printf "%s\n" " $dms_ids " | sed -n "s/.*IMEI: '\([0-9]\{14,16\}\)'/\1/p" | head -n1) "
imei_sv = " $( printf "%s\n" " $dms_ids " | sed -n "s/.*IMEI SV: '\([^']*\)'/\1/p" | head -n1) "
fi
if [ -z " $imei1 " ] ; then
imei1 = " $( mmcli -m any -K 2>/dev/null | awk -F': ' '/modem\.3gpp\.imei/ {print $2; exit}' ) "
fi
# Query secondary AT channel (/dev/wwan0at1) if present
if [ -c "/dev/wwan0at1" ] ; then
at_out = " $( timeout 1 sh -c '
stty -F /dev/wwan0at1 raw -echo 115200 2>/dev/null || true
(cat /dev/wwan0at1 & pid=$!; printf "AT+CGSN\r\n" > /dev/wwan0at1; sleep 0.25; kill $pid 2>/dev/null) 2>/dev/null
' 2>/dev/null | tr -d "\r" | grep -oE '^[0-9]{15}$' | head -n1 || true ) "
if [ -n " $at_out " ] && [ " $at_out " != " $imei1 " ] ; then
imei2 = " $at_out "
fi
fi
# Scan factory partitions for Luhn-valid 15-digit IMEIs (TAC 35...)
if [ -z " $imei2 " ] ; then
for part in /dev/disk/by-partlabel/traceability /dev/disk/by-partlabel/tunning /dev/disk/by-partlabel/devinfo /dev/disk/by-partlabel/oem; do
[ -e " $part " ] || continue
cand_list = " $( strings -n 14 " $part " 2>/dev/null | grep -oE '35[0-9]{13}' | awk '
{
val = $0
sum = 0
for (i = 1; i <= 14; i++) {
d = substr(val, i, 1) + 0
if (i % 2 == 0) {
d *= 2
if (d > 9) d -= 9
}
sum += d
}
chk = (10 - (sum % 10)) % 10
if (chk == (substr(val, 15, 1) + 0)) print val
}
' || true ) "
for c in $cand_list ; do
if [ -z " $imei1 " ] ; then
imei1 = " $c "
elif [ " $c " != " $imei1 " ] && [ -z " $imei2 " ] ; then
imei2 = " $c "
break 2
fi
done
done
fi
echo "IMEI1= ${ imei1 } "
echo "IMEI2= ${ imei2 } "
echo "IMEI_SV= ${ imei_sv } "
;;
usb-tether)
mode = " ${ 2 :- status } "
case " $mode " in
on)
2026-10-25 12:32:46 +02:00
ensure_usb_network_gadget
2026-10-24 19:58:11 +03:00
sysctl -w net.ipv4.ip_forward= 1 >/dev/null 2>& 1 || true
nft add table ip nat 2>/dev/null || true
nft 'add chain ip nat postrouting { type nat hook postrouting priority 100 ; }' 2>/dev/null || true
nft add rule ip nat postrouting oifname "wwan*" masquerade 2>/dev/null || true
nft add rule ip nat postrouting oifname "wlan*" masquerade 2>/dev/null || true
ip addr show dev usb0 2>/dev/null | grep -q "172.16.42.1" || ip addr add 172.16.42.1/16 dev usb0 2>/dev/null || true
ip link set usb0 up 2>/dev/null || true
echo "1" > /tmp/bananarchy_usb_tether.active
;;
off)
2026-10-25 12:32:46 +02:00
ensure_usb_network_gadget
2026-10-24 19:58:11 +03:00
sysctl -w net.ipv4.ip_forward= 0 >/dev/null 2>& 1 || true
nft delete table ip nat 2>/dev/null || true
nmcli connection delete USBNetworkShare >/dev/null 2>& 1 || true
ip addr show dev usb0 2>/dev/null | grep -q "172.16.42.1" || ip addr add 172.16.42.1/16 dev usb0 2>/dev/null || true
ip link set usb0 up 2>/dev/null || true
rm -f /tmp/bananarchy_usb_tether.active
;;
*)
if [ -f /tmp/bananarchy_usb_tether.active ] && [ " $( sysctl -n net.ipv4.ip_forward 2>/dev/null || echo 0) " = "1" ] ; then
echo "ACTIVE=1"
else
echo "ACTIVE=0"
fi
;;
esac
;;
at)
port = " ${ 2 :- auto } "
shift 2 2>/dev/null || true
at_cmd = " $* "
if [ -z " $at_cmd " ] ; then
echo "ERROR: Empty AT command"
exit 1
fi
resp = ""
2026-10-25 11:31:41 +02:00
if [ " $PREFERRED_SIM_SLOT " = "2" ] ; then
at_candidates = " $port /dev/wwan0at1 /dev/wwan0at0"
else
at_candidates = " $port /dev/wwan0at0 /dev/wwan0at1"
fi
for candidate in $at_candidates ; do
2026-10-24 19:58:11 +03:00
case " $candidate " in
wwan0at0| wwan0at1)
dev = "/dev/ $candidate "
2026-10-24 21:11:57 +03:00
;;
/dev/wwan0at0| /dev/wwan0at1)
dev = " $candidate "
;;
*)
dev = ""
2026-10-24 19:58:11 +03:00
;;
esac
2026-10-24 21:11:57 +03:00
if [ -n " $dev " ] && [ -c " $dev " ] ; then
2026-10-25 11:31:41 +02:00
resp = " $( timeout 3 sh -c '
2026-10-24 21:11:57 +03:00
dev="$1"
cmd="$2"
tmpf="/tmp/at_resp_$$.txt"
rm -f "$tmpf"
cat "$dev" > "$tmpf" 2>/dev/null &
rpid=$!
sleep 0.04
printf "%s\r\n" "$cmd" > "$dev" 2>/dev/null || true
i=0
2026-10-25 11:31:41 +02:00
while [ "$i" -lt 16 ]; do
2026-10-24 21:11:57 +03:00
if grep -qE "(^|[[:space:]])(OK|ERROR|\+CME ERROR|\+CMS ERROR)([[:space:]]|$)" "$tmpf" 2>/dev/null; then
break
fi
sleep 0.08
i=$((i + 1))
done
kill "$rpid" 2>/dev/null || true
wait "$rpid" 2>/dev/null || true
tr -d "\r" < "$tmpf" 2>/dev/null || true
rm -f "$tmpf"
' _ " $dev " " $at_cmd " 2>/dev/null | sed '/^[[:space:]]*$/d' || true ) "
if [ -n " $resp " ] ; then
printf "%s\n" " $resp "
exit 0
fi
fi
if [ " $port " != "auto" ] && [ " $candidate " = " $port " ] ; then
break
fi
2026-10-24 19:58:11 +03:00
done
mm_out = " $( mmcli -m any --command= " $at_cmd " 2>& 1 || true ) "
if [ -n " $mm_out " ] ; then
printf "%s\n" " $mm_out " | sed "s/^response: '\(.*\)' $/\1/"
exit 0
fi
echo "ERROR: No response from modem AT channel"
exit 1
;;
2026-10-25 11:31:41 +02:00
ussd)
ussd_mode = " ${ 2 :- initiate } "
shift 2 2>/dev/null || true
ussd_code = " $* "
if [ " $ussd_mode " = "cancel" ] ; then
mmcli -m any --3gpp-ussd-cancel >/dev/null 2>& 1 || true
" $0 " at auto 'AT+CUSD=2' >/dev/null 2>& 1 || true
echo "USSD session cancelled."
exit 0
fi
if [ -z " $ussd_code " ] ; then
echo "ERROR: Empty USSD code"
exit 1
fi
mm_state = " $( mmcli -m any -K 2>/dev/null | awk -F': ' '$1 == "modem.generic.state" {print $2; exit}' ) "
if [ " $mm_state " = "disabled" ] || [ -z " $mm_state " ] ; then
mmcli -m any -e >/dev/null 2>& 1 || true
sleep 1
fi
if [ " $ussd_mode " = "respond" ] ; then
mm_out = " $( mmcli -m any --3gpp-ussd-respond= " $ussd_code " 2>& 1) "
mm_rc = $?
else
mmcli -m any --3gpp-ussd-cancel >/dev/null 2>& 1 || true
mm_out = " $( mmcli -m any --3gpp-ussd-initiate= " $ussd_code " 2>& 1) "
mm_rc = $?
fi
if [ " $mm_rc " -eq 0 ] && [ -n " $mm_out " ] ; then
printf "%s\n" " $mm_out " | sed -e "s/^USSD session initiated; new reply from network: '\(.*\)' $/\1/" -e "s/^USSD response sent; new reply from network: '\(.*\)' $/\1/"
exit 0
fi
# Fallback to direct AT+CUSD on the active SIM AT port
if [ " $PREFERRED_SIM_SLOT " = "2" ] ; then
at_candidates = "/dev/wwan0at1 /dev/wwan0at0"
else
at_candidates = "/dev/wwan0at0 /dev/wwan0at1"
fi
for dev in $at_candidates ; do
[ -c " $dev " ] || continue
cusd_out = " $( timeout 12 sh -c '
dev="$1"
code="$2"
tmpf="/tmp/ussd_resp_$$.txt"
rm -f "$tmpf"
cat "$dev" > "$tmpf" 2>/dev/null &
rpid=$!
sleep 0.05
printf "AT+CSCS=\"GSM\";+CUSD=1,\"%s\",15\r\n" "$code" > "$dev" 2>/dev/null || true
i=0
while [ "$i" -lt 80 ]; do
if grep -qE "\+CUSD:[[:space:]]*[0-9]+" "$tmpf" 2>/dev/null; then
break
fi
if grep -qE "(^|[[:space:]])(\+CME ERROR|\+CMS ERROR|ERROR)([[:space:]]|$)" "$tmpf" 2>/dev/null; then
break
fi
sleep 0.1
i=$((i + 1))
done
kill "$rpid" 2>/dev/null || true
wait "$rpid" 2>/dev/null || true
tr -d "\r" < "$tmpf" 2>/dev/null || true
rm -f "$tmpf"
' _ " $dev " " $ussd_code " 2>/dev/null || true ) "
cusd_msg = " $( printf "%s\n" " $cusd_out " | sed -n 's/.*+CUSD:[[:space:]]*[0-9]*,[[:space:]]*"\([^"]*\)".*/\1/p' | tail -n1) "
if [ -n " $cusd_msg " ] ; then
printf "%s\n" " $cusd_msg "
exit 0
fi
done
if [ -n " $mm_out " ] ; then
printf "%s\n" " $mm_out "
else
echo "ERROR: USSD request failed"
fi
exit 1
;;
2026-10-25 07:56:44 +02:00
sim-contacts-import)
req_slot = " ${ 2 :- auto } "
if ! wait_for_qmicli_modem 3; then
echo "ERROR: No modem available"
exit 1
fi
QMI_CARDS = " $( $QMICLI_MODEM --uim-get-card-status 2>/dev/null || true ) "
if [ -z " $QMI_CARDS " ] ; then
echo "ERROR: Cannot read SIM card status"
exit 1
fi
eval " $( parse_uim_card_status " $QMI_CARDS " " $PREFERRED_SIM_SLOT " ) "
target_slot = ""
target_aid = ""
if [ " $req_slot " = "1" ] ; then
if [ " $SLOT1_STATE " != "present" ] ; then
echo "ERROR: SIM 1 is not inserted"
exit 1
fi
target_slot = "1"
target_aid = " $SLOT1_AID "
elif [ " $req_slot " = "2" ] ; then
if [ " $SLOT2_STATE " != "present" ] ; then
echo "ERROR: SIM 2 is not inserted"
exit 1
fi
target_slot = "2"
target_aid = " $SLOT2_AID "
else
if [ " $PRIMARY_GW_SLOT " = "1" ] && [ " $SLOT1_STATE " = "present" ] ; then
target_slot = "1"
target_aid = " $SLOT1_AID "
elif [ " $PRIMARY_GW_SLOT " = "2" ] && [ " $SLOT2_STATE " = "present" ] ; then
target_slot = "2"
target_aid = " $SLOT2_AID "
elif [ " $SLOT1_STATE " = "present" ] ; then
target_slot = "1"
target_aid = " $SLOT1_AID "
elif [ " $SLOT2_STATE " = "present" ] ; then
target_slot = "2"
target_aid = " $SLOT2_AID "
else
echo "ERROR: No SIM card detected"
exit 1
fi
fi
orig_gw_slot = " $PRIMARY_GW_SLOT "
orig_gw_aid = ""
if [ " $orig_gw_slot " = "1" ] ; then
orig_gw_aid = " $SLOT1_AID "
elif [ " $orig_gw_slot " = "2" ] ; then
orig_gw_aid = " $SLOT2_AID "
fi
switched_gw = 0
restore_gw_slot() {
if [ " $switched_gw " = "1" ] ; then
$QMICLI_MODEM --uim-change-provisioning-session= 'activate=no,session-type=primary-gw-provisioning' >/dev/null 2>& 1 || true
if [ -n " $orig_gw_slot " ] && [ " $orig_gw_slot " != "0" ] && [ -n " $orig_gw_aid " ] ; then
$QMICLI_MODEM --uim-change-provisioning-session= "slot= $orig_gw_slot ,activate=yes,session-type=primary-gw-provisioning,aid= $orig_gw_aid " >/dev/null 2>& 1 || true
fi
switched_gw = 0
fi
}
trap restore_gw_slot EXIT INT TERM
if [ " $PRIMARY_GW_SLOT " != " $target_slot " ] && [ -n " $target_aid " ] ; then
if [ " $PRIMARY_GW_SLOT " != "0" ] ; then
$QMICLI_MODEM --uim-change-provisioning-session= 'activate=no,session-type=primary-gw-provisioning' >/dev/null 2>& 1 || true
fi
if $QMICLI_MODEM --uim-change-provisioning-session= "slot= $target_slot ,activate=yes,session-type=primary-gw-provisioning,aid= $target_aid " >/dev/null 2>& 1; then
switched_gw = 1
sleep 1
fi
fi
tmp_contacts = "/tmp/sim_import_ $$ .tsv"
rm -f " $tmp_contacts "
: > " $tmp_contacts "
# Method 1: Standard 3GPP AT+CPBS="SM" / AT+CPBR
at_info = " $( " $0 " at auto 'AT+CMEE=2;+CSCS="GSM";+CPBS="SM";+CPBR=?' 2>/dev/null || true ) "
max_idx = " $( printf "%s\n" " $at_info " | sed -n 's/.*+CPBR:[[:space:]]*([0-9]*-\([0-9]*\)).*/\1/p' | head -n1) "
if [ -n " $max_idx " ] && [ " $max_idx " -gt 0 ] 2>/dev/null; then
if [ " $max_idx " -gt 500 ] ; then
max_idx = 500
fi
start_idx = 1
while [ " $start_idx " -le " $max_idx " ] ; do
end_idx = $(( start_idx + 49 ))
if [ " $end_idx " -gt " $max_idx " ] ; then
end_idx = " $max_idx "
fi
chunk = " $( " $0 " at auto "AT+CPBR= ${ start_idx } , ${ end_idx } " 2>/dev/null || true ) "
if [ -n " $chunk " ] ; then
printf "%s\n" " $chunk " | awk '
/^\+CPBR:[[:space:]]*[0-9]+,/ {
line = $0
sub(/^\+CPBR:[[:space:]]*[0-9]+,[[:space:]]*"/, "", line)
q1 = index(line, "\"")
if (q1 <= 0) next
num = substr(line, 1, q1 - 1)
rest = substr(line, q1 + 1)
sub(/^[[:space:]]*,[[:space:]]*/, "", rest)
c2 = index(rest, ",")
if (c2 <= 0) next
ton = substr(rest, 1, c2 - 1) + 0
name = substr(rest, c2 + 1)
sub(/^[[:space:]]*"/, "", name)
sub(/"[[:space:]]*$/, "", name)
gsub(/[\t\r\n]/, " ", name)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", name)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", num)
if (num == "") next
if (ton == 145 && substr(num, 1, 1) != "+") {
num = "+" num
}
if (name == "") name = num
printf "%s\t%s\n", num, name
}
' >> " $tmp_contacts "
fi
start_idx = $(( end_idx + 1 ))
done
fi
# Method 2: Direct QMI UIM EF_ADN (3F00:7F10:6F3A) record fallback
if [ ! -s " $tmp_contacts " ] ; then
adn_attr = " $( $QMICLI_MODEM --uim-get-file-attributes= 0x3F00,0x7F10,0x6F3A 2>/dev/null || true ) "
2026-10-25 11:31:41 +02:00
reclen = " $( printf "%s\n" " $adn_attr " | awk '/Record (size|length):/ {gsub(/[^0-9]/, "", $NF); print $NF + 0; exit}' ) "
reccount = " $( printf "%s\n" " $adn_attr " | awk '/Record count:/ {gsub(/[^0-9]/, "", $NF); print $NF + 0; exit}' ) "
2026-10-25 07:56:44 +02:00
if [ -n " $reclen " ] && [ -n " $reccount " ] && [ " $reclen " -ge 14 ] 2>/dev/null && [ " $reccount " -ge 1 ] 2>/dev/null; then
if [ " $reccount " -gt 250 ] ; then
reccount = 250
fi
rec = 1
while [ " $rec " -le " $reccount " ] ; do
raw_rec = " $( $QMICLI_MODEM --uim-read-record= "file=0x3F00,0x7F10,0x6F3A,record-number= ${ rec } ,record-length= ${ reclen } " 2>/dev/null || true ) "
if [ -n " $raw_rec " ] ; then
printf "%s\n" " $raw_rec " | awk -v rlen = " $reclen " '
function hex2val(h, i, c, v, res) {
res = 0
h = toupper(h)
for (i = 1; i <= length(h); i++) {
c = substr(h, i, 1)
v = index("0123456789ABCDEF", c) - 1
if (v < 0) return -1
res = res * 16 + v
}
return res
}
function bcd_digit(n) {
if (n >= 0 && n <= 9) return "" n
if (n == 10) return "*"
if (n == 11) return "#"
return ""
}
/^[[:space:]]*([0-9A-Fa-f]{2}:)+[0-9A-Fa-f]{2}[[:space:]]*$/ {
line = $0
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
n = split(line, hex, ":")
if (n < 14) next
alpha_len = n - 14
bcd_len = hex2val(hex[alpha_len + 1])
ton_npi = hex2val(hex[alpha_len + 2])
if (bcd_len <= 0 || bcd_len > 11 || bcd_len == 255) next
num = ""
if (int(ton_npi / 16) % 8 == 1) {
num = "+"
}
bcd_bytes = bcd_len - 1
if (bcd_bytes > 10) bcd_bytes = 10
for (i = 1; i <= bcd_bytes; i++) {
b = hex2val(hex[alpha_len + 2 + i])
if (b < 0 || b == 255) break
lo = b % 16
hi = int(b / 16)
d1 = bcd_digit(lo)
if (d1 == "") break
num = num d1
d2 = bcd_digit(hi)
if (d2 == "") break
num = num d2
}
if (num == "" || num == "+") next
name = ""
for (i = 1; i <= alpha_len; i++) {
b = hex2val(hex[i])
if (b < 0 || b == 255) break
if (b >= 32 && b < 127 && b != 9) {
name = name sprintf("%c", b)
}
}
gsub(/^[[:space:]]+|[[:space:]]+$/, "", name)
if (name == "") name = num
printf "%s\t%s\n", num, name
}
' >> " $tmp_contacts "
fi
rec = $(( rec + 1 ))
done
fi
fi
cat " $tmp_contacts "
rm -f " $tmp_contacts "
exit 0
;;
sim-contacts-export)
req_slot = " ${ 2 :- auto } "
src_tsv = " ${ 3 :- /home/user/.config/sxmo/contacts.tsv } "
if [ ! -r " $src_tsv " ] ; then
echo "ERROR: Contacts file not found"
exit 1
fi
if ! wait_for_qmicli_modem 3; then
echo "ERROR: No modem available"
exit 1
fi
QMI_CARDS = " $( $QMICLI_MODEM --uim-get-card-status 2>/dev/null || true ) "
if [ -z " $QMI_CARDS " ] ; then
echo "ERROR: Cannot read SIM card status"
exit 1
fi
eval " $( parse_uim_card_status " $QMI_CARDS " " $PREFERRED_SIM_SLOT " ) "
target_slot = ""
target_aid = ""
if [ " $req_slot " = "1" ] ; then
if [ " $SLOT1_STATE " != "present" ] ; then
echo "ERROR: SIM 1 is not inserted"
exit 1
fi
target_slot = "1"
target_aid = " $SLOT1_AID "
elif [ " $req_slot " = "2" ] ; then
if [ " $SLOT2_STATE " != "present" ] ; then
echo "ERROR: SIM 2 is not inserted"
exit 1
fi
target_slot = "2"
target_aid = " $SLOT2_AID "
else
if [ " $PRIMARY_GW_SLOT " = "1" ] && [ " $SLOT1_STATE " = "present" ] ; then
target_slot = "1"
target_aid = " $SLOT1_AID "
elif [ " $PRIMARY_GW_SLOT " = "2" ] && [ " $SLOT2_STATE " = "present" ] ; then
target_slot = "2"
target_aid = " $SLOT2_AID "
elif [ " $SLOT1_STATE " = "present" ] ; then
target_slot = "1"
target_aid = " $SLOT1_AID "
elif [ " $SLOT2_STATE " = "present" ] ; then
target_slot = "2"
target_aid = " $SLOT2_AID "
else
echo "ERROR: No SIM card detected"
exit 1
fi
fi
orig_gw_slot = " $PRIMARY_GW_SLOT "
orig_gw_aid = ""
if [ " $orig_gw_slot " = "1" ] ; then
orig_gw_aid = " $SLOT1_AID "
elif [ " $orig_gw_slot " = "2" ] ; then
orig_gw_aid = " $SLOT2_AID "
fi
switched_gw = 0
restore_gw_slot_exp() {
if [ " $switched_gw " = "1" ] ; then
$QMICLI_MODEM --uim-change-provisioning-session= 'activate=no,session-type=primary-gw-provisioning' >/dev/null 2>& 1 || true
if [ -n " $orig_gw_slot " ] && [ " $orig_gw_slot " != "0" ] && [ -n " $orig_gw_aid " ] ; then
$QMICLI_MODEM --uim-change-provisioning-session= "slot= $orig_gw_slot ,activate=yes,session-type=primary-gw-provisioning,aid= $orig_gw_aid " >/dev/null 2>& 1 || true
fi
switched_gw = 0
fi
}
trap restore_gw_slot_exp EXIT INT TERM
if [ " $PRIMARY_GW_SLOT " != " $target_slot " ] && [ -n " $target_aid " ] ; then
if [ " $PRIMARY_GW_SLOT " != "0" ] ; then
$QMICLI_MODEM --uim-change-provisioning-session= 'activate=no,session-type=primary-gw-provisioning' >/dev/null 2>& 1 || true
fi
if $QMICLI_MODEM --uim-change-provisioning-session= "slot= $target_slot ,activate=yes,session-type=primary-gw-provisioning,aid= $target_aid " >/dev/null 2>& 1; then
switched_gw = 1
sleep 1
fi
fi
at_info = " $( " $0 " at auto 'AT+CMEE=2;+CSCS="GSM";+CPBS="SM";+CPBR=?' 2>/dev/null || true ) "
if printf "%s\n" " $at_info " | grep -qE "ERROR" ; then
echo "ERROR: SIM $target_slot phonebook not ready"
exit 1
fi
tlen = " $( printf "%s\n" " $at_info " | sed -n 's/.*+CPBR:[[:space:]]*([0-9]*-[0-9]*),[0-9]*,\([0-9]*\).*/\1/p' | head -n1) "
if [ -z " $tlen " ] || [ " $tlen " -le 0 ] 2>/dev/null; then
tlen = 14
fi
exported = 0
while IFS = " $( printf '\t' ) " read -r raw_num raw_name; do
num = " $( printf "%s" " $raw_num " | tr -cd '+0-9*#' ) "
name = " $( printf "%s" " $raw_name " | tr -d '"\r\n\t\\' | cut -c1-" $tlen " ) "
[ -n " $num " ] || continue
[ -n " $name " ] || name = " $( printf "%s" " $num " | cut -c1-" $tlen " ) "
ton = 129
case " $num " in
+*) ton = 145 ;;
esac
res = " $( " $0 " at auto "AT+CPBW=,\" ${ num } \", ${ ton } ,\" ${ name } \"" 2>/dev/null || true ) "
if printf "%s\n" " $res " | grep -q "OK" ; then
exported = $(( exported + 1 ))
fi
done < " $src_tsv "
echo "EXPORTED= ${ exported } "
echo "SLOT= ${ target_slot } "
exit 0
;;
2026-10-24 18:59:48 +03:00
*)
2026-10-25 07:56:44 +02:00
echo "Usage: $0 [boot|status|switch <1|2>|verify-pin <slot> <pin>|unblock-puk <slot> <puk> <newpin>|set-pin-lock <slot> <enable|disable> <pin>|change-pin <slot> <oldpin> <newpin>|imei|usb-tether <on|off|status>|at <port> <cmd>|sim-contacts-import [1|2|auto]|sim-contacts-export [1|2|auto] [tsv_file]]" >& 2
2026-10-24 18:59:48 +03:00
exit 1
;;
esac