#!/bin/bash # shellcheck disable=SC2059 # #.USAGE # To start, run: # wget https://raw.githubusercontent.com/danielewood/sierra-wireless-modems/master/autoflash-7455.sh && sudo bash autoflash-7455.sh #.SYNOPSIS # - Only for use on Ubuntu 20.04 (or later) LiveUSB # - Changes all models of EM7455/MC7455 Modems to the Generic Sierra Wireless VID/PID # - Flashes the Current Generic Firmware #.DESCRIPTION # - Only for use on Ubuntu 20.04 (or later) LiveUSB # - All Needed Packages will Auto-Install # - Sets MBIM Mode with AT Commands Access # - Changes all models of EM74XX/MC74XX Modems to the Generic Sierra Wireless VID/PID # - Clears Band Restrictions and Places Modem in LTE only mode. # - Flashes the Current Generic Firmware # - Sets PCOFFEN=2 to tell the modem to ignore the W_DISABLE pin sent by many laptop's internal M2 slots. # - Sets FASTENUMEN=2 to skip bootloader on warm-boots. # - This, combined with PCOFFEN enables these modems to work in the X1G6/T470 and newer laptops. #.NOTES # License: The Unlicense / CCZero / Public Domain # Author: Daniel Wood / https://github.com/danielewood #.LINK # https://github.com/danielewood/sierra-wireless-modems #.VERSION # Version: 20221008 ################## ### Pre-Checks ### ################## if [ "$EUID" -ne 0 ] then echo "Please run with sudo" exit fi if [[ $(lsb_release -r | awk '{print ($2 >= "20.04")}') -eq 0 ]]; then echo "Please run on Ubuntu 20.04 (Focal Fossa) or later" lsb_release -a exit fi ######################### ### Variables & Input ### ######################### CYAN='\033[0;36m' NC='\033[0m' # No Color function display_usage() { printf "${CYAN}Usage:${NC} $0\n" printf "\n" printf "${CYAN}Modem Modes:${NC}\n" printf " -h Display usage instructions\n" printf " -u Set AT!USBSPEED\n" printf " 0 - High Speed USB 2 ${CYAN}[Default]${NC}\n" printf " 1 - SuperSpeed USB 3\n" printf " -m Set AT!USBCOMP\n" printf " 8 - MBIM mode (diag,nmea,modem,mbim) ${CYAN}[Default]${NC}\n" printf " 6 - QMI mode (diag,nmea,modem,rmnet0)\n" printf " -b Set AT!SELRAT and AT!BAND\n" printf " 9 - LTE Only ${CYAN}[Default]${NC}\n" printf " 0 - All Bands\n" printf " -e Set AT!FASTENUMEN\n" printf " 0 - Disable fast enumeration\n" printf " 1 - Enable fast enumeration for cold boot and disable for warm boot\n" printf " 2 - Enable fast enumeration for warm boot and disable for cold boot ${CYAN}[Default]${NC}\n" printf " 3 - Enable fast enumeration for warm and cold boot\n" printf "\n" printf "${CYAN}Script Modes:${NC}\n" printf " -a Enable All Script Functions ${CYAN}[Default]${NC}\n" printf " Same as: $0 -Mgcdfs\n" printf " -M Use swi_setusbcomp.pl to set modem composition\n" printf " -g Display Modem Settings\n" printf " -c Clear Existing Modem Firmwares\n" printf " -d Download and Unpack Modem Firmware from Sierra Wireless\n" printf " -f Flash Modem Firmware\n" printf " -s Change Modem Settings/Modes\n" printf " -l Legacy Stable Firmware\n" printf " Set SWI9X30C_ZIP=\"SWI9X30C_02.30.01.01_GENERIC_002.045_001.zip\"\n" printf " -q Quiet Mode -- suppress most output\n" printf " -v Verbose/Debug Mode\n" printf "\n" exit 0 } while getopts hu:m:b:e:Mgcdfsalqv option do case "${option}" in h) display_usage exit 0;; u) AT_USBSPEED=${OPTARG};; m) AT_USBCOMP=${OPTARG};; b) AT_SELRAT=${OPTARG};; e) AT_FASTENUMEN=${OPTARG};; M) set_swi_setusbcomp_trigger=1;; g) get_modem_settings_trigger=1;; c) clear_modem_firmware_trigger=1;; d) download_modem_firmware_trigger=1;; f) flash_modem_firmware_trigger=1;; s) set_modem_settings_trigger=1;; a) all_functions_trigger=1;; l) SWI9X30C_ZIP='SWI9X30C_02.30.01.01_GENERIC_002.045_001.zip';; q) quiet_trigger=1;; v) verbose_trigger=1;; *) display_usage>&2 exit 1;; esac done ################# ### Functions ### ################# # If no options are set, use defaults of -Mgcdfs if [[ -z $get_modem_settings_trigger && -z $clear_modem_firmware_trigger \ && -z $download_modem_firmware_trigger && -z $flash_modem_firmware_trigger \ && -z $set_modem_settings_trigger && -z $set_swi_setusbcomp_trigger ]]; then all_functions_trigger=1 fi if [[ all_functions_trigger -eq 1 ]]; then get_modem_settings_trigger=1 clear_modem_firmware_trigger=1 download_modem_firmware_trigger=1 flash_modem_firmware_trigger=1 set_modem_settings_trigger=1 set_swi_setusbcomp_trigger=1 fi function display_variables() { echo "AT_USBSPEED=$AT_USBSPEED" echo "AT_USBCOMP=$AT_USBCOMP" echo "AT_SELRAT=$AT_SELRAT" echo "AT_FASTENUMEN=$AT_FASTENUMEN" echo "all_functions_trigger=$all_functions_trigger" echo "get_modem_settings_trigger=$get_modem_settings_trigger" echo "clear_modem_firmware_trigger=$clear_modem_firmware_trigger" echo "download_modem_firmware_trigger=$download_modem_firmware_trigger" echo "flash_modem_firmware_trigger=$flash_modem_firmware_trigger" echo "set_modem_settings_trigger=$set_modem_settings_trigger" echo "set_swi_setusbcomp_trigger=$set_swi_setusbcomp_trigger" echo "SWI9X30C_CWE=$SWI9X30C_CWE" echo "SWI9X30C_NVU=$SWI9X30C_NVU" echo "AT_PRIID_STRING=$AT_PRIID_STRING" echo "AT_PRIID_PN=$AT_PRIID_PN" echo "AT_PRIID_REV=$AT_PRIID_REV" } function set_options() { # See if QMI desired, otherwise default to MBIM if [[ ${AT_USBCOMP^^} =~ ^QMI$|^6$ ]]; then echo 'Setting QMI Mode for Modem' echo 'Interface bitmask: 0000010D (diag,nmea,modem,rmnet0)' AT_USBCOMP="1,1,0000010D" swi_usbcomp='6' else echo 'Setting MBIM Mode for Modem' echo 'Interface bitmask: 0000100D (diag,nmea,modem,mbim)' AT_USBCOMP="1,1,0000100D" swi_usbcomp='8' fi # Check for ALL/00 bands and set correct SELRAT/BAND, otherwise default to LTE if [[ ${AT_SELRAT^^} =~ ^ALL$|^0$|^00$ ]]; then AT_SELRAT='00' AT_BAND='00' else AT_SELRAT='06' AT_BAND='09' fi # Check if valid FASTENUMEN mode, otherwise default to 2 if [[ ! $AT_FASTENUMEN =~ ^[0-3]$ ]]; then AT_FASTENUMEN=2 fi #FASTENUMEN_MODES="0 = Disable fast enumeration [Default] #1 = Enable fast enumeration for cold boot and disable for warm boot #2 = Enable fast enumeration for warm boot and disable for cold boot #3 = Enable fast enumeration for warm and cold boot" #echo '"FASTENUMEN"—Enable/disable fast enumeration for warm/cold boot.' #echo -n 'Set mode: ' && echo "$FASTENUMEN_MODES" | grep -E "^$AT_FASTENUMEN" # Check desired USB interface mode, otherwise default to 0 (USB 2.0) if [[ ${AT_USBSPEED^^} =~ SUPER|USB3|1 ]]; then AT_USBSPEED=1 else AT_USBSPEED=0 fi } function get_modem_deviceid() { deviceid='' while [ -z $deviceid ] do echo 'Waiting for modem to reboot...' sleep 3 deviceid=$(lsusb | grep -i -E '1199:9071|1199:9079|413C:81B6' | awk '{print $6}') done sleep 3 ttyUSB=$(dmesg | grep '.3: Qualcomm USB modem converter detected' -A1 | grep -Eo 'ttyUSB[0-9]$' | tail -1) devpath=$(find /dev -maxdepth 1 -regex '/dev/cdc-wdm[0-9]' -o -regex '/dev/qcqmi[0-9]') } function get_modem_bootloader_deviceid() { deviceid='' while [ -z $deviceid ] do echo 'Waiting for modem in boothold mode...' sleep 2 deviceid=$(lsusb | grep -i -E '1199:9070|1199:9078|413C:81B5' | awk '{print $6}') done echo "Found $deviceid" } function reset_modem { get_modem_deviceid # Reset Modem printf "${CYAN}---${NC}\n" echo 'Reseting modem...' ./swi_setusbcomp.pl --usbreset --device="$devpath" &>/dev/null } function get_modem_settings() { # cat the serial port to monitor output and commands. cat will exit when AT!RESET kicks off. sudo cat /dev/"$ttyUSB" 2>&1 | tee -a modem.log & # Display current modem settings printf "${CYAN}---${NC}\n" echo 'Current modem settings:' echo 'send AT send ATE1 sleep 1 send ATI sleep 1 send AT!ENTERCND=\"A710\" sleep 1 send AT!IMPREF? sleep 1 send AT!GOBIIMPREF? sleep 1 send AT!USBSPEED? sleep 1 send AT!USBSPEED=? sleep 1 send AT!USBCOMP? sleep 1 send AT!USBCOMP=? sleep 1 send AT!USBVID? sleep 1 send AT!USBPID? sleep 1 send AT!USBPRODUCT? sleep 1 send AT!PRIID? sleep 1 send AT!SELRAT? sleep 1 send AT!BAND? sleep 1 send AT!BAND=? sleep 1 send AT!PCINFO? sleep 1 send AT!PCOFFEN? sleep 1 send AT!CUSTOM? sleep 1 send AT!IMAGE? sleep 1 ! pkill cat sleep 1 ! pkill minicom ' > script.txt sudo minicom -b 115200 -D /dev/"$ttyUSB" -S script.txt &>/dev/null } function clear_modem_firmware() { # cat the serial port to monitor output and commands. cat will exit when AT!RESET kicks off. sudo cat /dev/"$ttyUSB" 2>&1 | tee -a modem.log & # Clear Previous PRI/FW Entries echo 'send AT send AT!IMAGE=0 sleep 1 send AT!IMAGE? sleep 1 ! pkill cat sleep 1 ! pkill minicom ' > script.txt sudo minicom -b 115200 -D /dev/"$ttyUSB" -S script.txt &>/dev/null } function download_modem_firmware() { # Find latest 7455 firmware and download it if [[ -z $SWI9X30C_ZIP ]]; then SWI9X30C_URL=$(curl -s https://source.sierrawireless.com/resources/airprime/minicard/74xx/em_mc74xx-approved-fw-packages/ 2>/dev/null | grep 'GCF Approved' -B1 | grep '7455' | sed 's/,-d-,/./g' | grep -iPo 'href="\K.+/swi9x30c[_0-9.]+_generic_[_0-9.]+' | tail -n1) SWI9X30C_ZIP=${SWI9X30C_URL##*/} SWI9X30C_ZIP="${SWI9X30C_ZIP^^}"'zip' fi SWI9X30C_URL="https://source.sierrawireless.com${SWI9X30C_URL}zip" SWI9X30C_LENGTH=$(curl -sI "$SWI9X30C_URL" | grep -iPo '^Content-Length[^0-9]+\K[0-9]+') # If remote file size is less than 40MiB, something went wrong, exit. if [[ $SWI9X30C_LENGTH -lt 40000000 ]]; then printf "${CYAN}---${NC}\n" printf "Download of ${CYAN}$SWI9X30C_ZIP${NC} failed.\nFile size on server is too small, something is wrong, exiting...\n" printf "Attempted download URL was: $SWI9X30C_URL\n" printf "curl info:\n" curl -sI "$SWI9X30C_URL" printf "${CYAN}---${NC}\n" exit fi if [[ $SWI9X30C_LENGTH -eq $(stat --printf="%s" "$SWI9X30C_ZIP" 2>/dev/null) ]]; then echo "Already downloaded $SWI9X30C_ZIP..." else echo "Downloading $SWI9X30C_URL" curl -o "$SWI9X30C_ZIP" "$SWI9X30C_URL" fi # If download size does not match what server says, exit: if [[ $SWI9X30C_LENGTH -ne $(stat --printf="%s" "$SWI9X30C_ZIP" 2>/dev/null) ]]; then printf "${CYAN}---${NC}\n" printf "Download of ${CYAN}$SWI9X30C_ZIP${NC} failed.\nDownloaded file size is inconsistent with server, exiting...\n" printf "${CYAN}---${NC}\n" exit fi # Cleanup old CWE/NVUs rm -f ./*.cwe ./*.nvu 2>/dev/null # Unzip SWI9X30C, force overwrite unzip -o "$SWI9X30C_ZIP" } function flash_modem_firmware() { # Kill cat processes used for monitoring status, if it hasnt already exited sudo pkill -9 cat &>/dev/null printf "${CYAN}---${NC}\n" echo "Flashing $SWI9X30C_CWE onto Generic Sierra Modem..." sleep 5 qmi-firmware-update --reset -d "$deviceid" get_modem_bootloader_deviceid qmi-firmware-update --update-download -d "$deviceid" "$SWI9X30C_CWE" "$SWI9X30C_NVU" rc=$? if [[ $rc != 0 ]] then echo "Firmware Update failed, exiting..." exit $rc fi } function set_modem_settings() { # cat the serial port to monitor output and commands. cat will exit when AT!RESET kicks off. sudo cat /dev/"$ttyUSB" 2>&1 | tee -a modem.log & # Set Generic Sierra Wireless VIDs/PIDs cat < script.txt send AT sleep 1 send ATE1 sleep 1 send ATI sleep 1 send AT!ENTERCND=\"A710\" sleep 1 send AT!IMPREF=\"GENERIC\" sleep 1 send AT!GOBIIMPREF=\"GENERIC\" sleep 1 send AT!USBCOMP=$AT_USBCOMP sleep 1 send AT!USBVID=1199 sleep 1 send AT!USBPID=9071,9070 sleep 1 send AT!USBPRODUCT=\"EM7455\" sleep 1 send AT!PRIID=\"$AT_PRIID_PN\",\"$AT_PRIID_REV\",\"Generic-Laptop\" sleep 1 send AT!SELRAT=$AT_SELRAT sleep 1 send AT!BAND=$AT_BAND sleep 1 send AT!CUSTOM=\"FASTENUMEN\",$AT_FASTENUMEN sleep 1 send AT!PCOFFEN=2 sleep 1 send AT!PCOFFEN? sleep 1 send AT!USBSPEED=$AT_USBSPEED sleep 1 send AT!USBSPEED? sleep 1 send AT!USBSPEED=? sleep 1 send AT!CUSTOM? sleep 1 send AT!IMAGE? sleep 1 send AT!PCINFO? sleep 1 send AT!RESET ! pkill minicom EOF sudo minicom -b 115200 -D /dev/"$ttyUSB" -S script.txt &>/dev/null } function script_prechecks() { printf "${CYAN}---${NC}\n" echo 'Searching for EM7455/MC7455 USB modems...' modemcount=$(lsusb | grep -c -i -E '1199:9071|1199:9079|413C:81B6') while [ "$modemcount" -eq 0 ] do printf "${CYAN}---${NC}\n" echo "Could not find any EM7455/MC7455 USB modems" echo 'Unplug and reinsert the EM7455/MC7455 USB connector...' modemcount=$(lsusb | grep -c -i -E '1199:9071|1199:9079|413C:81B6') sleep 3 done printf "${CYAN}---${NC}\n" echo "Found EM7455/MC7455: $(lsusb | grep -i -E '1199:9071|1199:9079|413C:81B6') " if [ "$modemcount" -gt 1 ] then printf "${CYAN}---${NC}\n" echo "Found more than one EM7455/MC7455, remove the one you dont want to flash and try again." exit fi # Stop modem manager to prevent AT command spam and allow firmware-update printf "${CYAN}---${NC}\n" echo 'Stoping modem manager to prevent AT command spam and allow firmware-update, this may take a minute...' systemctl stop ModemManager &>/dev/null systemctl disable ModemManager &>/dev/null printf "${CYAN}---${NC}\n" echo "Installing all needed prerequisites..." add-apt-repository universe -y 1>/dev/null apt update -y # need make and GCC for compiling perl modules apt-get install make gcc curl minicom libqmi-glib5 libqmi-proxy libqmi-utils unzip -y # Use cpan to install/compile all dependencies needed by swi_setusbcomp.pl yes | cpan install UUID::Tiny IPC::Shareable JSON # Install Modem Mode Switcher if [ ! -f swi_setusbcomp.pl ]; then wget https://git.mork.no/wwan.git/plain/scripts/swi_setusbcomp.pl fi chmod +x ./swi_setusbcomp.pl reset_modem } function set_swi_setusbcomp() { # Modem Mode Switch to usbcomp=8 (DM NMEA AT MBIM) printf "${CYAN}---${NC}\n" echo "Running Modem Mode Switch to usbcomp=$swi_usbcomp" ./swi_setusbcomp.pl --usbcomp=$swi_usbcomp --device="$devpath" reset_modem # cat the serial port to monitor output and commands. sudo cat /dev/"$ttyUSB" 2>&1 | tee -a modem.log & # Set Generic Sierra Wireless VIDs/PIDs cat < script.txt send AT sleep 1 send AT!ENTERCND=\"A710\" sleep 1 send AT!USBCOMP=$AT_USBCOMP sleep 1 send AT!RESET ! pkill cat sleep 1 ! pkill minicom EOF sudo minicom -b 115200 -D /dev/"$ttyUSB" -S script.txt &>/dev/null get_modem_deviceid } function script_cleanup() { # Restart ModemManager systemctl enable ModemManager &>/dev/null systemctl start ModemManager &>/dev/null echo "Done!" # Kill cat processes used for monitoring status, if it hasnt already exited sudo pkill -9 cat &>/dev/null } ######################## ### Script Execution ### ######################## if [[ $quiet_trigger ]]; then script_prechecks &>/dev/null else script_prechecks fi set_options devpath=$(find /dev -maxdepth 1 -regex '/dev/cdc-wdm[0-9]' -o -regex '/dev/qcqmi[0-9]') if [[ $set_swi_setusbcomp_trigger ]]; then set_swi_setusbcomp fi get_modem_deviceid [[ $get_modem_settings_trigger ]] && get_modem_settings if [[ $clear_modem_firmware_trigger ]]; then clear_modem_firmware get_modem_deviceid fi [[ $download_modem_firmware_trigger ]] && download_modem_firmware SWI9X30C_CWE=$(find . -maxdepth 1 -type f -iregex '.*SWI9X30C[0-9_.]+\.cwe' | cut -c 3- | tail -n1) SWI9X30C_NVU=$(find . -maxdepth 1 -type f -iregex '.*SWI9X30C[0-9_.]+generic[0-9_.]+\.nvu' | cut -c 3- | tail -n1) AT_PRIID_STRING=$(strings "$SWI9X30C_NVU" | grep '^9999999_.*_SWI9X30C_' | sort -u | head -1) AT_PRIID_PN="$(echo "$AT_PRIID_STRING" | awk -F'_' '{print $2}')" AT_PRIID_REV="$(echo "$AT_PRIID_STRING" | grep -Eo '[0-9]{3}\.[0-9]{3}')" [[ $flash_modem_firmware_trigger ]] && flash_modem_firmware [[ $set_modem_settings_trigger ]] && set_modem_settings [[ $verbose_trigger ]] && display_variables script_cleanup # Done