#!/bin/bash # Rules for chat scripts # No comments allowed at the end of AT+CGDCONT in chat script # The last AT+CGDCONT= must use the same context as the dialer. # If desired, The AT+CGDCONT may be prefixed by #MT[[:space:]]+ # Example: #MT AT+CGDCONT="IPV6","data","192.168.2.1",0,1,"EXTRA" # # If you do not use a comment, the entire AT+CGDCONT command # must be surrounded by apostrophes. This command will be executed # twice, once in the chat wrapper, and a 2nd time in the chat # itself. # # The space after "#MT" may be any number including tabs. # If #MT AT+CGDCONT= is found, only the last one is chosen. # Any uncommented AT+CGDCONT= is then ignored. # If there are not #MT AT+CGDCONT= lines, then any line without # a comment chararacter before AT+CGDONT= is accepted, but only the # last one in the file. # # On entry to this script, file descriptor 0 is the modem. # For ppp to work, stty must work on file descriptor 0. # If stty fails, so will ppp. NAME=chat_wrapper CONFIG=/etc/default/${NAME} CHAT_WRAPPER_RECURSION=/run/chat_wrapper_recursion RQCMD="/usr/bin/radio-query ${RADIOOPTION}" : ${LOG:="/usr/bin/logger -t ${NAME} -p daemon.notice"} function do_radio_reset { # our parent is PPID, which is pppd. Get the pppd # parameter list. while IFS= read -r -d '' eparm; do execarg+=( "$eparm" ) done < <(cat /proc/$PPID/cmdline) i=0 ${LOG} "List parameters" while ((i < ${#execarg[@]})) ; do ${LOG} "$i: ${execarg[$i]}" ((i++)) done if [[ ${execarg[0]} == "pppd" ]] ; then execarg[0]=/usr/sbin/pppd fi # No returning from this function. pppd will be # restarted by us. # Our terminal is the modem. When we reset the modem, # we will be sent a SIGHUP. Ignore it (:). trap : SIGHUP /usr/sbin/mts-io-sysfs store radio-reset 0 /usr/bin/mts-wait-for-cell-reset ${LOG} "Completed mts-wait-for-cell-reset" count=0 while ((count < 60)) ; do ((count++)) # Wait for radio-reset to get the modem responding if [[ $(radio-cmd ${RADIOOPTION} -t10000 'AT') =~ ^OK ]]; then # parent is dying, so remove the pppd locks for the new pppd. for f in /run/lock/LCK..modem_at0 /run/lock/LCK..modem_at1 ; do if [[ -f $f ]] ; then mypid=$(cat $f) mypid="${mypid#"${mypid%%[![:space:]]*}"}" mypid="${mypid%"${mypid##*[![:space:]]}"}" ${LOG} "PPID is $PPID, pid is $mypid" # Remove the lock, if it belongs to our parent, pppd # or if it is empty. if ((${#mypid} == 0)) || [[ $mypid = $PPID ]] ; then ${LOG} "match $mypid $PPID" rm -f $f else ${LOG} "mismatch |$mypid| |$PPID|" fi fi done # Locks gone, so start new pppd ${LOG} "{$execarg[0]}" nohup "${execarg[@]}" >/dev/null 2>&1 & # Tell our parent we failed, which causes our pppd to terminate. exit 1 else sleep 1 fi done ${LOG} "ERROR: After five minutes the modem is still not ready" ${LOG} "ERROR: Verify that SIM is in place" exit 1 } function finish { ${LOG} "Launch:" "$@" exec "$@" # NOTREACHED } function set_telit_dataonly { local result result1 mode cemode calldisa needreset needreset=1 # 1 means no reset of cell modem (function exit status) cemode=$(/usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+CEMODE?' 2>&1) result=$? if ((result == 0)) ; then if [[ $cemode =~ CEMODE:[[:space:]]*([0-9]+) ]] ; then mode=${BASH_REMATCH[1]} if ((${#mode} > 0)) && ((mode != 2)) ; then ${LOG} "Attempting to set CEMODE to 2" cemode1=$(/usr/bin/radio-cmd ${RADIOOPTION} -t10000 "AT+CEMODE=2" 2>&1) result1=$? needreset=0 ${LOG} "radio-cmd AT+CEMODE=2 command yields $cemode1 with exit status $result1" ${LOG} "Cellular modem will be reset after attmpted CEMODE change." fi else ${LOG} "radio-cmd AT+CEMODE? command yields $cemode" fi else ${LOG} "radio-cmd AT+CEMODE? command yields $cemode with exit status $result" fi calldisa=$(/usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT#CALLDISA?' 2>&1) result=$? if ((result == 0)) ; then if [[ $calldisa =~ CALLDISA:[[:space:]]*([0-9,]+) ]] ; then mode=${BASH_REMATCH[1]} if [[ ${mode} != 1,1 ]] ; then ${LOG} "Attempting to set CALLDISA to 1,1" cemode1=$(/usr/bin/radio-cmd ${RADIOOPTION} -t10000 "AT#CALLDISA=1,1" 2>&1) result1=$? needreset=0 ${LOG} "radio-cmd AT+CALLDISA=1,1 command yields $cemode1 with exit status $result1" ${LOG} "Cellular modem must be reset after attmpted calldisa change." fi else ${LOG} "radio-cmd AT+CALLDISA? command yields $calldisa" fi else ${LOG} "radio-cmd AT+CALLDISA? command yields $calldisa with exit status $result" fi return $needreset } function set_quectel_dataonly { local result result1 ue ims sms vc needreset needreset=1 # 1 means no reset of cell modem (function exit status) ue=$(radio-cmd ${RADIOOPTION} -t10000 'AT+QNVFR="/nv/item_files/modem/mmode/ue_usage_setting"' 2>&1) result=$? if ((result == 0)) ; then if [[ $ue =~ QNVFR:[[:space:]]*([0-9]+)[[:space:]]+ ]] ; then if [[ ${BASH_REMATCH[1]} != 01 ]] ; then ${LOG} "Attempting to set ue usage to 01" ue=$(radio-cmd ${RADIOOPTION} -t10000 'AT+QNVFW="/nv/item_files/modem/mmode/ue_usage_setting",01' 2>&1) result1=$? needreset=0 ${LOG} "write ue_usage_setting yields $ue with exit status $result1" fi else ${LOG} "read ue_usage_setting yields $ue" fi else ${LOG} "read ue_usage_setting yields $ue with exit status $result" fi ims=$(radio-cmd ${RADIOOPTION} -t10000 'AT+QNVFR="/nv/item_files/ims/IMS_enable"' 2>&1) result=$? if ((result == 0)) ; then if [[ $ims =~ QNVFR:[[:space:]]*([0-9]+)[[:space:]]+ ]] ; then if [[ ${BASH_REMATCH[1]} != 00 ]] ; then ${LOG} "Attempting to set IMS_enable to 00" ims=$(radio-cmd ${RADIOOPTION} -t10000 'AT+QNVFW="/nv/item_files/ims/IMS_enable",00' 2>&1) result1=$? needreset=0 ${LOG} "write IMS_enable yields $ims with exit status $result1" fi else ${LOG} "read IMS_enable yields $ims" fi else ${LOG} "read IMS_enable yields $ims with exit status $result" fi vc=$(radio-cmd ${RADIOOPTION} -t10000 'AT+QNVR=5280,0' 2>&1) result=$? if ((result == 0)) ; then if [[ $vc =~ QNVR:[[:space:]]\"*([0-9]+)\" ]] ; then if [[ ${BASH_REMATCH[1]} != 0102000000000000 ]] ; then ${LOG} "Attempting to set sms to 01" vc=$(radio-cmd ${RADIOOPTION} -t10000 'AT+QNVW=5280,0,"0102000000000000"' 2>&1) result1=$? needreset=0 ${LOG} "voice turn off yields $vc with exit status $result1" fi else ${LOG} "read voice state yields $vc" fi else ${LOG} "read voice state yields $vc with exit status $result" fi sms=$(radio-cmd ${RADIOOPTION} -t10000 'AT+QNVFR="/nv/item_files/modem/mmode/sms_only"' 2>&1) result=$? if ((result == 0)) ; then if [[ $sms =~ QNVFR:[[:space:]]\"*([0-9]+) ]] ; then if [[ ${BASH_REMATCH[1]} != 01 ]] ; then ${LOG} "Attempting to set sms to 01" sms=$(radio-cmd ${RADIOOPTION} -t10000 'AT+QNVFW="/nv/item_files/modem/mmode/sms_only",01' 2>&1) result1=$? needreset=0 ${LOG} "sms turn on yields $sms with exit status $result1" fi else ${LOG} "read sms state yields $sms" fi else ${LOG} "read sms state yields $sms with exit status $result" fi return $needreset } # This function is used to determine if the modem and serial driver # are working. In testing, the driver has gotten into states where # stty fails. Before the chat script is started, the ioctl TCGETS is # performed, and will fail if the driver is hung. This same function # is called by stty. The modem port used by pppd is STDIN. Cases # have been observed on the H5 where radio-cmd works, but stty does # not, resulting in pppd failing. When this happens, radio-reset is # the best thing to try. function sanity { id="^[0-9][0-9][0-9][0-9][0-9][0-9]" if ! log=$(stty 2>&1) || ! [[ $($RQCMD --iccid) =~ $id ]] ; then ${LOG} "Modem has response issues, so see if we can try again" if ((${#log} > 0)); then ${LOG} "stty error: ${log}" fi # Break any recursion loop. if [[ -f ${CHAT_WRAPPER_RECURSION} ]] ; then ${LOG} "Failure: modem is still unresponsive after reset" ${LOG} "Failure: Verify that modem and SIM are in place" rm -f ${CHAT_WRAPPER_RECURSION} exit 1 fi touch ${CHAT_WRAPPER_RECURSION} ${LOG} "Bad TTY, possibly due to modem in bad state/no SIM detected" ${LOG} "Reset modem, and we relaunch pppd" do_radio_reset fi return 0 } # End of sanity function # If > 0, need to wait because we were not registered. WAITREG=0 [[ -f $CONFIG ]] || exit 1 . ${CONFIG} : ${REGWAITTIME:=300} : ${FINALWAIT:=5} : ${COPSWAIT:=10} : ${CFUNWAIT:=10} ${LOG} Timeout is $REGWAITTIME, execute "$@" # Wait for udev to discover modem has been reset. /usr/bin/mts-wait-for-cell-reset ((i=$#)) chatscript="${!i}" ${LOG} Parsing chat script "$chatscript" # CONTEXT is last context string in chat script CONTEXT=$(egrep "^#MT[[:space:]]+(AT\+CGDCONT=.*)" ${chatscript} | tail -1) if ((${#CONTEXT} == 0)) ; then CONTEXT=$(egrep "^[^#]+AT\+CGDCONT=" ${chatscript} | tail -1) [[ $CONTEXT =~ \'(AT\+CGDCONT=([0-9]+)[^$\']+) ]] else [[ $CONTEXT =~ (AT\+CGDCONT=([0-9]+).*)$ ]] fi # CONTEXTNUM is the context number that is configured in the dialer. CONTEXT="${BASH_REMATCH[1]}" if ((${#CONTEXT} == 0)) ; then ${LOG} No context specifiction in the chat script finish "$@" # NOTREACHED fi CONTEXTNUM=${BASH_REMATCH[2]} # Make sure CFUN=1, in case the last pppd was aborted with it set to 0. ${LOG} "Verify CFUN=1" cfun=$(/usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+CFUN?') result1=$? if ((result1 != 0)) || ((${#cfun} == 0)) ; then ${LOG} "Is modem in bad state -- \"CFUN?\" is broken?" sanity ((WAITREG++)) fi # Must have a readable SIM in order to set DATA Mode if ((SETATTDATAMODE == 1)) ; then CARRIER=$($RQCMD --carrier 2>&1) if [[ $CARRIER =~ ^AT\&T[[:space:]]+Wireless ]] ; then MODEL=$($RQCMD --model 2>&1) ${LOG} "Cellular modem is $MODEL" if [[ ${MODEL} == EG25 ]] || [[ ${MODEL} == EG95 ]] ; then if set_quectel_dataonly ; then do_radio_reset fi else # Could be a problem when we add new Quectel models if set_telit_dataonly ; then do_radio_reset fi fi fi fi # Set ATT Data Mode if ! [[ $cfun =~ ^\+CFUN:\ 1[,[:space:]] ]] ; then /usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+CFUN=1' ${LOG} "Just set CFUN=1 so wait ${CFUNWAIT} seconds" sleep ${CFUNWAIT} ((WAITREG++)) fi # Set CFUN=1 ${LOG} "Using Context ${CONTEXTNUM} based on chat script: ${CONTEXT}" # At this point if there is no context number, we can skip everything else. # Get Modem's context settings MCONTEXT=$(/usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+CGDCONT?' 2>&1 | tr -d '\r') if [[ $MCONTEXT =~ ^[Ee][Rr][Rr][Oo][Rr] ]] ; then RADIOOPTION="${RADIOOPTION2}" sleep 1 MCONTEXT=$(/usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+CGDCONT?' 2>&1 | tr -d '\r') CRESULT=$? fi if ! [[ $MCONTEXT =~ \+CGDCONT:[[:space:]]+${CONTEXTNUM},\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",([0-9]+),([0-9]+)([^$'\n']*) ]] ; then logger -s -p daemon.error "No valid context in modem. Is it ready?" echo "$MCONTEXT" | logger -s -p daemon.error echo "Result is $CRESULT" | logger -s -p daemon.error if [[ $MCONTEXT =~ ^[Ee][Rr][Rr][Oo][Rr] ]] ; then logger -s -p daemon.error "Context query response error. Diagnose ..." sanity fi fi MPDP="${BASH_REMATCH[1]}" MAPN="${BASH_REMATCH[2]}" MADDR="${BASH_REMATCH[3]}" MDCOMP="${BASH_REMATCH[4]}" MHCOMP="${BASH_REMATCH[5]}" MFULLBOAT="${BASH_REMATCH[6]}" [[ $CONTEXT =~ AT\+CGDCONT=${CONTEXTNUM},\"([^\"]*)\",\"([^\"]*)\"(,\"([^\"]*)\"(,([0-9]+)(,([0-9]+)(,[^\']*))*)*)* ]] PDP="${BASH_REMATCH[1]}" APN="${BASH_REMATCH[2]}" ADDR="${BASH_REMATCH[4]}" # Optional DCOMP="${BASH_REMATCH[6]}" # Optional HCOMP="${BASH_REMATCH[8]}" # Optional FULLBOAT="${BASH_REMATCH[9]}" # Optional # On some modems there are more parameters than others. if [[ $MFULLBOAT != $FULLBOAT ]] ; then ${LOG} "Only the first five context parameters are considered. The rest will be ignored." ${LOG} "modem: \"$MFULLBOAT\"" ${LOG} "chat script: \"$FULLBOAT\"" fi if ((${#DCOMP} == 0)) ; then ((DCOMP=0)) # Default fi if ((${#HCOMP} == 0)) ; then ((HCOMP=0)) # Default fi # For Quectel, address 0 is expressed as "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0" Q0ADDR="0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0" Q1ADDR="0.0.0.0" ADDRMATCH=1 if [[ $MADDR != $ADDR ]] ; then ADDRMATCH=0 if [[ $MADDR = $Q0ADDR ]] || [[ $MADDR = $Q1ADDR ]] ; then if [[ -z $ADDR ]] || [[ $ADDR = 0 ]] ; then ADDRMATCH=1 fi fi fi # Only update context on a mismatch between chat and modem. if [[ $MPDP != $PDP ]] || [[ $MAPN != $APN ]] || \ (( $ADDRMATCH == 0 )) || ((MDCOMP != DCOMP)) || \ ((MHCOMP != HCOMP)) ; then ${LOG} "Modem context $MPDP,$MAPN,$MADDR,$MDCOMP,$MHCOMP does not match chat script" ${LOG} "Chat script context $PDP,$APN,$ADDR,$DCOMP,$HCOMP does not match the modem" if [[ $MPDP != $PDP ]] ; then ${LOG} "PDP mismatch" fi if [[ $MAPN != $APN ]] ; then ${LOG} "APN mismatch" fi if [[ $MADDR != $ADDR ]] ; then ${LOG} "ADDR mismatch" ${LOG} "Modem Address $MADDR" ${LOG} "Chat Address $ADDR" fi if [[ $MDCOMP != $DCOMP ]] ; then ${LOG} "DCOMP mismatch" fi if [[ $MHCOMP != $HCOMP ]] ; then ${LOG} "HCOMP mismatch" fi if [[ $MFULLBOAT != $FULLBOAT ]] ; then ${LOG} "Final parameter mismatches ignored" ${LOG} "Parameter 6 and up on the modem:" ${LOG} "\"${MFULLBOAT}\"" ${LOG} "Parameter 6 and up on the chat script:" ${LOG} "\"${FULLBOAT}\"" fi ${LOG} "$MCONTEXT" ${LOG} "Dropping registration with carrier to set context" # Need to deregister ((WAITREG++)) /usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+COPS=2' count=10 while ((count > 0)) && ! [[ $($RQCMD --netreg) =~ ^NOT\ REGISTERED$ ]] ; do sleep 2 ((count--)) done # H5 radios do not like addresses that are empty with "" if ((${#ADDR} == 0)) ; then CMDSTR="AT+CGDCONT=${CONTEXTNUM},\"${PDP}\",\"${APN}\",,$DCOMP,${HCOMP}${FULLBOAT}" else CMDSTR="AT+CGDCONT=${CONTEXTNUM},\"${PDP}\",\"${APN}\",\"${ADDR}\",$DCOMP,${HCOMP}${FULLBOAT}" fi ${LOG} "Issued command /usr/bin/radio-cmd ${RADIOOPTION} -t10000 ..." ${LOG} "... ${CMDSTR}" # If we fail to set the APN, sleep and try it again for up to 10 seconds. result=1 count=10 while ((result != 0)) && ((count > 0)) ; do ((count--)) || true LOGMSG=$(/usr/bin/radio-cmd ${RADIOOPTION} -t10000 "${CMDSTR}" 2>&1) result=$? ${LOG} "Setting context: Result ${result}, Got response ${LOGMSG}" if [[ $LOGMSG =~ ERROR ]] ; then result=1 fi sleep 1 done # re-enable the modem to defaults. /usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+COPS=0' # Some older modems will not re-register after the COPS # command if you do not reset them using CFUN. /usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+CFUN=0' ${LOG} "Set COPS=0, CFUN=1, so wait ${CFUNWAIT} seconds" sleep ${CFUNWAIT} /usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+CFUN=1' sleep ${CFUNWAIT} # Abort PPP if the Context is not correct. We don't want # to cause the carrier to disable the account by dialing # out with a bad APN. if ((result != 0)) ; then ${LOG} "AT+CGDCONT failed, aborting" exit $result fi sleep 1 ${LOG} "New context is set. Wait up to $REGWAITTIME seconds to register" else ${LOG} "Context $CONTEXTNUM matches verify COPS, then wait for registration." # Check and set COPS and CFUN to be sure we were not interrupted at some point # in the past. cops=$(/usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+COPS?') result0=$? if ((result0 != 0)) || ! [[ $cops =~ ^\+COPS:\ 0, ]] ; then /usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+COPS=0' ((WAITREG++)) ${LOG} "Just set COPS=0 so wait ${COPSWAIT} seconds" sleep ${COPSWAIT} fi fi # Wait for registration uptime=$(cat /proc/uptime) [[ $uptime =~ ^([^\.]*) ]] t0=${BASH_REMATCH[1]} while ! [[ $($RQCMD --netreg) =~ ^(REGISTERED|ROAMING)$ ]] ; do sleep 5 ((WAITREG++)) uptime=$(cat /proc/uptime) [[ $uptime =~ ^([^\.]*) ]] t1=${BASH_REMATCH[1]} if ((t1-t0 > REGWAITTIME)) ; then ${LOG} "$((t1-t0)) seconds has expired without registration" sanity fi done uptime=$(cat /proc/uptime) [[ $uptime =~ ^([^\.]*) ]] t1=${BASH_REMATCH[1]} ${LOG} "Re-registered in $((t1-t0)) seconds" if ((WAITREG > 0)) ; then ${LOG} "Wait ${FINALWAIT} more seconds" sleep $FINALWAIT fi sanity rm -f ${CHAT_WRAPPER_RECURSION} finish "$@" # NOTREACHED