#!/bin/sh
# Script to update HUAWEI ME909s-120 firmware
# Minimal FW: 2017.09.11-linux_4-01-7ee57df
# Features that set minimal firmware version:
#   * "/dev/modem_tty_cmd" since 2.0.116-d68c161-k4-pej1-upd-from-2.0.95 (2019.04.15)
#   * "gwmodem AT"         since 2.0.116-d68c161-k4-pej1-upd-from-2.0.95 (2019.04.15)
#   * "sx"                 since 2017.09.11-linux_4-01-7ee57df

FWNAME='ME909_UPDATE_11.617.24.00.00.FWL'
SHA256='176947c531270eb3d3e6902b3df8c432503b11f8cb22ba3490d844b75bfdd14f'
FWPATH_DEF="${1:-/tmp/huawei_upd.fwl}"
TTY="/dev/modem_tty_cmd"
F=

cat << EOF
Start v3 ($(date -Iseconds))

REMINDER
0. Modem is configured (otherwise modem will not be powered on and visible)
1. Copy Firmware "${FWNAME}", "sz.tar.xz" file and "upd.sh" script
   to the device, into "/home/martem" directory
2. Login as root
3. Execute upd.sh script in "/home/martem" directory:
     sh upd.sh

EOF

#################################################################################

#################################################################################

FAIL() {
	printf >&2 "$@\nFAIL\n"
	exit 1
}

FAILR() {
	printf >&2 "$@\nFAIL\n"
	reboot
	exit 1
}

OK() {
	printf "$@\nOK\n"
	exit 0
}

#################################################################################
# OLD FIRMWARE SUPPORT
# 2017.09.11-linux_4-01-7ee57df and newer
#
# 1. "sz --xmodem" instad of "sx"
# 2. use logfile to get modem info, asking modem directly may end badly.
# 3. Find dynamically assigned ttyUSB*, cant guarantee that this is correct entire runtime duration.
# 4. Monitor progress directly in device directory

FindTTY() {
	TTY="/dev/$(ls -1 "$(cat '/var/local/telem/modem_tty_cmd')"/ 2>/dev/null | grep "tty")"
}

call_socat() {
	# $1: AT command
	echo "$1" | socat "${TTY}",creat=0,raw,echo=0,crlf -
}

gwmodemATI_old() {
	awk '
	p&&/^[a-zA-Z0-9 .,-]*$/{print    ; p=0}
	/at.cgmi/{printf "Manufacturer: "; p=1}
	/at.cgmm/{printf "Model: "       ; p=1}
	/at.cgmr/{printf "Revision: "    ; p=1}
	/at.cgsn/{printf "IMEI: "        ; p=1}
	' /var/log/modem_st.log 2>/dev/null
}

gwmodemATI_old_noS42() {
	call_socat ATI
}

gwmodemFWLOAD_old_noS42() {
	call_socat 'AT^FWLOAD=0'
}

TTYisReady_old() {
	FindTTY
	test -c "${TTY}"
}

TTYisReady_old_noS42() {
	FindTTY
	test -c "${TTY}"
}

EnvCheck2_old() {
	command -v "sz"      &>/dev/null || FAIL "Missing 'sz' command"
	command -v "socat"   &>/dev/null || FAIL "Missing 'socat' command"
}

#################################################################################
# OLD FIRMWARE SUPPORT
# 2016.08.05-linux_4_1-04-65871e8 and newer
#
# 1. No sz available, we need to install this

InstallSZ() {
	# Do we need to install SZ
	command -v "sz" &>/dev/null && return 0

	SZ_TAR='sz.tar.xz'
	# Verify
	echo "90617f703b2865e392cefae58ae713db83b34525319fee0f1987b18354a0205d  ${SZ_TAR}" \
	| sha256sum -c \
	|| FAIL "'${SZ_TAR}' checksum failed"
	# Extract binaries
	tar -xf "${SZ_TAR}" -C /tmp/
	chown root:root /tmp/sz_GWM*
	# Copy correct binary
	SZ_VMX25=/tmp/sz_GWM_VMX25
	SZ_VMX53=/tmp/sz_GWM_VMX53
	if [ -e '/lib/ld-linux.so.3' ]; then
		cp "${SZ_VMX25}" /usr/bin/sz
	elif [ -e '/lib/ld-linux-armhf.so.3' ]; then
		cp "${SZ_VMX53}" /usr/bin/sz
	fi
}

#################################################################################

gwmodemATI() {
	gwmodem ATI
}

gwmodemFWLOAD() {
	gwmodem AT^FWLOAD=0
}

TTYisReady() {
	test -c "${TTY}"
}

getATI() {
	ATI=
	n=0
	while [ -z "${ATI}" ] && [ $((n++)) -le '3' ]; do
		ATI="$(gwmodemATI$F)"
		echo "${ATI}" | grep -iq -- "Revision:" || ATI=
		sleep 5
	done
	echo "${ATI}" | grep -iq -- "Revision:"
}

EnvCheck() {
	# Check if old firmware and replace functions as needed
	if ! command -v "gwmodem" &>/dev/null; then
		F="_old"
	fi
}

EnvCheck2() {
	command -v "gwmodem" &>/dev/null || FAIL "Missing 'gwmodem' command"
	command -v "sz"      &>/dev/null || FAIL "Missing 'sz' command"
	
	# Check for tty
	grep -Fq -- '/dev/modem_tty_cmd' '/usr/local/bin/telem/mdev_modem.sh' \
	|| FAIL "No support for /dev/modem_tty_cmd"
}

# TODO old firmware checks and replace functions as needed
Check() {
	# k2 or k4 (does k2 even have Huawei 4G?)
	KR="$(uname -r)"
	[ "${KR::1}" = '4' ] || FAIL "Old firmware (not K4)"
	EnvCheck
	InstallSZ
	EnvCheck2$F

	# Do we have universal modem dev symlinks? (or use hopefully correct ttyUSB*)
	TTYisReady$F || FAIL "'${TTY}' not ready"

	# Check if correct modem USB is available
	lsusb | grep -Fq -- '12d1:15c1' || FAIL "ME909s-120 USB id not found"

	# Get modem info
	getATI || FAIL "Could not get modem info"
	echo "# Before update ($(date -Iseconds))"
	echo "${ATI}" | grep -iA3 -- 'Model:'
	echo

	# Check if we actaully have a correct huawei modem
	echo "${ATI}" | grep -iq -- 'ME909s-120' || FAIL "Not a ME909s-120 modem"

	# check firmware version, maybe we dont need to upgrade
	REVA="$(echo "${ATI}" | grep -i -- 'Revision:')"
	[ "$1" = 'redo' ] && return
	echo "${REVA}" | grep -Fq -- '11.617.24.00.00' && OK "Update is not needed"
}

FindFW() {
	# Find firmware file
	FWPATH="${FWPATH_DEF}"
	for fw in "${FWPATH_DEF}" "./${FWNAME}" "/tmp/${FWNAME}" "/home/martem/${FWNAME}" '/tmp/huawei_upd.fwl'; do
		FWPATH="${fw}"
		echo "${SHA256}  ${FWPATH}" \
		| sha256sum -c &>/dev/null \
		&& break
	done
	echo "${SHA256}  ${FWPATH}" \
	| sha256sum -c 2>/dev/null \
	|| FAIL "'${FWPATH}' checksum failed"
}

#################################################################################
# FINAL PREPARATIONS

StartUpdate() {
	# Execute unsafe part
	echo "You may get disconnected for 10 minutes, if script is executed over modem connection"
	sleep 1
	echo "Starting update"
	sleep 1
	screen -d -m sh -c "sh $0 update ${FWPATH} > ./huawei_upd.log 2>&1"

	exit
}

#################################################################################

Update() {
	# check if execution is safe (ex: does not stop on disconnection)
	pstree -s "${PPID}" | grep -Fq -- 'screen' || FAIL "May not be safe to execute, stop just in case."

	# Kill S42pppd, S81telem, pingers?
	/etc/init.d/S81telem stop
	/etc/init.d/S42pppd stop
	F="${F:+${F}_noS42}"

	sleep 2

	# Keep watchdog happy for 20 minutes
	SECONDS=1200
	n=0
	while [ "$((n++))" -lt "${SECONDS}" ]; do touch /tmp/.telem_watchdog; sleep 1; done &

	# Do the upgrade (takes less than 3 minutes)
	gwmodemFWLOAD$F
	echo
	time sz --xmodem -q --1k "${FWPATH}" > "${TTY}" < "${TTY}" \
	|| FAILR "Modem firmware transfer failed"

	# wait for /dev/modem_tty_cmd? or monitor log (takes less than 2 minutes)
	sleep 10
	n=0
	echo
	while [ $((n++)) -le '15' ] && ! TTYisReady$F; do
		sleep 10;
		printf "\rWaiting $n"
	done
	echo
	TTYisReady$F || FAILR "Modem did not come up"
	sleep 10

	# Get modem info
	getATI || FAILR "Could not get modem info"
	echo "# After update ($(date -Iseconds))"
	echo "${ATI}" | grep -iA3 -- 'Model:'
	echo

	# chekc firmware version
	REVB="$(echo "${ATI}" | grep -i -- 'Revision:')"
	echo "${REVB}" | grep -Fq -- '11.617.24.00.00' || FAILR "Update failed"

	# All done
	printf "Update successful\nOK\n"
	reboot
}

#################################################################################

printf "FW: "
cat '/usr/local/etc/telem/version'
echo

# Are we root user
[ "$(id -u)" = "0" ] || FAIL "This script must be run as root"

case "${1}" in
	check)
		Check
	;;
	update)
		FWPATH="${2:-/tmp/huawei_upd.fwl}"
		EnvCheck
		TTYisReady$F || FAIL "'${TTY}' not ready"
		Update
	;;
	*)
		Check "$1"
		FindFW
		StartUpdate
esac

