diff --git a/README.en.md b/README.en.md
index 503e509..a54b221 100644
--- a/README.en.md
+++ b/README.en.md
@@ -44,6 +44,7 @@ In the configuration menu, you can select the following packages (all under Luci
| **QMI Driver Selection** | Choose between Generic QMI driver or Vendor QMI driver. |
| **Quectel Connect Manager Selection** | Choose one of:
- Tom customized Quectel CM: With options to block default route addition and resolv.conf modification
- QWRT quectel-CM-5G: Uses QWRT's quectel-CM-5G
- NORMAL quectel-cm: Uses the standard quectel-cm |
| **Add PCIe Modem SUPPORT** | Select PCIe driver, requires kmod_mhi in feeds. |
+| **Add Qfirehose SUPPORT** | Add Qfirehose support for Qualcomm chip module firmware upgrade. |
| **luci-app-qmodem-hc** | Supports hc-g80 SIM card switching, exclusive to specific devices. |
| **luci-app-qmodem-mwan** | Supports multi-WAN settings. |
| **luci-app-qmodem-sms** | SMS sending feature. |
diff --git a/README.md b/README.md
index 33be5bb..0ffdddc 100644
--- a/README.md
+++ b/README.md
@@ -44,15 +44,15 @@ make menuconfig
| -------------------------------------------- | :------------------: |
| **luci-app-qmodem**| 有模组信息、拨号设置、高级设置三大功能块。由于主程序在这里,因此其他功能依赖该程序(原谅我将后端程序也放在了这里)。|
| **Add Lua Luci Homepage**| 添加 Lua Luci 首页。luci2(Js Luci)首页默认已添加,若使用luci2时勾选了这个,会有两个首页|
-| **QMI驱动选择**| 可选择 Generic QMI driver(通用QMI驱动)或 Vendor QMI driver(厂商QMI驱动)|
-| **Quectel Connect Manager选择**| 可选择以下三种之一:
- Tom customized Quectel CM:使用本仓库定制的Quectel CM,支持屏蔽添加默认路由、屏蔽修改resolv.conf等功能
- QWRT Quectel-CM-5G:使用 QWRT 仓库的 quectel-CM-5G
- NORMAL Quectel-CM:使用普通的 quectel-cm|
+| **QMI Driver Selection**| 可选择 Generic QMI driver(通用QMI驱动)或 Vendor QMI driver(厂商QMI驱动)|
+| **Quectel Connect Manager Selection**| 可选择以下三种之一:
- Tom customized Quectel CM:使用本仓库定制的Quectel CM,支持屏蔽添加默认路由、屏蔽修改resolv.conf等功能
- QWRT Quectel-CM-5G:使用 QWRT 仓库的 quectel-CM-5G
- NORMAL Quectel-CM:使用普通的 quectel-cm|
| **Add PCIe Modem SUPPORT**| 勾选 PCIe 驱动,需要feeds里有kmod_mhi|
+| **Add Qfirehose SUPPORT**| 添加 Qfirehose 支持,用于高通芯片模组固件升级|
| **luci-app-qmodem-hc**| 支持 hc-g80 SIM 卡切换,该插件为设备专属插件|
| **luci-app-qmodem-mwan**| 支持多 WAN 设置。|
| **luci-app-qmodem-sms**| 短信首发功能|
| **luci-app-qmodem-ttl**| TTL 重写功能|
-
# 项目介绍
## 为什么选择该项目
diff --git a/application/qfirehose/Makefile b/application/qfirehose/Makefile
new file mode 100644
index 0000000..bf04e6a
--- /dev/null
+++ b/application/qfirehose/Makefile
@@ -0,0 +1,52 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=qfirehose
+PKG_VERSION:=1.4.21
+PKG_RELEASE:=1
+
+PKG_MAINTAINER:=Oskari Rauta
+PKG_LICENSE:=
+PKG_LICENSE_FILES:=NOTICE
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/qfirehose
+ SECTION:=utils
+ CATEGORY:=Utilities
+ TITLE:=Quectel Firehose Recovery application
+ URL:=https://github.com/nippynetworks/qfirehose
+endef
+
+define Package/qfirehose/description
+ Utility that is able to flash firmwares on Quectel's modems.
+ Usage: qfirehose -f FW_PATH
+
+ Warning.
+
+ - Use of software is completely on your own risk.
+ Flashing wrong firmware or failed flash can brick your modem permanently.
+ Avoid flashing, if device works without issues and updated firmware does not contain new necessary changes.
+ Do not flash, if you are not willing to take this risk or do not know what you are doing.
+
+ - After succesful flashing, you should use terminal to issue factory reset for modem settings with AT&F command.
+
+ - mPCIe users (mostly): If modem has completely disappeared after succesful flashing, reason might be that some firmware updates
+ set default mode to USB3 which is unsupported by some mPCIe slots, in this case, you should connect it to USB
+ port using mPCIe -> USB adapter, even most of cheap chinese modules can reveal device. After this you should issue
+ a command to use USB2, which may vary between models, but on most Quectel modems is: AT+QUSBCFG="SS",0
+ Changing value on end of AT command 0 to 1, selects USB3 instead. Refer to documents of your modem.
+endef
+
+define Build/Prepare
+ mkdir -p $(PKG_BUILD_DIR)
+ $(CP) ./src/* $(PKG_BUILD_DIR)/
+endef
+
+MAKE_ARGS += linux
+
+define Package/qfirehose/install
+ $(INSTALL_DIR) $(1)/usr/bin
+ $(INSTALL_BIN) $(PKG_BUILD_DIR)/out/QFirehose $(1)/usr/bin/qfirehose
+endef
+
+$(eval $(call BuildPackage,qfirehose))
diff --git a/application/qfirehose/src/.clang-format b/application/qfirehose/src/.clang-format
new file mode 100644
index 0000000..3e633a0
--- /dev/null
+++ b/application/qfirehose/src/.clang-format
@@ -0,0 +1,167 @@
+---
+BasedOnStyle: Google
+---
+Language: Cpp
+AccessModifierOffset: -4
+# AlignAfterOpenBracket: Align
+# AlignConsecutiveMacros: false
+# AlignConsecutiveAssignments: false
+# AlignConsecutiveDeclarations: false
+# AlignEscapedNewlines: Left
+# AlignOperands: true
+# AlignTrailingComments: true
+# AllowAllArgumentsOnNextLine: true
+AllowAllConstructorInitializersOnNextLine: false
+# AllowAllParametersOfDeclarationOnNextLine: true
+# AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: true
+# AllowShortFunctionsOnASingleLine: All
+# AllowShortLambdasOnASingleLine: All
+# AllowShortIfStatementsOnASingleLine: WithoutElse
+# AllowShortLoopsOnASingleLine: true
+# AlwaysBreakAfterDefinitionReturnType: None
+# AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+# AlwaysBreakTemplateDeclarations: Yes
+# BinPackArguments: true
+# BinPackParameters: true
+BraceWrapping:
+ # AfterCaseLabel: false
+ AfterClass: true
+ AfterControlStatement: Always
+ AfterEnum: true
+ AfterFunction: true
+ # AfterNamespace: false
+ # AfterObjCDeclaration: false
+ AfterStruct: true
+ AfterUnion: true
+ AfterExternBlock: true
+ BeforeCatch: true
+ BeforeElse: true
+ # IndentBraces: false
+ # SplitEmptyFunction: true
+ # SplitEmptyRecord: true
+ # SplitEmptyNamespace: true
+# BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+# BreakBeforeInheritanceComma: false
+# BreakInheritanceList: BeforeColon
+# BreakBeforeTernaryOperators: true
+# BreakConstructorInitializersBeforeComma: false
+# BreakConstructorInitializers: BeforeColon
+# BreakAfterJavaFieldAnnotations: false
+# BreakStringLiterals: true
+ColumnLimit: 180
+CommentPragmas: "^ NOLINT:"
+# CompactNamespaces: false
+# ConstructorInitializerAllOnOneLineOrOnePerLine: true
+# ConstructorInitializerIndentWidth: 4
+# ContinuationIndentWidth: 4
+# Cpp11BracedListStyle: true
+# DeriveLineEnding: true
+# DerivePointerAlignment: true
+# DisableFormat: false
+# ExperimentalAutoDetectBinPacking: false
+# FixNamespaceComments: true
+# ForEachMacros:
+# - foreach
+# - Q_FOREACH
+# - BOOST_FOREACH
+# IncludeBlocks: Regroup
+# IncludeCategories:
+# - Regex: '^'
+# Priority: 2
+# SortPriority: 0
+# - Regex: '^<.*\.h>'
+# Priority: 1
+# SortPriority: 0
+# - Regex: "^<.*"
+# Priority: 2
+# SortPriority: 0
+# - Regex: ".*"
+# Priority: 3
+# SortPriority: 0
+# IncludeIsMainRegex: "([-_](test|unittest))?$"
+# IncludeIsMainSourceRegex: ""
+# IndentCaseLabels: true
+# IndentGotoLabels: true
+# IndentPPDirectives: None
+IndentWidth: 4
+# IndentWrappedFunctionNames: false
+# JavaScriptQuotes: Leave
+# JavaScriptWrapImports: true
+# KeepEmptyLinesAtTheStartOfBlocks: false
+# MacroBlockBegin: ""
+# MacroBlockEnd: ""
+# MaxEmptyLinesToKeep: 1
+# NamespaceIndentation: None
+# ObjCBinPackProtocolList: Never
+# ObjCBlockIndentWidth: 2
+# ObjCSpaceAfterProperty: false
+# ObjCSpaceBeforeProtocolList: true
+# PenaltyBreakAssignment: 2
+# PenaltyBreakBeforeFirstCallParameter: 1
+# PenaltyBreakComment: 300
+# PenaltyBreakFirstLessLess: 120
+# PenaltyBreakString: 1000
+# PenaltyBreakTemplateDeclaration: 10
+# PenaltyExcessCharacter: 1000000
+# PenaltyReturnTypeOnItsOwnLine: 200
+PointerAlignment: Right
+# RawStringFormats:
+# - Language: Cpp
+# Delimiters:
+# - cc
+# - CC
+# - cpp
+# - Cpp
+# - CPP
+# - "c++"
+# - "C++"
+# CanonicalDelimiter: ""
+# BasedOnStyle: google
+# - Language: TextProto
+# Delimiters:
+# - pb
+# - PB
+# - proto
+# - PROTO
+# EnclosingFunctions:
+# - EqualsProto
+# - EquivToProto
+# - PARSE_PARTIAL_TEXT_PROTO
+# - PARSE_TEST_PROTO
+# - PARSE_TEXT_PROTO
+# - ParseTextOrDie
+# - ParseTextProtoOrDie
+# CanonicalDelimiter: ""
+# BasedOnStyle: google
+# ReflowComments: true
+SortIncludes: false
+SortUsingDeclarations: false
+# SpaceAfterCStyleCast: false
+# SpaceAfterLogicalNot: false
+# SpaceAfterTemplateKeyword: true
+# SpaceBeforeAssignmentOperators: true
+# SpaceBeforeCpp11BracedList: false
+# SpaceBeforeCtorInitializerColon: true
+# SpaceBeforeInheritanceColon: true
+# SpaceBeforeParens: ControlStatements
+# SpaceBeforeRangeBasedForLoopColon: true
+# SpaceInEmptyBlock: false
+# SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+# SpacesInAngles: false
+# SpacesInConditionalStatement: false
+SpacesInContainerLiterals: false
+# SpacesInCStyleCastParentheses: false
+# SpacesInParentheses: false
+# SpacesInSquareBrackets: false
+# SpaceBeforeSquareBrackets: false
+Standard: Cpp11
+# StatementMacros:
+# - Q_UNUSED
+# - QT_REQUIRE_VERSION
+TabWidth: 4
+# UseCRLF: false
+# UseTab: Never
diff --git a/application/qfirehose/src/Android.mk b/application/qfirehose/src/Android.mk
new file mode 100644
index 0000000..9c3413f
--- /dev/null
+++ b/application/qfirehose/src/Android.mk
@@ -0,0 +1,8 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES:= firehose_protocol.c qfirehose.c sahara.c usb_linux.c stream_download_protocol.c md5.c usb2tcp.c
+LOCAL_CFLAGS += -pie -fPIE -Wall -Wextra -Werror -O1
+LOCAL_LDFLAGS += -pie -fPIE
+LOCAL_MODULE_TAGS:= optional
+LOCAL_MODULE:= QFirehose
+include $(BUILD_EXECUTABLE)
diff --git a/application/qfirehose/src/CMakeLists.txt b/application/qfirehose/src/CMakeLists.txt
new file mode 100644
index 0000000..eb63b02
--- /dev/null
+++ b/application/qfirehose/src/CMakeLists.txt
@@ -0,0 +1,12 @@
+cmake_minimum_required(VERSION 2.4)
+
+project(QFirehose)
+add_definitions(-Wall -Wextra -Werror -O1)
+
+set( QFirehose_SRCS
+ firehose_protocol.c qfirehose.c sahara.c usb_linux.c stream_download_protocol.c md5.c usb2tcp.c
+ )
+
+add_executable(QFirehose ${QFirehose_SRCS})
+target_link_libraries(QFirehose PUBLIC pthread)
+install (TARGETS QFirehose DESTINATION bin)
diff --git a/application/qfirehose/src/Makefile b/application/qfirehose/src/Makefile
new file mode 100644
index 0000000..9870bf8
--- /dev/null
+++ b/application/qfirehose/src/Makefile
@@ -0,0 +1,22 @@
+NDK_BUILD:=/home/aaron/share-aaron-1/android-ndk-r10e/ndk-build
+SRC=firehose_protocol.c qfirehose.c sahara.c usb_linux.c stream_download_protocol.c md5.c usb2tcp.c
+
+CFLAGS += -Wall -Wextra -Werror -g #-s
+LDFLAGS += -lpthread -ldl
+BINPATH := ./out
+ifeq ($(CC),cc)
+CC=${CROSS_COMPILE}gcc
+endif
+
+linux: clean
+ $(shell if [ -e ${BINPATH}]; then echo "file existed";else mkdir ${BINPATH}; fi;)
+ ${CC} ${CFLAGS} ${SRC} -o ${BINPATH}/QFirehose ${LDFLAGS}
+
+android: clean
+ rm -rf android
+ $(NDK_BUILD) V=0 APP_BUILD_SCRIPT=Android.mk NDK_PROJECT_PATH=`pwd` NDK_DEBUG=0 APP_ABI='armeabi-v7a,arm64-v8a' APP_PLATFORM=android-22 #16~4.1 22~5.1 25~7.1 27-32~8-12
+ rm -rf obj
+ mv libs android
+
+clean:
+ rm -rf ${BINPATH}/QFirehose obj libs usb2tcp *~
diff --git a/application/qfirehose/src/NOTICE b/application/qfirehose/src/NOTICE
new file mode 100644
index 0000000..5ab26d3
--- /dev/null
+++ b/application/qfirehose/src/NOTICE
@@ -0,0 +1,6 @@
+Quectel hereby grants customers of Quectel a license to use, modify,
+distribute and publish the Software in binary form provided that
+customers shall have no right to reverse engineer, reverse assemble,
+decompile or reduce to source code form any portion of the Software.
+Under no circumstances may customers modify, demonstrate, use, deliver
+or disclose any portion of the Software in source code form.
\ No newline at end of file
diff --git a/application/qfirehose/src/README.md b/application/qfirehose/src/README.md
new file mode 100644
index 0000000..d9d23fb
--- /dev/null
+++ b/application/qfirehose/src/README.md
@@ -0,0 +1,108 @@
+# QFIREHOSE 升级进度通知机制说明
+
+> QFirehose 目前支持两种进度通知方式。基于文件的通知方式,基于消息队列的通知机制。两种方式均可使用。Android设备由于系统限制,system V IPC 机制支持不完善,不能支持消息队列的方式获取进度。
+>
+> 其中基于文件的形式很容易替换成,有名管道的形式,只需把创建并打开改成创建并打开管道的形式(参考`man mkfifio`)。「注:管道需要先以读方式打开,再以写方式打开,否则会有错误」
+
+## 一、进度读写说明
+> QFirehose 进度都是以**整数**形式写到文件或者消息队列里。
+
+1. 在升级开始阶段写入进度**0**预示这升级开始。
+2. 之后每次进度有变化都会重新更新进度信息。
+3. 升级完成最后结果是**100**。
+4. 升级发生异常会写入进度 **-1**. 「注:特殊情况,如程序异常崩溃进度信息可能来不及写入而是空的,需要第三方程序考虑文件内容为空的情况」
+
+## 二、文件形式的通知机制
+### A. 文件读写说明
+
+ 文件方式的交互文件路径为: Android **“/data/update.conf”**, 其他 **“/tmp/update.conf”**。(修改QFirehose 源码可以修改这个文件)。另外需要保证/data 或者/tmp 这个目录存在,且QFirehose有读写、创建文件的权限。
+ 由于文件是持续存在磁盘上的。为了保证命令行方式的cat 读与标准API 的read 两种方式的可读性。**进度写方式为覆盖写,意思就是新的进度会覆盖掉之前的内容。**表现在实现上就是,每次写之前移动文件指针到文件头,然后写入进度。读进度时候需要注意这一点!!!
+ 另外注意的是QFirehose 不负责文件的删除,如果需要删除需要由第三方程序在升级结束/出错删除进度文件!
+
+### B.使用说明
+ QFirehose 默认不使能通知功能。如需使能文件方式通知逻辑需要在编译时指定宏 **USE_IPC_FILE** , 建议在Makefile里用 **-DUSE_IPC_FILE**来使能. 如下代码所示
+
+```makefile
+NDK_BUILD:=/workspace/android-ndk/android-ndk-r10e/ndk-build
+
+ifeq ($(CC),cc)
+CC=${CROSS_COMPILE}gcc
+endif
+
+cflags += -Wno-format-overflow
+
+linux: clean
+ ${CC} -g -Wall -DUSE_IPC_FILE -s ${cflags} firehose_protocol.c qfirehose.c sahara_protocol.c usb_linux.c stream_download_protocol.c md5.c usb2tcp.c -o QFirehose -lpthread -ldl
+
+clean:
+ rm -rf QFirehose obj libs android usb2tcp *~
+
+```
+
+## 三、消息队列的通知机制
+### A. 原理说明
+System V 方式消息队列。QFirehose 负责创建消息队列,写入进度。**不负责删除消息,不负责删除消息队列。**
+如下图,QFirehose 定义的消息结构体,消息类型,与message key 获取机制。第三方应用需要与QFirehose 保持一致。如需修改,可以修改QFirehose 源码。
+
+System V message queue:
+
+```c
+#define MSGBUFFSZ 16
+struct message
+{
+ long mtype;
+ char mtext[MSGBUFFSZ];
+};
+
+#define MSG_FILE "/etc/passwd"
+#define MSG_TYPE_IPC 1
+```
+
+并同时修改编译脚本/源码,定义宏 **USE_IPC_MSG**, 如下所示
+
+```makefile
+NDK_BUILD:=/workspace/android-ndk/android-ndk-r10e/ndk-build
+
+ifeq ($(CC),cc)
+CC=${CROSS_COMPILE}gcc
+endif
+
+cflags += -Wno-format-overflow
+
+linux: clean
+ ${CC} -g -Wall -DUSE_IPC_MSG -s ${cflags} firehose_protocol.c qfirehose.c sahara_protocol.c usb_linux.c stream_download_protocol.c md5.c usb2tcp.c -o QFirehose -lpthread -ldl
+
+clean:
+ rm -rf QFirehose obj libs android usb2tcp *~
+```
+
+QFirehose 实现了四个操作函数:消息队列的创建,删除,写,读。但是只用到了创建与读函数。另外两个仅供参考与测试用。
+### B.使用说明
+QFirehose 负责创建消息队列,写入进度。不会删除消息以及消息队列。建议第三方应用在升级程序退出之后主动删除消息队列。
+
+QFirehose 支持对消息队列的测试,这需要启用宏 **IPC_TEST** 。开启这个宏之后,QFirehose 会在写入消息后再次读取消息,并打印到`STDOUT`,检查打印可知是否正确。
+
+```c
+/**
+ * this function will not delete the msg queue
+ */
+int update_progress_msg(int percent)
+{
+ char buff[MSGBUFFSZ];
+ int msgid = msg_get();
+ if (msgid < 0)
+ return -1;
+ snprintf(buff, sizeof(buff), "%d", percent);
+
+#ifndef IPC_TEST
+ return msg_send(msgid, MSG_TYPE_IPC, buff);
+#else
+ msg_send(msgid, MSG_TYPE_IPC, buff);
+ struct message info;
+ info.mtype = MSG_TYPE_IPC;
+ msg_recv(msgid, &info);
+ printf("msg queue read: %s\n", info.mtext);
+#endif
+}
+```
+
diff --git a/application/qfirehose/src/ReleaseNote.txt b/application/qfirehose/src/ReleaseNote.txt
new file mode 100644
index 0000000..8e85077
--- /dev/null
+++ b/application/qfirehose/src/ReleaseNote.txt
@@ -0,0 +1,198 @@
+Release Notes
+[QFirehose_Linux_Android_V1.4.21]
+Date: 08/14/2024
+enhancement:
+ 1. Support EG120KEABA upgrade in RNDIS mode
+fix:
+
+[QFirehose_Linux_Android_V1.4.20]
+Date: 07/18/2024
+enhancement:
+ 1. Support AG600K to upgrade using adb command let module into edl
+ 2. Support sec-boot upgrade file to burn
+fix:
+
+Release Notes
+[QFirehose_Linux_Android_V1.4.19]
+Date: 04/30/2024
+enhancement:
+ 1. The input parameter can be a precise file to specify the reading of the upgrade file.
+ 2. RG650V using rndis can use this tool to upgrade.(./QFirehose -f ${upgrade_file_path} -d emmc)
+fix:
+
+[QFirehose_Linux_Android_V1.4.18]
+Date: 12/29/2023
+enhancement:
+ 1. New support for upgrading the laptop module EM120K-GL
+ 2. New support for SA885GAPNA(ufs) upgrade
+ 3. Added support for AG215SGLBA(with a single image greater than 40M in the firmware package) upgrade
+ 4. New support for RM520NGL(custom PID is 0x0804) upgrade
+fix:
+
+[QFirehose_Linux_Android_V1.4.17]
+Date: 09/13/2023
+enhancement:
+ 1. Added support for upgrading using zip and 7z package
+ 2. Optimize pcie code
+ 3. Optimize sahara code
+fix:
+
+[QFirehose_Linux_Android_V1.4.16]
+Date: 08/08/2023
+enhancement:
+ 1. Update copyright
+ 2. Hide -e full wipe, but retain this feature
+ 3. Fix errors in klocwork scanning
+ 4. Code optimization for erase partitioning operations for nand storage modules
+fix:
+
+[QFirehose_Linux_Android_V1.4.15]
+Date: 06/29/2023
+enhancement:
+ 1. Added support for upgrading laptop EM05G-SE10
+ 2. Modify the copyright time and add copyright to some c or h files
+ 3. Optimize sahara protocol code
+fix:
+
+[QFirehose_Linux_Android_V1.4.14]
+Date: 04/29/2023
+enhancement:
+ 1. Added support for upgrading laptop EM05-G and EM061KGL
+ 2. Added support for AG590ECNAB upgrade
+ 3. Added support for upgrading the RM520NGL Lenovo 5G module (with a specified PID of 0x0803)
+ 4. Added support for upgrading AG215S-GLR signed firmware version packs
+fix:
+
+[QFirehose_Linux_Android_V1.4.13]
+Date: 02/20/2023
+enhancement:
+ 1. New support for upgrading multiple PCIE modules in a specified way, with a maximum of 10 PCIE modules connected at the same time
+ 2. Optimization module connection USB3.0 upgrade
+fix:
+
+[QFirehose_Linux_Android_V1.4.12]
+Date: 12/15/2022
+enhancement:
+ 1. Added support for Qualcomm subcontracting firmware upgrade (also compatible with non subcontracting firmware)
+ 2. Added EC20��3C93 FFFF��upgrade
+ 3. Add BG95M3 anti misburning function
+ 4. Add other device type full wipe upgrade functions except nand
+fix:
+
+[QFirehose_Linux_Android_V1.4.11]
+Date: 08/29/2022
+enhancement:
+ 1. Better support emmc and ufs smart module
+ 2. remove function upgrade one file by '-f filename', nobody use it
+ and make source code complex and difficult to understand.
+ if customer need this function, can by modify firehose/rawprogram_nand_p4K_b256K_factory.xml
+ 3. if there is no md5.txt, just ignore, donot treat as error
+ 4. New support for upgrading Qualcomm AG215SGLR module and AG215SGLBA module
+fix:
+
+[QFirehose_Linux_Android_V1.4.10]
+Date: 07/15/2022
+enhancement:
+ 1. New support for laptop EM05 series and EG060K-EA module upgrade
+fix:
+
+[QFirehose_Linux_Android_V1.4.9]
+Date: 01/17/2022
+enhancement:
+ 1. Solve the problem that DM upgrade is stuck because DM keeps spitting logs
+fix:
+
+[QFirehose_Linux_Android_V1.4.8]
+Date: 11/12/2021
+enhancement:
+ 1. EM05CE Interface0 is a non-DM port with input and output endpoints. The actual DM port is Interface3
+ 2. EM05-G Upgrade Process Power off and power on. The next upgrade takes a long time to switch to download mode. Increase the waiting time to 10 seconds
+ 3. Optimized receiving and sending buf, no longer sharing one buf
+fix:
+
+[QFirehose_Linux_Android_V1.4.7]
+Date: 9/28/2021
+enhancement:
+ 1. Support EM05-G, DM is at usb interface 3
+ 2. Added support for smart platform SC600Y-EM and SC60-CE upgrade
+ 3. Support sdx20 quallcomm default 056c/901f
+fix:
+
+[Quectel_LTE&5G_QFirehose_Linux&Android_V1.4.6]
+Date: 6/11/2021
+enhancement:
+fix:
+ 1. ���Ӷ�EC20������Ŀ
+
+[Quectel_LTE&5G_QFirehose_Linux&Android_V1.4.5]
+Date: 3/4/2021
+enhancement:
+fix:
+ 1. fix upgrdae fail of AG550/EM120
+
+[Quectel_LTE&5G_QFirehose_Linux&Android_V1.4.4]
+Date: 1/27/202
+enhancement:
+fix:
+ 1. increase timeout of erase cmd to 15 seconds (from 6 seconds)
+
+[Quectel_LTE&5G_QFirehose_Linux&Android_V1.4.3]
+Date: 12/2/2020
+enhancement:
+ 1. '-u usbmon.log': The time stamp in usbmon.log is same QFirehose self
+ 2. USB ZLP: if kernel verison <= 4.2 and usb controoler type is XHCI.
+ there is a USB ZLP bug which cause FW upgrade fail at 0%,
+ print Warnning for this bug and tell customer how to fix.
+fix:
+ 1. AG35: FW upgrade fail (A very low probability) in one customer's factory
+
+[Quectel_LTE&5G_QFirehose_Linux&Android_V1.4]
+Date: 8/26/2020
+enhancement:
+ 1. release to
+fix:
+
+[Quectel_LTE&5G_QFirehose_Linux&Android_V1.3.3]
+Date: 7/27/2020
+enhancement:
+fix:
+ 1. AG525 has more than 32 partitions, support this
+
+[Quectel_LTE&5G_QFirehose_Linux&Android_V1.3.2]
+Date: 7/27/2020
+enhancement:
+fix:
+ 1. filter out ASR/HISI modems, only support MDM/SDX modems
+ 2. FW upgrade fail when USB3.0 modems switch EDL mode (05c6:9008),
+ will become usb2.0 device, and the /sys/bus/usb/devices/x-x will change
+
+[Quectel_LTE&5G_QFirehose_Linux&Android_V1.3]
+Date: 4/14/2020
+enhancement:
+ 1. X55 support upgrade from RAM DUMP state
+fix:
+
+[LTE&5G_QFirehose_Linux&Android_V1.2.4]
+Date: 3/26/2020
+enhancement:
+ 1. make sure 'first erase SBL, last program SBL', even The sequence in update/firehose/rawprogram*.xml is wrong
+fix:
+
+[LTE&5G_QFirehose_Linux&Android_V1.2.3]
+Date: 2/24/2019
+enhancement:
+ 1. support 'vendor' attribute in rawprogram*.xml
+ 2. support '-u usbmon_logfile', can capture usbmon log when upgrade module.
+fix:
+
+[LTE&5G_QFirehose_Linux&Android_V1.2.2]
+Date: 12/11/2019
+enhancement:
+ 1. auto detect and detach kernel driver when modem in EDL mode (05c6:9008).
+fix:
+ 1. Do not exit if get version command fails
+
+[LTE&5G_QFirehose_Linux&Android_V1.2]
+Date: 2019/07/31
+enhancement:
+ 1. First Release Version of QFirehose Tool.
diff --git a/application/qfirehose/src/android/arm64-v8a/QFirehose b/application/qfirehose/src/android/arm64-v8a/QFirehose
new file mode 100644
index 0000000..98276b6
Binary files /dev/null and b/application/qfirehose/src/android/arm64-v8a/QFirehose differ
diff --git a/application/qfirehose/src/android/armeabi-v7a/QFirehose b/application/qfirehose/src/android/armeabi-v7a/QFirehose
new file mode 100644
index 0000000..22ffa7b
Binary files /dev/null and b/application/qfirehose/src/android/armeabi-v7a/QFirehose differ
diff --git a/application/qfirehose/src/firehose_protocol.c b/application/qfirehose/src/firehose_protocol.c
new file mode 100644
index 0000000..f6fb277
--- /dev/null
+++ b/application/qfirehose/src/firehose_protocol.c
@@ -0,0 +1,2297 @@
+/*
+ Copyright 2023 Quectel Wireless Solutions Co.,Ltd
+
+ Quectel hereby grants customers of Quectel a license to use, modify,
+ distribute and publish the Software in binary form provided that
+ customers shall have no right to reverse engineer, reverse assemble,
+ decompile or reduce to source code form any portion of the Software.
+ Under no circumstances may customers modify, demonstrate, use, deliver
+ or disclose any portion of the Software in source code form.
+*/
+
+#include "usb_linux.h"
+#include
+#include
+#include
+/*
+#define error_return() \
+ do \
+ { \
+ dbg_time("%s %s %d fail\n", __FILE__, __func__, __LINE__); \
+ return __LINE__; \
+ } while (0)
+ */
+int recv_sc600y_configure_num = 1;
+extern const char *q_device_type;
+static int fh_recv_cmd_sk[2];
+extern unsigned q_module_packet_sign;
+
+extern unsigned q_erase_all_before_download;
+extern int update_transfer_bytes(long long bytes_cur);
+extern int show_progress();
+
+char file_name_image[128] = {0};
+char file_name_image_dir[256] = {0};
+
+typedef struct sparse_header
+{
+ uint32_t magic; /* 0xed26ff3a */
+ uint16_t major_version; /* (0x1) - reject images with higher major versions */
+ uint16_t minor_version; /* (0x0) - allow images with higer minor versions */
+ uint16_t file_hdr_sz; /* 28 bytes for first revision of the file format */
+ uint16_t chunk_hdr_sz; /* 12 bytes for first revision of the file format */
+ uint32_t blk_sz; /* block size in bytes, must be a multiple of 4 (4096) */
+ uint32_t total_blks; /* total blocks in the non-sparse output image */
+ uint32_t total_chunks; /* total chunks in the sparse input image */
+ uint32_t image_checksum; /* CRC32 checksum of the original data, counting
+ "don't care" */
+ /* as 0. Standard 802.3 polynomial, use a Public Domain */
+ /* table implementation */
+} sparse_header_t;
+
+#define SPARSE_HEADER_MAGIC 0xed26ff3a
+
+typedef struct chunk_header
+{
+ uint16_t chunk_type; /* 0xCAC1 -> raw; 0xCAC2 -> fill; 0xCAC3 -> don't care */
+ uint16_t reserved1;
+ uint32_t chunk_sz; /* in blocks in output image */
+ uint32_t total_sz; /* in bytes of chunk input file including chunk header and
+ data */
+} chunk_header_t;
+
+typedef struct chunk_polymerization_params
+{
+ uint32_t total_chunk_sz;
+ uint32_t total_sz;
+ uint16_t total_chunk_count;
+ // uint16_t file_sector_offset;
+} chunk_polymerization_param;
+
+typedef struct SparseImgParams
+{
+ chunk_polymerization_param chunk_polymerization_data[100];
+ chunk_polymerization_param chunk_polymerization_cac3[100];
+ uint16_t total_count;
+ uint16_t total_cac3_count;
+ uint16_t file_first_sector_offset; //��һ����ͷ��ȡ�����涼���Լ������
+} SparseImgParam;
+
+SparseImgParam SparseImgData;
+
+struct fh_configure_cmd
+{
+ const char *type;
+ const char *MemoryName;
+ uint32_t Verbose;
+ uint32_t AlwaysValidate;
+ uint32_t MaxDigestTableSizeInBytes;
+ uint32_t MaxPayloadSizeToTargetInBytes;
+ uint32_t MaxPayloadSizeFromTargetInBytes; // 2048
+ uint32_t MaxPayloadSizeToTargetInByteSupported; // 16k
+ uint32_t ZlpAwareHost;
+ uint32_t SkipStorageInit;
+};
+
+struct fh_erase_cmd
+{
+ const char *type;
+ // uint32_t PAGES_PER_BLOCK;
+ uint32_t SECTOR_SIZE_IN_BYTES;
+ // char label[32];
+ uint32_t last_sector;
+ uint32_t num_partition_sectors;
+ // uint32_t physical_partition_number;
+ uint32_t start_sector;
+};
+
+struct fh_program_cmd
+{
+ const char *type;
+ char *filename;
+ char *sparse;
+ uint32_t filesz;
+ // uint32_t PAGES_PER_BLOCK;
+ uint32_t SECTOR_SIZE_IN_BYTES;
+ // char label[32];
+ // uint32_t last_sector;
+ uint32_t num_partition_sectors;
+ uint32_t physical_partition_number;
+ uint32_t start_sector;
+ uint32_t file_sector_offset;
+ uint32_t UNSPARSE_FILE_SIZE;
+ // char sparse[16];
+};
+
+struct fh_response_cmd
+{
+ const char *type;
+ const char *value;
+ uint32_t rawmode;
+ uint32_t MaxPayloadSizeToTargetInBytes;
+};
+
+struct fh_log_cmd
+{
+ const char *type;
+};
+
+struct fh_patch_cmd
+{
+ const char *type;
+ char *filename;
+ uint32_t filesz;
+ uint32_t SECTOR_SIZE_IN_BYTES;
+ uint32_t num_partition_sectors;
+};
+
+struct fh_cmd_header
+{
+ const char *type;
+};
+
+struct fh_vendor_defines
+{
+ const char *type; // "vendor"
+};
+
+struct fh_cmd
+{
+ union
+ {
+ struct fh_cmd_header cmd;
+ struct fh_configure_cmd cfg;
+ struct fh_erase_cmd erase;
+ struct fh_program_cmd program;
+ struct fh_response_cmd response;
+ struct fh_log_cmd log;
+ struct fh_patch_cmd patch;
+ struct fh_vendor_defines vdef;
+ };
+ int part_upgrade;
+ char xml_original_data[512];
+};
+
+#define fh_cmd_num 1024 // AG525 have more than 64 partition
+struct fh_data
+{
+ const char *firehose_dir;
+ const void *usb_handle;
+ unsigned MaxPayloadSizeToTargetInBytes;
+ unsigned fh_cmd_count;
+ unsigned fh_patch_count;
+ unsigned ZlpAwareHost;
+ struct fh_cmd fh_cmd_table[fh_cmd_num];
+
+ unsigned xml_tx_size;
+ unsigned xml_rx_size;
+ char xml_tx_buf[1024];
+ char xml_rx_buf[1024];
+};
+
+static const char *fh_xml_find_value(const char *xml_line, const char *key, char **ppend)
+{
+ char *pchar = strstr(xml_line, key);
+ char *pend;
+
+ if (!pchar)
+ {
+ if (strcmp(key, "sparse")) dbg_time("%s: no key %s in %s\n", __func__, key, xml_line);
+ return NULL;
+ }
+
+ pchar += strlen(key);
+ if (pchar[0] != '=' && pchar[1] != '"')
+ {
+ dbg_time("%s: no start %s in %s\n", __func__, "=\"", xml_line);
+ return NULL;
+ }
+
+ pchar += strlen("=\"");
+ pend = strstr(pchar, "\"");
+ if (!pend)
+ {
+ dbg_time("%s: no end %s in %s\n", __func__, "\"", xml_line);
+ return NULL;
+ }
+
+ *ppend = pend;
+ return pchar;
+}
+
+static const char *fh_xml_get_value(const char *xml_line, const char *key)
+{
+ static char value[64];
+ char *pend;
+ const char *pchar = fh_xml_find_value(xml_line, key, &pend);
+
+ if (!pchar)
+ {
+ return NULL;
+ }
+
+ int len = pend - pchar;
+ if (len >= 64) return NULL;
+
+ strncpy(value, pchar, pend - pchar);
+ value[pend - pchar] = '\0';
+
+ return value;
+}
+
+static void fh_xml_set_value(char *xml_line, const char *key, unsigned value)
+{
+ char *pend;
+ const char *pchar = fh_xml_find_value(xml_line, key, &pend);
+ char value_str[32];
+ char *tmp_line = malloc(strlen(xml_line) + 1 + sizeof(value_str));
+
+ if (!pchar || !tmp_line)
+ {
+ if (tmp_line)
+ {
+ free(tmp_line);
+ tmp_line = NULL;
+ }
+ return;
+ }
+
+ strcpy(tmp_line, xml_line);
+
+ snprintf(value_str, sizeof(value_str), "%u", value);
+ tmp_line[pchar - xml_line] = '\0';
+ strcat(tmp_line, value_str);
+ strcat(tmp_line, pend);
+
+ strcpy(xml_line, tmp_line);
+ free(tmp_line);
+}
+
+static int fh_parse_xml_line(const char *xml_line, struct fh_cmd *fh_cmd)
+{
+ const char *pchar = NULL;
+ size_t len = strlen(xml_line);
+
+ memset(fh_cmd, 0, sizeof(struct fh_cmd));
+ strncpy(fh_cmd->xml_original_data, xml_line, 512);
+ if (fh_cmd->xml_original_data[len - 1] == '\n') fh_cmd->xml_original_data[len - 1] = '\0';
+
+ if (strstr(xml_line, "vendor=\"quectel\""))
+ {
+ fh_cmd->vdef.type = "vendor";
+ return 0;
+ }
+ else if (!strncmp(xml_line, "erase.type = "erase";
+ if (strstr(xml_line, "last_sector"))
+ {
+ if ((pchar = fh_xml_get_value(xml_line, "last_sector"))) fh_cmd->erase.last_sector = atoi(pchar);
+ }
+ if ((pchar = fh_xml_get_value(xml_line, "start_sector"))) fh_cmd->erase.start_sector = atoi(pchar);
+ if ((pchar = fh_xml_get_value(xml_line, "num_partition_sectors"))) fh_cmd->erase.num_partition_sectors = atoi(pchar);
+ if ((pchar = fh_xml_get_value(xml_line, "SECTOR_SIZE_IN_BYTES"))) fh_cmd->erase.SECTOR_SIZE_IN_BYTES = atoi(pchar);
+
+ return 0;
+ }
+ else if (!strncmp(xml_line, "program.type = "program";
+ if ((pchar = fh_xml_get_value(xml_line, "filename")))
+ {
+ fh_cmd->program.filename = strdup(pchar);
+ if (fh_cmd->program.filename[0] == '\0')
+ { // some fw version have blank program line, ignore it.
+ return -1;
+ }
+ }
+
+ if ((pchar = fh_xml_get_value(xml_line, "sparse")))
+ {
+ fh_cmd->program.sparse = strdup(pchar);
+ }
+ else
+ fh_cmd->program.sparse = NULL;
+
+ if ((pchar = fh_xml_get_value(xml_line, "start_sector"))) fh_cmd->program.start_sector = atoi(pchar);
+ if ((pchar = fh_xml_get_value(xml_line, "num_partition_sectors"))) fh_cmd->program.num_partition_sectors = atoi(pchar);
+ if ((pchar = fh_xml_get_value(xml_line, "SECTOR_SIZE_IN_BYTES"))) fh_cmd->program.SECTOR_SIZE_IN_BYTES = atoi(pchar);
+
+ if (fh_cmd->program.sparse != NULL && !strncasecmp(fh_cmd->program.sparse, "true", 4))
+ {
+ if ((pchar = fh_xml_get_value(xml_line, "file_sector_offset"))) fh_cmd->program.file_sector_offset = atoi(pchar);
+ if ((pchar = fh_xml_get_value(xml_line, "physical_partition_number"))) fh_cmd->program.physical_partition_number = atoi(pchar);
+ }
+
+ return 0;
+ }
+ else if (!strncmp(xml_line, "patch.type = "patch";
+ pchar = fh_xml_get_value(xml_line, "filename");
+ if (pchar && strcmp(pchar, "DISK")) return -1;
+ return 0;
+ }
+ else if (!strncmp(xml_line, "response.type = "response";
+ pchar = fh_xml_get_value(xml_line, "value");
+ if (pchar)
+ {
+ if (!strcmp(pchar, "ACK"))
+ fh_cmd->response.value = "ACK";
+ else if (!strcmp(pchar, "NAK"))
+ fh_cmd->response.value = "NAK";
+ else
+ fh_cmd->response.value = "OTHER";
+ }
+ if (strstr(xml_line, "rawmode"))
+ {
+ pchar = fh_xml_get_value(xml_line, "rawmode");
+ if (pchar)
+ {
+ fh_cmd->response.rawmode = !strcmp(pchar, "true");
+ }
+ }
+ else if (strstr(xml_line, "MaxPayloadSizeToTargetInBytes"))
+ {
+ pchar = fh_xml_get_value(xml_line, "MaxPayloadSizeToTargetInBytes");
+ if (pchar)
+ {
+ fh_cmd->response.MaxPayloadSizeToTargetInBytes = atoi(pchar);
+ }
+ }
+ return 0;
+ }
+ else if (!strncmp(xml_line, "program.type = "log";
+ return 0;
+ }
+
+ error_return();
+}
+
+static int fh_parse_xml_file(struct fh_data *fh_data, const char *xml_file)
+{
+ FILE *fp = fopen(xml_file, "rb");
+
+ if (fp == NULL)
+ {
+ dbg_time("%s fail to fopen(%s), errno: %d (%s)\n", __func__, xml_file, errno, strerror(errno));
+ error_return();
+ }
+
+ while (fgets(fh_data->xml_tx_buf, fh_data->xml_tx_size, fp))
+ {
+ char *xml_line = strstr(fh_data->xml_tx_buf, "<");
+ char *c_start = NULL;
+
+ if (!xml_line) continue;
+
+ c_start = strstr(xml_line, "");
+
+ if (c_end)
+ {
+ /*
+
+
+
+ */
+ char *tmp = strstr(xml_line, "/>");
+ if (tmp && (tmp < c_start || tmp > c_end))
+ {
+ memset(c_start, ' ', c_end - c_start + strlen("-->"));
+ goto __fh_parse_xml_line;
+ }
+
+ continue;
+ }
+ else
+ {
+ /*
+
+ -->
+ */
+ do
+ {
+ if (fgets(fh_data->xml_tx_buf, fh_data->xml_tx_size, fp) == NULL)
+ {
+ break;
+ };
+ xml_line = fh_data->xml_tx_buf;
+ } while (!strstr(xml_line, "-->") && strstr(xml_line, " SAHARA_HELLO_RESPONSE
+[004.052]: STATE <-- SAHARA_WAIT_COMMAND
+[004.053]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.053]: 0x0000000d 0x00000000 0x00000034
+[004.056]: STATE <-- SAHARA_WAIT_COMMAND
+[004.056]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.056]: 0x0000000d 0x00000034 0x00000080
+[004.056]: STATE <-- SAHARA_WAIT_COMMAND
+[004.057]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.057]: 0x0000000d 0x00001000 0x00001000
+[004.058]: STATE <-- SAHARA_WAIT_COMMAND
+[004.087]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.087]: 0x0000000d 0x00002000 0x000009d0
+[004.087]: STATE <-- SAHARA_WAIT_COMMAND
+[004.088]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.088]: 0x0000000d 0x00003000 0x00001000
+[004.093]: STATE <-- SAHARA_WAIT_COMMAND
+[004.094]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.094]: 0x0000000d 0x00004000 0x00001000
+[004.094]: STATE <-- SAHARA_WAIT_COMMAND
+[004.095]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.095]: 0x0000000d 0x00005000 0x00001000
+[004.095]: STATE <-- SAHARA_WAIT_COMMAND
+[004.096]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.096]: 0x0000000d 0x00006000 0x00001000
+[004.096]: STATE <-- SAHARA_WAIT_COMMAND
+[004.097]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.097]: 0x0000000d 0x00007000 0x00001000
+[004.098]: STATE <-- SAHARA_WAIT_COMMAND
+[004.098]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.098]: 0x0000000d 0x00008000 0x00001000
+[004.099]: STATE <-- SAHARA_WAIT_COMMAND
+[004.099]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.099]: 0x0000000d 0x00009000 0x00001000
+[004.100]: STATE <-- SAHARA_WAIT_COMMAND
+[004.100]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.100]: 0x0000000d 0x0000a000 0x00001000
+[004.101]: STATE <-- SAHARA_WAIT_COMMAND
+[004.101]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.101]: 0x0000000d 0x0000b000 0x00001000
+[004.103]: STATE <-- SAHARA_WAIT_COMMAND
+[004.103]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.103]: 0x0000000d 0x0000c000 0x00001000
+[004.104]: STATE <-- SAHARA_WAIT_COMMAND
+[004.104]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.104]: 0x0000000d 0x0000d000 0x00001000
+[004.105]: STATE <-- SAHARA_WAIT_COMMAND
+[004.105]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.105]: 0x0000000d 0x0000e000 0x00001000
+[004.106]: STATE <-- SAHARA_WAIT_COMMAND
+[004.106]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.106]: 0x0000000d 0x0000f000 0x00001000
+[004.107]: STATE <-- SAHARA_WAIT_COMMAND
+[004.107]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.108]: 0x0000000d 0x00010000 0x00001000
+[004.108]: STATE <-- SAHARA_WAIT_COMMAND
+[004.108]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.109]: 0x0000000d 0x00011000 0x00001000
+[004.109]: STATE <-- SAHARA_WAIT_COMMAND
+[004.110]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.110]: 0x0000000d 0x00012000 0x00001000
+[004.110]: STATE <-- SAHARA_WAIT_COMMAND
+[004.111]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.111]: 0x0000000d 0x00013000 0x00001000
+[004.111]: STATE <-- SAHARA_WAIT_COMMAND
+[004.112]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.112]: 0x0000000d 0x00014000 0x00001000
+[004.113]: STATE <-- SAHARA_WAIT_COMMAND
+[004.113]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.113]: 0x0000000d 0x00015000 0x00001000
+[004.114]: STATE <-- SAHARA_WAIT_COMMAND
+[004.114]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.114]: 0x0000000d 0x00016000 0x00001000
+[004.115]: STATE <-- SAHARA_WAIT_COMMAND
+[004.115]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.115]: 0x0000000d 0x00017000 0x00001000
+[004.116]: STATE <-- SAHARA_WAIT_COMMAND
+[004.116]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.116]: 0x0000000d 0x00018000 0x00001000
+[004.117]: STATE <-- SAHARA_WAIT_COMMAND
+[004.117]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.117]: 0x0000000d 0x00019000 0x00001000
+[004.118]: STATE <-- SAHARA_WAIT_COMMAND
+[004.118]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.118]: 0x0000000d 0x0001a000 0x00001000
+[004.119]: STATE <-- SAHARA_WAIT_COMMAND
+[004.119]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.119]: 0x0000000d 0x0001b000 0x00001000
+[004.120]: STATE <-- SAHARA_WAIT_COMMAND
+[004.120]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.120]: 0x0000000d 0x0001c000 0x00001000
+[004.121]: STATE <-- SAHARA_WAIT_COMMAND
+[004.121]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.122]: 0x0000000d 0x0001d000 0x00001000
+[004.122]: STATE <-- SAHARA_WAIT_COMMAND
+[004.123]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.123]: 0x0000000d 0x0001e000 0x00001000
+[004.123]: STATE <-- SAHARA_WAIT_COMMAND
+[004.124]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.124]: 0x0000000d 0x0001f000 0x00001000
+[004.124]: STATE <-- SAHARA_WAIT_COMMAND
+[004.125]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.125]: 0x0000000d 0x00020000 0x00001000
+[004.125]: STATE <-- SAHARA_WAIT_COMMAND
+[004.126]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.126]: 0x0000000d 0x00021000 0x00001000
+[004.126]: STATE <-- SAHARA_WAIT_COMMAND
+[004.127]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.127]: 0x0000000d 0x00022000 0x00001000
+[004.127]: STATE <-- SAHARA_WAIT_COMMAND
+[004.128]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.128]: 0x0000000d 0x00023000 0x00001000
+[004.128]: STATE <-- SAHARA_WAIT_COMMAND
+[004.129]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.129]: 0x0000000d 0x00024000 0x00001000
+[004.129]: STATE <-- SAHARA_WAIT_COMMAND
+[004.130]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.130]: 0x0000000d 0x00025000 0x00001000
+[004.130]: STATE <-- SAHARA_WAIT_COMMAND
+[004.131]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.131]: 0x0000000d 0x00026000 0x00001000
+[004.131]: STATE <-- SAHARA_WAIT_COMMAND
+[004.132]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.132]: 0x0000000d 0x00027000 0x00001000
+[004.132]: STATE <-- SAHARA_WAIT_COMMAND
+[004.133]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.133]: 0x0000000d 0x00028000 0x00001000
+[004.133]: STATE <-- SAHARA_WAIT_COMMAND
+[004.134]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.134]: 0x0000000d 0x00029000 0x00001000
+[004.134]: STATE <-- SAHARA_WAIT_COMMAND
+[004.135]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.135]: 0x0000000d 0x0002a000 0x00001000
+[004.135]: STATE <-- SAHARA_WAIT_COMMAND
+[004.136]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.136]: 0x0000000d 0x0002b000 0x00000578
+[004.136]: STATE <-- SAHARA_WAIT_COMMAND
+[004.136]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.137]: 0x0000000d 0x0002b580 0x00001000
+[004.137]: STATE <-- SAHARA_WAIT_COMMAND
+[004.137]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.138]: 0x0000000d 0x0002c580 0x00001000
+[004.138]: STATE <-- SAHARA_WAIT_COMMAND
+[004.138]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.139]: 0x0000000d 0x0002d580 0x00001000
+[004.139]: STATE <-- SAHARA_WAIT_COMMAND
+[004.139]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.140]: 0x0000000d 0x0002e580 0x00001000
+[004.140]: STATE <-- SAHARA_WAIT_COMMAND
+[004.140]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.141]: 0x0000000d 0x0002f580 0x00001000
+[004.141]: STATE <-- SAHARA_WAIT_COMMAND
+[004.141]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.142]: 0x0000000d 0x00030580 0x00001000
+[004.142]: STATE <-- SAHARA_WAIT_COMMAND
+[004.143]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.143]: 0x0000000d 0x00031580 0x00001000
+[004.143]: STATE <-- SAHARA_WAIT_COMMAND
+[004.144]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.144]: 0x0000000d 0x00032580 0x00001000
+[004.144]: STATE <-- SAHARA_WAIT_COMMAND
+[004.145]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.145]: 0x0000000d 0x00033580 0x00001000
+[004.145]: STATE <-- SAHARA_WAIT_COMMAND
+[004.146]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.146]: 0x0000000d 0x00034580 0x00001000
+[004.146]: STATE <-- SAHARA_WAIT_COMMAND
+[004.147]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.147]: 0x0000000d 0x00035580 0x00001000
+[004.147]: STATE <-- SAHARA_WAIT_COMMAND
+[004.148]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.148]: 0x0000000d 0x00036580 0x00001000
+[004.148]: STATE <-- SAHARA_WAIT_COMMAND
+[004.149]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.149]: 0x0000000d 0x00037580 0x00001000
+[004.149]: STATE <-- SAHARA_WAIT_COMMAND
+[004.150]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.150]: 0x0000000d 0x00038580 0x00001000
+[004.150]: STATE <-- SAHARA_WAIT_COMMAND
+[004.151]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.151]: 0x0000000d 0x00039580 0x00001000
+[004.151]: STATE <-- SAHARA_WAIT_COMMAND
+[004.152]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.152]: 0x0000000d 0x0003a580 0x00001000
+[004.152]: STATE <-- SAHARA_WAIT_COMMAND
+[004.153]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.153]: 0x0000000d 0x0003b580 0x00001000
+[004.153]: STATE <-- SAHARA_WAIT_COMMAND
+[004.154]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.154]: 0x0000000d 0x0003c580 0x00001000
+[004.154]: STATE <-- SAHARA_WAIT_COMMAND
+[004.155]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.155]: 0x0000000d 0x0003d580 0x00001000
+[004.155]: STATE <-- SAHARA_WAIT_COMMAND
+[004.156]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.156]: 0x0000000d 0x0003e580 0x00001000
+[004.156]: STATE <-- SAHARA_WAIT_COMMAND
+[004.157]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.157]: 0x0000000d 0x0003f580 0x00001000
+[004.157]: STATE <-- SAHARA_WAIT_COMMAND
+[004.158]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.158]: 0x0000000d 0x00040580 0x00001000
+[004.158]: STATE <-- SAHARA_WAIT_COMMAND
+[004.159]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.159]: 0x0000000d 0x00041580 0x00001000
+[004.159]: STATE <-- SAHARA_WAIT_COMMAND
+[004.160]: RECEIVED <-- SAHARA_READ_DATA_ID
+[004.160]: 0x0000000d 0x00042580 0x00000800
+[004.160]: STATE <-- SAHARA_WAIT_COMMAND
+[004.163]: RECEIVED <-- SAHARA_END_IMAGE_TX_ID
+[004.163]: image_id = 13, status = 0
+[004.164]: SENDING --> SAHARA_DONE
+[004.164]: STATE <-- SAHARA_WAIT_DONE_RESP
+[004.164]: RECEIVED <-- SAHARA_DONE_RESP_ID
+[004.164]: image_tx_status = 1
+[004.164]: Successfully uploaded all images
+[004.164]: Sahara protocol completed
+[004.165]: dir=/mnt/RM500QGLAAR01A01M4G_BETA_20200428F_factory/update/firehose
+[004.165]: d_name=rawprogram_nand_p4K_b256K_factory.xml
+[005.185]:
+[005.185]:
+[005.185]:
+[005.186]:
+[005.186]:
+[005.187]:
+[005.187]:
+[005.187]:
+[005.188]:
+[005.188]:
+[005.188]:
+[005.188]:
+[005.189]:
+[005.189]:
+[005.189]:
+[005.190]:
+[005.190]:
+[005.190]:
+[005.191]:
+[005.191]:
+[006.182]: inf[0] ep_in -1/1024, errno = 110 (Operation timed out), timeout=1000
+[006.182]: qusb_noblock_read read=0, errno: 110 (Operation timed out)
+[006.182]: qusb_noblock_read cur=0, min_size=1
+[006.182]: firehose_protocol.c fh_recv_cmd 327 fail
+[006.183]:
+[006.184]:
+[006.184]:
+[006.186]:
+[006.186]:
+[006.187]:
+[006.256]:
+[006.256]:
+[006.645]:
+[006.653]:
+[006.654]:
+[006.656]:
+[006.657]:
+[006.658]:
+[006.660]:
+[006.661]:
+[006.662]:
+[006.663]:
+[006.665]:
+[006.807]:
+[006.807]:
+[006.808]:
+[006.810]:
+[006.811]:
+[006.818]:
+[006.819]:
+[006.820]:
+[006.821]:
+[006.822]:
+[006.825]:
+[006.826]:
+[006.827]:
+[006.828]:
+[006.829]:
+[006.832]:
+[006.833]:
+[006.834]:
+[006.835]:
+[006.835]:
+[006.839]:
+[006.840]:
+[006.841]:
+[006.842]:
+[006.843]:
+[006.846]:
+[006.847]:
+[006.848]:
+[006.848]:
+[006.849]:
+[006.853]:
+[006.854]:
+[006.855]:
+[006.856]:
+[006.856]:
+[006.860]:
+[006.861]:
+[006.862]:
+[006.863]:
+[006.864]:
+[006.867]:
+[006.868]:
+[006.869]:
+[006.870]:
+[006.871]:
+[006.880]:
+[006.880]:
+[006.881]:
+[006.882]:
+[006.883]:
+[006.887]:
+[006.888]:
+[006.889]:
+[006.889]:
+[006.890]:
+[006.894]:
+[006.894]:
+[006.895]:
+[006.896]:
+[006.897]:
+[006.953]:
+[006.953]:
+[006.955]:
+[006.955]:
+[006.956]:
+[007.014]:
+[007.014]:
+[007.016]:
+[007.016]:
+[007.017]:
+[007.548]:
+[007.548]:
+[007.550]:
+[007.550]:
+[007.551]:
+[007.628]:
+[007.628]:
+[007.629]:
+[007.630]:
+[007.631]:
+[008.119]:
+[008.119]:
+[008.120]:
+[008.121]:
+[008.122]:
+[008.228]:
+[008.229]:
+[008.230]:
+[008.230]:
+[008.231]:
+[008.739]:
+[008.739]:
+[008.740]:
+[008.741]:
+[008.742]:
+[008.742]: send partition_complete_p4K_b256K.mbn, filesize=16384
+.
+[008.748]: upgrade progress 0% 16384/191864239
+[008.749]: send finished
+[008.750]:
+[008.751]:
+[008.752]:
+[008.752]:
+[008.753]:
+[008.754]: send ../cefs.mbn, filesize=1826816
+.......................
+[009.198]: upgrade progress 0% 1843200/191864239
+[009.198]: send finished
+[009.200]:
+[009.200]:
+[009.201]:
+[009.202]:
+[009.203]:
+[009.203]: send ../tz.mbn, filesize=913408
+............
+[009.430]: upgrade progress 1% 2756608/191864239
+[009.430]: send finished
+[009.431]:
+[009.431]:
+[009.432]:
+[009.433]:
+[009.434]:
+[009.434]: send ../devcfg.mbn, filesize=42379
+.
+[009.455]: upgrade progress 1% 2798987/191864239
+[009.456]: send finished
+[009.456]:
+[009.457]:
+[009.458]:
+[009.459]:
+[009.459]:
+[009.460]: send ../apdp.mbn, filesize=13508
+.
+[009.466]: upgrade progress 1% 2812495/191864239
+[009.466]: send finished
+[009.468]:
+[009.468]:
+[009.469]:
+[009.470]:
+[009.471]:
+[009.471]: send ../xbl_cfg.elf, filesize=65724
+.
+[009.498]: upgrade progress 1% 2878219/191864239
+[009.498]: send finished
+[009.499]:
+[009.500]:
+[009.501]:
+[009.501]:
+[009.502]:
+[009.503]: send ../multi_image.mbn, filesize=13008
+.
+[009.515]: upgrade progress 1% 2891227/191864239
+[009.516]: send finished
+[009.517]:
+[009.518]:
+[009.519]:
+[009.520]:
+[009.520]:
+[009.521]: send ../hyp.mbn, filesize=84416
+..
+[009.547]: upgrade progress 1% 2975643/191864239
+[009.547]: send finished
+[009.548]:
+[009.549]:
+[009.550]:
+[009.551]:
+[009.551]:
+[009.552]: send ../abl.elf, filesize=176128
+...
+[009.599]: upgrade progress 1% 3151771/191864239
+[009.599]: send finished
+[009.600]:
+[009.601]:
+[009.602]:
+[009.603]:
+[009.603]:
+[009.604]: send ../uefi.elf, filesize=1191936
+...............
+[009.897]: upgrade progress 2% 4343707/191864239
+[009.897]: send finished
+[009.898]:
+[009.898]:
+[009.899]:
+[009.900]:
+[009.901]:
+[009.901]: send ../tools.fv, filesize=393216
+.....
+[009.995]: upgrade progress 2% 4736923/191864239
+[009.995]: send finished
+[009.997]:
+[009.997]:
+[009.998]:
+[009.999]:
+[010.000]:
+[010.000]: send ../sdxprairie-boot.img, filesize=10360832
+...............................................................................................................................
+[012.503]: upgrade progress 7% 15097755/191864239
+[012.504]: send finished
+[012.505]:
+[012.506]:
+[012.507]:
+[012.508]:
+[012.508]:
+[012.509]: send ../sdxprairie-boot.img, filesize=10360832
+...............................................................................................................................
+[014.934]: upgrade progress 13% 25458587/191864239
+[014.935]: send finished
+[014.936]:
+[014.937]:
+[014.938]:
+[014.939]:
+[014.939]:
+[014.940]: send ../NON-HLOS.ubi, filesize=76283904
+....................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
+[034.852]: upgrade progress 53% 101742491/191864239
+[034.852]: send finished
+[034.854]:
+[034.855]:
+[034.856]:
+[034.857]:
+[034.857]:
+[034.858]: send ../sdxprairie-recoveryfs.ubi, filesize=14417920
+................................................................................................................................................................................
+[038.365]: upgrade progress 60% 116160411/191864239
+[038.365]: send finished
+[038.367]:
+[038.367]:
+[038.368]:
+[038.369]:
+[038.370]:
+[038.370]: send ../usrdata.ubi, filesize=3932160
+................................................
+[039.286]: upgrade progress 62% 120092571/191864239
+[039.287]: send finished
+[039.288]:
+[039.289]:
+[039.290]:
+[039.291]:
+[039.291]:
+[039.292]: send ../oemdata.ubi, filesize=3932160
+................................................
+[040.219]: upgrade progress 64% 124024731/191864239
+[040.220]: send finished
+[040.221]:
+[040.222]:
+[040.223]:
+[040.224]:
+[040.224]:
+[040.225]: send ../sdxprairie-sysfs.ubi, filesize=67108864
+....................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
+[057.600]: upgrade progress 99% 191133595/191864239
+[057.600]: send finished
+[057.602]:
+[057.602]:
+[057.603]:
+[057.604]:
+[057.605]:
+[057.605]: send ../aop.mbn, filesize=154240
+..
+[057.647]: upgrade progress 100% 191864239/191864239
+[057.647]: send finished
+[057.648]:
+[057.649]:
+[057.650]:
+[057.651]:
+[057.651]:
+[057.652]: send ../sbl1.mbn, filesize=576404
+........
+[057.789]: upgrade progress 100% 192440643/191864239
+[057.789]: send finished
+[057.790]:
+[057.790]:
+[057.791]:
+[057.791]:
+[057.792]:
+[057.792]:
+[057.800]: inf[0] ep_in -1/1024, errno = 71 (Protocol error)
+[057.800]: qusb_noblock_read read=-1, errno: 71 (Protocol error)
+[057.800]: qusb_noblock_read cur=0, min_size=1
+[057.800]: firehose_protocol.c fh_recv_cmd 327 fail
+[057.800]: THE TOTAL DOWNLOAD TIME IS 53.750 s
+[057.801]: Upgrade module successfully.
+root@OpenWrt:~#
\ No newline at end of file
diff --git a/application/qfirehose/src/log/MCU_remote.log.txt b/application/qfirehose/src/log/MCU_remote.log.txt
new file mode 100644
index 0000000..7a2ea80
--- /dev/null
+++ b/application/qfirehose/src/log/MCU_remote.log.txt
@@ -0,0 +1,38 @@
+root@OpenWrt:~# ./QFirehose -p 9008
+[000.000]: QFirehose Version: Quectel_LTE&5G_QFirehose_Linux&Android_V1.3.2
+[000.000]: Builded: May 18 2020 11:32:04
+[000.004]: [1] /sys/bus/usb/devices/2-1.4 2c7c/800/414
+[000.012]: P: /dev/bus/usb/002/005 idVendor=2c7c idProduct=0800
+[000.012]: C: /dev/bus/usb/002/005 bNumInterfaces: 5
+[000.014]: I: If#= 0 Alt= 0 #EPs= 2 Cls=ff Sub=ff Prot=30
+[000.015]: E: Ad=81 Atr=02 MxPS= 1024 Ivl=0ms
+[000.015]: E: Ad=01 Atr=02 MxPS= 1024 Ivl=0ms
+[000.016]: I: If#= 1 Alt= 0 #EPs= 3 Cls=ff Sub=00 Prot=00
+[000.017]: I: If#= 2 Alt= 0 #EPs= 3 Cls=ff Sub=00 Prot=00
+[000.018]: I: If#= 3 Alt= 0 #EPs= 3 Cls=ff Sub=00 Prot=00
+[000.019]: I: If#= 4 Alt= 0 #EPs= 3 Cls=ff Sub=ff Prot=ff
+[000.021]: qusb_noblock_open port_name = /dev/ttyUSB0
+[000.024]: old software version: RM500QGLAAR01A01M4G_BETA_20200428F
+[001.025]: poll_wait events=POLLIN msec=1000 timeout
+[001.026]: qusb_noblock_read cur=0, min_size=1
+[001.026]: switch to 'Emergency download mode'
+[001.031]: successful, wait module reboot
+[002.034]: fail to fopen /sys/bus/usb/devices/2-1.4/uevent, errno: 2 (No such file or directory)
+[003.038]: fail to fopen /sys/bus/usb/devices/2-1.4/uevent, errno: 2 (No such file or directory)
+[004.041]: [1] /sys/bus/usb/devices/1-1.4 5c6/9008/0
+[004.046]: P: /dev/bus/usb/001/010 idVendor=05c6 idProduct=9008
+[004.046]: C: /dev/bus/usb/001/010 bNumInterfaces: 1
+[004.047]: I: If#= 0 Alt= 0 #EPs= 2 Cls=ff Sub=ff Prot=11
+[004.048]: E: Ad=81 Atr=02 MxPS= 512 Ivl=0ms
+[004.049]: E: Ad=01 Atr=02 MxPS= 512 Ivl=0ms
+[004.052]: create_tcp_server tcp_port=9008
+[004.053]: server_fd=4
+[004.055]: wait_client_connect
+[023.889]: clientfd = 5 192.168.1.153:58069 connect
+[023.889]: usb_fd = 6
+[083.310]: inf[0] ep_in -1/4096, errno = 71 (Protocol error)
+[083.310]: qusb_noblock_read read=-1, errno: 71 (Protocol error)
+[083.310]: qusb_noblock_read cur=0, min_size=1
+[083.310]: usb2tcp_main poll usb_fd = 6, revents = 0011
+root@OpenWrt:~#
+
diff --git a/application/qfirehose/src/log/Ubuntu_remote.log.txt b/application/qfirehose/src/log/Ubuntu_remote.log.txt
new file mode 100644
index 0000000..f511e0f
--- /dev/null
+++ b/application/qfirehose/src/log/Ubuntu_remote.log.txt
@@ -0,0 +1,607 @@
+carl@carl-Lenovo-ideapad-110-15ISK:~/Qfirehose_linux$ ./qfirehose -f ../EM20GRAR01A04V01M4G_factory -p 172.18.112.13:9008
+[000.000] QFirehose Version: LTE_QFirehose_Linux&Android_V1.1.8
+[000.000] Builded: Jul 5 2019 12:04:58
+[000.000] Find md5 check file <../EM20GRAR01A04V01M4G_factory/md5.txt>
+[000.000] Totals checking 0 files md5 value, 0 file fail!
+[000.001] qtcp_connect port_name = 172.18.112.13:9008
+[000.006] idVendor=2c7c, idProduct=0620, interfaceNum=6
+[001.008] poll_wait events=POLLIN msec=1000 timeout
+[001.008] qusb_noblock_read cur=0, min_size=1
+[001.008] switch to 'Emergency download mode'
+[001.011] successful, wait module reboot
+[002.011] qtcp_connect port_name = 172.18.112.13:9008
+[002.014] idVendor=05c6, idProduct=9008, interfaceNum=1
+[002.014] dir=../EM20GRAR01A04V01M4G_factory/update/firehose
+[002.014] dir=../EM20GRAR01A04V01M4G_factory/update/firehose
+[002.014] d_name=prog_firehose_sdx24.mbn
+[002.014] prog_nand_firehose_filename = prog_firehose_sdx24.mbn
+[002.015] STATE <-- SAHARA_WAIT_HELLO
+[002.015] RECEIVED <-- SAHARA_HELLO_ID
+[002.015] RECEIVED <-- SAHARA_MODE_IMAGE_TX_PENDING
+[002.015] SENDING --> SAHARA_HELLO_RESPONSE
+[002.015] STATE <-- SAHARA_WAIT_COMMAND
+[002.052] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.052] 0x0000000d 0x00000000 0x00000034
+[002.052] STATE <-- SAHARA_WAIT_COMMAND
+[002.121] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.121] 0x0000000d 0x00000034 0x00000080
+[002.121] STATE <-- SAHARA_WAIT_COMMAND
+[002.193] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.193] 0x0000000d 0x00001000 0x00001000
+[002.193] STATE <-- SAHARA_WAIT_COMMAND
+[002.232] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.232] 0x0000000d 0x00002000 0x000009d0
+[002.233] STATE <-- SAHARA_WAIT_COMMAND
+[002.272] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.272] 0x0000000d 0x00003000 0x00001000
+[002.273] STATE <-- SAHARA_WAIT_COMMAND
+[002.312] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.312] 0x0000000d 0x00004000 0x00001000
+[002.313] STATE <-- SAHARA_WAIT_COMMAND
+[002.353] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.353] 0x0000000d 0x00005000 0x00001000
+[002.353] STATE <-- SAHARA_WAIT_COMMAND
+[002.392] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.392] 0x0000000d 0x00006000 0x00001000
+[002.392] STATE <-- SAHARA_WAIT_COMMAND
+[002.432] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.432] 0x0000000d 0x00007000 0x00001000
+[002.433] STATE <-- SAHARA_WAIT_COMMAND
+[002.473] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.473] 0x0000000d 0x00008000 0x00001000
+[002.473] STATE <-- SAHARA_WAIT_COMMAND
+[002.513] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.513] 0x0000000d 0x00009000 0x00001000
+[002.513] STATE <-- SAHARA_WAIT_COMMAND
+[002.552] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.552] 0x0000000d 0x0000a000 0x00001000
+[002.553] STATE <-- SAHARA_WAIT_COMMAND
+[002.592] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.592] 0x0000000d 0x0000b000 0x00001000
+[002.593] STATE <-- SAHARA_WAIT_COMMAND
+[002.633] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.633] 0x0000000d 0x0000c000 0x00001000
+[002.633] STATE <-- SAHARA_WAIT_COMMAND
+[002.673] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.673] 0x0000000d 0x0000d000 0x00001000
+[002.673] STATE <-- SAHARA_WAIT_COMMAND
+[002.712] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.713] 0x0000000d 0x0000e000 0x00001000
+[002.713] STATE <-- SAHARA_WAIT_COMMAND
+[002.756] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.757] 0x0000000d 0x0000f000 0x00001000
+[002.757] STATE <-- SAHARA_WAIT_COMMAND
+[002.796] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.796] 0x0000000d 0x00010000 0x00001000
+[002.796] STATE <-- SAHARA_WAIT_COMMAND
+[002.837] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.837] 0x0000000d 0x00011000 0x00001000
+[002.837] STATE <-- SAHARA_WAIT_COMMAND
+[002.876] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.876] 0x0000000d 0x00012000 0x00001000
+[002.877] STATE <-- SAHARA_WAIT_COMMAND
+[002.916] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.917] 0x0000000d 0x00013000 0x00001000
+[002.917] STATE <-- SAHARA_WAIT_COMMAND
+[002.957] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.957] 0x0000000d 0x00014000 0x00001000
+[002.957] STATE <-- SAHARA_WAIT_COMMAND
+[002.997] RECEIVED <-- SAHARA_READ_DATA_ID
+[002.997] 0x0000000d 0x00015000 0x00001000
+[002.997] STATE <-- SAHARA_WAIT_COMMAND
+[003.036] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.036] 0x0000000d 0x00016000 0x00001000
+[003.037] STATE <-- SAHARA_WAIT_COMMAND
+[003.076] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.076] 0x0000000d 0x00017000 0x00001000
+[003.076] STATE <-- SAHARA_WAIT_COMMAND
+[003.117] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.117] 0x0000000d 0x00018000 0x00001000
+[003.117] STATE <-- SAHARA_WAIT_COMMAND
+[003.157] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.157] 0x0000000d 0x00019000 0x00001000
+[003.157] STATE <-- SAHARA_WAIT_COMMAND
+[003.196] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.197] 0x0000000d 0x0001a000 0x00001000
+[003.197] STATE <-- SAHARA_WAIT_COMMAND
+[003.236] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.236] 0x0000000d 0x0001b000 0x00001000
+[003.237] STATE <-- SAHARA_WAIT_COMMAND
+[003.276] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.276] 0x0000000d 0x0001c000 0x00001000
+[003.277] STATE <-- SAHARA_WAIT_COMMAND
+[003.317] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.317] 0x0000000d 0x0001d000 0x00001000
+[003.317] STATE <-- SAHARA_WAIT_COMMAND
+[003.356] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.356] 0x0000000d 0x0001e000 0x00001000
+[003.357] STATE <-- SAHARA_WAIT_COMMAND
+[003.396] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.396] 0x0000000d 0x0001f000 0x00001000
+[003.397] STATE <-- SAHARA_WAIT_COMMAND
+[003.437] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.437] 0x0000000d 0x00020000 0x00001000
+[003.437] STATE <-- SAHARA_WAIT_COMMAND
+[003.476] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.477] 0x0000000d 0x00021000 0x00001000
+[003.477] STATE <-- SAHARA_WAIT_COMMAND
+[003.516] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.517] 0x0000000d 0x00022000 0x00001000
+[003.517] STATE <-- SAHARA_WAIT_COMMAND
+[003.556] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.556] 0x0000000d 0x00023000 0x00001000
+[003.557] STATE <-- SAHARA_WAIT_COMMAND
+[003.597] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.597] 0x0000000d 0x00024000 0x00001000
+[003.597] STATE <-- SAHARA_WAIT_COMMAND
+[003.637] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.637] 0x0000000d 0x00025000 0x00001000
+[003.637] STATE <-- SAHARA_WAIT_COMMAND
+[003.676] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.676] 0x0000000d 0x00026000 0x00001000
+[003.677] STATE <-- SAHARA_WAIT_COMMAND
+[003.716] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.716] 0x0000000d 0x00027000 0x00001000
+[003.717] STATE <-- SAHARA_WAIT_COMMAND
+[003.757] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.757] 0x0000000d 0x00028000 0x00001000
+[003.757] STATE <-- SAHARA_WAIT_COMMAND
+[003.797] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.797] 0x0000000d 0x00029000 0x00001000
+[003.797] STATE <-- SAHARA_WAIT_COMMAND
+[003.836] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.837] 0x0000000d 0x0002a000 0x000005a8
+[003.837] STATE <-- SAHARA_WAIT_COMMAND
+[003.876] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.876] 0x0000000d 0x0002a600 0x00001000
+[003.877] STATE <-- SAHARA_WAIT_COMMAND
+[003.917] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.917] 0x0000000d 0x0002b600 0x00001000
+[003.917] STATE <-- SAHARA_WAIT_COMMAND
+[003.957] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.957] 0x0000000d 0x0002c600 0x00001000
+[003.957] STATE <-- SAHARA_WAIT_COMMAND
+[003.996] RECEIVED <-- SAHARA_READ_DATA_ID
+[003.997] 0x0000000d 0x0002d600 0x00001000
+[003.997] STATE <-- SAHARA_WAIT_COMMAND
+[004.036] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.036] 0x0000000d 0x0002e600 0x00001000
+[004.036] STATE <-- SAHARA_WAIT_COMMAND
+[004.076] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.077] 0x0000000d 0x0002f600 0x00001000
+[004.077] STATE <-- SAHARA_WAIT_COMMAND
+[004.116] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.117] 0x0000000d 0x00030600 0x00001000
+[004.117] STATE <-- SAHARA_WAIT_COMMAND
+[004.156] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.156] 0x0000000d 0x00031600 0x00001000
+[004.157] STATE <-- SAHARA_WAIT_COMMAND
+[004.196] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.196] 0x0000000d 0x00032600 0x00001000
+[004.197] STATE <-- SAHARA_WAIT_COMMAND
+[004.237] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.237] 0x0000000d 0x00033600 0x00001000
+[004.237] STATE <-- SAHARA_WAIT_COMMAND
+[004.277] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.277] 0x0000000d 0x00034600 0x00001000
+[004.277] STATE <-- SAHARA_WAIT_COMMAND
+[004.316] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.316] 0x0000000d 0x00035600 0x00001000
+[004.317] STATE <-- SAHARA_WAIT_COMMAND
+[004.356] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.357] 0x0000000d 0x00036600 0x00001000
+[004.357] STATE <-- SAHARA_WAIT_COMMAND
+[004.397] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.397] 0x0000000d 0x00037600 0x00001000
+[004.397] STATE <-- SAHARA_WAIT_COMMAND
+[004.437] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.437] 0x0000000d 0x00038600 0x00001000
+[004.437] STATE <-- SAHARA_WAIT_COMMAND
+[004.476] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.477] 0x0000000d 0x00039600 0x00001000
+[004.477] STATE <-- SAHARA_WAIT_COMMAND
+[004.516] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.516] 0x0000000d 0x0003a600 0x00001000
+[004.516] STATE <-- SAHARA_WAIT_COMMAND
+[004.557] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.557] 0x0000000d 0x0003b600 0x00001000
+[004.557] STATE <-- SAHARA_WAIT_COMMAND
+[004.597] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.597] 0x0000000d 0x0003c600 0x00001000
+[004.597] STATE <-- SAHARA_WAIT_COMMAND
+[004.636] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.637] 0x0000000d 0x0003d600 0x00001000
+[004.637] STATE <-- SAHARA_WAIT_COMMAND
+[004.676] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.676] 0x0000000d 0x0003e600 0x00001000
+[004.677] STATE <-- SAHARA_WAIT_COMMAND
+[004.717] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.717] 0x0000000d 0x0003f600 0x00001000
+[004.717] STATE <-- SAHARA_WAIT_COMMAND
+[004.756] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.757] 0x0000000d 0x00040600 0x00001000
+[004.757] STATE <-- SAHARA_WAIT_COMMAND
+[004.796] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.797] 0x0000000d 0x00041600 0x00001000
+[004.797] STATE <-- SAHARA_WAIT_COMMAND
+[004.836] RECEIVED <-- SAHARA_READ_DATA_ID
+[004.836] 0x0000000d 0x00042600 0x00001000
+[004.837] STATE <-- SAHARA_WAIT_COMMAND
+[004.880] RECEIVED <-- SAHARA_END_IMAGE_TX_ID
+[004.880] image_id = 13, status = 0
+[004.880] SENDING --> SAHARA_DONE
+[004.881] STATE <-- SAHARA_WAIT_DONE_RESP
+[004.953] RECEIVED <-- SAHARA_DONE_RESP_ID
+[004.953] image_tx_status = 1
+[004.953] Successfully uploaded all images
+[004.953] Sahara protocol completed
+[004.953] dir=../EM20GRAR01A04V01M4G_factory/update/firehose
+[004.953] d_name=rawprogram_nand_p4K_b256K_factory.xml
+[005.934]
+[005.935]
+[005.935]
+[005.935]
+[005.936]
+[005.936]
+[005.936]
+[005.936]
+[005.936]
+[005.936]
+[005.937]
+[005.937]
+[005.937]
+[005.937]
+[005.938]
+[005.938]
+[005.938]
+[005.938]
+[005.938]
+[005.939]
+[006.940] poll_wait events=POLLIN msec=1000 timeout
+[006.940] qusb_noblock_read cur=0, min_size=1
+[006.940] firehose_protocol.c fh_recv_cmd 294 fail
+[006.940]
+[006.974]
+[006.975]
+[006.976]
+[006.976]
+[007.053]
+[007.159]
+[007.159]
+[007.521]
+[007.530]
+[007.530]
+[007.601]
+[007.601]
+[007.601]
+[007.601]
+[007.601]
+[007.673]
+[007.673]
+[007.673]
+[007.722]
+[007.722]
+[007.793]
+[007.793]
+[007.793]
+[007.793]
+[007.793]
+[007.873]
+[007.873]
+[007.873]
+[007.873]
+[007.873]
+[007.953]
+[007.953]
+[007.953]
+[007.953]
+[007.953]
+[008.033]
+[008.033]
+[008.033]
+[008.033]
+[008.033]
+[008.113]
+[008.113]
+[008.113]
+[008.113]
+[008.113]
+[008.193]
+[008.193]
+[008.193]
+[008.193]
+[008.193]
+[008.273]
+[008.273]
+[008.273]
+[008.273]
+[008.273]
+[008.353]
+[008.353]
+[008.353]
+[008.353]
+[008.353]
+[008.433]
+[008.433]
+[008.433]
+[008.433]
+[008.433]
+[008.513]
+[008.513]
+[008.513]
+[008.513]
+[008.513]
+[008.593]
+[008.593]
+[008.593]
+[008.616]
+[008.616]
+[008.693]
+[008.693]
+[008.693]
+[008.717]
+[008.717]
+[008.793]
+[008.793]
+[008.793]
+[009.236]
+[009.236]
+[009.313]
+[009.313]
+[009.313]
+[009.416]
+[009.416]
+[009.493]
+[009.493]
+[009.493]
+[010.094]
+[010.094]
+[010.173]
+[010.173]
+[010.173]
+[010.715]
+[010.715]
+[010.793]
+[010.793]
+[010.793]
+[010.793] send partition_complete_p4K_b256K.mbn, filesize=16384
+[010.793] Upgrade progress: 0
+[010.793] send finished
+[010.840]
+[010.840]
+[010.913]
+[010.913]
+[010.913]
+[010.913] send ../cefs.mbn, filesize=1454080
+[011.487] send finished
+[011.573]
+[011.573]
+[011.653]
+[011.653]
+[011.653]
+[011.653] send ../tz.mbn, filesize=933888
+[011.697] Upgrade progress: 1
+[012.004] send finished
+[012.093]
+[012.093]
+[012.173]
+[012.173]
+[012.173]
+[012.173] send ../devcfg.mbn, filesize=40222
+[012.173] send finished
+[012.233]
+[012.233]
+[012.313]
+[012.313]
+[012.313]
+[012.313] send ../xbl_cfg.elf, filesize=53348
+[012.315] send finished
+[012.377]
+[012.377]
+[012.453]
+[012.453]
+[012.453]
+[012.453] send ../multi_image.mbn, filesize=13064
+[012.453] send finished
+[012.500]
+[012.501]
+[012.573]
+[012.573]
+[012.573]
+[012.573] send ../aop.mbn, filesize=142624
+[012.593] send finished
+[012.676]
+[012.676]
+[012.753]
+[012.753]
+[012.753]
+[012.753] send ../hyp.mbn, filesize=80192
+[012.759] send finished
+[012.829]
+[012.829]
+[012.901]
+[012.901]
+[012.901]
+[012.901] send ../abl.elf, filesize=151552
+[012.921] send finished
+[013.005]
+[013.005]
+[013.081]
+[013.081]
+[013.081]
+[013.081] send ../uefi.elf, filesize=1323008
+[013.233] Upgrade progress: 2
+[013.598] send finished
+[013.685]
+[013.685]
+[013.761]
+[013.761]
+[013.761]
+[013.761] send ../tools.fv, filesize=393216
+[013.874] send finished
+[013.969]
+[013.969]
+[014.041]
+[014.041]
+[014.041]
+[014.041] send ../sec.dat, filesize=80
+[014.041] send finished
+[014.084]
+[014.085]
+[014.161]
+[014.161]
+[014.161]
+[014.161] send ../sdxpoorwills-boot.img, filesize=9582592
+[014.303] Upgrade progress: 3
+[015.019] Upgrade progress: 4
+[015.731] Upgrade progress: 5
+[016.450] Upgrade progress: 6
+[017.173] Upgrade progress: 7
+[017.884] Upgrade progress: 8
+[018.196] send finished
+[018.289]
+[018.289]
+[018.361]
+[018.361]
+[018.361]
+[018.361] send ../sdxpoorwills-boot.img, filesize=9582592
+[018.722] Upgrade progress: 9
+[019.442] Upgrade progress: 10
+[020.154] Upgrade progress: 11
+[020.874] Upgrade progress: 12
+[021.594] Upgrade progress: 13
+[022.320] Upgrade progress: 14
+[022.397] send finished
+[022.488]
+[022.489]
+[022.561]
+[022.561]
+[022.561]
+[022.561] send ../NON-HLOS.ubi, filesize=60555264
+[023.140] Upgrade progress: 15
+[023.851] Upgrade progress: 16
+[024.566] Upgrade progress: 17
+[025.282] Upgrade progress: 18
+[026.022] Upgrade progress: 19
+[026.734] Upgrade progress: 20
+[027.460] Upgrade progress: 21
+[028.181] Upgrade progress: 22
+[028.903] Upgrade progress: 23
+[029.633] Upgrade progress: 24
+[030.360] Upgrade progress: 25
+[031.085] Upgrade progress: 26
+[031.812] Upgrade progress: 27
+[032.546] Upgrade progress: 28
+[033.278] Upgrade progress: 29
+[034.007] Upgrade progress: 30
+[034.738] Upgrade progress: 31
+[035.481] Upgrade progress: 32
+[036.214] Upgrade progress: 33
+[036.948] Upgrade progress: 34
+[037.683] Upgrade progress: 35
+[038.426] Upgrade progress: 36
+[039.167] Upgrade progress: 37
+[039.902] Upgrade progress: 38
+[040.649] Upgrade progress: 39
+[041.393] Upgrade progress: 40
+[042.135] Upgrade progress: 41
+[042.877] Upgrade progress: 42
+[043.629] Upgrade progress: 43
+[044.378] Upgrade progress: 44
+[045.121] Upgrade progress: 45
+[045.871] Upgrade progress: 46
+[046.627] Upgrade progress: 47
+[047.369] Upgrade progress: 48
+[048.127] Upgrade progress: 49
+[048.881] Upgrade progress: 50
+[048.917] send finished
+[049.013]
+[049.013]
+[049.093]
+[049.093]
+[049.093]
+[049.093] send ../sdxpoorwills-recoveryfs.ubi, filesize=17301504
+[049.711] Upgrade progress: 51
+[050.425] Upgrade progress: 52
+[051.143] Upgrade progress: 53
+[051.859] Upgrade progress: 54
+[052.577] Upgrade progress: 55
+[053.303] Upgrade progress: 56
+[054.029] Upgrade progress: 57
+[054.743] Upgrade progress: 58
+[055.472] Upgrade progress: 59
+[056.197] Upgrade progress: 60
+[056.421] send finished
+[056.517]
+[056.517]
+[056.593]
+[056.593]
+[056.593]
+[056.593] send ../usrdata.ubi, filesize=2097152
+[057.025] Upgrade progress: 61
+[057.422] send finished
+[057.517]
+[057.517]
+[057.593]
+[057.593]
+[057.593]
+[057.593] send ../sdxpoorwills-sysfs.ubi, filesize=64225280
+[057.854] Upgrade progress: 62
+[058.563] Upgrade progress: 63
+[059.274] Upgrade progress: 64
+[059.990] Upgrade progress: 65
+[060.714] Upgrade progress: 66
+[061.435] Upgrade progress: 67
+[062.150] Upgrade progress: 68
+[062.874] Upgrade progress: 69
+[063.602] Upgrade progress: 70
+[064.318] Upgrade progress: 71
+[065.048] Upgrade progress: 72
+[065.777] Upgrade progress: 73
+[066.498] Upgrade progress: 74
+[067.232] Upgrade progress: 75
+[067.964] Upgrade progress: 76
+[068.693] Upgrade progress: 77
+[069.423] Upgrade progress: 78
+[070.161] Upgrade progress: 79
+[070.898] Upgrade progress: 80
+[071.644] Upgrade progress: 81
+[072.378] Upgrade progress: 82
+[073.122] Upgrade progress: 83
+[073.865] Upgrade progress: 84
+[074.597] Upgrade progress: 85
+[075.343] Upgrade progress: 86
+[076.087] Upgrade progress: 87
+[076.828] Upgrade progress: 88
+[077.581] Upgrade progress: 89
+[078.333] Upgrade progress: 90
+[079.080] Upgrade progress: 91
+[079.823] Upgrade progress: 92
+[080.588] Upgrade progress: 93
+[081.340] Upgrade progress: 94
+[082.088] Upgrade progress: 95
+[082.838] Upgrade progress: 96
+[083.597] Upgrade progress: 97
+[084.353] Upgrade progress: 98
+[085.107] Upgrade progress: 99
+[085.613] send finished
+[085.713]
+[085.713]
+[085.793]
+[085.793]
+[085.793]
+[085.793] send ../sbl1.mbn, filesize=556180
+[085.965] Upgrade progress: 100
+[085.976] send finished
+[086.069]
+[086.069]
+[086.106]
+[086.107]
+[086.107]
+[086.107]
+[086.107] qtcp_read read=0, errno: 0 (Success)
+[086.107] qusb_noblock_read read=0, errno: 0 (Success)
+[086.107] qusb_noblock_read cur=0, min_size=1
+[086.107] firehose_protocol.c fh_recv_cmd 294 fail
+[086.107] THE TOTAL DOWNLOAD TIME IS 84.093 s
+[086.107] Upgrade module successfully.
diff --git a/application/qfirehose/src/log/pcie_mhi.log.txt b/application/qfirehose/src/log/pcie_mhi.log.txt
new file mode 100644
index 0000000..dd6a02a
--- /dev/null
+++ b/application/qfirehose/src/log/pcie_mhi.log.txt
@@ -0,0 +1,60 @@
+# ./QFirehose -p /dev/mhi_BHI -f v09
+
+[000.000]: QFirehose Version: Quectel_LTE&5G_QFirehose_Linux&Android_V1.4.5
+[000.000]: Builded: Feb 10 2021 14:14:00
+[000.000]: Find md5 check file
+[000.000]: Totals checking 0 files md5 value, 0 file fail!
+[000.024]: switch_to_edl_mode
+[001.027]: poll_wait events=POLLIN msec=1000 timeout
+[001.027]: qusb_noblock_read cur=0, min_size=1
+[001.027]: switch to 'Emergency download mode'
+[001.039]: successful, wait module reboot
+[002.040]: bhi_ee = 6
+[004.105]: dir=v09/update/firehose
+[004.105]: d_name=rawprogram_nand_p4K_b256K_factory.xml
+[004.108]:
+......
+[004.168]:
+[005.170]: poll_wait events=POLLIN msec=1000 timeout
+[005.170]: qusb_noblock_read cur=0, min_size=1
+[005.170]: firehose_protocol.c fh_recv_cmd 327 fail
+[005.170]:
+[005.178]:
+[005.181]:
+[005.186]:
+[005.186]:
+[005.193]:
+[005.365]:
+[005.368]:
+[005.761]:
+[005.777]:
+......
+[011.965]:
+[011.972]:
+[011.976]:
+[011.979]:
+[011.979]: send partition_complete_p4K_b256K.mbn, filesize=16384
+.
+[011.979]: upgrade progress 0% 16384/144903062
+[011.979]: send finished
+[011.997]:
+......
+[146.314]:
+[146.321]:
+[146.325]:
+[146.328]:
+[146.328]: send ../sbl1.mbn, filesize=566196
+.
+[146.616]: upgrade progress 100% 145469258/144903062
+[146.616]: send finished
+[146.834]:
+[146.834]:
+[146.840]:
+[146.843]:
+[146.847]:
+[146.850]:
+[147.852]: poll_wait events=POLLIN msec=1000 timeout
+[147.852]: qusb_noblock_read cur=0, min_size=1
+[147.852]: firehose_protocol.c fh_recv_cmd 327 fail
+[147.852]: THE TOTAL DOWNLOAD TIME IS 143.747 s
+[147.852]: Upgrade module successfully.
diff --git a/application/qfirehose/src/md5.c b/application/qfirehose/src/md5.c
new file mode 100644
index 0000000..a04a1b3
--- /dev/null
+++ b/application/qfirehose/src/md5.c
@@ -0,0 +1,477 @@
+/*
+ Copyright 2023 Quectel Wireless Solutions Co.,Ltd
+
+ Quectel hereby grants customers of Quectel a license to use, modify,
+ distribute and publish the Software in binary form provided that
+ customers shall have no right to reverse engineer, reverse assemble,
+ decompile or reduce to source code form any portion of the Software.
+ Under no circumstances may customers modify, demonstrate, use, deliver
+ or disclose any portion of the Software in source code form.
+*/
+
+#include "md5.h"
+#include //for __BYTE_ORDER
+
+#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
+#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y))))
+#define H(x, y, z) (((x) ^ (y)) ^ (z))
+#define H2(x, y, z) ((x) ^ ((y) ^ (z)))
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+
+#define STEP(f, a, b, c, d, x, t, s) \
+ (a) += f((b), (c), (d)) + (x) + (t); \
+ (a) = (((a) << (s)) | (((a)&0xffffffff) >> (32 - (s)))); \
+ (a) += (b);
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define SET(n) (*(uint32_t *)&ptr[(n)*4])
+#define GET(n) SET(n)
+#else
+#define SET(n) \
+ (block[(n)] = (uint32_t)ptr[(n)*4] | ((uint32_t)ptr[(n)*4 + 1] << 8) | \
+ ((uint32_t)ptr[(n)*4 + 2] << 16) | ((uint32_t)ptr[(n)*4 + 3] << 24))
+#define GET(n) (block[(n)])
+#endif
+
+extern char firehose_unzip_full_dir[256];
+extern char firehose_zip_name[80];
+
+static const void *body(md5_ctx_t *ctx, const void *data, unsigned long size)
+{
+ const unsigned char *ptr;
+ uint32_t a, b, c, d;
+ uint32_t saved_a, saved_b, saved_c, saved_d;
+#if __BYTE_ORDER != __LITTLE_ENDIAN
+ uint32_t block[16];
+#endif
+
+ ptr = (const unsigned char *)data;
+
+ a = ctx->a;
+ b = ctx->b;
+ c = ctx->c;
+ d = ctx->d;
+
+ do
+ {
+ saved_a = a;
+ saved_b = b;
+ saved_c = c;
+ saved_d = d;
+
+ /* Round 1 */
+ STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7)
+ STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12)
+ STEP(F, c, d, a, b, SET(2), 0x242070db, 17)
+ STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22)
+ STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7)
+ STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12)
+ STEP(F, c, d, a, b, SET(6), 0xa8304613, 17)
+ STEP(F, b, c, d, a, SET(7), 0xfd469501, 22)
+ STEP(F, a, b, c, d, SET(8), 0x698098d8, 7)
+ STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12)
+ STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17)
+ STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22)
+ STEP(F, a, b, c, d, SET(12), 0x6b901122, 7)
+ STEP(F, d, a, b, c, SET(13), 0xfd987193, 12)
+ STEP(F, c, d, a, b, SET(14), 0xa679438e, 17)
+ STEP(F, b, c, d, a, SET(15), 0x49b40821, 22)
+
+ /* Round 2 */
+ STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5)
+ STEP(G, d, a, b, c, GET(6), 0xc040b340, 9)
+ STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14)
+ STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20)
+ STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5)
+ STEP(G, d, a, b, c, GET(10), 0x02441453, 9)
+ STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14)
+ STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20)
+ STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5)
+ STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9)
+ STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14)
+ STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20)
+ STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5)
+ STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9)
+ STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14)
+ STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20)
+
+ /* Round 3 */
+ STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4)
+ STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11)
+ STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16)
+ STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23)
+ STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4)
+ STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11)
+ STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16)
+ STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23)
+ STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4)
+ STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11)
+ STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16)
+ STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23)
+ STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4)
+ STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11)
+ STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16)
+ STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23)
+
+ /* Round 4 */
+ STEP(I, a, b, c, d, GET(0), 0xf4292244, 6)
+ STEP(I, d, a, b, c, GET(7), 0x432aff97, 10)
+ STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15)
+ STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21)
+ STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6)
+ STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10)
+ STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15)
+ STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21)
+ STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6)
+ STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10)
+ STEP(I, c, d, a, b, GET(6), 0xa3014314, 15)
+ STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21)
+ STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6)
+ STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10)
+ STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15)
+ STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21)
+
+ a += saved_a;
+ b += saved_b;
+ c += saved_c;
+ d += saved_d;
+
+ ptr += 64;
+ } while (size -= 64);
+
+ ctx->a = a;
+ ctx->b = b;
+ ctx->c = c;
+ ctx->d = d;
+
+ return ptr;
+}
+
+void md5_begin(md5_ctx_t *ctx)
+{
+ ctx->a = 0x67452301;
+ ctx->b = 0xefcdab89;
+ ctx->c = 0x98badcfe;
+ ctx->d = 0x10325476;
+
+ ctx->lo = 0;
+ ctx->hi = 0;
+}
+
+void md5_hash(const void *data, size_t size, md5_ctx_t *ctx)
+{
+ uint32_t saved_lo;
+ unsigned long used, available;
+
+ saved_lo = ctx->lo;
+ if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) ctx->hi++;
+ ctx->hi += size >> 29;
+
+ used = saved_lo & 0x3f;
+
+ if (used)
+ {
+ available = 64 - used;
+
+ if (size < available)
+ {
+ memcpy(&ctx->buffer[used], data, size);
+ return;
+ }
+
+ memcpy(&ctx->buffer[used], data, available);
+ data = (const unsigned char *)data + available;
+ size -= available;
+ body(ctx, ctx->buffer, 64);
+ }
+
+ if (size >= 64)
+ {
+ data = body(ctx, data, size & ~((size_t)0x3f));
+ size &= 0x3f;
+ }
+
+ memcpy(ctx->buffer, data, size);
+}
+
+void md5_end(void *resbuf, md5_ctx_t *ctx)
+{
+ unsigned char *result = (unsigned char *)resbuf;
+ unsigned long used, available;
+
+ used = ctx->lo & 0x3f;
+
+ ctx->buffer[used++] = 0x80;
+
+ available = 64 - used;
+
+ if (available < 8)
+ {
+ memset(&ctx->buffer[used], 0, available);
+ body(ctx, ctx->buffer, 64);
+ used = 0;
+ available = 64;
+ }
+
+ if (used >= 64) return (void)0;
+
+ memset(&ctx->buffer[used], 0, available - 8);
+
+ ctx->lo <<= 3;
+ ctx->buffer[56] = ctx->lo;
+ ctx->buffer[57] = ctx->lo >> 8;
+ ctx->buffer[58] = ctx->lo >> 16;
+ ctx->buffer[59] = ctx->lo >> 24;
+ ctx->buffer[60] = ctx->hi;
+ ctx->buffer[61] = ctx->hi >> 8;
+ ctx->buffer[62] = ctx->hi >> 16;
+ ctx->buffer[63] = ctx->hi >> 24;
+
+ body(ctx, ctx->buffer, 64);
+
+ result[0] = ctx->a;
+ result[1] = ctx->a >> 8;
+ result[2] = ctx->a >> 16;
+ result[3] = ctx->a >> 24;
+ result[4] = ctx->b;
+ result[5] = ctx->b >> 8;
+ result[6] = ctx->b >> 16;
+ result[7] = ctx->b >> 24;
+ result[8] = ctx->c;
+ result[9] = ctx->c >> 8;
+ result[10] = ctx->c >> 16;
+ result[11] = ctx->c >> 24;
+ result[12] = ctx->d;
+ result[13] = ctx->d >> 8;
+ result[14] = ctx->d >> 16;
+ result[15] = ctx->d >> 24;
+
+ memset(ctx, 0, sizeof(*ctx));
+}
+
+int md5sum(char *file, void *md5_buf)
+{
+ char buf[256];
+ md5_ctx_t ctx;
+ int ret = 0;
+ FILE *f;
+
+ f = fopen(file, "r");
+ if (!f) return -1;
+
+ md5_begin(&ctx);
+ do
+ {
+ int len = fread(buf, 1, sizeof(buf), f);
+ if (!len) break;
+
+ md5_hash(buf, len, &ctx);
+ ret += len;
+ } while (1);
+
+ md5_end(md5_buf, &ctx);
+ fclose(f);
+
+ return ret;
+}
+
+int md5_check(const char *firehose_dir)
+{
+ FILE *fp = NULL;
+ char md5_file_path[256], buff[256], file_name[128], file_name_tmp[128], file_full_path[256],
+ file_md5_value[64];
+ char *ps = NULL, *pe = NULL;
+ unsigned char compute_md5_buff[16];
+ char convert_md5_buff[33];
+ int i, file_count = 0, fail_count = 0;
+
+ if (is_upgrade_fimeware_zip_7z)
+ {
+ memset(zip_cmd_buf, 0, sizeof(zip_cmd_buf));
+
+ if (is_upgrade_fimeware_only_zip)
+ {
+ if (is_firehose_zip_7z_name_exit)
+ {
+ snprintf(zip_cmd_buf, sizeof(zip_cmd_buf),
+ "unzip -o -q %.240s %.76s/'*md5.txt' -d /tmp/ > %s", firehose_dir,
+ firehose_zip_name, ZIP_PROCESS_INFO);
+ }
+ else
+ {
+ snprintf(zip_cmd_buf, sizeof(zip_cmd_buf),
+ "unzip -o -q %.240s '*md5.txt' -d /tmp/ > %s", firehose_dir,
+ ZIP_PROCESS_INFO);
+ }
+ }
+ else
+ {
+ if (is_firehose_zip_7z_name_exit)
+ {
+ snprintf(zip_cmd_buf, sizeof(zip_cmd_buf), "7z x %.240s -o/tmp/ %.76s/md5.txt > %s",
+ firehose_dir, firehose_zip_name, ZIP_PROCESS_INFO);
+ }
+ else
+ {
+ snprintf(zip_cmd_buf, sizeof(zip_cmd_buf), "7z x %.240s -o/tmp/ md5.txt > %s",
+ firehose_dir, ZIP_PROCESS_INFO);
+ }
+ }
+ printf("%s zip_cmd_buf:%s\n", __func__, zip_cmd_buf);
+
+ if (-1 == system(zip_cmd_buf))
+ {
+ printf("%s system return error\n", __func__);
+ return -1;
+ }
+ usleep(1000);
+ snprintf(md5_file_path, sizeof(md5_file_path), "%.240s/md5.txt", firehose_unzip_full_dir);
+ if (access(md5_file_path, R_OK))
+ {
+ dbg_time("Cann't find md5.txt in %s, Please check it!\n", md5_file_path);
+ return -1;
+ }
+ else
+ {
+ dbg_time("Find md5 check file <%s>\n", md5_file_path);
+ }
+ }
+ else
+ {
+ snprintf(md5_file_path, sizeof(md5_file_path), "%.240s/md5.txt", firehose_dir);
+ if (access(md5_file_path, R_OK))
+ {
+ dbg_time("Cann't find md5.txt in %s, Please check it!\n", firehose_dir);
+ return 0; // allow skip md5 check by delete md5.txt
+ }
+ else
+ {
+ dbg_time("Find md5 check file <%s>\n", md5_file_path);
+ }
+ }
+
+ fp = fopen(md5_file_path, "rb");
+ if (fp == NULL)
+ {
+ dbg_time("fail to fopen(%s), error: %d (%s)\n", md5_file_path, errno, strerror(errno));
+ return -1;
+ }
+
+ while (fgets(buff, sizeof(buff), fp))
+ {
+ if (strstr(buff, "targetfiles.zip")) continue;
+
+ ps = strstr(buff, ":\\");
+ if (ps == NULL) continue;
+
+ file_count++;
+ ps = ps + 1;
+ pe = strstr(ps, ":");
+ if (pe == NULL) continue;
+
+ memcpy(file_name, ps, pe - ps);
+ file_name[pe - ps] = '\0';
+
+ for (i = 0; file_name[i] != '\0'; i++)
+ {
+ if (file_name[i] == '\\') file_name[i] = '/';
+ }
+
+ memcpy(file_md5_value, pe + 1, 32);
+ file_md5_value[32] = '\0';
+
+ if (is_upgrade_fimeware_zip_7z)
+ {
+ char *p1 = strchr(file_name, '/');
+ memset(file_name_tmp, 0, sizeof(file_name_tmp));
+ strncpy(file_name_tmp, p1 + 1, strlen(p1) - 1);
+ memset(zip_cmd_buf, 0, sizeof(zip_cmd_buf));
+
+ if (is_upgrade_fimeware_only_zip)
+ {
+ if (is_firehose_zip_7z_name_exit)
+ {
+ snprintf(zip_cmd_buf, sizeof(zip_cmd_buf),
+ "unzip -o -q %.240s %.76s/'*%.120s' -d /tmp/ > %s", firehose_dir,
+ firehose_zip_name, file_name_tmp, ZIP_PROCESS_INFO);
+ }
+ else
+ {
+ snprintf(zip_cmd_buf, sizeof(zip_cmd_buf),
+ "unzip -o -q %.240s '*%.120s' -d /tmp/ > %s", firehose_dir,
+ file_name_tmp, ZIP_PROCESS_INFO);
+ }
+ }
+ else
+ {
+ if (is_firehose_zip_7z_name_exit)
+ {
+ snprintf(zip_cmd_buf, sizeof(zip_cmd_buf),
+ "7z x %.240s -o/tmp/ %.76s/%.120s > %s", firehose_dir,
+ firehose_zip_name, file_name_tmp, ZIP_PROCESS_INFO);
+ }
+ else
+ {
+ snprintf(zip_cmd_buf, sizeof(zip_cmd_buf), "7z x %.240s -o/tmp/ %.120s > %s",
+ firehose_dir, file_name_tmp, ZIP_PROCESS_INFO);
+ }
+ }
+ dbg_time("%s zip_cmd_buf:%s\n", __func__, zip_cmd_buf);
+
+ if (-1 == system(zip_cmd_buf))
+ {
+ dbg_time("%s system return error\n", __func__);
+ return -1;
+ }
+ usleep(1000);
+
+ snprintf(file_full_path, sizeof(file_full_path), "%.160s%.80s", firehose_unzip_full_dir,
+ file_name);
+ dbg_time("%s file_full_path:%s\n", __func__, file_full_path);
+ }
+ else
+ {
+ snprintf(file_full_path, sizeof(file_full_path), "%.160s%.80s", firehose_dir,
+ file_name);
+ }
+
+ if (access(file_full_path, R_OK))
+ {
+ continue;
+ }
+
+ md5sum(file_full_path, compute_md5_buff);
+
+ for (i = 0; i < 16; i++)
+ {
+ sprintf(convert_md5_buff + (i * 2), "%02X", compute_md5_buff[i]);
+ }
+
+ if (strncasecmp(file_md5_value, convert_md5_buff, 16))
+ {
+ fail_count++;
+ dbg_time("md5 checking: %s fail\n", file_full_path);
+ dbg_time("find %s, should be %s\n", file_md5_value, convert_md5_buff);
+ }
+ else
+ {
+ dbg_time("md5 checking: %s pass\n", file_full_path);
+ }
+
+ if (is_upgrade_fimeware_zip_7z)
+ {
+ dbg_time("%s delet %s ...\n", __func__, file_full_path);
+ unlink(file_full_path); // delete all file
+ }
+ }
+
+ fclose(fp);
+ if (is_upgrade_fimeware_zip_7z)
+ {
+ unlink(md5_file_path); // delete md5.txt
+ }
+
+ dbg_time("Totals checking %d files md5 value, %d file fail!\n", file_count, fail_count);
+
+ return (fail_count ? -1 : 0);
+}
diff --git a/application/qfirehose/src/md5.h b/application/qfirehose/src/md5.h
new file mode 100644
index 0000000..20d6eb0
--- /dev/null
+++ b/application/qfirehose/src/md5.h
@@ -0,0 +1,33 @@
+/*
+ Copyright 2023 Quectel Wireless Solutions Co.,Ltd
+
+ Quectel hereby grants customers of Quectel a license to use, modify,
+ distribute and publish the Software in binary form provided that
+ customers shall have no right to reverse engineer, reverse assemble,
+ decompile or reduce to source code form any portion of the Software.
+ Under no circumstances may customers modify, demonstrate, use, deliver
+ or disclose any portion of the Software in source code form.
+*/
+
+#ifndef _QUECTEL_MD5_H
+#define _QUECTEL_MD5_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include "usb_linux.h"
+
+typedef struct md5_ctx
+{
+ uint32_t lo, hi;
+ uint32_t a, b, c, d;
+ unsigned char buffer[64];
+} md5_ctx_t;
+
+// void dbg_time (const char *fmt, ...);
+extern int md5_check(const char *);
+
+#endif /* _QUECTEL_MD5_H */
diff --git a/application/qfirehose/src/qfirehose.c b/application/qfirehose/src/qfirehose.c
new file mode 100644
index 0000000..f86bf1e
--- /dev/null
+++ b/application/qfirehose/src/qfirehose.c
@@ -0,0 +1,1115 @@
+/*
+ Copyright 2023 Quectel Wireless Solutions Co.,Ltd
+
+ Quectel hereby grants customers of Quectel a license to use, modify,
+ distribute and publish the Software in binary form provided that
+ customers shall have no right to reverse engineer, reverse assemble,
+ decompile or reduce to source code form any portion of the Software.
+ Under no circumstances may customers modify, demonstrate, use, deliver
+ or disclose any portion of the Software in source code form.
+*/
+
+#include
+#include
+#include
+#include
+#ifdef USE_IPC_MSG
+#include
+#include
+#endif
+
+#include "usb_linux.h"
+#include "md5.h"
+
+/*
+[PATCH 3.10 27/54] usb: xhci: Add support for URB_ZERO_PACKET to bulk/sg transfers
+https://www.spinics.net/lists/kernel/msg2100618.html
+
+commit 4758dcd19a7d9ba9610b38fecb93f65f56f86346
+Author: Reyad Attiyat
+Date: Thu Aug 6 19:23:58 2015 +0300
+
+ usb: xhci: Add support for URB_ZERO_PACKET to bulk/sg transfers
+
+ This commit checks for the URB_ZERO_PACKET flag and creates an extra
+ zero-length td if the urb transfer length is a multiple of the endpoint's
+ max packet length.
+*/
+unsigned qusb_zlp_mode = 1; // MT7621 donot support USB ZERO PACKET
+unsigned q_erase_all_before_download = 0;
+unsigned q_module_packet_sign = 0;
+unsigned int g_from_ecm_to_rndis = 0;
+const char *q_device_type = "nand"; // nand/emmc/ufs
+int sahara_main(const char *firehose_dir, const char *firehose_mbn, void *usb_handle, int edl_mode_05c69008);
+int firehose_main(const char *firehose_dir, void *usb_handle, unsigned qusb_zlp_mode);
+int stream_download(const char *firehose_dir, void *usb_handle, unsigned qusb_zlp_mode);
+int retrieve_soft_revision(void *usb_handle, uint8_t *mobile_software_revision, unsigned length);
+int usb2tcp_main(const void *usb_handle, int tcp_port, unsigned qusb_zlp_mode);
+int ql_capture_usbmon_log(const char *usbmon_logfile);
+void ql_stop_usbmon_log();
+
+// process vals
+static long long all_bytes_to_transfer = 0; // need transfered
+static long long transfer_bytes = 0; // transfered bytes;
+
+char zip_cmd_buf[512] = {0}; // zip cmd buf
+char firehose_zip_name[80] = {0};
+char firehose_unzip_full_dir[256] = {0};
+file_name_backup file_name_b;
+int is_upgrade_fimeware_zip_7z = 0;
+int is_firehose_zip_7z_name_exit = 0;
+int is_upgrade_fimeware_only_zip = 0;
+int g_is_module_adb_entry_edl = 0;
+
+int g_is2mdn_path = 0;
+
+int switch_to_edl_mode(void *usb_handle)
+{
+ // DIAG commands used to switch the Qualcomm devices to EDL (Emergency download mode)
+ unsigned char edl_cmd[] = {0x4b, 0x65, 0x01, 0x00, 0x54, 0x0f, 0x7e};
+ // unsigned char edl_cmd[] = {0x3a, 0xa1, 0x6e, 0x7e}; //DL (download mode)
+ unsigned char *pbuf = malloc(512);
+ if (pbuf == NULL)
+ {
+ return 0;
+ }
+
+ int rx_len;
+ int rx_count = 0;
+
+ do
+ {
+ rx_len = qusb_noblock_read(usb_handle, pbuf, 512, 0, 1000);
+ if (rx_count++ > 100) break;
+ } while (rx_len > 0);
+
+ dbg_time("switch to 'Emergency download mode'\n");
+ rx_len = qusb_noblock_write(usb_handle, edl_cmd, sizeof(edl_cmd), sizeof(edl_cmd), 3000, 0);
+ if (rx_len < 0) return 0;
+
+ rx_count = 0;
+
+ do
+ {
+ rx_len = qusb_noblock_read(usb_handle, pbuf, 512, 0, 3000);
+ if (rx_len == sizeof(edl_cmd) && memcmp(pbuf, edl_cmd, sizeof(edl_cmd)) == 0)
+ {
+ dbg_time("successful, wait module reboot\n");
+ safe_free(pbuf);
+ return 1;
+ }
+
+ if (rx_count++ > 50) break;
+
+ } while (rx_len > 0);
+
+ safe_free(pbuf);
+ return 0;
+}
+
+int switch_to_edl_mode_in_adb_way()
+{
+ printf("entry switch_to_edl_mode_in_adb_way \r\n");
+ int res = -1;
+ res = system("adb shell lxc-power1 adb host");
+ // if (res == 127)
+ // {
+ // printf("call /bin/sh return error \r\n");
+ // return res;
+ // }
+ // else if (res == -1)
+ // {
+ // printf("just return: error \r\n");
+ // return res;
+ // }
+ // else if (res == 0)
+ // {
+ // printf("no child pid create: error \r\n");
+ // return res;
+ // }
+
+ printf("send lxc power1 success res=[%d] \r\n", res);
+ sleep(20);
+ res = system("adb reboot edl");
+ // if (res == 127)
+ // {
+ // printf("call /bin/sh return error \r\n");
+ // return res;
+ // }
+ // else if (res == -1)
+ // {
+ // printf("just return: error \r\n");
+ // return res;
+ // }
+ // else if (res == 0)
+ // {
+ // printf("no child pid create: error \r\n");
+ // return res;
+ // }
+
+ printf("send reboot edl success res=[%d] \r\n", res);
+ return 0;
+}
+
+static void usage(int status, const char *program_name)
+{
+ if (status != EXIT_SUCCESS)
+ {
+ printf("Try '%s --help' for more information.\n", program_name);
+ }
+ else
+ {
+ dbg_time("Upgrade Quectel's modules with Qualcomm's firehose protocol.\n");
+ dbg_time("Usage: %s [options...]\n", program_name);
+ dbg_time(" -f [package_dir] Upgrade package directory path\n");
+ dbg_time(" -p [/dev/ttyUSBx] Diagnose port, will auto-detect if not "
+ "specified\n");
+ dbg_time(" -s [/sys/bus/usb/devices/xx] When multiple modules exist on the board, use "
+ "-s specify which module you want to upgrade\n");
+ dbg_time(" -l [dir_name] Sync log into a file(will create "
+ "qfirehose_timestamp.log)\n");
+ dbg_time(" -u [usbmon_log] Catch usbmon log and save to file (need "
+ "debugfs and usbmon driver)\n");
+ dbg_time(" -n Skip MD5 check\n");
+ dbg_time(" -d Device Type, default nand, support emmc/ufs\n");
+ dbg_time(" -v For AG215S-GLR signed firmware packages\n");
+ }
+ exit(status);
+}
+
+/*
+1. enum dir, fix up dirhose_dir
+2. md5 examine
+3. furture
+*/
+static char *find_firehose_mbn(char **firehose_dir, size_t size)
+{
+ char *firehose_mbn = NULL;
+
+ if (is_upgrade_fimeware_zip_7z)
+ {
+ int i;
+ char file_name_prog[128] = {0};
+ char file_name_prog_dir[256] = {0};
+
+ firehose_mbn = (char *)malloc(256);
+ if (firehose_mbn == NULL)
+ {
+ return NULL;
+ }
+
+ for (i = 0; i < file_name_b.file_name_count; i++)
+ {
+ if ((strstr(file_name_b.file_backup_c[i].zip_file_name_backup, "prog_nand_firehose_") && strstr(file_name_b.file_backup_c[i].zip_file_name_backup, ".mbn")) ||
+ (strstr(file_name_b.file_backup_c[i].zip_file_name_backup, "prog_emmc_firehose_") && strstr(file_name_b.file_backup_c[i].zip_file_name_backup, ".mbn")) ||
+ (strstr(file_name_b.file_backup_c[i].zip_file_name_backup, "prog_firehose_") && strstr(file_name_b.file_backup_c[i].zip_file_name_backup, ".mbn")) ||
+ (strstr(file_name_b.file_backup_c[i].zip_file_name_backup, "prog_firehose_") && strstr(file_name_b.file_backup_c[i].zip_file_name_backup, ".elf")) ||
+ (strstr(file_name_b.file_backup_c[i].zip_file_name_backup, "firehose-prog") && strstr(file_name_b.file_backup_c[i].zip_file_name_backup, ".mbn")) ||
+ (strstr(file_name_b.file_backup_c[i].zip_file_name_backup, "prog_") && strstr(file_name_b.file_backup_c[i].zip_file_name_backup, ".mbn")) ||
+ (strstr(file_name_b.file_backup_c[i].zip_file_name_backup, "xbl_s_devprg_Qcm8550_ns") && strstr(file_name_b.file_backup_c[i].zip_file_name_backup, ".melf")) ||
+ (strstr(file_name_b.file_backup_c[i].zip_file_name_backup, "xbl_s_devprg_ns_SA52X") && strstr(file_name_b.file_backup_c[i].zip_file_name_backup, ".melf")))
+ {
+ printf("file_name_b.file_backup_c[i].zip_file_name_backup:%s\n", file_name_b.file_backup_c[i].zip_file_name_backup);
+ printf("file_name_b.file_backup_c[i].zip_file_dir_backup:%s\n", file_name_b.file_backup_c[i].zip_file_dir_backup);
+
+ if (strstr(file_name_b.file_backup_c[i].zip_file_dir_backup, "update/firehose"))
+ {
+ memmove(file_name_prog, file_name_b.file_backup_c[i].zip_file_name_backup, strlen(file_name_b.file_backup_c[i].zip_file_name_backup));
+ memmove(file_name_prog_dir, file_name_b.file_backup_c[i].zip_file_dir_backup, strlen(file_name_b.file_backup_c[i].zip_file_dir_backup));
+ break;
+ }
+ }
+ }
+
+ if (file_name_prog[0] != '\0')
+ {
+ memset(zip_cmd_buf, 0, sizeof(zip_cmd_buf));
+ if (is_upgrade_fimeware_only_zip)
+ {
+ snprintf(zip_cmd_buf, sizeof(zip_cmd_buf), "unzip -o -q %.240s '*%.200s' -d /tmp/ > %s", *firehose_dir, file_name_prog_dir, ZIP_PROCESS_INFO);
+ }
+ else
+ {
+ snprintf(zip_cmd_buf, sizeof(zip_cmd_buf), "7z x %.240s -o/tmp/ %.200s > %s", *firehose_dir, file_name_prog_dir, ZIP_PROCESS_INFO);
+ }
+ printf("%s zip_cmd_buf:%s\n", __func__, zip_cmd_buf);
+ if (-1 == system(zip_cmd_buf))
+ {
+ printf("%s system return error\n", __func__);
+ return NULL;
+ }
+ usleep(1000);
+
+ memmove(firehose_mbn, file_name_prog_dir, 240);
+ }
+ }
+ else
+ {
+ if (strstr(*firehose_dir, "/update/firehose") == NULL)
+ {
+ size_t len = strlen(*firehose_dir);
+
+ strncat(*firehose_dir, "/update/firehose", size);
+ if (access(*firehose_dir, R_OK))
+ {
+ (*firehose_dir)[len] = '\0'; // for smart module
+ }
+ }
+
+ if (access(*firehose_dir, R_OK))
+ {
+ dbg_time("%s access(%s fail), errno: %d (%s)\n", __func__, *firehose_dir, errno, strerror(errno));
+ return NULL;
+ }
+
+ if (!qfile_find_file(*firehose_dir, "prog_nand_firehose_", ".mbn", &firehose_mbn) && !qfile_find_file(*firehose_dir, "prog_emmc_firehose_", ".mbn", &firehose_mbn) &&
+ !qfile_find_file(*firehose_dir, "prog_firehose_", ".mbn", &firehose_mbn) && !qfile_find_file(*firehose_dir, "prog_firehose_", ".elf", &firehose_mbn) &&
+ !qfile_find_file(*firehose_dir, "firehose-prog", ".mbn", &firehose_mbn) && !qfile_find_file(*firehose_dir, "prog_", ".mbn", &firehose_mbn) &&
+ !qfile_find_file(*firehose_dir, "xbl_s_devprg_Qcm8550_ns", ".melf",
+ &firehose_mbn) // smart SA885GAPNA
+ && !qfile_find_file(*firehose_dir, "xbl_s_devprg_ns_SA52X", ".melf",
+ &firehose_mbn) // AG590ECNABR01A01M8G_OCPU_01.001.01
+ )
+ {
+ dbg_time("%s fail to find firehose mbn file in %s\n", __func__, *firehose_dir);
+ safe_free(firehose_mbn);
+ return NULL;
+ }
+ }
+
+ dbg_time("%s %s\n", __func__, firehose_mbn);
+ return firehose_mbn;
+}
+
+#if 0
+static int detect_and_judge_module_version(void *usb_handle) {
+ static uint8_t version[64] = {'\0'};
+
+ if (usb_handle && version[0] == '\0') {
+ retrieve_soft_revision(usb_handle, version, sizeof(version));
+ if (version[0]) {
+ size_t i = 0;
+ size_t length = strlen((const char *)version) - strlen("R00A00");
+ dbg_time("old software version: %s\n", version);
+ for (i = 0; i < length; i++) {
+ if (version[i] == 'R' && isdigit(version[i+1]) && isdigit(version[i+2])
+ && version[i+3] == 'A' && isdigit(version[i+4]) && isdigit(version[i+5]))
+ {
+ version[i] = '\0';
+ //dbg_time("old hardware version: %s\n", mobile_software_revision);
+ break;
+ }
+ }
+ }
+ }
+
+ if (version[0])
+ return 0;
+
+ error_return();
+}
+#endif
+
+FILE *loghandler = NULL;
+#ifdef FIREHOSE_ENABLE
+int firehose_main_entry(int argc, char *argv[])
+#else
+int main(int argc, char *argv[])
+#endif
+{
+ int opt;
+ int check_hash = 1;
+ int retval;
+ void *usb_handle = NULL;
+ int idVendor = 0, idProduct = 0, interfaceNum = 0;
+ int edl_retry = 30; // SDX55 require long time by now 20190412
+ double start;
+
+ // char *firehose_mbn = NULL;
+ int usb3_speed;
+ struct timespec usb3_atime;
+ int usb2tcp_port = 0;
+ char filename[128] = {'\0'};
+ char *usbmon_logfile = NULL;
+
+ char *file_message = malloc(MAX_PATH);
+ if (file_message == NULL)
+ {
+ return -1;
+ }
+
+ char *firehose_mbn = malloc(MAX_PATH);
+ if (firehose_mbn == NULL)
+ {
+ safe_free(file_message);
+ return -1;
+ }
+
+ char *firehose_dir = malloc(MAX_PATH);
+ if (firehose_dir == NULL)
+ {
+ safe_free(file_message);
+ safe_free(firehose_mbn);
+ return -1;
+ }
+
+ char *module_port_name = malloc(MAX_PATH);
+ if (module_port_name == NULL)
+ {
+ safe_free(file_message);
+ safe_free(firehose_dir);
+ safe_free(file_message);
+ safe_free(firehose_mbn);
+ return -1;
+ }
+
+ char *module_sys_path = malloc(MAX_PATH);
+ if (module_sys_path == NULL)
+ {
+ safe_free(file_message);
+ safe_free(module_port_name);
+ safe_free(firehose_dir);
+ safe_free(file_message);
+ safe_free(firehose_mbn);
+ return -1;
+ }
+
+ memset(firehose_dir, 0, MAX_PATH);
+ memset(module_port_name, 0, MAX_PATH);
+ memset(module_sys_path, 0, MAX_PATH);
+
+ // firehose_dir[0] = module_port_name[0] = module_sys_path[0] = '\0';
+
+ /* set file priviledge mask 0 */
+ umask(0);
+ /*build V1.0.8*/
+ dbg_time("Version: QFirehose_Linux_Android_V1.4.21\n"); // when release,
+ // rename to V1.X
+#ifndef __clang__
+ dbg_time("Builded: %s %s\n", __DATE__, __TIME__);
+#endif
+
+#ifdef ANDROID
+ struct passwd *pd;
+ pd = getpwuid(getuid());
+ dbg_time("------------------\n");
+ dbg_time("User:\t %s\n", pd->pw_name);
+ struct group *group;
+ group = getgrgid(pd->pw_gid);
+ dbg_time("Group:\t %s\n", group->gr_name);
+ dbg_time("------------------\n");
+#if 0 // not all customers need this function
+ loghandler = fopen("/data/upgrade.log", "w+");
+#endif
+ if (loghandler) dbg_time("upgrade log will be sync to /data/upgrade.log\n");
+#endif
+
+ optind = 1;
+ while (-1 != (opt = getopt(argc, argv, "f:p:z:s:l:u:d:nevhr")))
+ {
+ switch (opt)
+ {
+ case 'n': check_hash = 0; break;
+ case 'l':
+ if (loghandler) fclose(loghandler);
+ snprintf(filename, sizeof(filename), "%.80s/qfirehose_%lu.log", optarg, time(NULL));
+ loghandler = fopen(filename, "w+");
+ if (loghandler) dbg_time("upgrade log will be sync to %s\n", filename);
+ break;
+ case 'f': {
+ strncpy(file_message, optarg, MAX_PATH - 1);
+ if (strstr(file_message, ".mbn") != NULL || strstr(file_message, ".elf") != NULL)
+ {
+ g_is2mdn_path = 1;
+ char *tmp = strrchr(file_message, '/');
+ strncpy(firehose_mbn, tmp + 1, strlen(tmp) - 1);
+ strncpy(firehose_dir, file_message, strlen(file_message) - strlen(tmp));
+ dbg_time("f pargram: "
+ "g_is2mdn_path=[%d],file_message=[%s],firehose_mbn=[%s],"
+ "firehose_dir=[%s]\n",
+ g_is2mdn_path, file_message, firehose_mbn, firehose_dir);
+ break;
+ }
+ strncpy(firehose_dir, file_message, strlen(file_message));
+ break;
+ }
+ case 'p':
+ strncpy(module_port_name, optarg, MAX_PATH - 1);
+ if (!strcmp(module_port_name, "9008"))
+ {
+ usb2tcp_port = atoi(module_port_name);
+ module_port_name[0] = '\0';
+ }
+ break;
+ case 's':
+ strncpy(module_sys_path, optarg, MAX_PATH - 1);
+ int len = strlen(optarg);
+ if (len > 256)
+ {
+ safe_free(module_port_name);
+ safe_free(module_sys_path);
+ safe_free(usbmon_logfile);
+ safe_free(firehose_dir);
+ safe_free(file_message);
+ safe_free(firehose_mbn);
+ printf("optarg length is longer than 256\n");
+ return -1;
+ }
+
+ if (module_sys_path[strlen(optarg) - 1] == '/') module_sys_path[strlen(optarg) - 1] = '\0';
+ break;
+ case 'z': qusb_zlp_mode = !!atoi(optarg); break;
+ case 'e': q_erase_all_before_download = 1; break;
+ case 'u':
+ usbmon_logfile = strdup(optarg);
+ if (usbmon_logfile == NULL)
+ {
+ printf("usbmon_logfile is NULL\n");
+ return -1;
+ }
+ break;
+ case 'd':
+ q_device_type = strdup(optarg);
+ if (q_device_type == NULL)
+ {
+ printf("q_device_type is NULL\n");
+ return -1;
+ }
+ break;
+ case 'v': q_module_packet_sign = 1; break;
+ case 'r':
+ g_from_ecm_to_rndis = 1;
+ printf("will use rndis mode [%d]\r\n", g_from_ecm_to_rndis);
+ break;
+ case 'h': usage(EXIT_SUCCESS, argv[0]); break;
+ default: break;
+ }
+ }
+
+ if (usbmon_logfile) ql_capture_usbmon_log(usbmon_logfile);
+
+ update_transfer_bytes(0);
+ if (usb2tcp_port) goto _usb2tcp_start;
+
+ if (firehose_dir[0] == '\0')
+ {
+ usage(EXIT_SUCCESS, argv[0]);
+ update_transfer_bytes(-1);
+ error_return();
+ }
+
+ if (access(firehose_dir, R_OK))
+ {
+ dbg_time("fail to access %s, errno: %d (%s)\n", firehose_dir, errno, strerror(errno));
+ update_transfer_bytes(-1);
+ safe_free(firehose_dir);
+ safe_free(module_port_name);
+ safe_free(module_sys_path);
+ safe_free(usbmon_logfile);
+ safe_free(file_message);
+ safe_free(firehose_mbn);
+ error_return();
+ }
+
+ opt = strlen(firehose_dir);
+ if (firehose_dir[opt - 1] == '/')
+ {
+ firehose_dir[opt - 1] = '\0';
+ }
+
+ char buff[256] = {0};
+ int file_name_count = 0;
+
+ if (strstr(firehose_dir, ".zip") || strstr(firehose_dir, ".7z"))
+ {
+ if (strstr(firehose_dir, ".zip"))
+ {
+ is_upgrade_fimeware_only_zip = 1;
+ }
+
+ unlink(ZIP_INFO);
+ memset(zip_cmd_buf, 0, sizeof(zip_cmd_buf));
+ if (is_upgrade_fimeware_only_zip)
+ {
+ snprintf(zip_cmd_buf, sizeof(zip_cmd_buf), "unzip -l -q %.240s > %s", firehose_dir, ZIP_INFO);
+ }
+ else
+ {
+ snprintf(zip_cmd_buf, sizeof(zip_cmd_buf), "7z l %.240s > %s", firehose_dir, ZIP_INFO);
+ }
+ if (-1 == system(zip_cmd_buf))
+ {
+ dbg_time("%s system return error\n", __func__);
+ return -1;
+ }
+ usleep(1000);
+
+ char *p = strrchr(firehose_dir, '/'); // firehose_dir is the absolute path of the zip/7z
+ // file
+ if (p)
+ {
+ if (strstr(firehose_dir, ".zip"))
+ {
+ strncpy(firehose_zip_name, p + 1, strlen(p) - 4 - 1); // 4(.zip); 1(/)
+ }
+ else
+ {
+ strncpy(firehose_zip_name, p + 1, strlen(p) - 3 - 1); // 3(.7z); 1(/)
+ }
+ }
+ else
+ {
+ if (strstr(firehose_dir, ".zip"))
+ {
+ strncpy(firehose_zip_name, firehose_dir,
+ strlen(firehose_dir) - 4); // QFirehose -f RG520NEUDCR01A01M4G_01.001.01.001.zip
+ }
+ else
+ {
+ strncpy(firehose_zip_name, firehose_dir,
+ strlen(firehose_dir) - 3); // QFirehose -f RG520NEUDCR01A01M4G_01.001.01.001.7z
+ }
+ }
+
+ dbg_time("firehose_zip_name:%s\n", firehose_zip_name); // RG520NEUDCR01A01M4G_01.001.01.001
+ is_upgrade_fimeware_zip_7z = 1; // Judging as a zip/7z package upgrade
+
+ if (!access(ZIP_INFO, F_OK))
+ {
+ char *p0 = NULL;
+ char *p01 = NULL;
+ char *p1 = NULL;
+ char *p2 = NULL;
+ char *p3 = NULL;
+ char *p4 = NULL;
+ FILE *fp = fopen(ZIP_INFO, "rb");
+ if (fp == NULL)
+ {
+ dbg_time("fail to fopen(%s), error: %d (%s)\n", ZIP_INFO, errno, strerror(errno));
+ return -1;
+ }
+
+ while (fgets(buff, sizeof(buff), fp))
+ {
+ p0 = strstr(buff, firehose_zip_name);
+ if (p0)
+ {
+ int length_debug1 = strlen(p0);
+ if (p0[length_debug1 - 1] == 0x0a) length_debug1 -= 1;
+
+ memmove(file_name_b.file_backup_c[file_name_count].zip_file_dir_backup, p0, length_debug1);
+
+ p01 = strrchr(p0, '/');
+ if (p01 == NULL) continue;
+
+ if (p01[0] == '/' && p01[1] == '\0')
+ {
+ continue;
+ }
+
+ is_firehose_zip_7z_name_exit = 1; // Determine which type of package it is and whether it should be placed
+ // in one folder or several files or folders after decompression
+
+ int length_debug = strlen(p01);
+ if (p01[length_debug - 1] == 0x0a) length_debug -= 1;
+
+ memmove(file_name_b.file_backup_c[file_name_count].zip_file_name_backup, p01 + 1, length_debug - 1);
+
+ file_name_count++;
+ file_name_b.file_name_count = file_name_count;
+ }
+ else
+ {
+ p1 = strstr(buff, "contents.xml");
+ p2 = strstr(buff, "md5.txt");
+ p3 = strstr(buff, "update");
+
+ if (p1)
+ {
+ int length_debug1 = strlen(p1);
+ if (p1[length_debug1 - 1] == 0x0a) length_debug1 -= 1;
+
+ memmove(file_name_b.file_backup_c[file_name_count].zip_file_dir_backup, p1, length_debug1);
+
+ int length_debug = strlen(p1);
+ if (p1[length_debug - 1] == 0x0a) length_debug -= 1;
+
+ memmove(file_name_b.file_backup_c[file_name_count].zip_file_name_backup, p1 + 1, length_debug - 1);
+
+ file_name_count++;
+ file_name_b.file_name_count = file_name_count;
+ }
+ else if (p2)
+ {
+ int length_debug1 = strlen(p2);
+ if (p2[length_debug1 - 1] == 0x0a) length_debug1 -= 1;
+
+ memmove(file_name_b.file_backup_c[file_name_count].zip_file_dir_backup, p2, length_debug1);
+
+ int length_debug = strlen(p2);
+ if (p2[length_debug - 1] == 0x0a) length_debug -= 1;
+
+ memmove(file_name_b.file_backup_c[file_name_count].zip_file_name_backup, p2 + 1, length_debug - 1);
+
+ file_name_count++;
+ file_name_b.file_name_count = file_name_count;
+ }
+ else if (p3)
+ {
+ int length_debug1 = strlen(p3);
+ if (p3[length_debug1 - 1] == 0x0a) length_debug1 -= 1;
+
+ memmove(file_name_b.file_backup_c[file_name_count].zip_file_dir_backup, p3, length_debug1);
+
+ p4 = strrchr(p3, '/');
+ if (p4 == NULL) continue;
+
+ if (p4[0] == '/' && p4[1] == '\0')
+ {
+ dbg_time("continue..\n");
+ continue;
+ }
+
+ int length_debug = strlen(p4);
+ if (p4[length_debug - 1] == 0x0a) length_debug -= 1;
+
+ memmove(file_name_b.file_backup_c[file_name_count].zip_file_name_backup, p4 + 1, length_debug - 1);
+
+ file_name_count++;
+ file_name_b.file_name_count = file_name_count;
+ }
+ }
+ }
+
+ fclose(fp);
+ unlink(ZIP_INFO);
+
+ if (!is_firehose_zip_7z_name_exit)
+ {
+ memset(firehose_zip_name, 0, sizeof(firehose_zip_name));
+ }
+
+ if (firehose_zip_name[0] == '\0')
+ {
+ strcpy(firehose_unzip_full_dir, "/tmp");
+ }
+ else
+ {
+ snprintf(firehose_unzip_full_dir, sizeof(firehose_unzip_full_dir), "/tmp/%.76s", firehose_zip_name);
+ }
+
+ dbg_time("%s firehose_unzip_full_dir:%s\n", __func__, firehose_unzip_full_dir);
+ }
+ }
+
+ if (check_hash && md5_check(firehose_dir))
+ {
+ update_transfer_bytes(-1);
+ safe_free(firehose_dir);
+ safe_free(module_port_name);
+ safe_free(module_sys_path);
+ safe_free(usbmon_logfile);
+ safe_free(file_message);
+ safe_free(firehose_mbn);
+ error_return();
+ }
+
+ if (!g_is2mdn_path) firehose_mbn = find_firehose_mbn(&firehose_dir, MAX_PATH);
+ dbg_time("%s %s\n", __func__, firehose_mbn);
+ if (!firehose_mbn)
+ {
+ update_transfer_bytes(-1);
+ safe_free(module_port_name);
+ safe_free(module_sys_path);
+ safe_free(usbmon_logfile);
+ safe_free(firehose_dir);
+ safe_free(file_message);
+ safe_free(firehose_mbn);
+ error_return();
+ }
+
+ if (module_port_name[0] && !strncmp(module_port_name, "/dev/mhi", strlen("/dev/mhi")))
+ {
+ if (qpcie_open(firehose_dir, firehose_mbn, module_port_name))
+ {
+ update_transfer_bytes(-1);
+ safe_free(module_port_name);
+ safe_free(module_sys_path);
+ safe_free(usbmon_logfile);
+ safe_free(firehose_dir);
+ safe_free(file_message);
+ safe_free(firehose_mbn);
+ error_return();
+ }
+
+ usb_handle = &edl_pcie_mhifd;
+ start = get_now();
+ goto __firehose_main;
+ }
+ else if (module_port_name[0] && strstr(module_port_name, ":9008"))
+ {
+ strcpy(module_sys_path, module_port_name);
+ goto __edl_retry;
+ }
+
+_usb2tcp_start:
+ if (module_sys_path[0] && access(module_sys_path, R_OK))
+ {
+ dbg_time("fail to access %s, errno: %d (%s)\n", module_sys_path, errno, strerror(errno));
+ update_transfer_bytes(-1);
+ safe_free(module_port_name);
+ safe_free(module_sys_path);
+ safe_free(usbmon_logfile);
+ safe_free(firehose_dir);
+ safe_free(file_message);
+ safe_free(firehose_mbn);
+ error_return();
+ }
+
+ if (module_port_name[0] && access(module_port_name, R_OK | W_OK))
+ {
+ dbg_time("fail to access %s, errno: %d (%s)\n", module_port_name, errno, strerror(errno));
+ update_transfer_bytes(-1);
+ safe_free(module_port_name);
+ safe_free(module_sys_path);
+ safe_free(usbmon_logfile);
+ safe_free(firehose_dir);
+ safe_free(file_message);
+ safe_free(firehose_mbn);
+ error_return();
+ }
+
+ if (module_sys_path[0] == '\0' && module_port_name[0] != '\0')
+ {
+ // get sys path by port name
+ quectel_get_syspath_name_by_ttyport(module_port_name, module_sys_path, MAX_PATH);
+ }
+
+ g_is_module_adb_entry_edl = 0;
+
+ if (module_sys_path[0] == '\0')
+ {
+ int module_count = auto_find_quectel_modules(module_sys_path, MAX_PATH, NULL, NULL);
+ if (module_count <= 0)
+ {
+ dbg_time("Quectel module not found\n");
+ update_transfer_bytes(-1);
+ safe_free(module_port_name);
+ safe_free(module_sys_path);
+ safe_free(usbmon_logfile);
+ safe_free(firehose_dir);
+ safe_free(file_message);
+ safe_free(firehose_mbn);
+ error_return();
+ }
+ else if (module_count == 1)
+ {
+ if (g_is_module_adb_entry_edl > 0)
+ {
+ switch_to_edl_mode_in_adb_way();
+ }
+ }
+ else
+ {
+ dbg_time("There are multiple quectel modules in system, Please use <-s "
+ "/sys/bus/usb/devices/xx> specify which module you want to "
+ "upgrade!\n");
+ dbg_time("The module's path was printed in the "
+ "previous log!\n");
+ update_transfer_bytes(-1);
+ safe_free(module_port_name);
+ safe_free(module_sys_path);
+ safe_free(usbmon_logfile);
+ safe_free(firehose_dir);
+ safe_free(file_message);
+ safe_free(firehose_mbn);
+ error_return();
+ }
+ }
+
+__edl_retry:
+ qusb_read_speed_atime(module_sys_path, &usb3_atime, &usb3_speed);
+ while (edl_retry-- > 0)
+ {
+ usb_handle = qusb_noblock_open(module_sys_path, &idVendor, &idProduct, &interfaceNum);
+
+ if (usb_handle)
+ {
+ clock_gettime(CLOCK_REALTIME, &usb3_atime);
+ }
+ else
+ {
+ sleep(1); // in reset sate, wait connect
+ if (usb3_speed >= 5000 && access(module_sys_path, R_OK) && errno_nodev())
+ {
+ if (auto_find_quectel_modules(module_sys_path, MAX_PATH, "5c6/9008/", &usb3_atime) > 1)
+ {
+ dbg_time("There are multiple quectel EDL modules in system!\n");
+ update_transfer_bytes(-1);
+ safe_free(module_port_name);
+ safe_free(module_sys_path);
+ safe_free(usbmon_logfile);
+ safe_free(firehose_dir);
+ safe_free(file_message);
+ safe_free(firehose_mbn);
+ error_return();
+ }
+ }
+ continue;
+ }
+
+#if 0
+ if (idVendor == 0x2c7c && interfaceNum > 1) {
+ if (detect_and_judge_module_version(usb_handle)) {
+ // update_transfer_bytes(-1);
+ /* do not return here, this command will fail when modem is not ready */
+ // error_return();
+ }
+ }
+#endif
+
+ if (interfaceNum == 1)
+ {
+ if ((idVendor == 0x2C7C) && (idProduct == 0x0800))
+ {
+ // although 5G module stay in dump mode, after send edl command, it also
+ // can enter edl mode
+ dbg_time("5G module stay in dump mode!\n");
+ }
+ else
+ {
+ break;
+ }
+ dbg_time("something went wrong???, why only one interface left\n");
+ }
+
+ switch_to_edl_mode(usb_handle);
+ qusb_noblock_close(usb_handle);
+ usb_handle = NULL;
+ sleep(1); // wait usb disconnect and re-connect
+ }
+
+ if (usb_handle == NULL)
+ {
+ update_transfer_bytes(-1);
+ safe_free(module_port_name);
+ safe_free(module_sys_path);
+ safe_free(usbmon_logfile);
+ safe_free(firehose_dir);
+ safe_free(file_message);
+ safe_free(firehose_mbn);
+ error_return();
+ }
+
+ if (usb2tcp_port)
+ {
+ retval = usb2tcp_main(usb_handle, usb2tcp_port, qusb_zlp_mode);
+ qusb_noblock_close(usb_handle);
+ safe_free(module_port_name);
+ safe_free(module_sys_path);
+ safe_free(usbmon_logfile);
+ safe_free(firehose_dir);
+ safe_free(file_message);
+ safe_free(firehose_mbn);
+ return retval;
+ }
+
+ start = get_now();
+ retval = sahara_main(firehose_dir, firehose_mbn, usb_handle, idVendor == 0x05c6);
+
+ if (!retval)
+ {
+ if (idVendor != 0x05C6)
+ {
+ sleep(1);
+ stream_download(firehose_dir, usb_handle, qusb_zlp_mode);
+ qusb_noblock_close(usb_handle);
+ sleep(10); // EM05-G switching to download mode is slow and increases the waiting time
+ // to 10 seconds
+ goto __edl_retry;
+ }
+
+ __firehose_main:
+ retval = firehose_main(firehose_dir, usb_handle, qusb_zlp_mode);
+ if (retval == 0)
+ {
+ get_duration(start);
+ }
+ }
+
+ qusb_noblock_close(usb_handle);
+
+ safe_free(firehose_dir);
+ safe_free(module_port_name);
+ safe_free(module_sys_path);
+ safe_free(file_message);
+ safe_free(firehose_mbn);
+
+ dbg_time("Upgrade module %s.\n", retval == 0 ? "successfully" : "failed");
+ if (loghandler) fclose(loghandler);
+ if (retval) update_transfer_bytes(-1);
+ if (usbmon_logfile) ql_stop_usbmon_log();
+ unlink(ZIP_PROCESS_INFO);
+
+ return retval;
+}
+
+double get_now()
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ return (double)tv.tv_sec + (double)tv.tv_usec / 1000000;
+}
+
+void get_duration(double start) { dbg_time("THE TOTAL DOWNLOAD TIME IS %.3f s\n", (get_now() - start)); }
+
+void set_transfer_allbytes(long long bytes)
+{
+ transfer_bytes = 0;
+ all_bytes_to_transfer = bytes;
+}
+
+int update_progress_msg(int percent);
+int update_progress_file(int percent);
+/*
+return percent
+*/
+int update_transfer_bytes(long long bytes_cur)
+{
+ static int last_percent = -1;
+ int percent = 0;
+
+ if (bytes_cur == -1 || bytes_cur == 0)
+ {
+ percent = bytes_cur;
+ }
+ else
+ {
+ transfer_bytes += bytes_cur;
+ percent = (transfer_bytes * 100) / all_bytes_to_transfer;
+ }
+
+ if (percent != last_percent)
+ {
+ last_percent = percent;
+#ifdef USE_IPC_FILE
+ update_progress_file(percent);
+#endif
+#ifdef USE_IPC_MSG
+ update_progress_msg(percent);
+#endif
+ }
+
+ return percent;
+}
+
+void show_progress()
+{
+ static int percent = 0;
+
+ if (all_bytes_to_transfer) percent = (transfer_bytes * 100) / all_bytes_to_transfer;
+ dbg_time("upgrade progress %d%% %lld/%lld\n", percent, transfer_bytes, all_bytes_to_transfer);
+}
+
+#ifdef USE_IPC_FILE
+#define IPC_FILE_ANDROID "/data/update.conf"
+#define IPC_FILE_LINUX "/tmp/update.conf"
+int update_progress_file(int percent)
+{
+ static int ipcfd = -1;
+ char buff[16];
+
+ if (ipcfd < 0)
+ {
+#ifdef ANDROID
+ const char *ipc_file = IPC_FILE_ANDROID;
+#else
+ const char *ipc_file = IPC_FILE_LINUX;
+#endif
+ /* Have set umask previous, no need to call fchmod */
+ ipcfd = open(ipc_file, O_TRUNC | O_CREAT | O_WRONLY | O_NONBLOCK, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+ if (ipcfd < 0)
+ {
+ dbg_time("Fail to open(O_WRONLY) %s: %s\n", ipc_file, strerror(errno));
+ return -1;
+ }
+ }
+
+ lseek(ipcfd, 0, SEEK_SET);
+ snprintf(buff, sizeof(buff), "%d", percent);
+ if (write(ipcfd, buff, strlen(buff)) < 0) dbg_time("fail to write upgrade progress into %s: %s\n", ipc_file, strerror(errno));
+
+ if (percent == 100 || percent < 0) close(ipcfd);
+ return 0;
+}
+#endif
+
+#ifdef USE_IPC_MSG
+#define MSGBUFFSZ 16
+struct message
+{
+ long mtype;
+ char mtext[MSGBUFFSZ];
+};
+
+#define MSG_FILE "/etc/passwd"
+#define MSG_TYPE_IPC 1
+static int msg_get()
+{
+ key_t key = ftok(MSG_FILE, 'a');
+ int msgid = msgget(key, IPC_CREAT | 0644);
+
+ if (msgid < 0)
+ {
+ dbg_time("msgget fail: key %d, %s\n", key, strerror(errno));
+ return -1;
+ }
+ return msgid;
+}
+
+static int msg_rm(int msgid) { return msgctl(msgid, IPC_RMID, 0); }
+
+static int msg_send(int msgid, long type, const char *msg)
+{
+ struct message info;
+ info.mtype = type;
+ snprintf(info.mtext, MSGBUFFSZ, "%s", msg);
+ if (msgsnd(msgid, (void *)&info, MSGBUFFSZ, IPC_NOWAIT) < 0)
+ {
+ dbg_time("msgsnd faild: msg %s, %s\n", msg, strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+static int msg_recv(int msgid, struct message *info)
+{
+ if (msgrcv(msgid, (void *)info, MSGBUFFSZ, info->mtype, IPC_NOWAIT) < 0)
+ {
+ dbg_time("msgrcv faild: type %ld, %s\n", info->mtype, strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+/**
+ * this function will not delete the msg queue
+ */
+int update_progress_msg(int percent)
+{
+ char buff[MSGBUFFSZ];
+ int msgid = msg_get();
+ if (msgid < 0) return -1;
+ snprintf(buff, sizeof(buff), "%d", percent);
+
+#ifndef IPC_TEST
+ return msg_send(msgid, MSG_TYPE_IPC, buff);
+#else
+ msg_send(msgid, MSG_TYPE_IPC, buff);
+ struct message info;
+ info.mtype = MSG_TYPE_IPC;
+ msg_recv(msgid, &info);
+ printf("msg queue read: %s\n", info.mtext);
+#endif
+}
+#endif
diff --git a/application/qfirehose/src/sahara.c b/application/qfirehose/src/sahara.c
new file mode 100644
index 0000000..918b99e
--- /dev/null
+++ b/application/qfirehose/src/sahara.c
@@ -0,0 +1,529 @@
+/*
+ Copyright 2023 Quectel Wireless Solutions Co.,Ltd
+
+ Quectel hereby grants customers of Quectel a license to use, modify,
+ distribute and publish the Software in binary form provided that
+ customers shall have no right to reverse engineer, reverse assemble,
+ decompile or reduce to source code form any portion of the Software.
+ Under no circumstances may customers modify, demonstrate, use, deliver
+ or disclose any portion of the Software in source code form.
+*/
+
+#include "usb_linux.h"
+#include "sahara.h"
+
+static uint32_t le_uint32(uint32_t v32)
+{
+ const int is_bigendian = 1;
+ uint32_t tmp = v32;
+ if ((*(char *)&is_bigendian) == 0)
+ {
+ unsigned char *s = (unsigned char *)(&v32);
+ unsigned char *d = (unsigned char *)(&tmp);
+ d[0] = s[3];
+ d[1] = s[2];
+ d[2] = s[1];
+ d[3] = s[0];
+ }
+ return tmp;
+}
+
+static uint64_t le_uint64(uint64_t v64)
+{
+ const int is_bigendian = 1;
+ uint64_t tmp = v64;
+ if ((*(char *)&is_bigendian) == 0)
+ {
+ unsigned char *s = (unsigned char *)(&v64);
+ unsigned char *d = (unsigned char *)(&tmp);
+ d[0] = s[7];
+ d[1] = s[6];
+ d[2] = s[5];
+ d[3] = s[4];
+ d[4] = s[3];
+ d[5] = s[2];
+ d[6] = s[1];
+ d[7] = s[0];
+ }
+ return tmp;
+}
+
+#define dbg(log_level, fmt, arg...) \
+ do \
+ { \
+ dbg_time(fmt "\n", ##arg); \
+ } while (0)
+
+static int sahara_tx_data(void *usb_handle, void *tx_buffer, size_t bytes_to_send)
+{
+ int need_zlp = 0; // zlp is not mandatory
+ return qusb_noblock_write(usb_handle, tx_buffer, bytes_to_send, bytes_to_send, 3000, need_zlp);
+}
+
+int qusb_use_usbfs_interface(const void *handle);
+static int sahara_rx_data(void *usb_handle, void *rx_buffer, size_t bytes_to_read)
+{
+ q_sahara_packet_h *command_packet_header = NULL;
+ size_t bytes_read = 0;
+
+ const char *q_sahara_cmd_str[Q_SAHARA_NINETEEN] = {
+ "Q_SAHARA_ZERO", // = 0x00,
+ "Q_SAHARA_ONE", // = 0x01, // sent from target to host
+ "Q_SAHARA_TWO", // = 0x02, // sent from host to target
+ "Q_SAHARA_THREE", // = 0x03, // sent from target to host
+ "Q_SAHARA_FOUR", // = 0x04, // sent from target to host
+ "Q_SAHARA_FIVE", // = 0x05, // sent from host to target
+ "Q_SAHARA_SIX", // = 0x06, // sent from target to host
+ "Q_SAHARA_SEVEN", // = 0x07, // sent from host to target
+ "Q_SAHARA_EIGTH", // = 0x08, // sent from target to host
+ "Q_SAHARA_NINE", // = 0x09, // sent from target to host
+ "Q_SAHARA_TEN", // = 0x0A, // sent from host to target
+ "Q_SAHARA_ELEVEN", // = 0x0B, // sent from target to host
+ "Q_SAHARA_TWELEVE", // = 0x0C, // sent from host to target
+ "Q_SAHARA_THIRTEEN", // = 0x0D, // sent from host to target
+ "Q_SAHARA_FOURTEEN", // = 0x0E, // sent from target to host
+ "Q_SAHARA_FIFTEEN", // = 0x0F, // sent from host to target
+ "Q_SAHARA_SIXTEEN", // = 0x10, // sent from target to host
+ "Q_SAHARA_SEVENTEEN", // = 0x11, // sent from host to target
+ "Q_SAHARA_EIGHTEEN", // = 0x12,
+ };
+
+ if (0 == bytes_to_read)
+ {
+ if (qusb_use_usbfs_interface(usb_handle))
+ {
+ bytes_read = qusb_noblock_read(usb_handle, rx_buffer, Q_SAHARA_RAW_BUF_SZ, 0, 5000);
+ if (bytes_read < sizeof(q_sahara_packet_h)) return 0;
+ }
+ else
+ {
+ bytes_read =
+ qusb_noblock_read(usb_handle, rx_buffer, sizeof(q_sahara_packet_h), 0, 5000);
+ if (bytes_read != sizeof(q_sahara_packet_h)) return 0;
+ }
+
+ command_packet_header = (q_sahara_packet_h *)rx_buffer;
+ if (le_uint32(command_packet_header->q_cmd) < Q_SAHARA_NINETEEN)
+ {
+ dbg(LOG_EVENT, "<=== %s", q_sahara_cmd_str[le_uint32(command_packet_header->q_cmd)]);
+
+ if (!qusb_use_usbfs_interface(usb_handle))
+ {
+ bytes_read += qusb_noblock_read(
+ usb_handle, (uint8_t *)rx_buffer + sizeof(q_sahara_packet_h),
+ le_uint32(command_packet_header->q_len) - sizeof(q_sahara_packet_h), 0, 5000);
+ }
+
+ if (bytes_read != (le_uint32(command_packet_header->q_len)))
+ {
+ dbg(LOG_INFO, "Read %zd bytes, Header indicates q_cmd %d and packet q_len %d bytes",
+ bytes_read, le_uint32(command_packet_header->q_cmd),
+ le_uint32(command_packet_header->q_len));
+ return 0;
+ }
+ }
+ else
+ {
+ dbg(LOG_EVENT, "<=== SAHARA_CMD_UNKONOW_%d", le_uint32(command_packet_header->q_cmd));
+ return 0;
+ }
+ }
+ else
+ {
+ bytes_read = qusb_noblock_read(usb_handle, rx_buffer, bytes_to_read, bytes_to_read, 5000);
+ }
+
+ return 1;
+}
+
+static int send_reset_command(void *usb_handle, void *tx_buffer)
+{
+ struct sahara_pkt *sahara_reset;
+ sahara_reset = (struct sahara_pkt *)tx_buffer;
+ sahara_reset->q_header.q_cmd = le_uint32(Q_SAHARA_SEVEN);
+ sahara_reset->q_header.q_len =
+ le_uint32(sizeof(sahara_reset->q_sahara_reset_packet) + sizeof(q_sahara_packet_h));
+
+ /* Send the Reset Request */
+ dbg(LOG_EVENT, "SAHARA_RESET ===>");
+ if (0 ==
+ sahara_tx_data(usb_handle, tx_buffer,
+ sizeof(sahara_reset->q_sahara_reset_packet) + sizeof(q_sahara_packet_h)))
+ {
+ dbg(LOG_ERROR, "Sending RESET packet failed");
+ return 0;
+ }
+
+ return 1;
+}
+
+static int send_done_packet(void *usb_handle, void *tx_buffer)
+{
+ struct sahara_pkt *sahara_done;
+ sahara_done = (struct sahara_pkt *)tx_buffer;
+
+ sahara_done->q_header.q_cmd = le_uint32(Q_SAHARA_FIVE);
+ sahara_done->q_header.q_len =
+ le_uint32(sizeof(sahara_done->q_sahara_done_packet) + sizeof(q_sahara_packet_h));
+ // Send the image data
+ dbg(LOG_EVENT, "Q_SAHARA_FIVE ===>");
+ if (0 == sahara_tx_data(usb_handle, tx_buffer,
+ sizeof(sahara_done->q_sahara_done_packet) + sizeof(q_sahara_packet_h)))
+ {
+ dbg(LOG_ERROR, "Sending DONE packet failed");
+ return 0;
+ }
+ return 1;
+}
+
+static int start_image_transfer(void *usb_handle, void *tx_buffer,
+ const struct sahara_pkt *pr_sahara_pkt, FILE *file_handle)
+{
+ int retval = 0;
+ uint32_t bytes_read = 0, bytes_to_read_next;
+ uint32_t q_image_id = le_uint32(pr_sahara_pkt->q_sahara_read_packet_data.q_image_id);
+ uint32_t DataOffset = le_uint32(pr_sahara_pkt->q_sahara_read_packet_data.q_data_offset);
+ uint32_t DataLength = le_uint32(pr_sahara_pkt->q_sahara_read_packet_data.q_data_length);
+
+ if (le_uint32(pr_sahara_pkt->q_header.q_cmd) == Q_SAHARA_EIGHTEEN)
+ {
+ q_image_id = le_uint64(pr_sahara_pkt->q_sahara_read_packet_data_64bit.q_image_id);
+ DataOffset = le_uint64(pr_sahara_pkt->q_sahara_read_packet_data_64bit.q_data_offset);
+ DataLength = le_uint64(pr_sahara_pkt->q_sahara_read_packet_data_64bit.q_data_length);
+ }
+
+ dbg(LOG_INFO, "0x%08x 0x%08x 0x%08x", q_image_id, DataOffset, DataLength);
+
+ if (fseek(file_handle, (long)DataOffset, SEEK_SET))
+ {
+ dbg(LOG_INFO, "%d errno: %d (%s)", __LINE__, errno, strerror(errno));
+ return 0;
+ }
+
+ while (bytes_read < DataLength)
+ {
+ bytes_to_read_next = MIN((uint32_t)DataLength - bytes_read, Q_SAHARA_RAW_BUF_SZ);
+ retval = fread(tx_buffer, 1, bytes_to_read_next, file_handle);
+
+ if (retval < 0)
+ {
+ dbg(LOG_ERROR, "file read failed: %s", strerror(errno));
+ return 0;
+ }
+
+ if ((uint32_t)retval != bytes_to_read_next)
+ {
+ dbg(LOG_ERROR, "Read %d bytes, but was asked for 0x%08x bytes", retval, DataLength);
+ return 0;
+ }
+
+ /*send the image data*/
+ if (0 == sahara_tx_data(usb_handle, tx_buffer, bytes_to_read_next))
+ {
+ dbg(LOG_ERROR, "Tx Sahara Image Failed");
+ return 0;
+ }
+
+ bytes_read += bytes_to_read_next;
+ }
+
+ return 1;
+}
+
+static int send_hello_response(void *usb_handle, void *tx_buffer,
+ const struct sahara_pkt *sahara_hello)
+{
+ struct sahara_pkt *sahara_hello_resp;
+ sahara_hello_resp = (struct sahara_pkt *)tx_buffer;
+
+ // Recieved hello, send the hello response
+ // Create a Hello request
+ sahara_hello_resp->q_header.q_cmd = le_uint32(Q_SAHARA_TWO);
+ sahara_hello_resp->q_header.q_len = le_uint32(
+ sizeof(sahara_hello_resp->q_sahara_hello_packet_response) + sizeof(q_sahara_packet_h));
+ sahara_hello_resp->q_sahara_hello_packet_response.q_ver =
+ sahara_hello->q_sahara_hello_packet.q_ver;
+ sahara_hello_resp->q_sahara_hello_packet_response.q_ver_sup =
+ sahara_hello->q_sahara_hello_packet.q_ver_sup;
+ sahara_hello_resp->q_sahara_hello_packet_response.q_status = le_uint32(Q_SAHARA_STATUS_ZERO);
+ sahara_hello_resp->q_sahara_hello_packet_response.q_mode =
+ sahara_hello->q_sahara_hello_packet.q_mode;
+ sahara_hello_resp->q_sahara_hello_packet_response.q_reserve1 = le_uint32(1);
+ sahara_hello_resp->q_sahara_hello_packet_response.q_reserve2 = le_uint32(2);
+ sahara_hello_resp->q_sahara_hello_packet_response.q_reserve3 = le_uint32(3);
+ sahara_hello_resp->q_sahara_hello_packet_response.q_reserve4 = le_uint32(4);
+ sahara_hello_resp->q_sahara_hello_packet_response.q_reserve5 = le_uint32(5);
+ sahara_hello_resp->q_sahara_hello_packet_response.q_reserve6 = le_uint32(6);
+
+ if (le_uint32(sahara_hello->q_sahara_hello_packet.q_mode) != Q_SAHARA_MODE_ZERO)
+ {
+ dbg(LOG_ERROR, "ERROR NOT Q_SAHARA_MODE_ZERO");
+ sahara_hello_resp->q_sahara_hello_packet_response.q_mode = Q_SAHARA_MODE_ZERO;
+ }
+
+ /*Send the Hello Resonse Request*/
+ dbg(LOG_EVENT, "Q_SAHARA_TWO ===>");
+ if (0 == sahara_tx_data(usb_handle, tx_buffer,
+ sizeof(sahara_hello_resp->q_sahara_hello_packet_response) +
+ sizeof(q_sahara_packet_h)))
+ {
+ dbg(LOG_ERROR, "Tx Sahara Data Failed ");
+ return 0;
+ }
+
+ return 1;
+}
+
+static int sahara_flash_all(void *usb_handle, void *tx_buffer, void *rx_buffer, FILE *file_handle)
+{
+ uint32_t q_image_id = 0;
+ struct sahara_pkt *pr_sahara_pkt;
+
+ pr_sahara_pkt = (struct sahara_pkt *)rx_buffer;
+
+ if (0 == sahara_rx_data(usb_handle, rx_buffer, 0))
+ {
+ sahara_tx_data(usb_handle, tx_buffer, 1);
+ if (0 == sahara_rx_data(usb_handle, rx_buffer, 0)) return 0;
+ }
+
+ if (le_uint32(pr_sahara_pkt->q_header.q_cmd) != Q_SAHARA_ONE)
+ {
+ dbg(LOG_ERROR, "Received a different q_cmd: %x while waiting for hello packet",
+ pr_sahara_pkt->q_header.q_cmd);
+ send_reset_command(usb_handle, rx_buffer);
+ return 0;
+ }
+
+ if (0 == send_hello_response(usb_handle, tx_buffer, pr_sahara_pkt))
+ {
+ dbg(LOG_ERROR, "send_hello_response failed\n");
+ return 0;
+ }
+
+ while (1)
+ {
+ if (0 == sahara_rx_data(usb_handle, rx_buffer, 0)) return 0;
+
+ if (le_uint32(pr_sahara_pkt->q_header.q_cmd) == Q_SAHARA_THREE)
+ {
+ start_image_transfer(usb_handle, tx_buffer, pr_sahara_pkt, file_handle);
+ }
+ else if (le_uint32(pr_sahara_pkt->q_header.q_cmd) == Q_SAHARA_EIGHTEEN)
+ {
+ start_image_transfer(usb_handle, tx_buffer, pr_sahara_pkt, file_handle);
+ }
+ else if (le_uint32(pr_sahara_pkt->q_header.q_cmd) == Q_SAHARA_FOUR)
+ {
+ dbg(LOG_EVENT, "q_image_id = %d, q_status = %d",
+ le_uint32(pr_sahara_pkt->q_sahara_end_packet_image_tx.q_image_id),
+ le_uint32(pr_sahara_pkt->q_sahara_end_packet_image_tx.q_status));
+ if (le_uint32(pr_sahara_pkt->q_sahara_end_packet_image_tx.q_status) ==
+ Q_SAHARA_STATUS_ZERO)
+ {
+ q_image_id = le_uint32(pr_sahara_pkt->q_sahara_end_packet_image_tx.q_image_id);
+ send_done_packet(usb_handle, tx_buffer);
+ break;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ else if (le_uint32(pr_sahara_pkt->q_header.q_cmd) == Q_SAHARA_ONE)
+ {
+ continue;
+ }
+ else
+ {
+ dbg(LOG_ERROR, "Received an unknown q_cmd: %d ",
+ le_uint32(pr_sahara_pkt->q_header.q_cmd));
+ send_reset_command(usb_handle, tx_buffer);
+ return 0;
+ }
+ }
+
+ if (0 == sahara_rx_data(usb_handle, rx_buffer, 0)) return 0;
+
+ dbg(LOG_INFO, "q_image_tx_status = %d",
+ le_uint32(pr_sahara_pkt->q_sahara_done_packet_response.q_image_tx_status));
+
+ if (Q_SAHARA_MODE_ZERO ==
+ le_uint32(pr_sahara_pkt->q_sahara_done_packet_response.q_image_tx_status))
+ {
+ if (q_image_id == 13) // prog_nand_firehose_9x07.mbn
+ return 1;
+ if (q_image_id == 7) // NPRG9x55.mbn
+ return 1;
+ if (q_image_id == 21) // sbl1.mbn, October 22 2020 2:12 PM, AG35CEVAR05A07T4G
+ return 1;
+ }
+ else if (Q_SAHARA_MODE_ONE ==
+ le_uint32(pr_sahara_pkt->q_sahara_done_packet_response.q_image_tx_status))
+ {
+ dbg(LOG_EVENT, "Successfully flash all images");
+ return 1;
+ }
+ else
+ {
+ dbg(LOG_ERROR, "Received unrecognized q_status %d at Q_SAHARA_WAIT_FOUR state",
+ le_uint32(pr_sahara_pkt->q_sahara_done_packet_response.q_image_tx_status));
+ return 0;
+ }
+
+ return 0;
+}
+
+int sahara_main(const char *firehose_dir, const char *firehose_mbn, void *usb_handle,
+ int edl_mode_05c69008)
+{
+ int retval = 0;
+ char full_path[512];
+ FILE *file_handle;
+ void *tx_buffer;
+ void *rx_buffer;
+
+ if (edl_mode_05c69008)
+ {
+ if (is_upgrade_fimeware_zip_7z)
+ {
+ snprintf(full_path, sizeof(full_path), "/tmp/%.240s", firehose_mbn);
+ dbg_time("%s full_path:%s\n", __func__, full_path);
+ }
+ else
+ {
+ snprintf(full_path, sizeof(full_path), "%.255s/%.240s", firehose_dir, firehose_mbn);
+ }
+ }
+ else
+ {
+ char *prog_nand_firehose_filename = NULL;
+
+ if (is_upgrade_fimeware_zip_7z)
+ {
+ int i;
+ char prog_nand_firehose_filename_tmp[128] = {0};
+ char prog_nand_firehose_filename_dir_tmp[256] = {0};
+
+ prog_nand_firehose_filename = (char *)malloc(256);
+ if (prog_nand_firehose_filename == NULL)
+ {
+ return ENOENT;
+ }
+
+ for (i = 0; i < file_name_b.file_name_count; i++)
+ {
+ if ((strstr(file_name_b.file_backup_c[i].zip_file_name_backup, "NPRG9x") &&
+ strstr(file_name_b.file_backup_c[i].zip_file_name_backup, ".mbn")))
+ {
+ dbg_time("file_name_b.file_backup_c[i].zip_file_name_backup:%s\n",
+ file_name_b.file_backup_c[i].zip_file_name_backup);
+ dbg_time("file_name_b.file_backup_c[i].zip_file_dir_backup:%s\n",
+ file_name_b.file_backup_c[i].zip_file_dir_backup);
+
+ if (strstr(file_name_b.file_backup_c[i].zip_file_dir_backup, "update/firehose"))
+ {
+ memmove(prog_nand_firehose_filename_tmp,
+ file_name_b.file_backup_c[i].zip_file_name_backup,
+ strlen(file_name_b.file_backup_c[i].zip_file_name_backup));
+ memmove(prog_nand_firehose_filename_dir_tmp,
+ file_name_b.file_backup_c[i].zip_file_dir_backup,
+ strlen(file_name_b.file_backup_c[i].zip_file_dir_backup));
+ break;
+ }
+ }
+ }
+
+ if (prog_nand_firehose_filename_tmp[0] != '\0')
+ {
+ memset(zip_cmd_buf, 0, sizeof(zip_cmd_buf));
+ if (is_upgrade_fimeware_only_zip)
+ {
+ snprintf(zip_cmd_buf, sizeof(zip_cmd_buf),
+ "unzip -o -q %.240s '*%.200s' -d /tmp/ > %s", firehose_dir,
+ prog_nand_firehose_filename_dir_tmp, ZIP_PROCESS_INFO);
+ }
+ else
+ {
+ snprintf(zip_cmd_buf, sizeof(zip_cmd_buf), "7z x %.240s -o/tmp/ %.200s > %s",
+ firehose_dir, prog_nand_firehose_filename_dir_tmp, ZIP_PROCESS_INFO);
+ }
+ dbg_time("%s zip_cmd_buf:%s\n", __func__, zip_cmd_buf);
+ if (-1 == system(zip_cmd_buf))
+ {
+ dbg_time("%s system return error\n", __func__);
+ safe_free(prog_nand_firehose_filename);
+ return ENOENT;
+ }
+ usleep(1000);
+
+ memmove(prog_nand_firehose_filename, prog_nand_firehose_filename_dir_tmp, 240);
+ dbg(LOG_INFO, "prog_nand_firehose_filename = %s", prog_nand_firehose_filename);
+
+ snprintf(full_path, sizeof(full_path), "/tmp/%.240s", prog_nand_firehose_filename);
+ }
+ }
+ else
+ {
+ snprintf(full_path, sizeof(full_path), "%.255s/..", firehose_dir);
+ if (!qfile_find_file(full_path, "NPRG9x", ".mbn", &prog_nand_firehose_filename) &&
+ !qfile_find_file(full_path, "NPRG9x", ".mbn", &prog_nand_firehose_filename))
+ {
+ dbg(LOG_ERROR, "retrieve NPRG MBN failed.");
+ safe_free(prog_nand_firehose_filename);
+ return ENOENT;
+ }
+ dbg(LOG_INFO, "prog_nand_firehose_filename = %s", prog_nand_firehose_filename);
+
+ snprintf(full_path, sizeof(full_path), "%.255s/../%.240s", firehose_dir,
+ prog_nand_firehose_filename);
+ }
+
+ safe_free(prog_nand_firehose_filename);
+ }
+
+ file_handle = fopen(full_path, "rb");
+ if (file_handle == NULL)
+ {
+ dbg(LOG_INFO, "%s %d %s errno: %d (%s)", __func__, __LINE__, full_path, errno,
+ strerror(errno));
+ return ENOENT;
+ }
+
+ rx_buffer = malloc(Q_SAHARA_RAW_BUF_SZ);
+ tx_buffer = malloc(Q_SAHARA_RAW_BUF_SZ);
+
+ if (NULL == rx_buffer || NULL == tx_buffer)
+ {
+ dbg(LOG_ERROR, "Failed to allocate sahara buffers");
+ safe_free(rx_buffer);
+ safe_free(tx_buffer);
+ fclose(file_handle);
+ file_handle = NULL;
+ return ENOMEM;
+ }
+
+ retval = sahara_flash_all(usb_handle, tx_buffer, rx_buffer, file_handle);
+ if (0 == retval)
+ {
+ dbg(LOG_ERROR, "Sahara protocol error");
+ }
+ else
+ {
+ dbg(LOG_STATUS, "Sahara protocol completed");
+ }
+
+ safe_free(rx_buffer);
+ safe_free(tx_buffer);
+ fclose(file_handle);
+ file_handle = NULL;
+
+ if (is_upgrade_fimeware_zip_7z)
+ {
+ unlink(full_path);
+ }
+
+ if (retval) return 0;
+
+ return __LINE__;
+}
diff --git a/application/qfirehose/src/sahara.h b/application/qfirehose/src/sahara.h
new file mode 100644
index 0000000..7e467a3
--- /dev/null
+++ b/application/qfirehose/src/sahara.h
@@ -0,0 +1,94 @@
+/*
+ Copyright 2023 Quectel Wireless Solutions Co.,Ltd
+
+ Quectel hereby grants customers of Quectel a license to use, modify,
+ distribute and publish the Software in binary form provided that
+ customers shall have no right to reverse engineer, reverse assemble,
+ decompile or reduce to source code form any portion of the Software.
+ Under no circumstances may customers modify, demonstrate, use, deliver
+ or disclose any portion of the Software in source code form.
+*/
+
+#ifndef SAHARA_H
+#define SAHARA_H
+
+#define Q_SAHARA_RAW_BUF_SZ (4*1024)
+#define Q_SAHARA_STATUS_ZERO 0x00
+#define Q_SAHARA_MODE_ZERO 0x00
+#define Q_SAHARA_MODE_ONE 0x01
+
+#define Q_SAHARA_ZERO 0x00
+#define Q_SAHARA_ONE 0x01
+#define Q_SAHARA_TWO 0x02
+#define Q_SAHARA_THREE 0x03
+#define Q_SAHARA_FOUR 0x04
+#define Q_SAHARA_FIVE 0x05
+#define Q_SAHARA_SEVEN 0x07
+#define Q_SAHARA_EIGHTEEN 0x12
+#define Q_SAHARA_NINETEEN 0x13
+
+typedef struct
+{
+ uint32_t q_cmd;
+ uint32_t q_len;
+} q_sahara_packet_h;
+
+struct sahara_pkt
+{
+ q_sahara_packet_h q_header;
+
+ union
+ {
+ struct
+ {
+ uint32_t q_ver;
+ uint32_t q_ver_sup;
+ uint32_t q_cmd_packet_len;
+ uint32_t q_mode;
+ } q_sahara_hello_packet;
+ struct
+ {
+ uint32_t q_ver;
+ uint32_t q_ver_sup;
+ uint32_t q_status;
+ uint32_t q_mode;
+ uint32_t q_reserve1;
+ uint32_t q_reserve2;
+ uint32_t q_reserve3;
+ uint32_t q_reserve4;
+ uint32_t q_reserve5;
+ uint32_t q_reserve6;
+ } q_sahara_hello_packet_response;
+ struct
+ {
+ uint32_t q_image_id;
+ uint32_t q_data_offset;
+ uint32_t q_data_length;
+ } q_sahara_read_packet_data;
+ struct
+ {
+ uint32_t q_image_id;
+ uint32_t q_status;
+ } q_sahara_end_packet_image_tx;
+ struct
+ {
+ } q_sahara_done_packet;
+ struct
+ {
+ uint32_t q_image_tx_status;
+ } q_sahara_done_packet_response;
+ struct
+ {
+ uint64_t q_image_id;
+ uint64_t q_data_offset;
+ uint64_t q_data_length;
+ } q_sahara_read_packet_data_64bit;
+ struct
+ {
+ } q_sahara_reset_packet;
+ struct
+ {
+ } q_sahara_reset_packet_response;
+ };
+};
+#endif
diff --git a/application/qfirehose/src/stream_download_protocol.c b/application/qfirehose/src/stream_download_protocol.c
new file mode 100644
index 0000000..1b10dd4
--- /dev/null
+++ b/application/qfirehose/src/stream_download_protocol.c
@@ -0,0 +1,826 @@
+/*
+ Copyright 2023 Quectel Wireless Solutions Co.,Ltd
+
+ Quectel hereby grants customers of Quectel a license to use, modify,
+ distribute and publish the Software in binary form provided that
+ customers shall have no right to reverse engineer, reverse assemble,
+ decompile or reduce to source code form any portion of the Software.
+ Under no circumstances may customers modify, demonstrate, use, deliver
+ or disclose any portion of the Software in source code form.
+*/
+
+#include "usb_linux.h"
+#include "hostdl_packet.h"
+
+#define true (1 == 1)
+#define false (1 != 1)
+
+#define MAX_SEND_BUFFER_SIZE 1280
+#define MAX_RECEIVE_BUFFER_SIZE 1280
+unsigned char g_Transmit_Buffer[MAX_SEND_BUFFER_SIZE];
+int g_Transmit_Length;
+
+unsigned char g_Receive_Buffer[MAX_RECEIVE_BUFFER_SIZE];
+int g_Receive_Bytes;
+
+static void *stream_usb_handle;
+
+static void dump_buffer(unsigned char *buff, int len)
+{
+ int i = 0;
+
+ dbg_time("dump buffer: %d bytes\n", len);
+ for (i = 0; i < len; i++)
+ {
+ dbg_time("%02x ", buff[i]);
+ }
+ dbg_time("\nend\n");
+}
+
+#define CRC_16_L_SEED 0xFFFF
+#define CRC_TAB_SIZE 256 /* 2^CRC_TAB_BITS */
+#define CRC_16_L_POLYNOMIAL 0x8408
+
+static const uint16_t crc_16_l_table[CRC_TAB_SIZE] = {
+ 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, 0x9dc1, 0xaf5a, 0xbed3,
+ 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
+ 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399,
+ 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
+ 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50,
+ 0xfbef, 0xea66, 0xd8fd, 0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
+ 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, 0x430c, 0x7197, 0x601e,
+ 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
+ 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5,
+ 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
+ 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693,
+ 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
+ 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, 0x3bd3, 0x2a5a,
+ 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
+ 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710,
+ 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
+ 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df,
+ 0x0c60, 0x1de9, 0x2f72, 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
+ 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, 0xf687, 0xc41c, 0xd595,
+ 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
+ 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c,
+ 0x3de3, 0x2c6a, 0x1ef1, 0x0f78};
+
+unsigned short crc_16_l_calc(unsigned char *buf_ptr, int len)
+{
+ int data, crc_16;
+ for (crc_16 = CRC_16_L_SEED; len >= 8; len -= 8, buf_ptr++)
+ {
+ crc_16 = crc_16_l_table[(crc_16 ^ *buf_ptr) & 0x00ff] ^ (crc_16 >> 8);
+ }
+ if (len != 0)
+ {
+ data = ((int)(*buf_ptr)) << (16 - 8);
+
+ while (len-- != 0)
+ {
+ if (((crc_16 ^ data) & 0x01) != 0)
+ {
+ crc_16 >>= 1;
+ crc_16 ^= CRC_16_L_POLYNOMIAL;
+ }
+ else
+ {
+ crc_16 >>= 1;
+ }
+
+ data >>= 1;
+ }
+ }
+ return (~crc_16);
+}
+
+void compute_reply_crc()
+{
+ unsigned short crc = crc_16_l_calc(g_Transmit_Buffer, g_Transmit_Length * 8);
+ g_Transmit_Buffer[g_Transmit_Length] = crc & 0xFF;
+ g_Transmit_Buffer[g_Transmit_Length + 1] = crc >> 8;
+ g_Transmit_Length += 2;
+}
+
+static void compose_packet(unsigned char cmd, unsigned char *parameter, uint32_t parameter_len,
+ unsigned char *data, uint32_t data_len)
+{
+ uint32_t i;
+
+ g_Transmit_Buffer[0] = cmd;
+ if (parameter == NULL) parameter_len = 0;
+ if (data == NULL) data_len = 0;
+ for (i = 0; i < parameter_len; i++)
+ {
+ g_Transmit_Buffer[1 + i] = parameter[i];
+ }
+ for (i = 0; i < data_len; i++)
+ {
+ g_Transmit_Buffer[1 + parameter_len + i] = data[i];
+ }
+ g_Transmit_Length = 1 + parameter_len + data_len;
+ g_Transmit_Buffer[g_Transmit_Length] = 0;
+}
+
+static unsigned char stream_tx_buf[1280];
+#define CHECK_FOR_DATA() \
+ do \
+ { \
+ } while (0)
+#define TRANSMIT_BYTE(_byte) \
+ do \
+ { \
+ stream_tx_buf[j++] = _byte; \
+ } while (0)
+
+static int send_packet(int flag)
+{
+ int i;
+ int ch;
+ int j;
+
+ j = 0;
+
+ CHECK_FOR_DATA();
+
+ /* Since we don't know how long it's been. */
+ if (!!flag)
+ {
+ TRANSMIT_BYTE(0x7E);
+ }
+
+ for (i = 0; i < g_Transmit_Length; i++)
+ {
+ /* we only need to check once every 31 characters, since RX and TX
+ * run at about the same speed, and our RX FIFO is 64 characters
+ */
+ if ((i & 31) == 31) CHECK_FOR_DATA();
+
+ ch = g_Transmit_Buffer[i];
+
+ if (ch == 0x7E || ch == 0x7D)
+ {
+ TRANSMIT_BYTE(0x7D);
+ TRANSMIT_BYTE(0x20 ^ ch); /*lint !e734 */
+ }
+ else
+ {
+ TRANSMIT_BYTE(ch); /*lint !e734 */
+ }
+ }
+
+ CHECK_FOR_DATA();
+ TRANSMIT_BYTE(0x7E);
+
+#if 0
+ /* Hack for USB protocol. If we have an exact multiple of the USB frame
+ * size, then the last frame will not be sent out. The USB standard says
+ * that a "short packet" needs to be sent to flush the data. Two flag
+ * characters can serve as the short packet. Doing it this way, we only
+ * perform this test once on every entire packet from the target, so the
+ * over head is not too much.
+ */
+ if ((j%512) == 0)
+ {
+ TRANSMIT_BYTE (0x7E);
+ TRANSMIT_BYTE (0x7E);
+ }
+#endif
+
+ return (qusb_noblock_write(stream_usb_handle, stream_tx_buf, j, j, 3000, 1) == j) ? 0 : -1;
+}
+
+static int remove_escape_hdlc_flag(unsigned char *buffer, int len)
+{
+ int i = 0;
+ int index = 0;
+ int escape = 0;
+ // dump_buffer(buffer, len);
+ if (len == 0) return 0;
+ // ignore the first HDLC FLAG bytes
+ while (buffer[i] == 0x7e)
+ {
+ i++;
+ }
+ // all bytes is HDLC FLAG
+ if (i == len) return 0;
+ for (; i < len; i++)
+ {
+ if (buffer[i] == 0x7D)
+ {
+ escape = 1;
+ continue;
+ }
+ if (escape == 1)
+ {
+ escape = 0;
+ buffer[i] ^= 0x20;
+ }
+ buffer[index++] = buffer[i];
+ }
+ buffer[index] = 0;
+ // dump_buffer(buffer, index);
+ return index;
+}
+
+static int receive_packet(void)
+{
+ int bytesread = 0;
+ unsigned char *buff = g_Receive_Buffer;
+ if (buff == NULL)
+ {
+ return -1;
+ }
+
+ int idx = 0;
+ do
+ {
+ bytesread =
+ qusb_noblock_read(stream_usb_handle, &buff[idx], MAX_RECEIVE_BUFFER_SIZE, 0, 3000);
+ if (bytesread == 0)
+ {
+ // timeout may be error
+ dbg_time("%s timeout\n", __FUNCTION__);
+ break;
+ }
+ // dump_buffer(&buff[idx], bytesread);
+ idx += bytesread;
+ if (buff[idx - 1] == 0x7e)
+ {
+ // check the packet whether valid.
+ g_Receive_Bytes = remove_escape_hdlc_flag(buff, idx);
+ if (g_Receive_Bytes == 0)
+ {
+ continue;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+ } while (1);
+
+ return 0;
+}
+
+static int handle_hello(void)
+{
+ static const char host_header[] = "QCOM fast download protocol host";
+ // static const char target_header[] = "QCOM fast download protocol targ";
+ // char string1[64];
+ // int size;
+ int err;
+ dbg_time("%s\n", __func__);
+
+ memset(&g_Transmit_Buffer[0], 0, sizeof(g_Transmit_Buffer));
+ g_Transmit_Buffer[HELLO_CMD_OFFSET] = HELLO_REQ;
+ memcpy(&g_Transmit_Buffer[HELLO_MAGIC_NUM_OFFSET], host_header, 32);
+ g_Transmit_Buffer[HELLO_MAX_VER_OFFSET] = STREAM_DLOAD_MAX_VER;
+ g_Transmit_Buffer[HELLO_MIN_VER_OFFSET] = STREAM_DLOAD_MIN_VER;
+ g_Transmit_Buffer[HELLO_MAX_DATA_SZ_1_OFFSET] = 0;
+ g_Transmit_Length = 36;
+
+ compute_reply_crc();
+ send_packet(1);
+
+ int timeout = 5;
+ do
+ {
+ err = receive_packet();
+ if (err == 1)
+ {
+ switch (g_Receive_Buffer[0])
+ {
+ case 0x02: return 1;
+ case 0x0d: continue;
+ default:
+ // dump_buffer(g_Receive_Buffer, 64);
+ return 0;
+ }
+ }
+ else if (err == -1)
+ {
+ dbg_time("error = %d, strerr = %s\n", errno, strerror(errno));
+ return 0;
+ }
+ timeout--;
+ } while (timeout);
+
+ return 0;
+}
+
+static int handle_security_mode(unsigned char trusted)
+{
+ dbg_time("%s trusted = %d\n", __func__, trusted);
+ compose_packet(0x17, &trusted, 1, NULL, 0);
+ compute_reply_crc();
+ send_packet(1);
+ int timeout = 5;
+ do
+ {
+ if (receive_packet() == 1)
+ {
+ switch (g_Receive_Buffer[0])
+ {
+ case 0x18: return 1;
+ default: return 0;
+ }
+ }
+ else
+ {
+ timeout--;
+ if (timeout == 0)
+ {
+ dbg_time("%s timeout\n", __FUNCTION__);
+ break; // return 0; -Werror,-Wunreachable-code-return
+ }
+ }
+ } while (1);
+ return 0;
+}
+/*
+set download flag in module, quectel custom command,
+if flag : reboot, module will enter DM
+if not flag: reboot normal
+*/
+static int handle_quectel_download_flag(unsigned char mode)
+{
+ // byte mode = 1;
+ compose_packet(0x60, &mode, 1, NULL, 0);
+ compute_reply_crc();
+ send_packet(1);
+ int timeout = 5;
+ do
+ {
+ if (receive_packet() == 1)
+ {
+ switch (g_Receive_Buffer[0])
+ {
+ case 0x61:
+ switch (g_Receive_Buffer[1])
+ {
+ case 0x00: return 1;
+ default: return 0;
+ }
+ break;
+ case 0x0E: dbg_time("Invalid command"); return 2;
+ default: dump_buffer(g_Receive_Buffer, 64); return 0;
+ }
+ }
+ else
+ {
+ timeout--;
+ if (timeout == 0)
+ {
+ dbg_time("%s timeout\n", __FUNCTION__);
+ return 0;
+ }
+ }
+ } while (1);
+}
+
+static const char *stream_firehose_dir;
+static int stread_fread(const char *filename, void **pp_filebuf)
+{
+ int filesize = 0;
+ FILE *fp;
+ char fullpath[MAX_PATH * 2];
+
+ snprintf(fullpath, sizeof(fullpath), "%.240s/../%.240s", stream_firehose_dir, filename);
+ fp = fopen(fullpath, "rb");
+ if (fp == NULL)
+ {
+ dbg_time("fail to fope %s, errno: %d (%s)\n", fullpath, errno, strerror(errno));
+ return 0;
+ }
+
+ fseek(fp, 0, SEEK_END);
+ filesize = ftell(fp);
+
+ *pp_filebuf = malloc(filesize);
+ if (pp_filebuf == NULL)
+ {
+ dbg_time("fail to malloc %d, errno: %d (%s)\n", filesize, errno, strerror(errno));
+ if (fp)
+ {
+ fclose(fp);
+ fp = NULL;
+ }
+ return 0;
+ }
+
+ fseek(fp, 0, SEEK_SET);
+ filesize = fread(*pp_filebuf, 1, filesize, fp);
+ fclose(fp);
+
+ dbg_time("%s filename=%s, filesize=%d\n", __func__, filename, filesize);
+ return filesize;
+}
+
+static int handle_parti_tbl(unsigned char override)
+{
+ int timeout = 5;
+ int filesize;
+ void *filebuf;
+ const char *partition_path = "partition.mbn";
+ dbg_time("%s override = %d\n", __func__, override);
+
+ filesize = stread_fread(partition_path, &filebuf);
+ if (filesize <= 0)
+ {
+ if (filebuf)
+ {
+ free(filebuf);
+ filebuf = NULL;
+ }
+ return 0;
+ }
+
+ compose_packet(0x19, &override, 1, filebuf, filesize);
+ compute_reply_crc();
+ send_packet(1);
+ free(filebuf);
+
+ do
+ {
+ if (receive_packet() == 1)
+ {
+ dbg_time("handle_parti_tbl command = %02x, status = %02x\n", g_Receive_Buffer[0],
+ g_Receive_Buffer[1]);
+ switch (g_Receive_Buffer[0])
+ {
+ case 0x1a:
+ switch (g_Receive_Buffer[1])
+ {
+ case 0x00: return 1;
+ case 0x01: // 0x1 this means that the original partition is different from
+ // the current partition,try to send partition
+ return 0;
+ case 0x02: // 0x2 Partition table format not recognized, does not accept
+ // override
+ return 0;
+ case 0x03: // 0x3 Erase operation failed
+ return 0;
+ break;
+ default: return 0;
+ }
+ default: return 0;
+ }
+ }
+ else
+ {
+ timeout--;
+ if (timeout == 0)
+ {
+ dbg_time("%s timeout\n", __FUNCTION__);
+ return 0;
+ }
+ }
+ } while (1);
+}
+
+static int handle_reset(void)
+{
+ dbg_time("%s\n", __func__);
+ compose_packet(0x0b, NULL, 0, NULL, 0);
+ compute_reply_crc();
+ send_packet(1);
+#if 1
+ return 1;
+#else
+ int timeout = 5;
+ do
+ {
+ if (receive_packet() == 1)
+ {
+ switch (g_Receive_Buffer[0])
+ {
+ case 0x0c: return 1;
+ case 0x0d: continue;
+ default: dump_buffer(g_Receive_Buffer, 64); return 0;
+ }
+ }
+ else
+ {
+ timeout--;
+ if (timeout == 0)
+ {
+ dbg_time("%s timeout\n", __FUNCTION__);
+ return 0;
+ }
+ }
+ } while (1);
+#endif
+}
+
+/******pkt_open_multi_image*******/
+
+static void pkt_open_multi_image(unsigned char mode, unsigned char *data, uint32_t size)
+{
+ compose_packet(0x1b, &mode, 1, data, size);
+ compute_reply_crc();
+}
+
+static int handle_openmulti(uint32_t size, unsigned char *data)
+{
+ int timeout = 5;
+ unsigned char mode = 0x0e;
+
+ pkt_open_multi_image(mode, data, size);
+ send_packet(1);
+ do
+ {
+ if (receive_packet() == 1)
+ {
+ switch (g_Receive_Buffer[0])
+ {
+ case 0x1c: return 1;
+ case 0x0d: continue;
+ default: return 0;
+ }
+ }
+ else
+ {
+ timeout--;
+ if (timeout == 0)
+ {
+ dbg_time("%s timeout\n", __FUNCTION__);
+ break;
+ }
+ }
+ } while (1);
+ return 0;
+}
+
+/******pkt_write_multi_image*******/
+static void pkt_write_multi_image(uint32_t addr, unsigned char *data, uint16_t size)
+{
+ unsigned char parameter[4] = {(unsigned char)(addr)&0xff, (unsigned char)(addr >> 8) & 0xff,
+ (unsigned char)(addr >> 16) & 0xff,
+ (unsigned char)(addr >> 24) & 0xff};
+ compose_packet(0x07, parameter, 4, data, size);
+ compute_reply_crc();
+}
+
+static int handle_write(unsigned char *data, uint32_t size)
+{
+ // uint32_t total_size;
+ uint32_t addr = 0;
+ uint32_t writesize;
+ uint32_t buffer_size = 1024;
+ // int loop = 1;
+ int retry_cnt = 3; // if send failed,send again
+ int ret;
+
+ // total_size = size;
+ while (size)
+ {
+ writesize = (size < buffer_size) ? size : buffer_size;
+
+ pkt_write_multi_image(addr, data, writesize);
+ start_send_packet:
+ ret = send_packet(1);
+ if (0 != ret)
+ {
+ dbg_time("io read/write failed\n");
+ return 0;
+ }
+ if (receive_packet() == 1)
+ {
+ switch (g_Receive_Buffer[0])
+ {
+ case 0x08:
+ size -= writesize;
+ addr += writesize;
+ // retry_cnt=5;
+ break;
+ default:
+ goto retry_send_packet;
+ // return 0;
+ }
+ }
+ else
+ {
+ retry_send_packet:
+ retry_cnt--;
+ if (retry_cnt > 0)
+ {
+ goto start_send_packet;
+ }
+ else
+ {
+ dbg_time("value is [0x%02x]", g_Receive_Buffer[0]);
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+/******PARTITION*******/
+static int handle_close(void)
+{
+ int timeout = 5;
+ compose_packet(0x15, NULL, 0, NULL, 0);
+ compute_reply_crc();
+ send_packet(1);
+
+ do
+ {
+ if (receive_packet() == 1)
+ {
+ switch (g_Receive_Buffer[0])
+ {
+ case 0x16: return 1;
+ default: return 0;
+ }
+ }
+ else
+ {
+ timeout--;
+ if (timeout == 0)
+ {
+ dbg_time("%s timeout\n", __FUNCTION__);
+ break;
+ }
+ }
+ } while (1);
+ return 0;
+}
+
+static int do_flash_mbn(const char *partion, const char *filepath)
+{
+ int result = false;
+ void *filebuf = NULL;
+ int filesize = 0;
+
+ dbg_time("%s %s\n", __func__, partion);
+
+ if (filepath)
+ {
+ filesize = stread_fread(filepath, &filebuf);
+ if (filesize <= 0)
+ {
+ if (filebuf)
+ {
+ free(filebuf);
+ filebuf = NULL;
+ }
+ return 0;
+ }
+ }
+ else
+ {
+ filesize = 4 * 1024;
+ filebuf = (unsigned char *)malloc(filesize);
+ if (filebuf == NULL)
+ {
+ return 0;
+ }
+
+ memset(filebuf, 0x00, filesize);
+ }
+
+ result = handle_openmulti(strlen(partion) + 1, (unsigned char *)partion);
+ if (result == false)
+ {
+ dbg_time("%s open failed\n", partion);
+ goto __fail;
+ }
+
+ dbg_time("sending '%s' (%dKB)\n", partion, (int)(filesize / 1024));
+
+ result = handle_write(filebuf, filesize);
+ if (result == false)
+ {
+ dbg_time("%s download failed\n", partion);
+ goto __fail;
+ }
+
+ result = handle_close();
+ if (result == false)
+ {
+ dbg_time("%s close failed.\n", partion);
+ goto __fail;
+ }
+
+ dbg_time("OKAY\n");
+
+__fail:
+ free(filebuf);
+
+ return result;
+}
+
+int stream_download(const char *firehose_dir, void *usb_handle, unsigned qusb_zlp_mode)
+{
+ (void)qusb_zlp_mode;
+ stream_usb_handle = usb_handle;
+ stream_firehose_dir = firehose_dir;
+
+ if (handle_hello() == false)
+ {
+ dbg_time("Send hello command fail\n");
+ return false;
+ }
+
+ /*
+ hello packet will set dload flag in module, when upgrade interrup, restart module,module will
+ enter dm(quectel sbl)
+ */
+ if (handle_security_mode(1) == false)
+ {
+ dbg_time("Send trust command fail\n");
+ return false;
+ }
+
+ if (handle_parti_tbl(0) == false)
+ {
+ dbg_time("----------------------------------\n");
+ dbg_time("Detect partition mismatch.\n");
+ dbg_time("Download parition with override.\n");
+ dbg_time("----------------------------------\n");
+
+ if (handle_parti_tbl(1) == false)
+ {
+ dbg_time("override failed. \n");
+ return false;
+ }
+
+ /*
+ partition is not match, the download flag will be clear, so set it again, reset will clear
+ it
+ */
+ if (handle_quectel_download_flag(1) == false)
+ {
+ dbg_time("Set Quectel download flag failed\n");
+ }
+ else
+ {
+ dbg_time("Set Quectel download flag successfully\n");
+ }
+ }
+
+#if 1
+ if (do_flash_mbn("0:SBL", "sbl1.mbn") == false)
+ {
+ return false;
+ }
+#endif
+
+ if (handle_reset() == false)
+ {
+ dbg_time("Send reset command failed\n");
+ return false;
+ }
+
+ dbg_time("%s successful\n", __func__);
+
+ return true;
+}
+
+// retrieve module soft revision
+
+typedef struct
+{
+ unsigned char cmd_code;
+ unsigned char version;
+ unsigned char reserved[2];
+ unsigned char msm[4];
+ unsigned char mobile_modle_number[4];
+ unsigned char mobile_software_revision[1];
+} __attribute__((packed)) extended_build_id_response_t;
+
+int retrieve_soft_revision(void *usb_handle, uint8_t *mobile_software_revision, unsigned length)
+{
+ /*
+ 80-v1294-1_yyd_serial_interface_control_document_(icd)_for_cdma_dual-mode_subscriber_station_data
+ 3.4.122 Extended Build ID
+ */
+ uint8_t req1[] = {0x7E, 0x7C, 0x93, 0x49, 0x7E};
+ int ret;
+ uint8_t *rx_buff = malloc(2048);
+
+ memset(mobile_software_revision, 0x00, length);
+
+ if (rx_buff == NULL) return 0;
+
+ ret = qusb_noblock_write(usb_handle, req1, sizeof(req1), sizeof(req1), 1000, 0);
+ if (ret > 0)
+ {
+ ret = qusb_noblock_read(usb_handle, rx_buff, 2048, 1, 3000);
+ if (ret > 0)
+ {
+ if (rx_buff[0] == 0x7C && rx_buff[ret - 1] == 0x7E)
+ {
+ extended_build_id_response_t *rsp = (extended_build_id_response_t *)rx_buff;
+ (void)length;
+ memcpy(mobile_software_revision, rsp->mobile_software_revision,
+ strlen((const char *)rsp->mobile_software_revision));
+ }
+ }
+ }
+
+ free(rx_buff);
+ return (mobile_software_revision[0] != '\0');
+}
diff --git a/application/qfirehose/src/usb2tcp.c b/application/qfirehose/src/usb2tcp.c
new file mode 100644
index 0000000..6b8ce7e
--- /dev/null
+++ b/application/qfirehose/src/usb2tcp.c
@@ -0,0 +1,381 @@
+/*
+ Copyright 2023 Quectel Wireless Solutions Co.,Ltd
+
+ Quectel hereby grants customers of Quectel a license to use, modify,
+ distribute and publish the Software in binary form provided that
+ customers shall have no right to reverse engineer, reverse assemble,
+ decompile or reduce to source code form any portion of the Software.
+ Under no circumstances may customers modify, demonstrate, use, deliver
+ or disclose any portion of the Software in source code form.
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 20)
+#include
+#else
+#include
+#endif
+//#include
+#include "usb_linux.h"
+#include //for __BYTE_ORDER
+char *inet_ntoa(struct in_addr in);
+
+#define dprintf dbg_time
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+#define MAX_USBFS_BULK_IN_SIZE (4 * 1024)
+#define MAX_USBFS_BULK_OUT_SIZE (16 * 1024)
+
+static uint32_t cpu_to_le32(uint32_t v32)
+{
+ uint32_t tmp = v32;
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#else
+ unsigned char *s = (unsigned char *)(&v32);
+ unsigned char *d = (unsigned char *)(&tmp);
+ d[0] = s[3];
+ d[1] = s[2];
+ d[2] = s[1];
+ d[3] = s[0];
+#endif
+ return tmp;
+}
+#define le32_to_cpu(_v32) cpu_to_le32(_v32)
+
+static int qusb_control[2];
+
+static int noblock_full_read(int fd, void *pbuf, ssize_t size)
+{
+ ssize_t cur = 0;
+
+ while (cur < size)
+ {
+ ssize_t ret = read(fd, (char *)pbuf + cur, size - cur);
+
+ if (ret > 0)
+ cur += ret;
+ else if (ret < 0 && errno == EAGAIN)
+ {
+ struct pollfd pollfds[] = {{fd, POLLIN, 0}};
+ poll(pollfds, 1, -1);
+ if (pollfds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) break;
+ }
+ else
+ {
+ dprintf("fd=%d read=%zd, errno: %d (%s)\n", fd, ret, errno, strerror(errno));
+ break;
+ }
+ }
+
+ if (cur != size)
+ {
+ dprintf("%s fd=%d cur=%zd, size=%zd\n", __func__, fd, cur, size);
+ }
+
+ return cur;
+}
+
+static ssize_t noblock_full_write(int fd, const void *pbuf, ssize_t size)
+{
+ ssize_t cur = 0;
+
+ while (cur < size)
+ {
+ ssize_t ret = write(fd, (char *)pbuf + cur, size - cur);
+ if (ret > 0)
+ cur += ret;
+ else if (ret <= 0 && errno == EAGAIN)
+ {
+ struct pollfd pollfds[] = {{fd, POLLOUT, 0}};
+ poll(pollfds, 1, -1);
+ if (pollfds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) break;
+ }
+ else
+ {
+ dprintf("fd=%d write=%zd, errno: %d (%s)\n", fd, ret, errno, strerror(errno));
+ break;
+ }
+ }
+
+ if (cur != size)
+ {
+ dprintf("%s fd=%d cur=%zd, size=%zd\n", __func__, fd, cur, size);
+ }
+
+ return cur;
+}
+
+static void *usb_bulk_read_thread(void *arg)
+{
+ const void *usb_handle = arg;
+ void *buf = malloc(MAX_USBFS_BULK_IN_SIZE);
+ int fd = qusb_control[1];
+
+ if (buf == NULL) return NULL;
+
+ while (usb_handle)
+ {
+ int count = qusb_noblock_read(usb_handle, buf, MAX_USBFS_BULK_IN_SIZE, 1, 30000);
+
+ if (count > 0)
+ {
+ count = write(fd, buf, count);
+ count = read(fd, buf, 32); // wait usb2tcp_main read
+ if (count <= 0)
+ {
+ dprintf("read=%d\n", count);
+ break;
+ }
+ }
+ else if (count <= 0)
+ {
+ break;
+ }
+ }
+
+ close(fd);
+ free(buf);
+ return NULL;
+}
+
+static int qusb_open(const void *usb_handle)
+{
+ int fd = -1;
+ pthread_t thread_id;
+
+ pthread_attr_t usb_thread_attr;
+ pthread_attr_init(&usb_thread_attr);
+ pthread_attr_setdetachstate(&usb_thread_attr, PTHREAD_CREATE_DETACHED);
+
+ socketpair(AF_LOCAL, SOCK_STREAM, 0, qusb_control);
+ pthread_create(&thread_id, &usb_thread_attr, usb_bulk_read_thread, (void *)usb_handle);
+
+ fd = qusb_control[0];
+
+ // pthread_attr_destroy(&usb_thread_attr); //aaron 2023.07.27
+ return fd;
+}
+
+static ssize_t qusb_read(int fd, void *pbuf, size_t size) { return read(fd, pbuf, size); }
+
+static int create_tcp_server(int socket_port)
+{
+ int sockfd = -1;
+ int reuse_addr = 1;
+ struct sockaddr_in sockaddr;
+
+ dprintf("%s tcp_port=%d\n", __func__, socket_port);
+ /*Create server socket*/
+ sockfd = socket(AF_INET, SOCK_STREAM, 0);
+ if (sockfd <= 0) return sockfd;
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ sockaddr.sin_family = AF_INET;
+ sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
+ sockaddr.sin_port = htons(socket_port);
+
+ setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse_addr, sizeof(reuse_addr));
+ if (bind(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0)
+ {
+ close(sockfd);
+ dprintf("%s bind %d errno: %d (%s)\n", __func__, socket_port, errno, strerror(errno));
+ return -1;
+ }
+
+ return sockfd;
+}
+
+static int wait_client_connect(int server_fd)
+{
+ int client_fd = -1;
+ unsigned char addr[128];
+ socklen_t alen = sizeof(addr);
+
+ dprintf("%s\n", __func__);
+ listen(server_fd, 1);
+ client_fd = accept(server_fd, (struct sockaddr *)addr, &alen);
+ if (client_fd <= 0) return client_fd;
+
+ if (client_fd > 0)
+ {
+ struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
+ dprintf("clientfd = %d %s:%d connect\n", client_fd, inet_ntoa(addr_in->sin_addr),
+ addr_in->sin_port);
+ }
+
+ return client_fd;
+}
+
+int usb2tcp_main(const void *usb_handle, int tcp_port, unsigned qusb_zlp_mode)
+{
+ void *pbuf = malloc(MAX_USBFS_BULK_OUT_SIZE);
+ int server_fd = -1, client_fd = -1, usb_fd = -1, size = -1;
+ TLV_USB tlv_usb;
+
+ if (pbuf == NULL) return -1;
+
+ server_fd = create_tcp_server(tcp_port);
+ dprintf("server_fd=%d\n", server_fd);
+ if (server_fd <= 0)
+ {
+ dprintf("Fail create_tcp_server\n");
+ goto _out;
+ }
+
+ if (client_fd <= 0)
+ {
+ client_fd = wait_client_connect(server_fd);
+ if (client_fd < 0)
+ {
+ dprintf("Fail wait_client_connect\n");
+ goto _out;
+ }
+ }
+
+ usb_fd = qusb_open(usb_handle);
+ dprintf("usb_fd = %d\n", usb_fd);
+
+ tlv_usb.tag = cpu_to_le32(Q_USB2TCP_VERSION);
+ tlv_usb.length = cpu_to_le32(12);
+ tlv_usb.idVendor = cpu_to_le32(0x05c6);
+ tlv_usb.idProduct = cpu_to_le32(0x9008);
+ tlv_usb.interfaceNum = cpu_to_le32(1);
+ if (write(client_fd, &tlv_usb, sizeof(tlv_usb)) == -1)
+ {
+ };
+
+ fcntl(usb_fd, F_SETFL, fcntl(usb_fd, F_GETFL) | O_NONBLOCK);
+ fcntl(client_fd, F_SETFL, fcntl(client_fd, F_GETFL) | O_NONBLOCK);
+
+ while (usb_fd > 0 && client_fd > 0)
+ {
+ struct pollfd pollfds[] = {{usb_fd, POLLIN, 0}, {client_fd, POLLIN, 0}};
+ int ne, ret, nevents = sizeof(pollfds) / sizeof(pollfds[0]);
+
+ do
+ {
+ ret = poll(pollfds, nevents, -1);
+ } while (ret < 0 && errno == EINTR);
+
+ if (ret <= 0)
+ {
+ dprintf("%s poll=%d, errno: %d (%s)\n", __func__, ret, errno, strerror(errno));
+ goto _hangup;
+ }
+
+ if (pollfds[0].revents & (POLLERR | POLLHUP | POLLNVAL))
+ {
+ dprintf("%s poll usb_fd = %d, revents = %04x\n", __func__, usb_fd, pollfds[0].revents);
+ goto _hangup;
+ }
+
+ if (pollfds[1].revents & (POLLERR | POLLHUP | POLLNVAL))
+ {
+ dprintf("%s poll client_fd = %d, revents = %04x\n", __func__, client_fd,
+ pollfds[1].revents);
+ goto _hangup;
+ }
+
+ for (ne = 0; ne < nevents; ne++)
+ {
+ int fd = pollfds[ne].fd;
+ TLV tlv = {Q_USB2TCP_VERSION, 0};
+
+ if ((pollfds[ne].revents & POLLIN) == 0) continue;
+
+ if (fd == usb_fd)
+ {
+ size = qusb_read(usb_fd, pbuf, MAX_USBFS_BULK_IN_SIZE);
+ if (size <= 0)
+ {
+ dprintf("usb_fd=%d read=%d, errno: %d (%s)\n", fd, size, errno,
+ strerror(errno));
+ goto _hangup;
+ ;
+ }
+ if (write(usb_fd, pbuf, 1) == -1)
+ {
+ }; // wakeup usb_bulk_read_thread
+
+ tlv.tag = cpu_to_le32(Q_USB2TCP_VERSION);
+ tlv.length = cpu_to_le32(size);
+ if (sizeof(tlv) != noblock_full_write(client_fd, &tlv, sizeof(tlv)))
+ {
+ goto _hangup;
+ break;
+ }
+
+ if (size != noblock_full_write(client_fd, pbuf, size))
+ {
+ goto _hangup;
+ break;
+ }
+ }
+ else if (fd == client_fd)
+ {
+ size = noblock_full_read(client_fd, &tlv, sizeof(tlv));
+ if (size != sizeof(tlv))
+ {
+ dprintf("client_fd=%d read=%d, errno: %d (%s)\n", fd, size, errno,
+ strerror(errno));
+ goto _hangup;
+ }
+
+ if (le32_to_cpu(tlv.tag) != Q_USB2TCP_VERSION)
+ {
+ break;
+ }
+
+ size = le32_to_cpu(tlv.length);
+ if (size != noblock_full_read(client_fd, pbuf, size))
+ {
+ goto _hangup;
+ break;
+ }
+ qusb_noblock_write(usb_handle, pbuf, size, size, 3000, qusb_zlp_mode);
+ }
+ }
+ }
+
+_hangup:
+ if (usb_fd > 0)
+ {
+ close(usb_fd);
+ usb_fd = -1;
+ }
+ if (client_fd > 0)
+ {
+ close(client_fd);
+ client_fd = -1;
+ }
+
+_out:
+ if (server_fd > 0)
+ {
+ close(server_fd);
+ server_fd = -1;
+ }
+
+ free(pbuf);
+ return 0;
+}
diff --git a/application/qfirehose/src/usb_linux.c b/application/qfirehose/src/usb_linux.c
new file mode 100644
index 0000000..a2f62c4
--- /dev/null
+++ b/application/qfirehose/src/usb_linux.c
@@ -0,0 +1,1657 @@
+/*
+ Copyright 2023 Quectel Wireless Solutions Co.,Ltd
+
+ Quectel hereby grants customers of Quectel a license to use, modify,
+ distribute and publish the Software in binary form provided that
+ customers shall have no right to reverse engineer, reverse assemble,
+ decompile or reduce to source code form any portion of the Software.
+ Under no circumstances may customers modify, demonstrate, use, deliver
+ or disclose any portion of the Software in source code form.
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 20)
+#include
+#else
+#include
+#endif
+#include
+#include
+#include
+#include "usb_linux.h"
+
+int edl_pcie_mhifd = -1;
+int switch_to_edl_mode(void *usb_handle);
+
+extern uint32_t inet_addr(const char *);
+
+#define MAX_USBFS_BULK_IN_SIZE (4 * 1024)
+#define EC20_MAX_INF 4
+#define MKDEV(__ma, __mi) (((__ma & 0xfff) << 8) | (__mi & 0xff) | ((__mi & 0xfff00) << 12))
+
+struct quectel_usb_device
+{
+ char devname[64];
+ int desc;
+ int ttyfd;
+ int idVendor;
+ int idProduct;
+ uint8_t bNumInterfaces;
+ uint8_t intr_ep[EC20_MAX_INF];
+ uint8_t bulk_ep_in[EC20_MAX_INF];
+ uint8_t bulk_ep_out[EC20_MAX_INF];
+ int wMaxPacketSize[EC20_MAX_INF];
+ int control[EC20_MAX_INF][2];
+};
+
+static struct quectel_usb_device quectel_9x07;
+static int tcp_socket_fd = -1;
+static int usb_dm_interface = 0;
+
+typedef struct
+{
+ char diag[32];
+ char edl[32];
+ char bhi[32];
+} pcie_port_classification;
+
+typedef struct
+{
+ pcie_port_classification pcie_port_one[10];
+ int pcie_port_number;
+} pcie_port;
+
+static pcie_port pcie_port_defult = {{{"/dev/mhi_DIAG", "/dev/mhi_EDL", "/dev/mhi_BHI"},
+ {"/dev/mhi_DIAG1", "/dev/mhi_EDL1", "/dev/mhi_BHI1"},
+ {"/dev/mhi_DIAG2", "/dev/mhi_EDL2", "/dev/mhi_BHI2"},
+ {"/dev/mhi_DIAG3", "/dev/mhi_EDL3", "/dev/mhi_BHI3"},
+ {"/dev/mhi_DIAG4", "/dev/mhi_EDL4", "/dev/mhi_BHI4"},
+ {"/dev/mhi_DIAG5", "/dev/mhi_EDL5", "/dev/mhi_BHI5"},
+ {"/dev/mhi_DIAG6", "/dev/mhi_EDL6", "/dev/mhi_BHI6"},
+ {"/dev/mhi_DIAG7", "/dev/mhi_EDL7", "/dev/mhi_BHI7"},
+ {"/dev/mhi_DIAG8", "/dev/mhi_EDL8", "/dev/mhi_BHI8"},
+ {"/dev/mhi_DIAG9", "/dev/mhi_EDL9", "/dev/mhi_BHI9"}},
+ 10};
+
+static int strStartsWith(const char *line, const char *prefix)
+{
+ if (!prefix || prefix[0] == '\0') return 1;
+
+ for (; *line != '\0' && *prefix != '\0'; line++, prefix++)
+ {
+ if (*line != *prefix)
+ {
+ return 0;
+ }
+ }
+
+ return *prefix == '\0';
+}
+
+static int strEndsWith(const char *line, const char *suffix)
+{
+ size_t a, b;
+
+ if (!suffix || suffix[0] == '\0') return 1;
+
+ a = strlen(line);
+ b = strlen(suffix);
+ return (a >= b) && (strcmp(line + (a - b), suffix) == 0);
+}
+
+static const char *ctimespec(const struct timespec *ts, char *time_name, size_t len)
+{
+ time_t ltime = ts->tv_sec;
+ struct tm *currtime = localtime(<ime);
+ if (currtime == NULL)
+ {
+ return NULL;
+ }
+
+ snprintf(time_name, len, "%04d%02d%02d_%02d:%02d:%02d", (currtime->tm_year + 1900), (currtime->tm_mon + 1), currtime->tm_mday, currtime->tm_hour, currtime->tm_min,
+ currtime->tm_sec);
+ return time_name;
+}
+
+static int quectel_get_sysinfo_by_uevent(const char *uevent, MODULE_SYS_INFO *pSysInfo)
+{
+ FILE *fp;
+ char line[MAX_PATH];
+
+ if (!pSysInfo)
+ {
+ dbg_time("pSysInfo is NULL, errno: %d (%s)\n", errno, strerror(errno));
+ return 0;
+ }
+
+ memset(pSysInfo, 0x00, sizeof(MODULE_SYS_INFO));
+
+ fp = fopen(uevent, "r");
+ if (fp == NULL)
+ {
+ dbg_time("fail to fopen %s, errno: %d (%s)\n", uevent, errno, strerror(errno));
+ return 0;
+ }
+
+ // dbg_time("%s\n", uevent);
+ while (fgets(line, sizeof(line), fp))
+ {
+ if (line[strlen(line) - 1] == '\n' || line[strlen(line) - 1] == '\r')
+ {
+ line[strlen(line) - 1] = '\0';
+ }
+
+ // dbg_time("%s\n", line);
+ if (strStartsWith(line, "MAJOR="))
+ {
+ pSysInfo->MAJOR = atoi(&line[strlen("MAJOR=")]);
+ }
+ else if (strStartsWith(line, "MINOR="))
+ {
+ pSysInfo->MINOR = atoi(&line[strlen("MINOR=")]);
+ }
+ else if (strStartsWith(line, "DEVNAME="))
+ {
+ strncpy(pSysInfo->DEVNAME, &line[strlen("DEVNAME=")], sizeof(pSysInfo->DEVNAME));
+ }
+ else if (strStartsWith(line, "DEVTYPE="))
+ {
+ strncpy(pSysInfo->DEVTYPE, &line[strlen("DEVTYPE=")], sizeof(pSysInfo->DEVTYPE));
+ }
+ else if (strStartsWith(line, "PRODUCT="))
+ {
+ strncpy(pSysInfo->PRODUCT, &line[strlen("PRODUCT=")], sizeof(pSysInfo->PRODUCT));
+ }
+ }
+
+ fclose(fp);
+
+ return 1;
+}
+
+// the return value is the number of quectel modules
+int auto_find_quectel_modules(char *module_sys_path, unsigned size, const char *product, const struct timespec *atime)
+{
+ const char *base = "/sys/bus/usb/devices";
+ DIR *busdir = NULL;
+ struct dirent *de = NULL;
+ int count = 0;
+
+ busdir = opendir(base);
+ if (busdir == NULL) return -1;
+
+ while ((de = readdir(busdir)))
+ {
+ static char uevent[MAX_PATH];
+ static MODULE_SYS_INFO sysinfo;
+
+ if (!isdigit(de->d_name[0])) continue;
+
+ snprintf(uevent, sizeof(uevent), "%.24s/%.16s/uevent", base, de->d_name);
+ if (!quectel_get_sysinfo_by_uevent(uevent, &sysinfo)) continue;
+
+ if (sysinfo.MAJOR != 189) continue;
+
+ // dbg_time("MAJOR=%d, MINOR=%d, DEVNAME=%s, DEVTYPE=%s, PRODUCT=%s\n",
+ // sysinfo.MAJOR, sysinfo.MINOR, sysinfo.DEVNAME, sysinfo.DEVTYPE, sysinfo.PRODUCT);
+
+ if (sysinfo.DEVTYPE[0] == '\0' || strStartsWith(sysinfo.DEVTYPE, "usb_device") == 0) continue;
+
+ if (sysinfo.PRODUCT[0] == '\0')
+ {
+ continue;
+ }
+
+ if (!(strStartsWith(sysinfo.PRODUCT, "2c7c/") //
+ || strStartsWith(sysinfo.PRODUCT, "5c6/9008") //
+ || strStartsWith(sysinfo.PRODUCT, "5c6/901f") //
+ || strStartsWith(sysinfo.PRODUCT, "5c6/9091") //
+ || strStartsWith(sysinfo.PRODUCT, "5c6/90db") //
+ || strStartsWith(sysinfo.PRODUCT, "3c93/ffff") //
+ || strStartsWith(sysinfo.PRODUCT, "3763/3c93")))
+ {
+ continue;
+ }
+
+ if (strStartsWith(sysinfo.PRODUCT, "5c6/90db"))
+ {
+ g_is_module_adb_entry_edl += 1;
+ }
+
+ if ((strStartsWith(sysinfo.PRODUCT, "2c7c/6") || strStartsWith(sysinfo.PRODUCT, "2c7c/8")) && (sysinfo.PRODUCT[strlen("2c7c/6000")] == '/')) // skip ASR&HISI modules
+ {
+ if ((strStartsWith(sysinfo.PRODUCT, "2c7c/6008")) || strStartsWith(sysinfo.PRODUCT, "2c7c/6009"))
+ {
+ // EM061KGL, not ASR module, do not skip
+ }
+ else
+ continue;
+ }
+
+ if (product && !strStartsWith(sysinfo.PRODUCT, product))
+ {
+ dbg_time("skip %.24s/%s for PRODUCT %s is not %s\n", base, de->d_name, sysinfo.PRODUCT, product);
+ continue;
+ }
+
+ if (atime)
+ {
+ struct timespec this_atime;
+ int speed;
+
+ snprintf(uevent, sizeof(uevent), "%.24s/%.16s", base, de->d_name);
+ if (qusb_read_speed_atime(uevent, &this_atime, &speed))
+ {
+ if ((this_atime.tv_sec < atime->tv_sec) || (this_atime.tv_sec == atime->tv_sec && this_atime.tv_nsec < atime->tv_nsec))
+ {
+ char t1[64], t2[64];
+
+ dbg_time("skip %.24s/%s for atime {%s} old than {%s}\n", base, de->d_name, ctimespec(&this_atime, t1, sizeof(t1)), ctimespec(atime, t2, sizeof(t2)));
+ continue;
+ }
+ }
+ }
+
+ snprintf(module_sys_path, size, "%.24s/%s", base, de->d_name);
+ count++;
+ dbg_time("[%d] %s %s\n", count, module_sys_path, sysinfo.PRODUCT);
+ }
+
+ closedir(busdir);
+ return count;
+}
+
+void quectel_get_ttyport_by_syspath(const char *module_sys_path, char *module_port_name, unsigned size)
+{
+ char infname[256];
+ DIR *infdir = NULL;
+ struct dirent *de = NULL;
+
+ module_port_name[0] = '\0';
+
+ sprintf(infname, "%s:1.%d", module_sys_path, usb_dm_interface);
+ infdir = opendir(infname);
+ if (infdir == NULL) return;
+
+ while ((de = readdir(infdir)))
+ {
+ if (strStartsWith(de->d_name, "ttyUSB"))
+ {
+ snprintf(module_port_name, size, "/dev/%s", de->d_name);
+ break;
+ }
+ else if (!strncmp(de->d_name, "tty", strlen("tty")))
+ {
+ sprintf(infname, "%s:1.%d/tty", module_sys_path, usb_dm_interface);
+ closedir(infdir);
+ infdir = opendir(infname);
+ if (infdir == NULL) break;
+ }
+ }
+
+ if (infdir) closedir(infdir);
+}
+
+static void quectel_fixup_sysport(const char *module_port_name, char *sysport, unsigned size)
+{
+ char syspath[MAX_PATH + 16];
+ const char *sys_base = "/sys/class/tty";
+ DIR *sys_dir = NULL;
+ struct dirent *dev = NULL;
+
+ sysport[0] = '\0';
+ sys_dir = opendir(sys_base);
+ if (!sys_dir)
+ {
+ dbg_time("fail to opendir('%s'), errno: %d (%s)\n", sys_base, errno, strerror(errno));
+ return;
+ }
+
+ while (NULL != (dev = readdir(sys_dir)))
+ {
+ if (!strncasecmp("ttyUSB", dev->d_name, strlen("ttyUSB")))
+ {
+ MODULE_SYS_INFO sysinfo;
+
+ snprintf(syspath, sizeof(syspath), "%.24s/%.16s/uevent", sys_base, dev->d_name);
+ if (quectel_get_sysinfo_by_uevent(syspath, &sysinfo))
+ {
+ struct stat buf;
+ dev_t devt;
+
+ devt = makedev(sysinfo.MAJOR, sysinfo.MINOR);
+ if (!stat(module_port_name, &buf) && buf.st_rdev == devt)
+ {
+ snprintf(sysport, size, "/sys/class/tty/%.16s", dev->d_name);
+ break;
+ }
+ }
+ }
+ }
+ closedir(sys_dir);
+}
+
+void quectel_get_syspath_name_by_ttyport(const char *module_port_name, char *module_sys_path, unsigned size)
+{
+ char syspath[MAX_PATH];
+ char sysport[64];
+ int count;
+ char *pchar = NULL;
+ char dm_tty[24];
+
+ snprintf(dm_tty, sizeof(dm_tty), ":1.%d/tty", usb_dm_interface);
+ module_sys_path[0] = '\0';
+
+ snprintf(sysport, sizeof(sysport), "/sys/class/tty/%.48s", &module_port_name[strlen("/dev/")]);
+ if (access(sysport, F_OK) && errno == ENOENT)
+ {
+ quectel_fixup_sysport(module_port_name, sysport, sizeof(sysport)); // query real name
+ }
+ if (access(sysport, F_OK) && errno == ENOENT) return;
+ count = readlink(sysport, syspath, sizeof(syspath) - 1);
+ if (count < (int)strlen(dm_tty)) return;
+
+ // ttyUSB0 ->
+ // ../../devices/soc0/soc/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb1/1-1/1-1:1.0/ttyUSB0/tty/ttyUSB0
+ pchar = strstr(syspath, dm_tty);
+ if (pchar == NULL) return;
+
+ *pchar = '\0';
+ while (*pchar != '/') pchar--;
+
+ snprintf(module_sys_path, size, "/sys/bus/usb/devices/%.232s", pchar + 1);
+}
+
+static void quectel_get_usb_device_info(const char *module_sys_path, struct quectel_usb_device *udev)
+{
+ static unsigned char devdesc[1024];
+ size_t desclength, len;
+ char devname[64];
+ int desc_fd;
+ __u8 bInterfaceNumber = 0;
+ int dev_mknod_and_delete_after_use = 0;
+
+ MODULE_SYS_INFO sysinfo;
+ snprintf(devname, sizeof(devname), "%.56s/%s", module_sys_path, "uevent");
+ if (!quectel_get_sysinfo_by_uevent(devname, &sysinfo)) return;
+
+ snprintf(devname, sizeof(devname), "/dev/%.56s", sysinfo.DEVNAME);
+ if (access(devname, R_OK) && errno_nodev())
+ {
+ // maybe Linux have create /sys/ device, but not ready to create /dev/ device.
+ usleep(100 * 1000);
+ }
+
+ if (access(devname, R_OK) && errno_nodev())
+ {
+ char *p = strstr(devname + strlen("/dev/"), "/");
+
+ while (p)
+ {
+ p[0] = '_';
+ p = strstr(p, "/");
+ }
+
+ if (mknod(devname, S_IFCHR | 0666, MKDEV(sysinfo.MAJOR, sysinfo.MINOR)))
+ {
+ devname[1] = 't';
+ devname[2] = 'm';
+ devname[3] = 'p';
+
+ if (mknod(devname, S_IFCHR | 0666, MKDEV(sysinfo.MAJOR, sysinfo.MINOR)))
+ {
+ dbg_time("Fail to mknod %s, errno : %d (%s)\n", devname, errno, strerror(errno));
+ return;
+ }
+ }
+
+ dev_mknod_and_delete_after_use = 1;
+ }
+
+ desc_fd = open(devname, O_RDWR | O_NOCTTY);
+
+ if (dev_mknod_and_delete_after_use)
+ {
+ remove(devname);
+ }
+
+ if (desc_fd <= 0)
+ {
+ dbg_time("fail to open %s, errno: %d (%s)\n", devname, errno, strerror(errno));
+ return;
+ }
+
+ desclength = read(desc_fd, devdesc, sizeof(devdesc));
+ len = 0;
+ while (len < desclength)
+ {
+ struct usb_descriptor_header *h = (struct usb_descriptor_header *)(&devdesc[len]);
+
+ if (h->bLength == sizeof(struct usb_device_descriptor) && h->bDescriptorType == USB_DT_DEVICE)
+ {
+ struct usb_device_descriptor *device = (struct usb_device_descriptor *)h;
+
+ udev->idVendor = device->idVendor;
+ udev->idProduct = device->idProduct;
+ dbg_time("P: %s idVendor=%04x idProduct=%04x\n", devname, device->idVendor, device->idProduct);
+ }
+ else if (h->bLength == sizeof(struct usb_config_descriptor) && h->bDescriptorType == USB_DT_CONFIG)
+ {
+ struct usb_config_descriptor *config = (struct usb_config_descriptor *)h;
+
+ dbg_time("C: %s bNumInterfaces: %d\n", devname, config->bNumInterfaces);
+ udev->bNumInterfaces = config->bNumInterfaces;
+ }
+ else if (h->bLength == sizeof(struct usb_interface_descriptor) && h->bDescriptorType == USB_DT_INTERFACE)
+ {
+ struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)h;
+
+ dbg_time("I: If#= %d Alt= %d #EPs= %d Cls=%02x Sub=%02x Prot=%02x\n", interface->bInterfaceNumber, interface->bAlternateSetting, interface->bNumEndpoints,
+ interface->bInterfaceClass, interface->bInterfaceSubClass, interface->bInterfaceProtocol);
+ bInterfaceNumber = interface->bInterfaceNumber;
+ }
+ else if (h->bLength == USB_DT_ENDPOINT_SIZE && h->bDescriptorType == USB_DT_ENDPOINT)
+ {
+ if (bInterfaceNumber < EC20_MAX_INF)
+ {
+ struct usb_endpoint_descriptor *endpoint = (struct usb_endpoint_descriptor *)h;
+
+ dbg_time("E: Ad=%02x Atr=%02x MxPS= %d Ivl=%dms\n", endpoint->bEndpointAddress, endpoint->bmAttributes, endpoint->wMaxPacketSize, endpoint->bInterval);
+
+ if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK)
+ {
+ if (endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
+ udev->bulk_ep_in[bInterfaceNumber] = endpoint->bEndpointAddress;
+ else
+ udev->bulk_ep_out[bInterfaceNumber] = endpoint->bEndpointAddress;
+ udev->wMaxPacketSize[bInterfaceNumber] = endpoint->wMaxPacketSize;
+ }
+ else if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)
+ {
+ udev->intr_ep[bInterfaceNumber] = endpoint->bEndpointAddress;
+ }
+ }
+ }
+ else
+ {
+ }
+
+ len += h->bLength;
+ }
+
+ if (len == desclength)
+ {
+ strcpy(udev->devname, devname);
+ udev->desc = desc_fd;
+ }
+
+ usb_dm_interface = 0;
+
+ if ((udev->idVendor == 0x2c7c && udev->idProduct == 0x0127) // EM05CEFC-LNV Laptop
+ || (udev->idVendor == 0x2c7c && udev->idProduct == 0x0310) // EM05-CN Laptop
+ || (udev->idVendor == 0x2c7c && udev->idProduct == 0x030a) // EM05-G Laptop
+ || (udev->idVendor == 0x2c7c && udev->idProduct == 0x0311) // EM05-G-SE10 Laptop
+ || (udev->idVendor == 0x2c7c && udev->idProduct == 0x0315) // EM05-G STD Laptop
+ || (udev->idVendor == 0x2c7c && udev->idProduct == 0x0309) // EM05E-EDU Laptop
+ || (udev->idVendor == 0x2c7c && udev->idProduct == 0x6008) // EM061KGL laptop
+ || (udev->idVendor == 0x2c7c && udev->idProduct == 0x0128) // Google EM060KGL laptop
+ || (udev->idVendor == 0x2c7c && udev->idProduct == 0x6009) // EM061KGL laptop
+ || (udev->idVendor == 0x2c7c && udev->idProduct == 0x0803) // RM520NGL thinkpad 5G module dedicated pid
+ || (udev->idVendor == 0x2c7c && udev->idProduct == 0x012E) // EM120K-GL laptop
+ || (udev->idVendor == 0x2c7c && udev->idProduct == 0x012F) // EM120K-GL laptop
+ || (udev->idVendor == 0x2c7c && udev->idProduct == 0x0804) // Customized PID for Zebra project laptop
+ || (udev->idVendor == 0x2c7c && udev->idProduct == 0x030d)) // EM05G-FCCL Laptop
+ {
+ usb_dm_interface = 3;
+ }
+ else if ((udev->idVendor == 0x2c7c && udev->idProduct == 0x0514) // EG060K-EA
+ || (udev->idVendor == 0x2c7c && udev->idProduct == 0x0133)) // RG650VEU-rndis
+ {
+ usb_dm_interface = 2;
+ }
+ else if (udev->idVendor == 0x3c93 && udev->idProduct == 0xffff) // EG060K-EA
+ {
+ usb_dm_interface = 8;
+ }
+ else if (udev->idVendor == 0x05c6 && udev->idProduct == 0x90db) // (udev->idVendor == 0x05c6 && udev->idProduct == 0x90db) // AG600K-EM
+ {
+ usb_dm_interface = 2;
+ }
+ else if (udev->idVendor == 0x2c7c && udev->idProduct == 0x030b) // EG120KEABA-RNDIS
+ {
+ usb_dm_interface = 0;
+ if (g_from_ecm_to_rndis) usb_dm_interface = 2;
+ }
+}
+
+static int usbfs_bulk_write(struct quectel_usb_device *udev, const void *data, int len, int timeout_msec, int need_zlp)
+{
+ struct usbdevfs_urb bulk;
+ struct usbdevfs_urb *urb = &bulk;
+ int n = -1;
+ int bInterfaceNumber = usb_dm_interface;
+
+ (void)timeout_msec;
+ // if (urb->type == 0)
+ {
+ memset(urb, 0, sizeof(struct usbdevfs_urb));
+ urb->type = USBDEVFS_URB_TYPE_BULK;
+ urb->endpoint = udev->bulk_ep_out[bInterfaceNumber];
+ }
+
+ urb->status = -1;
+ urb->buffer = (void *)data;
+ urb->buffer_length = len;
+ urb->usercontext = urb;
+
+ if (need_zlp && (len % udev->wMaxPacketSize[bInterfaceNumber]) == 0)
+ {
+ // dbg_time("USBDEVFS_URB_ZERO_PACKET\n");
+#ifndef USBDEVFS_URB_ZERO_PACKET
+#define USBDEVFS_URB_ZERO_PACKET 0x40
+#endif
+ urb->flags = USBDEVFS_URB_ZERO_PACKET;
+ }
+ else
+ {
+ urb->flags = 0;
+ }
+
+ do
+ {
+ n = ioctl(udev->desc, USBDEVFS_SUBMITURB, urb);
+ } while ((n < 0) && (errno == EINTR));
+
+ if (n != 0)
+ {
+ dbg_time("inf[%d] USBDEVFS_SUBMITURB %d/%d, errno = %d (%s)\n", bInterfaceNumber, n, urb->buffer_length, errno, strerror(errno));
+ return -1;
+ }
+
+ do
+ {
+ urb = NULL;
+ n = ioctl(udev->desc, USBDEVFS_REAPURB, &urb);
+ } while ((n < 0) && (errno == EINTR));
+
+ if (n != 0)
+ {
+ dbg_time("inf[%d] ep_out %d/%d, errno = %d (%s)\n", bInterfaceNumber, n, urb->buffer_length, errno, strerror(errno));
+ }
+
+ // dbg_time("[ urb @%p status = %d, actual = %d ]\n", urb, urb->status, urb->actual_length);
+
+ if (urb && urb->status == 0 && urb->actual_length) return urb->actual_length;
+
+ return -1;
+}
+
+static int poll_wait(int poll_fd, short events, int timeout_msec)
+{
+ struct pollfd pollfds[] = {{poll_fd, events, 0}};
+ int ret;
+
+ do
+ {
+ ret = poll(pollfds, 1, timeout_msec);
+ } while (ret == -1 && errno == EINTR);
+
+ if (ret == 1 && (pollfds[0].revents & (events)))
+ return 0;
+ else if (ret == 0)
+ { // timeout
+ dbg_time("poll_wait events=%s msec=%d timeout\n", (events & POLLIN) ? "POLLIN" : "POLLOUT", timeout_msec);
+ return ETIMEDOUT;
+ }
+
+ return EIO;
+}
+
+static int usbfs_bulk_read(struct quectel_usb_device *udev, void *pbuf, int len, int timeout)
+{
+ struct usbdevfs_bulktransfer bulk;
+ int n = -1;
+ int bInterfaceNumber = usb_dm_interface;
+
+ if (len < 512)
+ {
+ dbg_time("%s len=%d is too short\n", __func__, len);
+ return 0;
+ }
+
+ bulk.ep = udev->bulk_ep_in[bInterfaceNumber];
+ bulk.len = (len > MAX_USBFS_BULK_IN_SIZE) ? MAX_USBFS_BULK_IN_SIZE : len;
+ bulk.data = (void *)pbuf;
+ bulk.timeout = timeout;
+
+ n = ioctl(udev->desc, USBDEVFS_BULK, &bulk);
+ if (n <= 0)
+ {
+ if (errno == ETIMEDOUT)
+ {
+ dbg_time("inf[%d] ep_in %d/%d, errno = %d (%s), timeout=%d\n", bInterfaceNumber, n, bulk.len, errno, strerror(errno), timeout);
+ n = 0;
+ }
+ else
+ dbg_time("inf[%d] ep_in %d/%d, errno = %d (%s)\n", bInterfaceNumber, n, bulk.len, errno, strerror(errno));
+ }
+
+ return n;
+}
+
+static int qtcp_connect(const char *port_name, int *idVendor, int *idProduct, int *interfaceNum)
+{
+ int fd = -1;
+ char *tcp_host = strdup(port_name);
+ if (tcp_host == NULL) return -1;
+
+ char *tcp_port = strchr(tcp_host, ':');
+ struct sockaddr_in sockaddr;
+ TLV_USB tlv_usb;
+
+ dbg_time("%s port_name = %s\n", __func__, port_name);
+
+ if (tcp_port == NULL)
+ {
+ if (tcp_host)
+ {
+ free(tcp_host);
+ tcp_host = NULL;
+ }
+ return -1;
+ }
+
+ *tcp_port++ = '\0';
+ if (atoi(tcp_port) < 1 || atoi(tcp_port) > 0xFFFF)
+ {
+ if (tcp_host)
+ {
+ free(tcp_host);
+ tcp_host = NULL;
+ }
+ return -1;
+ }
+
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+
+ if (fd <= 0)
+ {
+ if (tcp_host)
+ {
+ free(tcp_host);
+ tcp_host = NULL;
+ }
+ dbg_time("Device could not be socket: Linux System Errno: %s\n", strerror(errno));
+ return -1;
+ }
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ sockaddr.sin_family = AF_INET;
+ sockaddr.sin_addr.s_addr = inet_addr(tcp_host);
+ sockaddr.sin_port = htons(atoi(tcp_port));
+
+ if (tcp_host)
+ {
+ free(tcp_host);
+ tcp_host = NULL;
+ }
+ if (connect(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0)
+ {
+ close(fd);
+ fd = -1;
+ dbg_time("Device could not be connect: Linux System Errno: %s\n", strerror(errno));
+ return -1;
+ }
+
+ // block read, untill usb2tcp tell me the usb device information
+ memset(&tlv_usb, 0x00, sizeof(tlv_usb));
+ if (read(fd, &tlv_usb, sizeof(tlv_usb)) == -1)
+ {
+ };
+ *idVendor = tlv_usb.idVendor;
+ *idProduct = tlv_usb.idProduct;
+ *interfaceNum = tlv_usb.interfaceNum;
+
+ dbg_time("idVendor=%04x, idProduct=%04x, interfaceNum=%d\n", *idVendor, *idProduct, *interfaceNum);
+
+ return fd;
+}
+
+static int qtcp_read(int fd, void *pbuf, int size, int timeout_msec)
+{
+ static TLV tlv = {Q_USB2TCP_VERSION, 0};
+ int cur = 0;
+ int len;
+
+ if (tlv.length == 0)
+ {
+ len = read(fd, &tlv, sizeof(tlv));
+ if (len != sizeof(tlv))
+ {
+ dbg_time("%s read=%d, errno: %d (%s)\n", __func__, len, errno, strerror(errno));
+ return 0;
+ }
+
+ if (tlv.tag != Q_USB2TCP_VERSION)
+ {
+ dbg_time("%s tlv->tag=0x%x\n", __func__, tlv.tag);
+ return 0;
+ }
+ }
+
+ if (size > tlv.length) size = tlv.length;
+ tlv.length -= size;
+
+ while (cur < size)
+ {
+ if (poll_wait(fd, POLLIN, timeout_msec)) break;
+
+ len = read(fd, (uint8_t *)pbuf + cur, size - cur);
+ if (len > 0)
+ {
+ cur += len;
+ }
+ else
+ {
+ dbg_time("%s read=%d, errno: %d (%s)\n", __func__, len, errno, strerror(errno));
+ break;
+ }
+ }
+
+ if (cur != size)
+ {
+ dbg_time("%s cur=%d, size=%d\n", __func__, cur, size);
+ }
+
+ return cur;
+}
+
+static int qtcp_write(int fd, void *pbuf, int size, int timeout_msec)
+{
+ TLV tlv = {Q_USB2TCP_VERSION, size};
+ int cur = 0;
+ int len;
+
+ len = write(fd, &tlv, sizeof(tlv));
+ if (len != sizeof(tlv))
+ {
+ dbg_time("%s write=%d, errno: %d (%s)\n", __func__, len, errno, strerror(errno));
+ return 0;
+ }
+
+ while (cur < size)
+ {
+ if (poll_wait(fd, POLLOUT, timeout_msec)) break;
+
+ len = write(fd, (uint8_t *)pbuf + cur, size - cur);
+ if (len > 0)
+ {
+ cur += len;
+ }
+ else
+ {
+ dbg_time("%s write=%d, errno: %d (%s)\n", __func__, len, errno, strerror(errno));
+ break;
+ }
+ }
+
+ if (cur != size)
+ {
+ dbg_time("%s cur=%d, size=%d\n", __func__, cur, size);
+ }
+
+ return cur;
+}
+
+struct usbfs_getdriver
+{
+ unsigned int interface;
+ char driver[255 + 1];
+};
+
+struct usbfs_ioctl
+{
+ int ifno; /* interface 0..N ; negative numbers reserved */
+ int ioctl_code; /* MUST encode size + direction of data so the
+ * macros in give correct values */
+ void *data; /* param buffer (in, or out) */
+};
+
+#define IOCTL_USBFS_DISCONNECT _IO('U', 22)
+#define IOCTL_USBFS_CONNECT _IO('U', 23)
+
+int usbfs_is_kernel_driver_alive(int fd, int ifnum)
+{
+ struct usbfs_getdriver getdrv;
+ getdrv.interface = ifnum;
+ if (ioctl(fd, USBDEVFS_GETDRIVER, &getdrv) < 0)
+ {
+ if (errno != ENODATA) dbg_time("%s ioctl USBDEVFS_GETDRIVER failed, errno: %d (%s)\n", __func__, errno, strerror(errno));
+ return 0;
+ }
+ dbg_time("%s find interface %d has match the driver %s\n", __func__, ifnum, getdrv.driver);
+ return 1;
+}
+
+void usbfs_detach_kernel_driver(int fd, int ifnum)
+{
+ struct usbfs_ioctl operate;
+ operate.data = NULL;
+ operate.ifno = ifnum;
+ operate.ioctl_code = IOCTL_USBFS_DISCONNECT;
+ if (ioctl(fd, USBDEVFS_IOCTL, &operate) < 0)
+ {
+ dbg_time("%s detach kernel driver failed\n", __func__);
+ }
+ else
+ {
+ dbg_time("%s detach kernel driver success\n", __func__);
+ }
+}
+
+#define KVERSION(j, n, p) ((j)*1000000 + (n)*1000 + (p))
+static struct utsname utsname; /* for the kernel version */
+static int ql_get_kernel_version(void)
+{
+ int osmaj, osmin, ospatch;
+ int kernel_version;
+
+ uname(&utsname);
+ osmaj = osmin = ospatch = 0;
+ sscanf(utsname.release, "%d.%d.%d", &osmaj, &osmin, &ospatch);
+ kernel_version = KVERSION(osmaj, osmin, ospatch);
+
+ return kernel_version;
+}
+
+static int detect_xhci_usb_zero_packet_bug_not_fix(const char *module_sys_path)
+{
+ char buf[256];
+ int tmp;
+ char *driver;
+
+ tmp = snprintf(buf, sizeof(buf), "/sys/bus/usb/devices/usb%c/../driver", module_sys_path[strlen("/sys/bus/usb/devices/")]);
+ driver = buf + (++tmp);
+ *driver = '\0';
+
+ tmp = readlink(buf, driver, sizeof(buf) - tmp);
+ if (tmp <= 0) return 0;
+ driver[tmp] = '\0';
+ dbg_time("tmp=%s, driver=%s\n", buf, driver);
+
+ if (!strstr(driver, "xhci")) return 0;
+
+ tmp = ql_get_kernel_version();
+ if (tmp >= KVERSION(4, 3, 0)) return 0;
+
+ dbg_time("WARNNING ON File:%s Function:%s Line:%d\n", __FILE__, __func__, __LINE__);
+ dbg_time("The module attach to XHCI controller, but your kernel verison less than V4.3.0\n");
+ dbg_time("Please make sure your kernel had apply patch 'usb: xhci: Add support for "
+ "URB_ZERO_PACKET to bulk/sg transfers'\n");
+ dbg_time("https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/drivers/"
+ "usb/host/xhci-ring.c?id=4758dcd19a7d9ba9610b38fecb93f65f56f86346\n");
+ sleep(2); // sleep 2 seconds, make sure FAE/customers can notice this warnning.
+
+ return 1;
+}
+
+void *qusb_noblock_open(const char *module_sys_path, int *idVendor, int *idProduct, int *interfaceNum)
+{
+ struct termios ios;
+ int retval;
+ int fd = -1;
+ struct quectel_usb_device *udev = &quectel_9x07;
+
+ *idVendor = *idProduct = *interfaceNum = 0;
+ tcp_socket_fd = -1;
+
+ if (module_sys_path && module_sys_path[0] == '/')
+ {
+ char port_name[64];
+
+ memset(udev, 0, sizeof(struct quectel_usb_device));
+ quectel_get_usb_device_info(module_sys_path, udev);
+ if (udev->desc <= 0) return NULL;
+ quectel_get_ttyport_by_syspath(module_sys_path, port_name, sizeof(port_name));
+ detect_xhci_usb_zero_packet_bug_not_fix(module_sys_path);
+
+ *idVendor = udev->idVendor;
+ *idProduct = udev->idProduct;
+ *interfaceNum = udev->bNumInterfaces;
+
+ if (port_name[0] == '\0' || (port_name[0] != '\0' && access(port_name, R_OK)) || (udev->idVendor == 0x05c6 && udev->idProduct == 0x9008))
+ {
+ int bInterfaceNumber = usb_dm_interface;
+
+ if (usbfs_is_kernel_driver_alive(udev->desc, bInterfaceNumber))
+ {
+ usbfs_detach_kernel_driver(udev->desc, bInterfaceNumber);
+ }
+ retval = ioctl(udev->desc, USBDEVFS_CLAIMINTERFACE, &bInterfaceNumber);
+ if (retval != 0)
+ {
+ dbg_time("Fail to claim interface %d, errno: %d (%s)\n", bInterfaceNumber, errno, strerror(errno));
+ if (udev->idVendor == 0x05c6)
+ {
+ int n;
+ struct
+ {
+ char infname[255 * 2];
+ char driver[255 * 2];
+ } *pl;
+ const char *driver = NULL;
+
+ pl = (typeof(pl))malloc(sizeof(*pl));
+ if (pl == NULL)
+ {
+ dbg_time("pl is NULL\n");
+ return NULL;
+ }
+
+ snprintf(pl->infname, sizeof(pl->infname), "%.255s:1.%d/driver", module_sys_path, usb_dm_interface);
+ n = readlink(pl->infname, pl->driver, sizeof(pl->driver));
+ if (n > 0 && n < 510)
+ {
+ pl->driver[n] = '\0';
+ while (pl->driver[n] != '/') n--;
+ driver = (&pl->driver[n + 1]);
+ }
+
+ dbg_time("Error: when module in 'Emergency download mode', should not register "
+ "any usb driver\n");
+ if (driver)
+ dbg_time("Error: it register to usb driver ' %s ' now, should delete "
+ "05c6&9008 from the source file of this driver\n",
+ driver);
+ if (driver && !strcmp(driver, "qcserial"))
+ dbg_time("Delete 05c6&9008 from 'drivers/usb/serial/qcserial.c' or disable "
+ "qcserial from kernel config\n");
+ qusb_noblock_close(udev);
+ free(pl);
+ }
+ return NULL;
+ }
+
+ udev->ttyfd = -1;
+ return udev;
+ }
+ else if (!access(port_name, R_OK))
+ {
+ dbg_time("%s port_name = %s\n", __func__, port_name);
+
+ fd = open(port_name, O_RDWR | O_SYNC);
+
+ if (fd <= 0)
+ {
+ dbg_time("Device %s could not be open: Linux System Errno: %s", port_name, strerror(errno));
+ return NULL;
+ }
+
+ retval = tcgetattr(fd, &ios);
+ if (-1 == retval)
+ {
+ dbg_time("ermio settings could not be fetched Linux System Error:%s", strerror(errno));
+ if (fd > 0)
+ {
+ close(fd);
+ fd = -1;
+ }
+ return NULL;
+ }
+
+ cfmakeraw(&ios);
+ cfsetispeed(&ios, B115200);
+ cfsetospeed(&ios, B115200);
+
+ retval = tcsetattr(fd, TCSANOW, &ios);
+ if (-1 == retval)
+ {
+ dbg_time("Device could not be configured: Linux System Errno: %s", strerror(errno));
+ }
+ udev->ttyfd = fd;
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
+
+ return udev;
+ }
+ else
+ {
+ dbg_time("fail to access %s errno: %d (%s)\n", port_name, errno, strerror(errno));
+ }
+ }
+ else if (module_sys_path && module_sys_path[0] != '/')
+ {
+ fd = qtcp_connect(module_sys_path, idVendor, idProduct, interfaceNum);
+ if (fd > 0)
+ {
+ tcp_socket_fd = fd;
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
+ return &tcp_socket_fd;
+ }
+ }
+
+ return NULL;
+}
+
+int qusb_noblock_close(void *handle)
+{
+ struct quectel_usb_device *udev = &quectel_9x07;
+
+ if (handle == &tcp_socket_fd)
+ {
+ close(tcp_socket_fd);
+ tcp_socket_fd = -1;
+ }
+ if (handle == udev && udev->ttyfd > 0)
+ {
+ close(udev->ttyfd);
+ udev->ttyfd = -1;
+ close(udev->desc);
+ udev->desc = -1;
+ }
+ else if (handle == udev && udev->desc > 0)
+ {
+ int bInterfaceNumber = usb_dm_interface;
+ ioctl(udev->desc, USBDEVFS_RELEASEINTERFACE, &bInterfaceNumber);
+ close(udev->desc);
+ udev->desc = -1;
+ }
+ else if (handle == &edl_pcie_mhifd && edl_pcie_mhifd > 0)
+ {
+ close(edl_pcie_mhifd);
+ edl_pcie_mhifd = -1;
+ }
+ memset(udev, 0, sizeof(*udev));
+
+ return 0;
+}
+
+int qusb_use_usbfs_interface(const void *handle)
+{
+ struct quectel_usb_device *udev = &quectel_9x07;
+
+ return (handle == udev && udev->ttyfd <= 0 && udev->desc > 0);
+}
+
+int qusb_read_speed_atime(const char *module_sys_path, struct timespec *out_time, int *out_speed)
+{
+ char speed[256];
+ int fd;
+ struct stat stat;
+
+ snprintf(speed, sizeof(speed), "%.240s/speed", module_sys_path);
+ fd = open(speed, O_RDONLY);
+ if (fd == -1) return 0;
+
+ if (read(fd, speed, sizeof(speed)))
+ {
+ }
+ fstat(fd, &stat);
+ close(fd);
+ fd = -1;
+
+ *out_speed = atoi(speed);
+
+#ifdef ANDROID
+ struct timespec out_time_tmp;
+ memset(&out_time_tmp, 0, sizeof(struct timespec));
+ out_time_tmp.tv_sec = stat.st_atime;
+ *out_time = out_time_tmp;
+#else
+ *out_time = stat.st_atim;
+#endif
+
+ dbg_time("%s speed: %d, st_atime: %s\n", __func__, *out_speed, ctimespec(out_time, speed, sizeof(speed)));
+ return 1;
+}
+
+int qusb_noblock_read(const void *handle, void *pbuf, int max_size, int min_size, int timeout_msec)
+{
+ struct quectel_usb_device *udev = &quectel_9x07;
+ int cur = 0;
+ int poll_ret = 0;
+
+ if (min_size == 0) min_size = 1;
+ if (timeout_msec == 0) timeout_msec = 3000;
+
+#if 0 // depend on your worst net speed
+ if (handle == &tcp_socket_fd) {
+ if (timeout_msec > 1000) //before sahala&firebose, we allow read timeout occurs
+ timeout_msec = 120*1000;
+ }
+#endif
+
+ while (cur < min_size)
+ {
+ int len = 0;
+
+ if (handle == &tcp_socket_fd)
+ {
+ if ((poll_ret = poll_wait(tcp_socket_fd, POLLIN, timeout_msec))) break;
+ len = qtcp_read(tcp_socket_fd, (uint8_t *)pbuf + cur, max_size - cur, timeout_msec);
+ }
+ else if (handle == udev && udev->ttyfd > 0)
+ {
+ if ((poll_ret = poll_wait(udev->ttyfd, POLLIN, timeout_msec))) break;
+ len = read(udev->ttyfd, (uint8_t *)pbuf + cur, max_size - cur);
+ }
+ else if (handle == udev && udev->desc > 0)
+ {
+ len = usbfs_bulk_read(udev, (uint8_t *)pbuf + cur, max_size - cur, timeout_msec);
+ }
+ else if (handle == &edl_pcie_mhifd && edl_pcie_mhifd > 0)
+ {
+ if ((poll_ret = poll_wait(edl_pcie_mhifd, POLLIN, timeout_msec))) break;
+ len = read(edl_pcie_mhifd, (uint8_t *)pbuf + cur, max_size - cur);
+ }
+ else
+ {
+ break;
+ }
+
+ if (len > 0)
+ {
+ cur += len;
+ }
+ else
+ {
+ dbg_time("%s read=%d, errno: %d (%s)\n", __func__, len, errno, strerror(errno));
+ break;
+ }
+ }
+
+ if (poll_ret == EIO)
+ return -1;
+ else if (poll_ret == ETIMEDOUT)
+ return cur;
+
+ if (cur < min_size)
+ {
+ dbg_time("%s cur=%d, min_size=%d\n", __func__, cur, min_size);
+ }
+
+ return cur;
+}
+
+int qusb_noblock_write(const void *handle, void *pbuf, int max_size, int min_size, int timeout_msec, int need_zlp)
+{
+ struct quectel_usb_device *udev = &quectel_9x07;
+ int cur = 0;
+
+ if (min_size == 0) min_size = 1;
+ if (timeout_msec == 0) timeout_msec = 3000;
+
+#if 0 // depend on your worst net speed
+ if (handle == &tcp_socket_fd) {
+ timeout_msec = 120*1000;
+ }
+#endif
+
+ while (cur < min_size)
+ {
+ int len = 0;
+
+ if (handle == &tcp_socket_fd)
+ {
+ if (poll_wait(tcp_socket_fd, POLLOUT, timeout_msec)) break;
+ len = qtcp_write(tcp_socket_fd, (uint8_t *)pbuf + cur, max_size - cur, timeout_msec);
+ }
+ else if (handle == udev && udev->ttyfd > 0)
+ {
+ if (poll_wait(udev->ttyfd, POLLOUT, timeout_msec)) break;
+ len = write(udev->ttyfd, (uint8_t *)pbuf + cur, max_size - cur);
+ }
+ else if (handle == udev && udev->desc > 0)
+ {
+ len = usbfs_bulk_write(udev, (uint8_t *)pbuf + cur, max_size - cur, timeout_msec, need_zlp);
+ }
+ else if (handle == &edl_pcie_mhifd && edl_pcie_mhifd > 0)
+ {
+ if (poll_wait(edl_pcie_mhifd, POLLOUT, timeout_msec)) break;
+ len = write(edl_pcie_mhifd, (uint8_t *)pbuf + cur, max_size - cur);
+ }
+ else
+ {
+ break;
+ }
+
+ if (len > 0)
+ {
+ cur += len;
+ }
+ else
+ {
+ dbg_time("%s write=%d, errno: %d (%s)\n", __func__, len, errno, strerror(errno));
+ break;
+ }
+ }
+
+ if (cur < min_size)
+ {
+ dbg_time("%s cur=%d, min_size=%d\n", __func__, cur, min_size);
+ }
+
+ return cur;
+}
+
+int qfile_find_file(const char *dir, const char *prefix, const char *suffix, char **filename)
+{
+ DIR *pdir;
+ struct dirent *ent = NULL;
+ pdir = opendir(dir);
+ if (pdir == NULL)
+ {
+ return 0;
+ }
+
+ *filename = NULL;
+ if (pdir)
+ {
+ while ((ent = readdir(pdir)) != NULL)
+ {
+ if (strStartsWith(ent->d_name, prefix) && strEndsWith(ent->d_name, suffix))
+ {
+ dbg_time("find '%s'\n", ent->d_name);
+ *filename = strdup(ent->d_name);
+ break;
+ }
+ }
+ }
+
+ closedir(pdir);
+ return *filename != NULL;
+}
+
+const char *firehose_get_time(void)
+{
+ static char time_buf[50];
+ struct timeval tv;
+ static int s_start_msec = -1;
+ int now_msec, cost_msec;
+
+ gettimeofday(&tv, NULL);
+ now_msec = tv.tv_sec * 1000;
+ now_msec += (tv.tv_usec + 500) / 1000;
+
+ if (s_start_msec == -1)
+ {
+ s_start_msec = now_msec;
+ }
+
+ cost_msec = now_msec - s_start_msec;
+
+ sprintf(time_buf, "[%03d.%03d]", cost_msec / 1000, cost_msec % 1000);
+ return time_buf;
+}
+
+// void dbg_time (const char *fmt, ...) {
+// va_list args;
+// va_start(args, fmt);
+// static char line[2048];
+// snprintf(line, sizeof(line), "%s ", firehose_get_time());
+// vsnprintf(line + strlen(line), sizeof(line) - strlen(line), fmt, args);
+// fprintf(stdout, "%s", line);
+// fflush(stdout);
+// }
+
+int qpcie_open(const char *firehose_dir, const char *firehose_mbn, const char *module_port_name)
+{
+ int bhifd, edlfd, diagfd;
+ long ret;
+ FILE *fp;
+ char prog_firehose_sdx24[256 + 32];
+ size_t filesize;
+ void *filebuf;
+ int cur_number = 0;
+
+ BHI_INFO_TYPE *bhi_info = malloc(sizeof(BHI_INFO_TYPE));
+ if (bhi_info == NULL)
+ {
+ dbg_time("bhi_info is NULL, errno: %d (%s)\n", errno, strerror(errno));
+ error_return();
+ }
+
+ if (is_upgrade_fimeware_zip_7z)
+ {
+ snprintf(prog_firehose_sdx24, sizeof(prog_firehose_sdx24), "/tmp/%.240s", firehose_mbn);
+ dbg_time("%s prog_firehose_sdx24:%s\n", __func__, prog_firehose_sdx24);
+ }
+ else
+ {
+ snprintf(prog_firehose_sdx24, sizeof(prog_firehose_sdx24), "%.240s/%.32s", firehose_dir, firehose_mbn);
+ }
+
+ fp = fopen(prog_firehose_sdx24, "rb");
+ if (fp == NULL)
+ {
+ dbg_time("fail to fopen %s, errno: %d (%s)\n", prog_firehose_sdx24, errno, strerror(errno));
+ if (bhi_info)
+ {
+ free(bhi_info);
+ bhi_info = NULL;
+ }
+ error_return();
+ }
+
+ fseek(fp, 0, SEEK_END);
+ filesize = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+
+ filebuf = malloc(sizeof(filesize) + filesize);
+ if (filebuf == NULL)
+ {
+ dbg_time("filebuf is NULL, errno: %d (%s)\n", errno, strerror(errno));
+ if (bhi_info)
+ {
+ free(bhi_info);
+ bhi_info = NULL;
+ }
+
+ if (fp)
+ {
+ fclose(fp);
+ fp = NULL;
+ }
+ error_return();
+ }
+ memcpy(filebuf, &filesize, sizeof(filesize));
+ if (fread((uint8_t *)filebuf + sizeof(filesize), 1, filesize, fp) == (size_t)0)
+ {
+ };
+ fclose(fp);
+
+ if (is_upgrade_fimeware_zip_7z)
+ {
+ unlink(prog_firehose_sdx24);
+ }
+
+ int i;
+ for (i = 0; i < pcie_port_defult.pcie_port_number; i++)
+ {
+ if (!strncasecmp(module_port_name, pcie_port_defult.pcie_port_one[i].bhi, 28))
+ {
+ cur_number = i;
+ break;
+ }
+ }
+
+ if (i == pcie_port_defult.pcie_port_number)
+ {
+ dbg_time("PCIE port assignment error\n");
+ if (bhi_info)
+ {
+ free(bhi_info);
+ bhi_info = NULL;
+ }
+
+ if (filebuf)
+ {
+ free(filebuf);
+ filebuf = NULL;
+ }
+ error_return();
+ }
+
+ diagfd = open(pcie_port_defult.pcie_port_one[cur_number].diag, O_RDWR | O_NOCTTY);
+ if (diagfd > 0)
+ {
+ int edl_retry = 30; // SDX55 require long time by now 20190412
+ void *usb_handle = &edl_pcie_mhifd;
+ edl_pcie_mhifd = diagfd;
+
+ while (access(pcie_port_defult.pcie_port_one[cur_number].diag, R_OK) == 0 && edl_retry-- > 0)
+ {
+ dbg_time("switch_to_edl_mode\n");
+ switch_to_edl_mode(usb_handle);
+ sleep(1);
+ }
+
+ close(diagfd);
+ diagfd = -1;
+ edl_pcie_mhifd = -1;
+ }
+
+ sleep(1); // see https://ticket.quectel.com/browse/FAE-39737
+ bhifd = open(pcie_port_defult.pcie_port_one[cur_number].bhi, O_RDWR | O_NOCTTY);
+ if (bhifd <= 0)
+ {
+ dbg_time("fail to open %s, errno: %d (%s)\n", "/dev/mhi_BHI", errno, strerror(errno));
+ if (bhi_info)
+ {
+ free(bhi_info);
+ bhi_info = NULL;
+ }
+
+ if (filebuf)
+ {
+ free(filebuf);
+ filebuf = NULL;
+ }
+
+ if (diagfd > 0)
+ {
+ close(diagfd);
+ diagfd = -1;
+ }
+ error_return();
+ }
+
+ ret = ioctl(bhifd, IOCTL_BHI_GETDEVINFO, bhi_info);
+ if (ret)
+ {
+ dbg_time("fail to ioctl IOCTL_BHI_GETDEVINFO, errno: %d (%s)\n", errno, strerror(errno));
+ if (bhi_info)
+ {
+ free(bhi_info);
+ bhi_info = NULL;
+ }
+
+ if (filebuf)
+ {
+ free(filebuf);
+ filebuf = NULL;
+ }
+
+ if (diagfd > 0)
+ {
+ close(diagfd);
+ diagfd = -1;
+ }
+
+ if (bhifd > 0)
+ {
+ close(bhifd);
+ bhifd = -1;
+ }
+ error_return();
+ }
+
+ dbg_time("bhi_ee = %d\n", bhi_info->bhi_ee);
+ if (bhi_info->bhi_ee != MHI_EE_EDL)
+ {
+ dbg_time("bhi_ee is not MHI_EE_EDL\n");
+ close(bhifd);
+ bhifd = -1;
+ free(filebuf);
+ filebuf = NULL;
+ if (bhi_info)
+ {
+ free(bhi_info);
+ bhi_info = NULL;
+ }
+
+ if (diagfd > 0)
+ {
+ close(diagfd);
+ diagfd = -1;
+ }
+ error_return();
+ }
+
+ if (bhi_info)
+ {
+ free(bhi_info);
+ bhi_info = NULL;
+ }
+
+ ret = ioctl(bhifd, IOCTL_BHI_WRITEIMAGE, filebuf);
+ if (ret)
+ {
+ dbg_time("fail to ioctl IOCTL_BHI_GETDEVINFO, errno: %d (%s)\n", errno, strerror(errno));
+ if (filebuf)
+ {
+ free(filebuf);
+ filebuf = NULL;
+ }
+
+ if (diagfd > 0)
+ {
+ close(diagfd);
+ diagfd = -1;
+ }
+
+ if (bhifd > 0)
+ {
+ close(bhifd);
+ bhifd = -1;
+ }
+ error_return();
+ }
+
+ if (bhifd > 0)
+ {
+ close(bhifd);
+ bhifd = -1;
+ }
+ if (filebuf)
+ {
+ free(filebuf);
+ filebuf = NULL;
+ }
+
+ sleep(1);
+ edlfd = open(pcie_port_defult.pcie_port_one[cur_number].edl, O_RDWR | O_NOCTTY);
+ if (edlfd <= 0)
+ {
+ dbg_time("fail to access %s, errno: %d (%s)\n", pcie_port_defult.pcie_port_one[cur_number].edl, errno, strerror(errno));
+ if (diagfd > 0)
+ {
+ close(diagfd);
+ diagfd = -1;
+ }
+ error_return();
+ }
+
+ edl_pcie_mhifd = edlfd;
+
+ if (diagfd > 0)
+ {
+ close(diagfd);
+ diagfd = -1;
+ }
+
+ return 0;
+}
+
+int usbmon_fd = -1;
+int usbmon_logfile_fd = -1;
+
+void *catch_log(void *arg)
+{
+ int nreads = 0;
+ char tbuff[256];
+ size_t off = strlen("[999.999] ");
+
+ (void)arg;
+ tbuff[off - 1] = ' ';
+ while (1)
+ {
+ nreads = read(usbmon_fd, tbuff + off, sizeof(tbuff) - off - 1);
+ if (nreads == -1 && errno == EINTR) continue;
+ if (nreads <= 0) break;
+
+ tbuff[off + nreads] = '\0';
+ memcpy(tbuff, firehose_get_time(), off - 1);
+
+ if (write(usbmon_logfile_fd, tbuff, strlen(tbuff)) == -1)
+ {
+ };
+ }
+
+ return NULL;
+}
+
+int ql_capture_usbmon_log(const char *usbmon_logfile)
+{
+ const char *usbmon_path = "/sys/kernel/debug/usb/usbmon/0u";
+ pthread_t pt;
+ pthread_attr_t attr;
+
+ if (access("/sys/kernel/debug/usb", F_OK))
+ {
+ dbg_time("debugfs is not mount, please execute \"mount -t debugfs none_debugs "
+ "/sys/kernel/debug\"\n");
+ return -1;
+ }
+ if (access("/sys/kernel/debug/usb/usbmon", F_OK))
+ {
+ dbg_time("usbmon is not load, please execute \"modprobe usbmon\" or \"insmod usbmon.ko\"\n");
+ return -1;
+ }
+
+ usbmon_fd = open(usbmon_path, O_RDONLY);
+ if (usbmon_fd < 0)
+ {
+ dbg_time("open %s error(%d) (%s)\n", usbmon_path, errno, strerror(errno));
+ return -1;
+ }
+
+ usbmon_logfile_fd = open(usbmon_logfile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (usbmon_logfile_fd < 0)
+ {
+ dbg_time("open %s error(%d) (%s)\n", usbmon_logfile, errno, strerror(errno));
+ close(usbmon_fd);
+ usbmon_fd = -1;
+ return -1;
+ }
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ pthread_create(&pt, &attr, catch_log, NULL);
+
+ // pthread_attr_destroy(&attr); //aaron 2023.07.27
+ return 0;
+}
+
+void ql_stop_usbmon_log()
+{
+ if (usbmon_logfile_fd > 0)
+ {
+ close(usbmon_logfile_fd);
+ usbmon_logfile_fd = -1;
+ }
+ if (usbmon_fd > 0)
+ {
+ close(usbmon_fd);
+ usbmon_fd = -1;
+ }
+}
diff --git a/application/qfirehose/src/usb_linux.h b/application/qfirehose/src/usb_linux.h
new file mode 100644
index 0000000..9a6ad84
--- /dev/null
+++ b/application/qfirehose/src/usb_linux.h
@@ -0,0 +1,193 @@
+/*
+ Copyright 2023 Quectel Wireless Solutions Co.,Ltd
+
+ Quectel hereby grants customers of Quectel a license to use, modify,
+ distribute and publish the Software in binary form provided that
+ customers shall have no right to reverse engineer, reverse assemble,
+ decompile or reduce to source code form any portion of the Software.
+ Under no circumstances may customers modify, demonstrate, use, deliver
+ or disclose any portion of the Software in source code form.
+*/
+
+#ifndef __QFIREHOSE_USB_LINUX_H__
+#define __QFIREHOSE_USB_LINUX_H__
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define MAX_PATH 256
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define ZIP_INFO "/tmp/zip_info.txt"
+#define ZIP_PROCESS_INFO "/tmp/zip_process_info.txt"
+
+#define safe_free(p) \
+ do \
+ { \
+ if (p != NULL) \
+ { \
+ free((void *)p); \
+ p = NULL; \
+ } \
+ } while (0)
+
+extern char zip_cmd_buf[512];
+extern int g_is_module_adb_entry_edl;
+
+typedef struct module_sys_info
+{
+ /*
+ MAJOR=189
+ MINOR=1
+ DEVNAME=bus/usb/001/002
+ DEVTYPE=usb_device
+ DRIVER=usb
+ PRODUCT=2c7c/415/318
+ TYPE=239/2/1
+ BUSNUM=001
+ */
+ // char sys_path[MAX_PATH];
+ int MAJOR;
+ int MINOR;
+ char DEVNAME[64];
+ char DEVTYPE[64];
+ char PRODUCT[64];
+} MODULE_SYS_INFO;
+
+void *qusb_noblock_open(const char *module_sys_path, int *idVendor, int *idProduct, int *interfaceNum);
+int qusb_noblock_close(void *handle);
+int qusb_noblock_write(const void *handle, void *pbuf, int max_size, int min_size, int timeout_msec, int need_zlp);
+int qusb_noblock_read(const void *handle, void *pbuf, int max_size, int min_size, int timeout_msec);
+int qusb_read_speed_atime(const char *module_sys_path, struct timespec *out_time, int *out_speed);
+int qfile_find_file(const char *dir, const char *prefix, const char *suffix, char **filename);
+#define errno_nodev() (errno == ENOENT || errno == ENODEV)
+// void dbg_time (const char *fmt, ...);
+const char *firehose_get_time(void);
+extern FILE *loghandler;
+#ifdef FH_DEBUG
+#define dbg_time(fmt, args...) \
+ do \
+ { \
+ fprintf(stdout, "[%15s-%04d]%s: " fmt, __FILE__, __LINE__, firehose_get_time(), ##args); \
+ fflush(stdout); \
+ if (loghandler) fprintf(loghandler, "[%15s-%04d]%s: " fmt, __FILE__, __LINE__, firehose_get_time(), ##args); \
+ } while (0);
+#else
+#define dbg_time(fmt, args...) \
+ do \
+ { \
+ fprintf(stdout, "%s: " fmt, firehose_get_time(), ##args); \
+ fflush(stdout); \
+ if (loghandler) fprintf(loghandler, "%s: " fmt, firehose_get_time(), ##args); \
+ } while (0);
+#endif
+double get_now();
+void get_duration(double start);
+int update_transfer_bytes(long long bytes_cur);
+void set_transfer_allbytes(long long bytes);
+int auto_find_quectel_modules(char *module_sys_path, unsigned size, const char *product, const struct timespec *atime);
+void quectel_get_syspath_name_by_ttyport(const char *module_port_name, char *module_sys_path, unsigned size);
+void quectel_get_ttyport_by_syspath(const char *module_sys_path, char *module_port_name, unsigned size);
+#define error_return() \
+ do \
+ { \
+ dbg_time("%s %s %d fail\n", __FILE__, __func__, __LINE__); \
+ return __LINE__; \
+ } while (0)
+
+extern int edl_pcie_mhifd;
+
+#define IOCTL_BHI_GETDEVINFO 0x8BE0 + 1
+#define IOCTL_BHI_WRITEIMAGE 0x8BE0 + 2
+
+typedef unsigned int ULONG;
+
+typedef struct _bhi_info_type
+{
+ ULONG bhi_ver_minor;
+ ULONG bhi_ver_major;
+ ULONG bhi_image_address_low;
+ ULONG bhi_image_address_high;
+ ULONG bhi_image_size;
+ ULONG bhi_rsvd1;
+ ULONG bhi_imgtxdb;
+ ULONG bhi_rsvd2;
+ ULONG bhi_msivec;
+ ULONG bhi_rsvd3;
+ ULONG bhi_ee;
+ ULONG bhi_status;
+ ULONG bhi_errorcode;
+ ULONG bhi_errdbg1;
+ ULONG bhi_errdbg2;
+ ULONG bhi_errdbg3;
+ ULONG bhi_sernum;
+ ULONG bhi_sblantirollbackver;
+ ULONG bhi_numsegs;
+ ULONG bhi_msmhwid[6];
+ ULONG bhi_oempkhash[48];
+ ULONG bhi_rsvd5;
+} BHI_INFO_TYPE, *PBHI_INFO_TYPE;
+
+enum MHI_EE
+{
+ MHI_EE_PBL = 0x0, /* Primary Boot Loader */
+ MHI_EE_SBL = 0x1, /* Secondary Boot Loader */
+ MHI_EE_AMSS = 0x2, /* AMSS Firmware */
+ MHI_EE_RDDM = 0x3, /* WIFI Ram Dump Debug Module */
+ MHI_EE_WFW = 0x4, /* WIFI (WLAN) Firmware */
+ MHI_EE_PT = 0x5, /* PassThrough, Non PCIe BOOT (PCIe is BIOS locked, not used for boot */
+ MHI_EE_EDL = 0x6, /* PCIe enabled in PBL for emergency download (Non PCIe BOOT) */
+ MHI_EE_FP = 0x7, /* FlashProg, Flash Programmer Environment */
+ MHI_EE_BHIE = MHI_EE_FP,
+ MHI_EE_UEFI = 0x8, /* UEFI */
+
+ MHI_EE_DISABLE_TRANSITION = 0x9,
+ MHI_EE_MAX
+};
+int qpcie_open(const char *firehose_dir, const char *firehose_mbn, const char *module_port_name);
+
+#define Q_USB2TCP_VERSION 0x12345678
+typedef struct
+{
+ int tag;
+ int length;
+ int value[];
+} TLV;
+
+typedef struct
+{
+ int tag;
+ int length;
+ int idVendor;
+ int idProduct;
+ int interfaceNum;
+} TLV_USB;
+
+typedef struct
+{
+ char zip_file_name_backup[128];
+ char zip_file_dir_backup[256];
+} file_name_backup_count;
+
+typedef struct
+{
+ int file_name_count;
+ file_name_backup_count file_backup_c[256]; // smart SA885GAPNA number of image file is 96
+} file_name_backup;
+
+extern file_name_backup file_name_b;
+
+extern int is_upgrade_fimeware_zip_7z;
+extern int is_firehose_zip_7z_name_exit;
+extern int is_upgrade_fimeware_only_zip;
+extern unsigned int g_from_ecm_to_rndis;
+#endif
diff --git a/luci/luci-app-qmodem/Makefile b/luci/luci-app-qmodem/Makefile
index beb1d16..b561bd2 100644
--- a/luci/luci-app-qmodem/Makefile
+++ b/luci/luci-app-qmodem/Makefile
@@ -27,6 +27,7 @@ LUCI_DEPENDS:=+luci-compat \
+usbutils \
+PACKAGE_luci-app-qmodem_INCLUDE_ADD_PCI_SUPPORT:kmod-pcie_mhi \
+PACKAGE_luci-app-qmodem_INCLUDE_ADD_PCI_SUPPORT:pciutils \
+ +PACKAGE_luci-app-qmodem_INCLUDE_ADD_QFIREHOSE_SUPPORT:qfirehose \
+PACKAGE_luci-app-qmodem_USE_TOM_CUSTOMIZED_QUECTEL_CM:quectel-CM-5G-M \
+PACKAGE_luci-app-qmodem_USING_QWRT_QUECTEL_CM_5G:quectel-CM-5G \
+PACKAGE_luci-app-qmodem_USING_NORMAL_QUECTEL_CM:quectel-cm \
@@ -69,6 +70,10 @@ endchoice
config PACKAGE_luci-app-qmodem_INCLUDE_ADD_PCI_SUPPORT
bool "Add PCIe Modem SUPPORT"
default n
+
+ config PACKAGE_luci-app-qmodem_INCLUDE_ADD_QFIREHOSE_SUPPORT
+ bool "Add Qfirehose SUPPORT"
+ default n
endef
define Package/luci-app-qmodem_with_lua_index_page