Add Fastboot/EDL reboot modes and SIM contact import/export

This commit is contained in:
Luxferre
2026-10-25 07:56:44 +02:00
parent a1e4f6ef8c
commit a117e74452
17 changed files with 864 additions and 37 deletions
@@ -558,8 +558,331 @@ case "$cmd" in
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 -F"'" '/Record length:/ {print $2 + 0; exit}')"
reccount="$(printf "%s\n" "$adn_attr" | awk -F"'" '/Record count:/ {print $2 + 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>]" >&2
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