1409 lines
48 KiB
Bash
Executable File
1409 lines
48 KiB
Bash
Executable File
#!/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
|
|
|
|
SIM_WAIT_TIME="${SIM_WAIT_TIME:-${sim_wait_time:-4}}"
|
|
PREFERRED_SIM_SLOT="${PREFERRED_SIM_SLOT:-${preferred_sim_slot:-1}}"
|
|
QMICLI_MODEM=""
|
|
|
|
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
|
|
}
|
|
|
|
detect_qmicli_modem() {
|
|
if ! command -v qmicli >/dev/null 2>&1; then
|
|
return 1
|
|
fi
|
|
if timeout 3 qmicli --silent -pd qrtr://0 --uim-noop >/dev/null 2>&1; then
|
|
QMICLI_MODEM="timeout 4 qmicli --silent -pd qrtr://0"
|
|
return 0
|
|
elif [ -e "/dev/wwan0qmi0" ]; then
|
|
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"
|
|
return 0
|
|
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"
|
|
return 0
|
|
fi
|
|
elif [ -e "/dev/modem" ]; then
|
|
if timeout 3 qmicli --silent -pd /dev/modem --uim-noop >/dev/null 2>&1; then
|
|
QMICLI_MODEM="timeout 4 qmicli --silent -pd /dev/modem"
|
|
return 0
|
|
elif timeout 3 qmicli --silent -d /dev/modem --uim-noop >/dev/null 2>&1; then
|
|
QMICLI_MODEM="timeout 4 qmicli --silent -d /dev/modem"
|
|
return 0
|
|
fi
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
wait_for_qmicli_modem() {
|
|
max_wait="${1:-12}"
|
|
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
|
|
sec_slot = 0
|
|
sec_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
|
|
want_aid[s] = 0
|
|
need_aid_line[s] = 0
|
|
}
|
|
}
|
|
/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
|
|
}
|
|
}
|
|
/Secondary GW:/ {
|
|
if ($0 ~ /slot \x27[0-9]+\x27/) {
|
|
line = $0
|
|
sub(/.*slot \x27/, "", line)
|
|
sub(/\x27.*/, "", line)
|
|
sec_slot = line + 0
|
|
}
|
|
if ($0 ~ /application \x27[0-9]+\x27/) {
|
|
line = $0
|
|
sub(/.*application \x27/, "", line)
|
|
sub(/\x27.*/, "", line)
|
|
sec_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
|
|
sub(/.*Card state:[[:space:]]*\x27/, "", line)
|
|
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
|
|
sub(/.*UPIN state:[[:space:]]*\x27/, "", line)
|
|
sub(/\x27.*/, "", line)
|
|
upin_state[cur_slot] = line
|
|
}
|
|
if (!in_app && $0 ~ /UPIN retries:/) {
|
|
line = $0
|
|
sub(/.*UPIN retries:[[:space:]]*\x27/, "", line)
|
|
sub(/\x27.*/, "", line)
|
|
upin_ret[cur_slot] = line + 0
|
|
}
|
|
if (!in_app && $0 ~ /UPUK retries:/) {
|
|
line = $0
|
|
sub(/.*UPUK retries:[[:space:]]*\x27/, "", line)
|
|
sub(/\x27.*/, "", line)
|
|
upuk_ret[cur_slot] = line + 0
|
|
}
|
|
if (in_app && $0 ~ /Application type:/) {
|
|
line = $0
|
|
sub(/.*Application type:[[:space:]]*\x27/, "", line)
|
|
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
|
|
} else if (app_type[cur_slot] == "none") {
|
|
app_type[cur_slot] = "sim"
|
|
want_aid[cur_slot] = 1
|
|
} else {
|
|
want_aid[cur_slot] = 0
|
|
}
|
|
}
|
|
if (in_app && $0 ~ /Application state:/ && want_aid[cur_slot] > 0) {
|
|
line = $0
|
|
sub(/.*Application state:[[:space:]]*\x27/, "", line)
|
|
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)
|
|
if (line != "" && line !~ /:.*\x27/) {
|
|
aid[cur_slot] = line
|
|
}
|
|
need_aid_line[cur_slot] = 0
|
|
}
|
|
if (in_app && $0 ~ /PIN1 state:/ && want_aid[cur_slot] > 0) {
|
|
line = $0
|
|
sub(/.*PIN1 state:[[:space:]]*\x27/, "", line)
|
|
sub(/\x27.*/, "", line)
|
|
pin1_state[cur_slot] = line
|
|
}
|
|
if (in_app && $0 ~ /PIN1 retries:/ && want_aid[cur_slot] > 0) {
|
|
line = $0
|
|
sub(/.*PIN1 retries:[[:space:]]*\x27/, "", line)
|
|
sub(/\x27.*/, "", line)
|
|
pin1_ret[cur_slot] = line + 0
|
|
}
|
|
if (in_app && $0 ~ /PUK1 retries:/ && want_aid[cur_slot] > 0) {
|
|
line = $0
|
|
sub(/.*PUK1 retries:[[:space:]]*\x27/, "", line)
|
|
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
|
|
printf "SECONDARY_GW_SLOT=%d\n", sec_slot
|
|
printf "SECONDARY_GW_APP=%d\n", sec_app
|
|
for (s = 1; s <= 2; s++) {
|
|
gsub(/[^A-Za-z0-9_.:-]/, "_", cstate[s])
|
|
gsub(/[^A-Za-z0-9_.:-]/, "_", rawstate[s])
|
|
gsub(/[^A-Za-z0-9_.:-]/, "_", app_type[s])
|
|
gsub(/[^A-Za-z0-9_.:-]/, "_", app_state[s])
|
|
gsub(/[^A-Za-z0-9_.:-]/, "", aid[s])
|
|
gsub(/[^A-Za-z0-9_.:-]/, "_", pin1_state[s])
|
|
gsub(/[^A-Za-z0-9_.:-]/, "_", upin_state[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]
|
|
}
|
|
}
|
|
'
|
|
}
|
|
|
|
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_RADIO=%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
|
|
}
|
|
'
|
|
}
|
|
|
|
save_preferred_slot() {
|
|
target_slot="$1"
|
|
PREFERRED_SIM_SLOT="$target_slot"
|
|
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
|
|
printf "sim_wait_time=10\npreferred_sim_slot=%s\n" "$target_slot" > "$CONF_FILE"
|
|
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
|
|
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
|
|
if [ "$count" -ge "$SIM_WAIT_TIME" ]; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
count=$((count + 1))
|
|
QMI_CARDS="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)"
|
|
done
|
|
|
|
if [ -z "$QMI_CARDS" ]; then
|
|
echo "No SIM card status returned."
|
|
return 0
|
|
fi
|
|
|
|
eval "$(parse_uim_card_status "$QMI_CARDS" "$req_slot")"
|
|
|
|
chosen_slot=""
|
|
chosen_aid=""
|
|
if [ "$req_slot" = "2" ]; then
|
|
if [ "$SLOT2_STATE" = "present" ]; then
|
|
chosen_slot="2"
|
|
chosen_aid="$SLOT2_AID"
|
|
elif [ "$SLOT1_STATE" = "present" ]; then
|
|
chosen_slot="1"
|
|
chosen_aid="$SLOT1_AID"
|
|
fi
|
|
else
|
|
if [ "$SLOT1_STATE" = "present" ]; then
|
|
chosen_slot="1"
|
|
chosen_aid="$SLOT1_AID"
|
|
elif [ "$SLOT2_STATE" = "present" ]; then
|
|
chosen_slot="2"
|
|
chosen_aid="$SLOT2_AID"
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$chosen_slot" ]; then
|
|
echo "No SIM detected after $SIM_WAIT_TIME seconds."
|
|
return 0
|
|
fi
|
|
|
|
if [ "$chosen_slot" != "$PREFERRED_SIM_SLOT" ]; then
|
|
save_preferred_slot "$chosen_slot"
|
|
fi
|
|
|
|
# If already provisioned on the chosen slot and application is ready or waiting for PIN, keep session intact
|
|
eval "chosen_app_state=\$SLOT${chosen_slot}_APP_STATE"
|
|
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)."
|
|
return 0
|
|
fi
|
|
|
|
if [ "${SECONDARY_GW_SLOT:-0}" = "$chosen_slot" ]; then
|
|
$QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=secondary-gw-provisioning' >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
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 "$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
|
|
}
|
|
|
|
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 [ "${SECONDARY_GW_SLOT:-0}" = "$slot" ]; then
|
|
$QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=secondary-gw-provisioning' >/dev/null 2>&1 || true
|
|
fi
|
|
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.5
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
restart_modemmanager_clean() {
|
|
if command -v rc-service >/dev/null 2>&1; then
|
|
timeout 4 rc-service polkit start >/dev/null 2>&1 || true
|
|
timeout 6 rc-service modemmanager restart >/dev/null 2>&1 || {
|
|
killall -q ModemManager 2>/dev/null || true
|
|
sleep 0.3
|
|
timeout 6 rc-service modemmanager zap >/dev/null 2>&1 || true
|
|
timeout 6 rc-service modemmanager start >/dev/null 2>&1 || true
|
|
}
|
|
elif command -v systemctl >/dev/null 2>&1; then
|
|
timeout 6 systemctl restart ModemManager >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
|
|
enable_and_register_modem() {
|
|
target_slot="${1:-$PREFERRED_SIM_SLOT}"
|
|
force_unlock="${2:-0}"
|
|
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=""
|
|
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)"
|
|
if [ -n "$mm_state" ]; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
i=$((i + 1))
|
|
done
|
|
|
|
if [ -z "$mm_state" ] || [ "$mm_state" = "failed" ] || { [ "$force_unlock" = "1" ] && [ "$mm_state" = "locked" ]; }; then
|
|
restart_modemmanager_clean
|
|
i=0
|
|
mm_state=""
|
|
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)"
|
|
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
|
|
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)"
|
|
case "$mm_state" in
|
|
disabled)
|
|
timeout 4 mmcli -m any -e >/dev/null 2>&1 || true
|
|
;;
|
|
enabled)
|
|
timeout 4 mmcli -m any --3gpp-register-home >/dev/null 2>&1 || true
|
|
break
|
|
;;
|
|
searching|registered|connected|connecting)
|
|
break
|
|
;;
|
|
locked)
|
|
break
|
|
;;
|
|
esac
|
|
sleep 1
|
|
i=$((i + 1))
|
|
done
|
|
|
|
timeout 4 mmcli -m any --3gpp-register-home >/dev/null 2>&1 || true
|
|
if command -v sxmo_hook_statusbar.sh >/dev/null 2>&1; then
|
|
DISPLAY="${DISPLAY:-:0}" timeout 3 sxmo_hook_statusbar.sh modem >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
|
|
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"
|
|
) &
|
|
}
|
|
|
|
cmd="${1:-boot}"
|
|
|
|
case "$cmd" in
|
|
boot)
|
|
ensure_usb_network_gadget
|
|
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
|
|
provision_slot "$PREFERRED_SIM_SLOT" 10 || true
|
|
exit 0
|
|
;;
|
|
|
|
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
|
|
eval "$(parse_uim_card_status "$QMI_CARDS" "$PREFERRED_SIM_SLOT")"
|
|
if [ "$PRIMARY_GW_SLOT" = "0" ] && { [ "$SLOT1_STATE" = "present" ] || [ "$SLOT2_STATE" = "present" ]; }; then
|
|
SIM_WAIT_TIME=1
|
|
provision_slot "$PREFERRED_SIM_SLOT" 2 >/dev/null 2>&1 || true
|
|
QMI_CARDS_NEW="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)"
|
|
if [ -n "$QMI_CARDS_NEW" ]; then
|
|
QMI_CARDS="$QMI_CARDS_NEW"
|
|
fi
|
|
fi
|
|
echo "MODEM_AVAILABLE=1"
|
|
parse_uim_card_status "$QMI_CARDS" "$PREFERRED_SIM_SLOT"
|
|
QMI_NAS="$($QMICLI_MODEM --nas-get-serving-system 2>/dev/null || true)"
|
|
if [ -n "$QMI_NAS" ]; then
|
|
parse_nas_serving_system "$QMI_NAS"
|
|
fi
|
|
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"
|
|
SIM_WAIT_TIME=3
|
|
provision_slot "$target" 5
|
|
restart_modemmanager_clean
|
|
spawn_enable_and_register "$target" 0
|
|
exit 0
|
|
;;
|
|
|
|
verify-pin)
|
|
slot="${2:-1}"
|
|
pin="${3:-}"
|
|
if [ -z "$pin" ]; then
|
|
echo "Missing PIN" >&2
|
|
exit 1
|
|
fi
|
|
prepare_slot_for_pin_op "$slot"
|
|
eval "before_ret=\${SLOT${slot}_PIN1_RETRIES:-3}"
|
|
verified=0
|
|
wrong_pin=0
|
|
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" ]; then
|
|
mm_pin_out="$(timeout 5 mmcli -i "$sim_id" --pin="$pin" 2>&1)" && verified=1 || {
|
|
if printf "%s\n" "$mm_pin_out" | grep -qiE "IncorrectPin|SimPin|SimPuk|PinBlocked|incorrect"; then
|
|
wrong_pin=1
|
|
fi
|
|
}
|
|
fi
|
|
if [ "$verified" = "0" ] && [ "$wrong_pin" = "0" ] && [ -n "$QMICLI_MODEM" ]; then
|
|
qmi_pin_out="$($QMICLI_MODEM --uim-verify-pin="PIN1,$pin,session-type=primary-gw-provisioning" 2>&1)" && verified=1 || {
|
|
if printf "%s\n" "$qmi_pin_out" | grep -qiE "IncorrectPin|PinBlocked|Blocked"; then
|
|
wrong_pin=1
|
|
fi
|
|
}
|
|
if [ "$verified" = "0" ] && [ "$wrong_pin" = "0" ]; then
|
|
qmi_pin_out="$($QMICLI_MODEM --uim-verify-pin="PIN1,$pin,session-type=card-slot-$slot" 2>&1)" && verified=1 || {
|
|
if printf "%s\n" "$qmi_pin_out" | grep -qiE "IncorrectPin|PinBlocked|Blocked"; then
|
|
wrong_pin=1
|
|
fi
|
|
}
|
|
fi
|
|
fi
|
|
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"
|
|
eval "after_ret=\$SLOT${slot}_PIN1_RETRIES"
|
|
if [ "$after_pin1" = "enabled-verified" ] || [ "$after_pin1" = "disabled" ] || [ "$after_app" = "ready" ]; then
|
|
verified=1
|
|
elif [ "$after_ret" -lt "$before_ret" ] 2>/dev/null; then
|
|
wrong_pin=1
|
|
fi
|
|
fi
|
|
fi
|
|
if [ "$verified" = "0" ] && [ "$wrong_pin" = "0" ]; then
|
|
at_cpin="$("$0" at auto "AT+CPIN=\"$pin\"" 2>/dev/null || true)"
|
|
if printf "%s\n" "$at_cpin" | grep -qE "(^|[[:space:]])OK([[:space:]]|$)"; then
|
|
verified=1
|
|
fi
|
|
fi
|
|
if [ "$verified" = "1" ]; then
|
|
save_preferred_slot "$slot"
|
|
SIM_WAIT_TIME=2
|
|
provision_slot "$slot" 3 >/dev/null 2>&1 || true
|
|
timeout 4 mmcli -m any -e >/dev/null 2>&1 || true
|
|
spawn_enable_and_register "$slot" 1
|
|
exit 0
|
|
fi
|
|
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
|
|
prepare_slot_for_pin_op "$slot"
|
|
unblocked=0
|
|
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
|
|
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
|
|
unblocked=1
|
|
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
|
|
unblocked=1
|
|
fi
|
|
fi
|
|
if [ "$unblocked" = "0" ]; then
|
|
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
|
|
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
|
|
timeout 4 mmcli -m any -e >/dev/null 2>&1 || true
|
|
spawn_enable_and_register "$slot" 1
|
|
exit 0
|
|
fi
|
|
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
|
|
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
|
|
if $QMICLI_MODEM --uim-set-pin-protection="PIN1,$mode,$pin,session-type=card-slot-$slot" >/dev/null 2>&1; then
|
|
exit 0
|
|
fi
|
|
if $QMICLI_MODEM --dms-uim-set-pin-protection="PIN,$mode,$pin" >/dev/null 2>&1; then
|
|
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" ]; 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
|
|
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
|
|
if $QMICLI_MODEM --uim-change-pin="PIN1,$oldpin,$newpin,session-type=card-slot-$slot" >/dev/null 2>&1; then
|
|
exit 0
|
|
fi
|
|
if $QMICLI_MODEM --dms-uim-change-pin="PIN,$oldpin,$newpin" >/dev/null 2>&1; then
|
|
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
|
|
fi
|
|
exit 1
|
|
;;
|
|
|
|
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)
|
|
ensure_usb_network_gadget
|
|
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)
|
|
ensure_usb_network_gadget
|
|
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=""
|
|
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
|
|
case "$candidate" in
|
|
wwan0at0|wwan0at1)
|
|
dev="/dev/$candidate"
|
|
;;
|
|
/dev/wwan0at0|/dev/wwan0at1)
|
|
dev="$candidate"
|
|
;;
|
|
*)
|
|
dev=""
|
|
;;
|
|
esac
|
|
if [ -n "$dev" ] && [ -c "$dev" ]; then
|
|
resp="$(timeout 3 sh -c '
|
|
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
|
|
while [ "$i" -lt 16 ]; do
|
|
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
|
|
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
|
|
;;
|
|
|
|
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
|
|
;;
|
|
|
|
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)"
|
|
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}')"
|
|
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
|
|
;;
|
|
|
|
*)
|
|
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
|
|
exit 1
|
|
;;
|
|
esac
|