2023-04-11 21:15:01 +08:00

634 lines
20 KiB
C

/******************************************************************************
@file qfirehose.c
@brief entry point.
DESCRIPTION
QFirehoe Tool for USB and PCIE of Quectel wireless cellular modules.
INITIALIZATION AND SEQUENCING REQUIREMENTS
None.
---------------------------------------------------------------------------
Copyright (c) 2016 - 2020 Quectel Wireless Solution, Co., Ltd. All Rights Reserved.
Quectel Wireless Solution Proprietary and Confidential.
---------------------------------------------------------------------------
******************************************************************************/
#include <getopt.h>
#include <grp.h>
#include <sys/types.h>
#include <pwd.h>
#ifdef USE_IPC_MSG
#include <sys/msg.h>
#include <sys/ipc.h>
#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 <reyad.attiyat@gmail.com>
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;
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;
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);
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");
free(pbuf);
return 1;
}
if (rx_count++ > 50)
break;
} while (rx_len > 0);
free(pbuf);
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(" -e Erase All Before Download (will Erase calibration data, careful to USE)\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");
}
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 (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)
) {
dbg_time("%s fail to find firehose mbn file in %s\n", __func__, *firehose_dir);
return NULL;
}
dbg_time("%s %s\n", __func__, firehose_mbn);
return firehose_mbn;
}
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();
}
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_dir = malloc(MAX_PATH);
char *firehose_mbn = NULL;
char *module_port_name = malloc(MAX_PATH);
char *module_sys_path = malloc(MAX_PATH);
int xhci_usb3_to_usb2_cause_syspatch_chage = 1;
int usb2tcp_port = 0;
char filename[128] = {'\0'};
const char *usbmon_logfile = NULL;
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.11\n"); //when release, rename to V1.X
#ifndef __clang__
dbg_time("Builded: %s %s\n", __DATE__,__TIME__);
#endif
#ifdef ANDROID
struct passwd* passwd;
passwd = getpwuid(getuid());
dbg_time("------------------\n");
dbg_time("User:\t %s\n",passwd->pw_name);
struct group* group;
group = getgrgid(passwd->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:neh"))) {
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(firehose_dir, optarg, MAX_PATH);
break;
case 'p':
strncpy(module_port_name, optarg, MAX_PATH);
if (!strcmp(module_port_name, "9008")) {
usb2tcp_port = atoi(module_port_name);
module_port_name[0] = '\0';
}
break;
case 's':
xhci_usb3_to_usb2_cause_syspatch_chage = 0;
strncpy(module_sys_path, optarg, MAX_PATH);
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);
break;
case 'd':
q_device_type = strdup(optarg);
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);
error_return();
}
opt = strlen(firehose_dir);
if (firehose_dir[opt-1] == '/') {
firehose_dir[opt-1] = '\0';
}
// check the md5 value of the upgrade file
if (check_hash && md5_check(firehose_dir)) {
update_transfer_bytes(-1);
error_return();
}
//hunter.lv add check dir 2018-07-28
firehose_mbn = find_firehose_mbn(&firehose_dir, MAX_PATH);
if (!firehose_mbn) {
update_transfer_bytes(-1);
error_return();
}
//hunter.lv add check dir 2018-07-28
if (module_port_name[0] && !strncmp(module_port_name, "/dev/mhi", strlen("/dev/mhi"))) {
if (qpcie_open(firehose_dir, firehose_mbn)) {
update_transfer_bytes(-1);
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);
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);
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);
}
if (module_sys_path[0] == '\0') {
int module_count = auto_find_quectel_modules(module_sys_path, MAX_PATH);
if (module_count <= 0) {
dbg_time("Quectel module not found\n");
update_transfer_bytes(-1);
error_return();
}
else if (module_count == 1) {
} 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 </sys/bus/usb/devices/xx> path was printed in the previous log!\n");
update_transfer_bytes(-1);
error_return();
}
}
__edl_retry:
while (edl_retry-- > 0) {
usb_handle = qusb_noblock_open(module_sys_path, &idVendor, &idProduct, &interfaceNum);
if (usb_handle == NULL && module_sys_path[0] == '/') {
sleep(1); //in reset sate, wait connect
if (xhci_usb3_to_usb2_cause_syspatch_chage && access(module_sys_path, R_OK) && errno_nodev()) {
auto_find_quectel_modules(module_sys_path, MAX_PATH);
}
else if (access(module_sys_path, R_OK) && errno_nodev()) {
int busidx = strlen("/sys/bus/usb/devices/");
char busnum = module_sys_path[busidx];
module_sys_path[busidx] = busnum-1;
if (access(module_sys_path, R_OK) && errno_nodev())
module_sys_path[busidx] = busnum+1;
if (!access(module_sys_path, R_OK)) {
usb_handle = qusb_noblock_open(module_sys_path, &idVendor, &idProduct, &interfaceNum);
if (usb_handle && (idVendor != 0x05c6 || idProduct != 0x9008)) {
qusb_noblock_close(usb_handle);
usb_handle = NULL;
}
}
module_sys_path[busidx] = busnum;
}
if (usb_handle == NULL)
continue;
}
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();
}
}
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);
error_return();
}
if (usb2tcp_port) {
retval = usb2tcp_main(usb_handle, usb2tcp_port, qusb_zlp_mode);
qusb_noblock_close(usb_handle);
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);
if (firehose_dir) free(firehose_dir);
if (module_port_name) free(module_port_name);
if (module_sys_path) free(module_sys_path);
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();
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