#!/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="" detect_qmicli_modem() { if ! command -v qmicli >/dev/null 2>&1; then return 1 fi if [ -e "/dev/modem" ]; then QMICLI_MODEM="qmicli --silent -d /dev/modem" return 0 elif [ -e "/dev/wwan0qmi0" ]; then QMICLI_MODEM="qmicli --silent -d /dev/wwan0qmi0 --device-open-qmi" return 0 elif qmicli --silent -pd qrtr://0 --uim-noop >/dev/null 2>&1; then QMICLI_MODEM="qmicli --silent -pd qrtr://0" return 0 fi return 1 } wait_for_qmicli_modem() { max_wait="${1:-45}" 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 } } /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 sub(/.*Card state: \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: \x27/, "", line) sub(/\x27.*/, "", line) upin_state[cur_slot] = line } if (!in_app && $0 ~ /UPIN retries:/) { line = $0 sub(/.*UPIN retries: \x27/, "", line) sub(/\x27.*/, "", line) upin_ret[cur_slot] = line + 0 } if (!in_app && $0 ~ /UPUK retries:/) { line = $0 sub(/.*UPUK retries: \x27/, "", line) sub(/\x27.*/, "", line) upuk_ret[cur_slot] = line + 0 } if (in_app && $0 ~ /Application type:/) { line = $0 sub(/.*Application type: \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 { want_aid[cur_slot] = 0 } } if (in_app && $0 ~ /Application state:/ && want_aid[cur_slot] > 0) { line = $0 sub(/.*Application state: \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 != "") { 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: \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: \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: \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 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] } } ' } save_preferred_slot() { target_slot="$1" 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=4\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 ! printf "%s" "$QMI_CARDS" | grep -Fq "Card state: 'present'"; do if [ "$count" -ge "$SIM_WAIT_TIME" ]; then echo "No SIM detected after $SIM_WAIT_TIME seconds." return 0 fi sleep 1 count=$((count + 1)) QMI_CARDS="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)" done eval "$(parse_uim_card_status "$QMI_CARDS" "$req_slot")" chosen_slot="" chosen_aid="" if [ "$req_slot" = "2" ]; then if [ "$SLOT2_STATE" = "present" ] && [ -n "$SLOT2_AID" ]; then chosen_slot="2" chosen_aid="$SLOT2_AID" elif [ "$SLOT1_STATE" = "present" ] && [ -n "$SLOT1_AID" ]; then chosen_slot="1" chosen_aid="$SLOT1_AID" fi else if [ "$SLOT1_STATE" = "present" ] && [ -n "$SLOT1_AID" ]; then chosen_slot="1" chosen_aid="$SLOT1_AID" elif [ "$SLOT2_STATE" = "present" ] && [ -n "$SLOT2_AID" ]; then chosen_slot="2" chosen_aid="$SLOT2_AID" fi fi if [ -z "$chosen_slot" ] || [ -z "$chosen_aid" ]; then echo "No valid USIM/SIM application found on any slot." return 0 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 echo "Selecting AID $chosen_aid on slot $chosen_slot" $QMICLI_MODEM --uim-change-provisioning-session="slot=$chosen_slot,activate=yes,session-type=primary-gw-provisioning,aid=$chosen_aid" >/dev/null return $? } cmd="${1:-boot}" case "$cmd" in boot) 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" 45 exit $? ;; 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" 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=2 provision_slot "$target" 5 if command -v rc-service >/dev/null 2>&1; then rc-service modemmanager restart >/dev/null 2>&1 || true elif command -v systemctl >/dev/null 2>&1; then systemctl restart ModemManager >/dev/null 2>&1 || true fi exit 0 ;; verify-pin) slot="${2:-1}" pin="${3:-}" if [ -z "$pin" ]; then echo "Missing PIN" >&2 exit 1 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" --pin="$pin" >/dev/null 2>&1; then exit 0 fi if detect_qmicli_modem; then if $QMICLI_MODEM --uim-verify-pin="PIN1,$pin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then exit 0 fi if $QMICLI_MODEM --uim-verify-pin="PIN1,$pin,session-type=card-slot-$slot" >/dev/null 2>&1; then exit 0 fi 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 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" --puk="$puk" --pin="$newpin" >/dev/null 2>&1; then exit 0 fi if detect_qmicli_modem; then if $QMICLI_MODEM --uim-unblock-pin="PIN1,$puk,$newpin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then exit 0 fi if $QMICLI_MODEM --uim-unblock-pin="PIN1,$puk,$newpin,session-type=card-slot-$slot" >/dev/null 2>&1; then exit 0 fi 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> " >&2 exit 1 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 if detect_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 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> " >&2 exit 1 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 if detect_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 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) 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) 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="" for candidate in "$port" /dev/wwan0at1 /dev/wwan0at0; 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 2 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 12 ]; 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 ;; *) echo "Usage: $0 [boot|status|switch <1|2>|verify-pin |unblock-puk |set-pin-lock |change-pin |imei|usb-tether |at ]" >&2 exit 1 ;; esac