From 729bebe78f227a584dc746e6732bd448870b4f30 Mon Sep 17 00:00:00 2001 From: sfwtw Date: Thu, 6 Mar 2025 18:21:10 +0800 Subject: [PATCH] Feat: add support for qfirehose 1.4.21 --- README.en.md | 1 + README.md | 6 +- application/qfirehose/Makefile | 52 + application/qfirehose/src/.clang-format | 167 ++ application/qfirehose/src/Android.mk | 8 + application/qfirehose/src/CMakeLists.txt | 12 + application/qfirehose/src/Makefile | 22 + application/qfirehose/src/NOTICE | 6 + application/qfirehose/src/README.md | 108 + application/qfirehose/src/ReleaseNote.txt | 198 ++ .../qfirehose/src/android/arm64-v8a/QFirehose | Bin 0 -> 137904 bytes .../src/android/armeabi-v7a/QFirehose | Bin 0 -> 104764 bytes application/qfirehose/src/firehose_protocol.c | 2297 +++++++++++++++++ application/qfirehose/src/hostdl_packet.h | 113 + .../qfirehose/src/log/MCU_local.log.txt | 581 +++++ .../qfirehose/src/log/MCU_remote.log.txt | 38 + .../qfirehose/src/log/Ubuntu_remote.log.txt | 607 +++++ .../qfirehose/src/log/pcie_mhi.log.txt | 60 + application/qfirehose/src/md5.c | 477 ++++ application/qfirehose/src/md5.h | 33 + application/qfirehose/src/qfirehose.c | 1115 ++++++++ application/qfirehose/src/sahara.c | 529 ++++ application/qfirehose/src/sahara.h | 94 + .../qfirehose/src/stream_download_protocol.c | 826 ++++++ application/qfirehose/src/usb2tcp.c | 381 +++ application/qfirehose/src/usb_linux.c | 1657 ++++++++++++ application/qfirehose/src/usb_linux.h | 193 ++ luci/luci-app-qmodem/Makefile | 5 + 28 files changed, 9583 insertions(+), 3 deletions(-) create mode 100644 application/qfirehose/Makefile create mode 100644 application/qfirehose/src/.clang-format create mode 100644 application/qfirehose/src/Android.mk create mode 100644 application/qfirehose/src/CMakeLists.txt create mode 100644 application/qfirehose/src/Makefile create mode 100644 application/qfirehose/src/NOTICE create mode 100644 application/qfirehose/src/README.md create mode 100644 application/qfirehose/src/ReleaseNote.txt create mode 100644 application/qfirehose/src/android/arm64-v8a/QFirehose create mode 100644 application/qfirehose/src/android/armeabi-v7a/QFirehose create mode 100644 application/qfirehose/src/firehose_protocol.c create mode 100644 application/qfirehose/src/hostdl_packet.h create mode 100644 application/qfirehose/src/log/MCU_local.log.txt create mode 100644 application/qfirehose/src/log/MCU_remote.log.txt create mode 100644 application/qfirehose/src/log/Ubuntu_remote.log.txt create mode 100644 application/qfirehose/src/log/pcie_mhi.log.txt create mode 100644 application/qfirehose/src/md5.c create mode 100644 application/qfirehose/src/md5.h create mode 100644 application/qfirehose/src/qfirehose.c create mode 100644 application/qfirehose/src/sahara.c create mode 100644 application/qfirehose/src/sahara.h create mode 100644 application/qfirehose/src/stream_download_protocol.c create mode 100644 application/qfirehose/src/usb2tcp.c create mode 100644 application/qfirehose/src/usb_linux.c create mode 100644 application/qfirehose/src/usb_linux.h 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 0000000000000000000000000000000000000000..98276b6073d977c19470c9709e10ef82758ba3ef GIT binary patch literal 137904 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 FzW{5Aon`<4 literal 0 HcmV?d00001 diff --git a/application/qfirehose/src/android/armeabi-v7a/QFirehose b/application/qfirehose/src/android/armeabi-v7a/QFirehose new file mode 100644 index 0000000000000000000000000000000000000000..22ffa7b95db67e7c8538a172f6897a7b2836e1b4 GIT binary patch literal 104764 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 literal 0 HcmV?d00001 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