You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
balong-usbload/balong-usbload.c

610 lines
15 KiB
C

// Balong-usbload is an emergency USB boot loader utility for Huawei LTE modems and routers with Balong V2R7, V7R11 and V7R22 chipsets.
// It loads external boot loader/firmware update tool file (usbloader.bin) via emergency serial port available if the firmware is corrupted or boot pin (test point) is shorted to the ground.
#include <stdio.h>
#include <stdint.h>
#ifndef WIN32
//%%%%
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <arpa/inet.h>
#else
//%%%%
#include <windows.h>
#include <setupapi.h>
#include "getopt.h"
#include "printf.h"
#endif
#include "parts.h"
#include "patcher.h"
#ifndef WIN32
int siofd;
struct termios sioparm;
#else
static HANDLE hSerial;
#endif
FILE* ldr;
//*************************************************
//* HEX-dump of the memory area *
//*************************************************
void dump(unsigned char buffer[],int len) {
int i,j;
unsigned char ch;
printf("\n");
for (i=0;i<len;i+=16) {
printf("%04x: ",i);
for (j=0;j<16;j++){
if ((i+j) < len) printf("%02x ",buffer[i+j]&0xff);
else printf(" ");}
printf(" *");
for (j=0;j<16;j++) {
if ((i+j) < len) {
// byte conversion for character display
ch=buffer[i+j];
if ((ch < 0x20)||((ch > 0x7e)&&(ch<0xc0))) putchar('.');
else putchar(ch);
}
// filling with spaces for incomplete lines
else printf(" ");
}
printf("*\n");
}
}
//*************************************************
//* Calculating the checksum of a command packet *
//*************************************************
void csum(unsigned char* buf, int len) {
unsigned int i,c,csum=0;
unsigned int cconst[]={0,0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF};
for (i=0;i<(len-2);i++) {
c=(buf[i]&0xff);
csum=((csum<<4)&0xffff)^cconst[(c>>4)^(csum>>12)];
csum=((csum<<4)&0xffff)^cconst[(c&0xf)^(csum>>12)];
}
buf[len-2]=(csum>>8)&0xff;
buf[len-1]=csum&0xff;
}
//*************************************************
//* Sending a command packet to a modem *
//*************************************************
int sendcmd(unsigned char* cmdbuf, int len) {
unsigned char replybuf[1024];
unsigned int replylen;
#ifndef WIN32
csum(cmdbuf,len);
write(siofd,cmdbuf,len); // sending a command
tcdrain(siofd);
replylen=read(siofd,replybuf,1024);
#else
DWORD bytes_written = 0;
DWORD t;
csum(cmdbuf, len);
WriteFile(hSerial, cmdbuf, len, &bytes_written, NULL);
FlushFileBuffers(hSerial);
t = GetTickCount();
do {
ReadFile(hSerial, replybuf, 1024, (LPDWORD)&replylen, NULL);
} while (replylen == 0 && GetTickCount() - t < 1000);
#endif
if (replylen == 0) return 0;
if (replybuf[0] == 0xaa) return 1;
return 0;
}
//*************************************************
// Opening and configuring the serial port *
//*************************************************
int open_port(char* devname) {
//============= Linux ===============
#ifndef WIN32
int i,dflag=1;
char devstr[200]={0};
// only the ttyUSB port number can be transmitted instead of the full device name.
// check the device name for nonnumeric characters
for(i=0;i<strlen(devname);i++) {
if ((devname[i]<'0') || (devname[i]>'9')) dflag=0;
}
// if there are only digits in the string, add the prefix /dev/ttyUSB
if (dflag) strcpy(devstr,"/dev/ttyUSB");
// copy the device name
strcat(devstr,devname);
siofd = open(devstr, O_RDWR | O_NOCTTY |O_SYNC);
if (siofd == -1) return 0;
bzero(&sioparm, sizeof(sioparm)); // prepare the attribute block termios
sioparm.c_cflag = B115200 | CS8 | CLOCAL | CREAD ;
sioparm.c_iflag = 0; // INPCK;
sioparm.c_oflag = 0;
sioparm.c_lflag = 0;
sioparm.c_cc[VTIME]=30; // timeout
sioparm.c_cc[VMIN]=0;
tcsetattr(siofd, TCSANOW, &sioparm);
return 1;
//============= Win32 ===============
#else
char device[20] = "\\\\.\\COM";
DCB dcbSerialParams = {0};
COMMTIMEOUTS CommTimeouts;
strcat(device, devname);
hSerial = CreateFileA(device, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hSerial == INVALID_HANDLE_VALUE)
return 0;
ZeroMemory(&dcbSerialParams, sizeof(dcbSerialParams));
dcbSerialParams.DCBlength=sizeof(dcbSerialParams);
dcbSerialParams.BaudRate=CBR_115200;
dcbSerialParams.ByteSize=8;
dcbSerialParams.StopBits=ONESTOPBIT;
dcbSerialParams.Parity=NOPARITY;
dcbSerialParams.fBinary = TRUE;
dcbSerialParams.fDtrControl = DTR_CONTROL_ENABLE;
dcbSerialParams.fRtsControl = RTS_CONTROL_ENABLE;
if(!SetCommState(hSerial, &dcbSerialParams))
{
CloseHandle(hSerial);
return 0;
}
CommTimeouts.ReadIntervalTimeout = MAXDWORD;
CommTimeouts.ReadTotalTimeoutConstant = 0;
CommTimeouts.ReadTotalTimeoutMultiplier = 0;
CommTimeouts.WriteTotalTimeoutConstant = 0;
CommTimeouts.WriteTotalTimeoutMultiplier = 0;
if (!SetCommTimeouts(hSerial, &CommTimeouts))
{
CloseHandle(hSerial);
return 0;
}
return 1;
#endif
}
//*******************************************************
//* Searching for the linux core in the partition image *
//*******************************************************
int locate_kernel(char* pbuf, uint32_t size) {
int off;
for(off=(size-8);off>0;off--) {
if (strncmp(pbuf+off,"ANDROID!",8) == 0) return off;
}
return 0;
}
#ifdef WIN32
DEFINE_GUID(GUID_DEVCLASS_PORTS, 0x4D36E978, 0xE325, 0x11CE, 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18);
static int find_port(int* port_no, char* port_name)
{
HDEVINFO device_info_set;
DWORD member_index = 0;
SP_DEVINFO_DATA device_info_data;
DWORD reg_data_type;
char property_buffer[256];
DWORD required_size;
char* p;
int result = 1;
device_info_set = SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS, NULL, 0, DIGCF_PRESENT);
if (device_info_set == INVALID_HANDLE_VALUE)
return result;
while (TRUE)
{
ZeroMemory(&device_info_data, sizeof(SP_DEVINFO_DATA));
device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
if (!SetupDiEnumDeviceInfo(device_info_set, member_index, &device_info_data))
break;
member_index++;
if (!SetupDiGetDeviceRegistryPropertyA(device_info_set, &device_info_data, SPDRP_HARDWAREID,
&reg_data_type, (PBYTE)property_buffer, sizeof(property_buffer), &required_size))
continue;
if (strstr(_strupr(property_buffer), "VID_12D1&PID_1443") != NULL)
{
if (SetupDiGetDeviceRegistryPropertyA(device_info_set, &device_info_data, SPDRP_FRIENDLYNAME,
&reg_data_type, (PBYTE)property_buffer, sizeof(property_buffer), &required_size))
{
p = strstr(property_buffer, " (COM");
if (p != NULL)
{
*port_no = atoi(p + 5);
strcpy(port_name, property_buffer);
result = 0;
}
}
break;
}
}
SetupDiDestroyDeviceInfoList(device_info_set);
return result;
}
#endif
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
void main(int argc, char* argv[]) {
unsigned int i,res,opt,datasize,pktcount,adr;
int bl; // current block
unsigned char c;
int fbflag=0, tflag=0, mflag=0, bflag=0, cflag=0;
int koff; // offset to ANDROID header
char ptfile[100];
FILE* pt;
char ptbuf[2048];
uint32_t ptoff;
struct ptable_t* ptable;
unsigned char cmdhead[14]={0xfe,0, 0xff};
unsigned char cmddata[1040]={0xda,0,0};
unsigned char cmdeod[5]={0xed,0,0,0,0};
// list of partitions to set the file flag
uint8_t fileflag[41];
struct {
int lmode; // loading mode: 1 - direct start, 2 - via restarting A-core
int size; // size of a component
int adr; // address of the component loading into memory
int offset; // offset to the component from the beginning of the file
char* pbuf; // buffer for loading the component image
} blk[10];
#ifndef WIN32
unsigned char devname[50]="/dev/ttyUSB0";
#else
char devname[50]="";
DWORD bytes_written, bytes_read;
int port_no;
char port_name[256];
#endif
#ifndef WIN32
bzero(fileflag,sizeof(fileflag));
#else
memset(fileflag, 0, sizeof(fileflag));
#endif
while ((opt = getopt(argc, argv, "hp:ft:ms:bc")) != -1) {
switch (opt) {
case 'h':
printf("\n This utility is used for emergency USB loading of devices with a Balong V7 chip\n\n\
%s [option] <boot file name>\n\n\
Following options are allowed:\n\n"
#ifndef WIN32
"-p <tty> - Serial port for communicating with the bootloader (by default /dev/ttyUSB0)\n"
#else
"-p # - Number of the serial port for communicating with the loader (e.g. -p8)\n"
" if the -p option is not specified, the port is auto-detected\n"
#endif
"-f - Load usbloader only to fastboot (without starting Linux)\n\
-b - Similarly to -f, additionally disable check of defective blocks during wipeout\n\
-t <file> - Take partition table from specified file\n\
-m - Show the loader partition table and exit\n\
-s <n> - Set file flag for partition <n> (option can be set multiple times)\n\
-c - Do not automatically patch partitions\n\
\n",argv[0]);
return;
case 'p':
strcpy(devname,optarg);
break;
case 'f':
fbflag=1;
break;
case 'c':
cflag=1;
break;
case 'b':
fbflag=1;
bflag=1;
break;
case 'm':
mflag=1;
break;
case 't':
tflag=1;
strcpy(ptfile,optarg);
break;
case 's':
i=atoi(optarg);
if (i>41) {
printf("\n Partition #%i does not exist\n",i);
return;
}
fileflag[i]=1;
break;
case '?':
case ':':
return;
}
}
printf("\nBalong-chipset emergency USB loader, version 2.20, (c) forth32, 2015\n");
printf("English Version (c) robertzaage, 2022\n");
#ifdef WIN32
printf("Windows Port 32bit (c) rust3028, 2016\n");
#endif
if (optind>=argc) {
printf("\n - File to upload is not specified\n");
return;
}
ldr=fopen(argv[optind],"rb");
if (ldr == 0) {
printf("\n Opening error %s",argv[optind]);
return;
}
// Checking the usloader signature
fread(&i,1,4,ldr);
if (i != 0x20000) {
printf("\n File %s is not a usbloader\n",argv[optind]);
return;
}
fseek(ldr,36,SEEK_SET); // beginning of block descriptors to load
// parsing the header
fread(&blk[0],1,16,ldr); // raminit
fread(&blk[1],1,16,ldr); // usbldr
//---------------------------------------------------------------------
// reading components into memory
for(bl=0;bl<2;bl++) {
// allocate memory for the full image of the partition
blk[bl].pbuf=(char*)malloc(blk[bl].size);
// read partition image into memory
fseek(ldr,blk[bl].offset,SEEK_SET);
res=fread(blk[bl].pbuf,1,blk[bl].size,ldr);
if (res != blk[bl].size) {
printf("\n Unexpected end of file: read %i expected %i\n",res,blk[bl].size);
return;
}
if (bl == 0) continue; // nothing more needs to be done for raminit
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// fastboot patch
if (fbflag) {
koff=locate_kernel(blk[bl].pbuf,blk[bl].size);
if (koff != 0) {
blk[bl].pbuf[koff]=0x55; // patch the signature
blk[bl].size=koff+8; // trim the partition to the start of the kernel
}
else {
printf("\n There is no ANDROID component in the loader - fastboot-loading is not possible.\n");
exit(0);
}
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// look for the partition table in the boot loader
ptoff=find_ptable_ram(blk[bl].pbuf,blk[bl].size);
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// patch partition table
if (tflag) {
pt=fopen(ptfile,"rb");
if (pt == 0) {
printf("\n File %s is not found - partition table cannot be replaced\n",ptfile);
return;
}
fread(ptbuf,1,2048,pt);
fclose(pt);
if (memcmp(headmagic,ptbuf,sizeof(headmagic)) != 0) {
printf("\n The file %s is not a partition table\n",ptfile);
return;
}
if (ptoff == 0) {
printf("\n No partition table found in loader - cannot be replaced");
return;
}
memcpy(blk[bl].pbuf+ptoff,ptbuf,2048);
}
ptable=(struct ptable_t*)(blk[bl].pbuf+ptoff);
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// patch file flags
for(i=0;i<41;i++) {
if (fileflag[i]) {
ptable->part[i].nproperty |= 1;
}
}
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// output partition table
if (mflag) {
if (ptoff == 0) {
printf("\n Partition table not found - mapping output not possible\n");
return;
}
show_map(*ptable);
return;
}
// patch the erase procedure to ignore deadlocks
if (bflag) {
res=perasebad(blk[bl].pbuf, blk[bl].size);
if (res == 0) {
printf("\n! No isbad signature found - cannot load\n");
return;
}
}
// removing the flash_eraseall procedure
if (!cflag) {
res = pv7r1(blk[bl].pbuf, blk[bl].size);
if (res == 0)
res = pv7r2(blk[bl].pbuf, blk[bl].size);
if (res == 0)
res = pv7r11(blk[bl].pbuf, blk[bl].size);
if (res == 0)
res = pv7r22(blk[bl].pbuf, blk[bl].size);
if (res == 0)
res = pv7r22_2(blk[bl].pbuf, blk[bl].size);
if (res == 0)
res = pv7r22_3(blk[bl].pbuf, blk[bl].size);
if (res != 0) printf("\n\n * Removed flash_eraseall procedure at offset %08x", blk[bl].offset + res);
else {
printf("\n Procedure eraseall not found in the bootloader - use key -s to boot without patch!\n");
return;
}
}
}
//---------------------------------------------------------------------
#ifdef WIN32
if (*devname == '\0')
{
printf("\n\nSearching for the emergency loading port...\n");
if (find_port(&port_no, port_name) == 0)
{
sprintf(devname, "%d", port_no);
printf("Port: \"%s\"\n", port_name);
}
else
{
printf("No port found!\n");
return;
}
}
#endif
if (!open_port(devname)) {
printf("\n Can't open serial port\n");
return;
}
// Check the boot port
c=0;
#ifndef WIN32
write(siofd,"A",1);
res=read(siofd,&c,1);
#else
WriteFile(hSerial, "A", 1, &bytes_written, NULL);
FlushFileBuffers(hSerial);
Sleep(100);
ReadFile(hSerial, &c, 1, &bytes_read, NULL);
#endif
if (c != 0x55) {
printf("\n! Port is not in USB-Boot mode\n");
return;
}
//----------------------------------
// main loading cycle - load all the blocks found in the header
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
printf("\n\n Component Address Size %% loaded\n------------------------------------------\n");
for(bl=0;bl<2;bl++) {
datasize=1024;
pktcount=1;
// create a package of the beginning of the block
*((unsigned int*)&cmdhead[4])=htonl(blk[bl].size);
*((unsigned int*)&cmdhead[8])=htonl(blk[bl].adr);
cmdhead[3]=blk[bl].lmode;
// send a block start packet
res=sendcmd(cmdhead,14);
if (!res) {
printf("\nModem rejected the header packet\n");
return;
}
// ---------- Block-by-block data loading cycle ------------
for(adr=0;adr<blk[bl].size;adr+=1024) {
// get the size of the last downloaded packet
if ((adr+1024)>=blk[bl].size) datasize=blk[bl].size-adr;
printf("\r %s %08x %8i %i%%",bl?"usbboot":"raminit",blk[bl].adr,blk[bl].size,(adr+datasize)*100/blk[bl].size);
// prepare the data package
cmddata[1]=pktcount;
cmddata[2]=(~pktcount)&0xff;
memcpy(cmddata+3,blk[bl].pbuf+adr,datasize);
pktcount++;
if (!sendcmd(cmddata,datasize+5)) {
printf("\nModem rejected the data packet");
return;
}
}
free(blk[bl].pbuf);
// prepare an end-of-data packet
cmdeod[1]=pktcount;
cmdeod[2]=(~pktcount)&0xff;
if (!sendcmd(cmdeod,5)) {
printf("\nModem rejected the end of data packet");
}
printf("\n");
}
printf("\n Loading finished!\n");
}