#include "esptool.h"
#include "espcom.h"
#include "image.h"
#include "miniz.h"
//#include <stdio.h>
#include <malloc.h> // _alloca, realloc
// Return a/b rounded up to nearest integer,
static unsigned div_ceil(unsigned a,unsigned b) {
return (a+b-1)/b;
}
// Konstruktor
ESP::ESP(unsigned port):chip(esp::undefined),info1(0),info2(0),lastError(0),statusbytes(0) {
printf(0x209,port+1); // "Open COM%u:"
_port=ComSel::Open(port);
putchar(' '); // in case Open() takes a while, you get a clue looking at cursor movement
if (!_port) {
println(0x20A); // "Failure"
return;
}
if (!changeHostBaud(currentBaud=115200)) {
CloseHandle(_port);
_port=0;
}
}
bool ESP::changeHostBaud(uint32 baud) {
DCB dcb;
dcb.DCBlength=sizeof dcb;
GetCommState(_port,&dcb);
dcb.BaudRate=baud;
((DWORD*)&dcb)[2]=1; // alle Flags=0 außer fBinary
dcb.ByteSize=8;
dcb.Parity=NOPARITY;
dcb.StopBits=ONESTOPBIT;
if (!SetCommState(_port,&dcb)) {
println(0x20B,baud); // "Could not set baudrate %u"
return false;
}
return true;
}
/* Checks status bytes of answer data */
byte ESP::HDR::getStatus(const ESP*o) const{
byte distFromEnd=o->statusbytes;
if (!distFromEnd) // unbekannt, Initialisierungsphase
distFromEnd = siz>=4 && !(data()[siz-4]&0xFE) ? 4 : 2;
int i = siz - distFromEnd;
if (i<0) return 1; // Antwort zu kurz
byte error=data()[i]; // (Hier ist der Fehlerkode vom Stubben!)
switch (error) {
case 0: return 0; // okay, evtl. Fehlerkode nicht auswerten
case 1: return data()[i+1]; // Fehlerkode 5..0x0B (ROM-Lader), 0xC*, 0xFF (Stub-Lader)
default: return error>=0xC0?error:2; // Falsches Statusbyte (muss 0 oder 1 sein)
}
}
int ESP::readByte() {
int c=0;
DWORD br;
if (!ReadFile(_port,&c,1,&br,0)) {ComError("Failure");}
if (br!=1) {c=-1; setError(3);} //3 = read timeout
return c;
}
unsigned ESP::readSlip(void*buf,unsigned length,byte&flags) {
unsigned i=0;
byte*bu=reinterpret_cast<byte*>(buf);
int c;
if (flags&1<<1) {
while ((c=readByte())!=0xC0) if (c<0) {flags|=1<<4; goto reti;}
flags&=~(1<<1);
}
if (flags&1<<2) {
c=readByte();
if (c<0) {flags|=1<<4; goto reti;} // Timeout: Raus!
if (c==0xC0) flags&=~(1<<2); // nur 1×
else if (length) goto jumpin;
else{ // only happens if empty SLIP packet expected and flags&1<<2 on entry and read character != C0
setError(5);
flags|=1<<5;
if (flags&1<<6) ComError("Invalid zero-length SLIP packet");
goto reti;
}
}
for (; i<length; i++) {
c=readByte();
if (c<0) {flags|=1<<4; goto reti;} // Timeout: Raus!
jumpin:
switch (c) {
case 0xDB: {
c=readByte();
if (c<0) {flags|=1<<4; goto reti;} // Timeout: Raus!
switch (c) {
case 0xDC: c = 0xC0; break;
case 0xDD: c = 0xDB; break;
default:
setError(5); // "Empfangenes Paket ungültig"
flags|=1<<5;
if (flags&1<<6) ComError("Invalid SLIP escape DB-%02X",c);
goto reti;
}
}break;
case 0xC0: flags&=~(1<<3); goto reti;// Voreilendes SLIP-Ende
}
*bu++=c;
}
if (flags&1<<3) {
if (readByte()==0xC0) flags&=~(1<<3);
else if (flags&1<<6) ComError("Invalid SLIP termination");
}
if (i==length) flags|=1; // complete
reti:
flags&=~(1<<6);
return i;
}
bool ESP::writeByte(byte c) {
DWORD bw;
if (!WriteFile(_port,&c,1,&bw,0)) {ComError("wFailure"); return false;}
if (bw!=1) return setError(4); // 4 = write timeout
return true;
}
/* Write bytes to the serial port while performing SLIP escaping */
bool ESP::writeSlip(const void*data, unsigned dlen) {
const byte*da=reinterpret_cast<const byte*>(data);
if (!writeByte(0xC0)) return false;
if (dlen) do{
byte c=*da++;
switch (c) {
case 0xDB: if (!writeByte(c)||!writeByte(0xDD)) return false; break;
case 0xC0: if (!writeByte(0xDB)||!writeByte(0xDC)) return false; break;
default: if (!writeByte(c)) return false;
}
}while(--dlen);
return writeByte(0xC0);
}
// Same but length given by header
bool ESP::writeSlip(const HDR*packet) {
return writeSlip(packet,sizeof*packet+packet->siz);
}
/* Receive a response to a command */
ESP::autoHDR ESP::readSlip(const HDR*sent,uint32 to_ms) {
HDR*recv=0;
setReadTimeout(to_ms); // fürs Löschen für das ganze Paket erforderlich: Blockiert vor den Statusbytes
// Read header of response and parse
do{
recv=(HDR*)realloc(recv,sizeof*recv);
byte flags=6; // Lesebytes ignorieren bis 0xC0 kommt, dann darf ein zweites 0xC0 kommen
readSlip(recv,sizeof*recv,flags);
if (flags!=1 && flags!=5) goto err; // Kein Fehler (= normal) wenn kein zweites 0xC0 kam
if (recv->dir!=1) {setError(5)/* "Invalid direction of response" */; goto err;}
// The variable-length data and terminating byte
if (recv->siz) recv=(HDR*)realloc(recv,sizeof*recv+recv->siz);
flags=1<<3; // 0xC0 am Ende erwarten
readSlip(recv->data(),recv->siz,flags);
if (flags!=1) goto err;
}while (sent && sent->cmd!=recv->cmd); // solange lesen bis die Antwort stimmt oder ein Timeout auftritt
return recv;
err:
delete recv; return 0;
}
/* Send a request and read the response */
ESP::autoHDR ESP::command(byte op, const void*data, int dlen, uint32 chk, uint32 to_ms,bool check_status) {
HDR*h = (HDR*)_alloca(sizeof*h+dlen);
h->set(op,data,dlen,chk);
if (!writeSlip(h)) return 0;
autoHDR r(readSlip(h,to_ms));
if (r && check_status) {
byte sta=r->getStatus(this);
if (sta) {setError(sta); r=0;}
}
return r;
}
// tries to get a response until that response has the
// same operation as the request or a retries limit has exceeded.
// This is needed for some esp8266s that
// reply with more sync responses than expected.
// for (int retries = 5; retries > 0; --retries) {
// silent = retries>1; // Beim letzten Versuch bei Fehler aussteigen
// rh=readSlip(h,body,to_ms);
// if (silent==1) break; // fehlerfrei durchgelaufen
// }
// silent=0;
// return rh;
// }
void _cdecl ESP::ComError(const char*msg,...) {
// PurgeComm(_port,PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
bool writing = *msg=='w';
if (writing) ++msg;
fprintf(stderr," Error %s COM data: ",writing?"writing":"reading");
va_list va;
va_start(va,msg);
vFatal(msg,va);
}
/* Perform a connection test */
bool ESP::sync(uint32 trial) {
byte pack[36];
pack[0]=7;
pack[1]=7;
pack[2]=0x12;
pack[3]=0x20;
memset(pack+4,0x55,32);
autoHDR recv(0);
for (; trial; trial--) {
lastError=0;
printf(".");
recv=command(SYNC,pack,sizeof pack);
if (recv) break; // keine Fehler
if (lastError==4 && trial>2) trial=2; // Reduzierte Versuche bei Timeout beim Schreiben (vermutlich COM2COM)
}
if (!recv) return false;
/* Zitat aus esptool/loader.py:
# ROM bootloaders send some non-zero "val" response. The flasher stub sends 0.
# If we receive 0 then it probably indicates that the chip wasn't or couldn't be
# reseted (sic!) properly and esptool is talking to the flasher stub.
*/
if (stub_active = !recv->val) statusbytes=2;
return true;
}
bool ESP::tryBaud(uint32 baud, uint32 syncs) {
if (currentBaud==baud) return false; // schon versucht
if (!baud) return false; // Keine Angabe
if (!changeHostBaud(baud)) return false; // Kann nicht
putchar('*'); Sleep(50); PurgeComm(_port,PURGE_RXCLEAR);
if (!sync(syncs)) return false; // gibt Punkte aus
lastB=currentBaud=baud; // merken wenn's klappt
return true;
}
bool ESP::changeDeviceBaud(uint32 baud) {
if (info1->image_chip_id==66) return true; // nichts tun!
uint32 a[2]={baud,stub_active?currentBaud:0};
if (!command(CHANGE_BAUDRATE,a,sizeof a)) {
// Kommt die Antwort in alter oder neuer Baudrate??
println(0x20B,baud); // "Could not set baudrate %u"
// kann schiefgehen, muss aber nicht
}
if (!tryBaud(baud,20)){// sollte niemals false liefern
println(0x20B,baud); // "Could not set baudrate %u"
changeHostBaud(currentBaud); // Änderung auf der Hostseite rückgängig machen
return false;
}
currentBaud=baud;
return true;
}
/* Try connecting repeatedly until successful, or giving up */
bool ESP::connect(unsigned baud) {
printf(0x20C); // "Connect"
// issue reset-to-bootloader:
// RTS = either CH_PD or nRESET (both active low = chip in reset)
// DTR = GPIO0 (low = boot to flasher) // Reset Boot
EscapeCommFunction(_port,CLRDTR); // H H
EscapeCommFunction(_port,SETRTS); // L H
Sleep(50); // L H
printf(".");
EscapeCommFunction(_port,SETDTR); // L L
EscapeCommFunction(_port,CLRRTS); // H L
Sleep(50); // H L
printf(".");
EscapeCommFunction(_port,CLRDTR); // H H
setReadTimeout(50);
Sleep(100);
if (!sync(8) // Erster Versuch mit 115200 Baud (40-MHz-Quarz)
&& !tryBaud(lastB,8) // Zweiter Versuch mit persistent gespeicherter Baudrate vom letzten Mal
&& !tryBaud(74800,8) // Dritter Versuch mit 74800 Baud (26-MHz-Quarz)
&& !tryBaud(baud,8)){ // Vierter Versuch mit angegebener Baudrate
println(0x20D); // "Failed to connect to ESP microcontroller\n"
return false;
}
autoHDR recv(command(GET_SECURITY_INFO));
if (recv) { // ESP32S2 oder höher
if (recv->siz>=20) chip = esp::Chipmagic(((uint32*)recv->data())[3]);
}
lastError=0;
// ESP32 oder ESP8266?
if (chip==esp::undefined) chip = esp::Chipmagic(read_reg(0x40001000));
info1=esp::find1(chip);
if (!info1) {
println(0x200);
return false;
}
if (!statusbytes) // Die Statusbytes werden bei ESP::sync() auf 2 gesetzt, wenn der RAM-Urlader detektiert wurde
statusbytes = chip==esp::ESP8266 ? 2 : 4;
info2=esp::find2(info1->image_chip_id);
println(0x201,info1->name,get_chip_description().c_str());
if (currentBaud!=baud) {
tty.x=printf("Change baudrate from %u to %u",currentBaud,baud);
putchar(' ');
if (!changeDeviceBaud(baud)) return false; // may take a while!
putchar(' ');
println("done.");
}else println("Current baudrate is %u",baud);
println(0x202,features().c_str());
println(0x203,get_crystal_freq());
println(0x204,read_mac().toString().c_str());
return true;
}
void ESP::setReadTimeout(DWORD ms) const{
COMMTIMEOUTS to={0,0,ms,0,50};
SetCommTimeouts(_port,&to);
}
/*
static const char*statusToString(byte sta) {
switch (sta) {
case 0: return "Okay";
case 1: return "Answer too short";
case 2: return "Wrong status byte";
case 3: return "Read Timeout";
case 4: return "Write Timeout";
case 5: return "Received message is invalid";
case 6: return "Failed to act on received message";
case 7: return "Invalid CRC in message";
case 8: return "Flash write error";
case 9: return "Flash read error";
case 10: return "Flash read length error";
case 11: return "Deflate error";
case 0xC0: return "Bad data length";
case 0xC1: return "Bad data checksum";
case 0xC2: return "Bad blocksize";
case 0xC3: return "Invalid command";
case 0xC4: return "Failed SPI operation";
case 0xC5: return "Failed SPI unlock";
case 0xC6: return "Not in flash mode";
case 0xC7: return "Inflate error";
case 0xC8: return "Not enough data";
case 0xC9: return "Too much data";
case 0xFF: return "Command not implemented";
default: return "Unknown error code";
}
}
*/
/* Read memory address in target */
uint32 ESP::read_reg(uint32 addr) {
autoHDR recv(command(READ_REG,&addr,4));
if (!recv) Fatal(0x110,lastError); // "Failed to read target memory (%02X)"
return recv->val;
}
void ESP::write_reg(uint32 addr, uint32 value, uint32 mask, uint32 delay_us) {
uint32 a[4]={addr,value,mask,delay_us};
autoHDR recv(command(WRITE_REG,a,4*4));
if (!recv) Fatal(0x111,lastError); // "Failed to write target memory (%02X)"
}
void _cdecl Printbuf::operator()(const char*t,...) {
va_list va;
va_start(va,t);
k+=_vsnprintf(s+k,n-k-1,t,va);
va_end(va);
s[k]=0; // Nullterminierung sicherstellen
};
String<32>ESP::get_chip_description() {
MAKEPRINTBUF(32)
switch (chip) {
case esp::ESP32: {
uint32 efuse3 = read_reg(info2->efuse_rd_reg_base+3*4),
efuse5 = read_reg(info2->efuse_rd_reg_base+5*4),
c_date = read_reg(0x3FF6607C),
pkg_v = efuse3>>9&7 | efuse3<<1&8,
major = efuse3>>15&1 | efuse5>>19&2 | c_date>>29&4,
minor = efuse5>>24&3;
switch (major) {
case 1: break;
case 3: major=2; break;
case 7: major=3; break;
default: major=0;
}
bool rev3 = major==3,
single_core = efuse3&1;
const char*suffix;
switch (pkg_v) {
case 0: suffix = single_core ? "S0WDQ6" : "D0WDQ6"; break;
case 1: suffix = single_core ? "S0WD" : "D0WD"; break;
case 2: suffix = "D2WD"; break;
case 4: suffix = "U4WDH"; break;
case 5: suffix = rev3 ? "PICO-V3" : "PICO-D4"; break;
case 6: suffix = "PICO-V3-02"; break;
case 7: suffix = "D0WDR2"; break;
default: suffix = "??";
}
print("ESP32-%s%s (revision v%u.%u)",
suffix,
!single_core && pkg_v<2 && rev3 || pkg_v==7 ? "-V3" : "",
major,
minor);
}break;
default: print("no description");
}
return buf;
}
String<100>ESP::features() {
MAKEPRINTBUF(100)
size_t k=0;
switch (chip) {
case esp::ESP32: {
print("WiFi");
uint32 efuse3 = read_reg(info2->efuse_rd_reg_base+3*4),
efuse4 = read_reg(info2->efuse_rd_reg_base+4*4),
efuse6 = read_reg(info2->efuse_rd_reg_base+6*4);
if (!(efuse3&2)) print(", Bluetooth");
print(", %s Core",efuse3&1?"Single":"Dual");
if (efuse3&1<<13)
print(", %u MHz",efuse3&1<<12 ? 160 : 240);
switch (efuse3 & 0x0E04) {
case 0xC00: print(", Embedded %s","PSRAM"); nobreak;
case 0xA00:
case 0x800:
case 0x400: print(", Embedded %s","Flash");
}
if (efuse4&0x1F00)
print(", VRef calibration in efuse");
if (efuse3&0x4000)
print(", BLK3 partially reserved");
const char*scheme;
switch (efuse6&3) {
case 0: scheme = "None"; break;
case 1: scheme = "3/4"; break;
case 2: scheme = "Repeat (UNSUPPORTED)"; break;
default: scheme = "Invalid";
}
print(", Coding Scheme: %s",scheme);
}break;
}
return buf;
}
int ESP::get_crystal_freq() {
uint32 UART_CLKDIV_REG=0,
XTAL_CLK_DIVIDER=0;
switch (chip) {
case esp::ESP8266: UART_CLKDIV_REG=0x60000014; XTAL_CLK_DIVIDER=2; break;
case esp::ESP32: UART_CLKDIV_REG=0x3FF40014; XTAL_CLK_DIVIDER=1; break;
}
if (!UART_CLKDIV_REG) return 0;
DCB dcb;
dcb.DCBlength=sizeof dcb;
GetCommState(_port,&dcb);
uint32 f = read_reg(UART_CLKDIV_REG) & 0xFFFFF;
f*= dcb.BaudRate/XTAL_CLK_DIVIDER;
return f > 33000000 ? 40 : 26;
}
/* Start downloading an application image to RAM */
bool ESP::mem_begin(BeginInfo&bi) {
autoHDR recv(command(MEM_BEGIN,&bi,sizeof bi));
if (!recv) Fatal(0x112,lastError); //"Failed to enter RAM download mode (%02X)"
return true;
}
/* Send a block of an image to RAM */
bool ESP::mem_block(DataInfo&di,const void*data) {
uint32*a=(uint32*)_alloca(sizeof di+di.sblk);
memcpy(a,&di,sizeof di); memcpy(a+4,data,di.sblk);
autoHDR recv(command(MEM_DATA,a,sizeof di+di.sblk,esp::xor8(data,di.sblk)));
if (!recv) Fatal(0x113,lastError); //"Failed to write to target RAM (%02X)"
return true;
}
/* Leave download mode and run the application */
bool ESP::mem_finish(uint32 entrypoint) {
uint32 a[2]={!entrypoint,entrypoint};
autoHDR recv(command(MEM_END,a,sizeof a));
if (!recv) Fatal(0x114,lastError); //"Failed to leave RAM download mode (%02X)"
return true;
}
/* Start downloading to Flash (performs an erase) */
bool ESP::flash_begin(BeginInfo&bi,byte compress) {
if (!stub_active && info1->image_chip_id==66) {
unsigned sectors_per_block = 16;
unsigned sector_size = 4096;
unsigned num_sectors = div_ceil(bi.size,sector_size);
unsigned start_sector = bi.addr / sector_size;
unsigned head_sectors = sectors_per_block - (start_sector % sectors_per_block);
if (num_sectors < head_sectors) head_sectors = num_sectors;
if (num_sectors < 2 * head_sectors) bi.size = (num_sectors + 1) / 2 * sector_size;
else bi.size = (num_sectors - head_sectors) * sector_size;
}
DWORD t = GetTickCount();
if (!command(FLASH_BEGIN+compress,&bi,sizeof bi)) {
println("Failed to enter Flash download mode: %s",loadString(lastError).c_str());
return false;
}
tty.x+=printf("Took %u ms to erase flash block",GetTickCount() - t);
return true;
}
/* Write block to flash */
bool ESP::flash_block(DataInfo&di,const void*data,byte compress) {
uint32 tlen=sizeof di+di.sblk;
void*t=_alloca(tlen);
memcpy(t,&di,sizeof di);
memcpy((byte*)t+sizeof di,data,di.sblk);
if (!command(FLASH_DATA+compress,t,tlen,esp::xor8(data,di.sblk),1000)) {
if (tty.x) newline();
println("Failed to write to target Flash after seq %d: %s",di.iblk,loadString(lastError).c_str());
return false;
}
putchar('.');
return true;
}
/* Leave flash mode and run/reboot */
bool ESP::flash_finish(bool reboot,byte compress) {
uint32 pkt = !reboot;
if (!command(FLASH_END+compress,&pkt,sizeof pkt)) {
println("Failed to run/reboot after flash write: %s",loadString(lastError).c_str());
return false;
}
return true;
}
// So stellt der Aufrufer den Platz für den String auf dem Stack bereit!
// Allerdings ist das Ergebnis ungeeignet als Argument für ...!
// (C++ kopiert dann den String als Ganzes auf den Stack!)
// Daher hat String<> die Memberfunktion c_str() der daraus den Zeiger macht.
// Vorteil: Man kommt ohne Heap und ohne Hilfsvariablen aus.
// Erstaunlich: MSVC kann klaglos auf __$ReturnUdt zugreifen.
String<18>ESP::MAC::toString() {
MAKEPRINTBUF(18)
byte*m=((byte*)&mac)+6; // So nur für Little Endian
for(int i=6;;){
print("%02X",*--m);
if (!--i) break;
print(":");
}
return buf;
}
/* Read MAC from OTP ROM */
ESP::MAC ESP::read_mac(int) {
switch (chip) {
case esp::ESP8266: {
uint32 mac0 = read_reg(OTP_MAC+0), // MMMM.MMMM 0000.0000 0000.0000 0000.0000
mac1 = read_reg(OTP_MAC+4), // 0000.0000 UUUU.UUUU MMMM.MMMM MMMM.MMMM
mac2 = read_reg(OTP_MAC+8), // 0000.SSSF 0000.0000 0000.0000 0000.0000
mac3 = read_reg(OTP_MAC+12), // 0000.0000 MMMM.MMMM MMMM.MMMM MMMM.MMMM
maclo= (uint16)mac1<<8 | mac0>>24; // low 24 bit
switch (mac2>>13&7) { // "S"-Bits
case 5: printf("THIS IS ESP8266EX"); break; //ESP8266EX
case 4: printf("THIS IS ESP8266"); break; //ESP8266
default: Fatal("OTHER CHIPS...");
}
if (mac2>>12&1) return (uint64)mac3<<24 | maclo; // "F"-Bit: Full 48 bit MAC address format
//a fixed 3 bytes MAC header + 3 bytes MAC
uint32 oui;
switch (mac1>>16&0xff) {
case 0: oui = 0x18FE34; break;
case 1: oui = 0xACD074; break;
default: Fatal("Unknown OUI");
}
return (uint64)oui<<24|maclo;
}break;
case esp::ESP32: {
// Keine Doku gefunden und liefert Nullen. Was soll der Quatsch? Alles Fehler!
// unsigned word16 = read_reg(EFUSE_RDATA16+0), // MMMM.MMMM MMMM.MMMM MMMM.MMMM 0000.0000
// word17 = read_reg(EFUSE_RDATA16+4), // 0000.0000 MMMM.MMMM MMMM.MMMM MMMM.MMMM
// word18 = read_reg(EFUSE_RDATA16+8), // BBBB.BBBB BBBB.BBBB BBBB.BBBB 0000.0000
// word19 = read_reg(EFUSE_RDATA16+12); // 0000.0000 BBBB.BBBB BBBB.BBBB BBBB.BBBB
// macs[1](word17>>16,word17>>8,word17,word16>>24,word16>>16,word16>>8);
// macs[2](word19>>16,word19>>8,word19,word18>>24,word18>>16,word18>>8);
const unsigned EFUSE_BLK0_RDATA1_REG = 0x3FF5A004;
// So wie ich das lese ist das die MAC-Adresse für (hypothetisches) Kabel-Ethernet.
// Die sich ergebende Adresse unterscheidet sich (etwas) von der WLAN-MAC-Adresse!
uint32 w1 = read_reg(EFUSE_BLK0_RDATA1_REG),
w2 = read_reg(EFUSE_BLK0_RDATA1_REG+4);
return (uint64)w2<<32|w1;
}break;
}
return 0;
}
/* Read SPI flash manufacturer and device id */
uint32 ESP::flash_id() {
if (chip!=esp::ESP8266) return 0; // Scheiße!
BeginInfo bi={0};
flash_begin(bi,0);
write_reg(0x60000240,0,0xffffffff);
write_reg(0x60000200,0x10000000,0xffffffff);
uint32 flash_id = read_reg(0x60000240);
flash_finish(false,0);
return flash_id;
}
bool ESP::chunk2ram(const Dumpfile&df) {
BeginInfo bi={
df.size,
div_ceil(df.size,RAM_BLOCK),
RAM_BLOCK,
df.addr};
if (!bi.size) return true; // nichts zu tun
const byte*d=reinterpret_cast<const byte*>(df.data());
if (!mem_begin(bi)) return false;
printf(" @%X",df.addr);
DataInfo di={bi.sblk};
do{
if (di.sblk>bi.size) di.sblk=bi.size;
if (!mem_block(di,d)) return false;
printf("L%X",di.sblk);
d+=di.sblk;
bi.size-=di.sblk;
di.iblk++;
}while (bi.size);
// return mem_finish(0);
return true;
}
bool ESP::chunk2ram(void*cbd,const Dumpfile&df) {
return reinterpret_cast<ESP*>(cbd)->chunk2ram(df);
}
bool ESP::load_stub() {
if (stub_active) {
println(0x205); // "Stub is already running. No upload necessary."
return true;
}
printf(0x206); // "Load stub"
if (!info2) return false;
Dumpfile sub(info2->image_chip_id);
if (!sub.data()) return false;
uint32 start;
if (!ESPFirmwareImage::parse(sub,chunk2ram,this,&start)) return false;
printf(0x207,start); // " done, ^%X"
DWORD ohai; // Der Stub sendet nach dem Start ein Slip-Paket mit 4 Byte Kennung
byte flags=1<<2|1<<3; // ecpect full packet
bool ok=mem_finish(start) && readSlip(&ohai,4,flags)==4 && flags==1 && ohai=='IAHO';
if (ok) {
printf(0x208); // " done."
stub_active=true;
statusbytes=2;
}
putchar('\n');
return ok;
}
/* Read or verify SPI flash */
bool ESP::flash2file(const char*fname) {
Dumpfile df(fname);
if (!(df.flags&5)) {
printf("Address and (possibly) length must be given!\n");
return false;
}
// Leselänge von Adresse abhängig machen, für typische Szenarien!
if (df.flags&10) {
if (!df.size) {
printf("Zero-length file %s!\n",df.name());
return false;
}
}else switch (df.addr) {
case 0x1000: df.size=0x7000; break; // Zweiter Urlader
case 0x8000: df.size=3072; break; // Partitionstabelle
case 0x10000: df.size=1024*0x1000; break; // „Exerner“ Flash: 4 MByte ansetzen
default: df.size=1024*1024; break; // Alles andere: 1 MByte
}
tty.x=printf("Reading %u (0x%X) bytes",df.size,df.size); fflush(stdout);
byte*buf=new byte[df.size];
BeginInfo bi={df.addr,df.size,0x400,4};
command(0xD2,&bi,sizeof bi,0,200);
uint32 truncated=df.size;
bool checkimage=true;
bool aborted=false;
uint32 i;
for(i=0;i<df.size;) { // Muss alles lesen, vorzeitiges Abbrechen nicht möglich
uint32 l=df.size-i; if (l>bi.sblk) l=bi.sblk;
byte flags=1<<2|1<<3; // Start- und Endrahmen
uint32 ll=readSlip(buf+i,l,flags);
if (flags!=1) {
if (tty.x) newline();
println("Read aborted at %u (%X) bytes with %u bytes: %s",i,i,ll,loadString(lastError).c_str());
aborted=true;
break;
}
i+=l;
const uint32 deadbeef=0xDEADBEEF;
if (!writeSlip(i>truncated?&deadbeef:&i,4)) {delete[]buf; return false;}
putchar('.'); fflush(stdout); ++tty.x;
if (checkimage) {
uint32 j = ESPFirmwareImage::detectSize(buf,i);
switch (j) {
case 0: checkimage=false; // Kein FirmwareImage, nicht mehr testen
printf(" Not a firmware image ");
break;
case 1: break; // FirmwareImage, Länge (noch) unbekannt
default: checkimage=false; truncated=j;
if (j<df.size) {
if (tty.x) newline();
tty.x+=printf("Detected firmware image has %u (%X) bytes, no idea how to stop reading ",j,j);
}
}
}
}
if (tty.x) newline();
if (!aborted) { // Bei vorzeitigem Abbruch kommt kein MD5-Hash mehr.
Md5::Digest md5;
byte flags=1<<2|1<<3; // Erwarte genau 16 Bytes
readSlip(&md5,sizeof md5,flags);
if (flags!=1) {
println("Error (%02X) getting MD5 hash: %s",flags,loadString(lastError).c_str());
lastError=0;
}else{
println("MD5 hash from ESP%s = %s",info1->name,md5.toString().c_str());
}
}
bool ret=false, doSave=true;
if (df.data()) { // Wenn Datei bereits vorhanden, dann Vergleich
printf("Verify: ");
for (i=0; i<truncated; i++) if (buf[i]!=df.data()[i]) break;
if (i==truncated) {
println("okay.");
doSave=false;
ret=true;
}else{
println("failed at offset %u!",i);
if (MessageBoxA(0,"Überschreiben mit Flash-Daten?","esptool",MB_YESNO|MB_ICONQUESTION)!=IDYES) doSave=false;
}
}
if (doSave) {
printf("Save: ");
// struct{ // Create a custom stub
// uint32 offset,size,count;
// byte assembly[sizeof SFLASH_STUB];
// }stub;
// stub.offset=offset;
// stub.size=size;
// stub.count=count;
// memcpy(stub.assembly,SFLASH_STUB,sizeof SFLASH_STUB);
// Trick ROM to initialize SFlash
// flash_begin(0,0);
// Download stub
// mem_begin(sizeof stub,1,sizeof stub,0x40100000);
// mem_block((byte*)&stub,sizeof stub,0);
// mem_finish(0x4010001c);
// Fetch the data
// if (readByte() != 0xC0) Fatal("Invalid head of packet (sflash read)");
// for (uint32 i=0; i<fsize; i++) {
// if ((buf[i]=readByte()) != 0xC0) Fatal("Invalid end of packet (sflash read)");
// }
FILE*f=fopen(df.name(),"wb");
if (f) {
ret = fwrite(buf,1,truncated,f)==truncated;
ret = !fclose(f) && ret;
f = 0;
printf(ret?"okay.":"Could not write all bytes, disk full?");
}else printf("Could not create file %s!",df.name());
}
putchar('\n');
delete[]buf;
return ret;
}
bool ESP::flash_verify(const char*fname) {
Dumpfile df(fname);
if (!df.data()) {
println(0x20E,df.name()); // "File %s could not be open for reading!"
return false;
}
if (!(df.flags&5)) {
println(0x20F); // "Address must be given!"
return false;
}
if (df.flags&1<<4) {
println(0x215,df.name()); // "Verify with compressed file yet unsupported"
return false;
}
Md5 md5;
md5.update(df.data(),df.size);
Md5::Digest filehash = md5.final(), flashhash;
println(0x211,filehash.toString().c_str(),df.name()); // "%s = MD5 hash for file %s"
uint32 cmdargs[4]={df.addr,df.size};
if (stub_active) {
unsigned to=100+(df.size>>8);
printf("wait upto %u ms while firmware calculates MD5 hash...",to);
autoHDR r(command(SPI_FLASH_MD5,cmdargs,sizeof cmdargs,0,to));
putchar('\r');
if (!r) Fatal(0x115,lastError); // "SPI_FLASH_MD5 command failed (%02X)"
if (r->siz!=18) Fatal(0x116,r->siz); // "Wrong answer length (%u)"
flashhash.fromData(r->data());
}else{ // same command but different timeout and answer
// Geht nicht, keine Antwort! Also (sowieso besser) Stub laden
autoHDR r(command(SPI_FLASH_MD5,cmdargs,sizeof cmdargs,0,1000,false));
if (!r) Fatal(0x115,lastError);
if (r->siz!=32) Fatal(0x116,r->siz);
flashhash.fromString(reinterpret_cast<const char*>(r->data()));
}
println(0x212, // "%s = MD5 hash for flash from %X to %X, %s"
flashhash.toString().c_str(),
df.addr,
df.addr+df.size-1,
loadString(flashhash==filehash ? 0x214 : 0x213).c_str()); // "equal, okay." : "not equal!"
return true;
}
/* Perform a chip erase of SPI flash. Stub is loaded */
bool ESP::flash_erase() {
printf("Flash erase");
DWORD t=GetTickCount();
autoHDR r(command(ERASE_FLASH,0,0,0,5000));
if (!r) {
printf(" failed (%u)!\n",lastError);
return false;
}
t = GetTickCount()-t;
printf("d, %u ms\n",t);
return true;
/*
// Trick ROM to initialize SFlash
flash_begin(0,0); // der Bootcode verabschiedet sich in die ewigen Jagdgründe!
// This is hacky: we don't have a custom stub, instead we trick
// the bootloader to jump to the SPIEraseChip() routine and then halt/crash
// when it tries to boot an unconfigured system.
mem_begin(0,0,0,0x40100000);
mem_finish(0x40004984);
*/
}
// Yup - there's no good way to detect if we succeeded.
// It it on the other hand unlikely to fail.
/* Set the flash params for ESP booter */
bool ESP::flash_spi_param_set() {
printf("Set flash params");
uint32 a[6]={0, (128/8)*1024*1024, 64*1024, 4*1024, 256, 0xffff};
autoHDR recv(command(SPI_SET_PARAMS,a,sizeof a,0,1000));
if (!recv) {
printf(" failed (%u)\n",lastError);
return false;
}
printf(" done\n");
return true;
}
byte ESP::crc8(byte abyte) {
byte b = 0;
for (int i=0; i<8; i++) {
if ((b^abyte)&1) {
b ^= 0x18;
b >>= 1;
b |= 0x80;
}else b >>= 1;
abyte >>= 1;
}
return b;
}
byte ESP::crc8(const void*data,int len) {
const byte*dlist=reinterpret_cast<const byte*>(data);
byte crc = 0;
for (int i=0; i<len; i++) crc = crc8(crc ^ dlist[i]);
return crc;
}
unsigned ESP::crc16(const void*data,int dlen) {
const byte*dlist=reinterpret_cast<const byte*>(data);
const unsigned CRC_CCITT = 0x1021;
unsigned crc = 0;
for (int i=0; i<dlen; i++) {
for (byte m=0x80; m; m>>=1) {
crc<<=1;
if (crc&0x10000) crc^=0x10000|CRC_CCITT;
if (dlist[i]&m) crc^= CRC_CCITT;
}
}
return crc;
}
bool ESP::check_crc() {
switch (chip) {
case esp::ESP8266: {
uint32 mac0 = read_reg(OTP_MAC+0),
mac1 = read_reg(OTP_MAC+4),
mac2 = read_reg(OTP_MAC+8),
mac3 = read_reg(OTP_MAC+12);
// efuse = (mac3<<96)|(mac2<<64)|(mac1<<32)|(mac0<<0)
byte t[3]={byte(mac3),byte(mac3>>8),byte(mac3>>16)};
byte crc8_high_cal = crc8(t,sizeof t);
byte crc8_high = byte(mac2>>24);
if (crc8_high_cal!=crc8_high) {
printf("CRC8_H ERROR...");
return false;
}
byte crc_low_version = byte(mac1>>24&0xf);
printf("crc_low_version: 0x%X",crc_low_version);
byte crc8_low = byte(mac0>>16);
if (!crc_low_version) {
if (crc8_low) {
printf("CRC8_L V0 ERROR...");
return false;
}else if (crc_low_version==1 || crc_low_version==2) {
byte t[4]={byte(mac0>>24),byte(mac1),byte(mac1>>8),byte(mac1>>16)};
byte crc8_low_cal = crc8(t,sizeof t);
if (crc8_low_cal!=crc8_low) {
printf("CRC8_L V1 ERROR... ");
return false;
}
}
}
}return true;
case esp::ESP32: {
uint32 word16 = read_reg(EFUSE_RDATA16+0),
word17 = read_reg(EFUSE_RDATA16+4),
word18 = read_reg(EFUSE_RDATA16+8),
word19 = read_reg(EFUSE_RDATA16+12);
byte wifi_crc_data[6] = {
byte(word16>>8),
byte(word16>>16),
byte(word16>>24),
byte(word17),
byte(word17>>8),
byte(word17>>16)},
bt_crc_data[6] = {
byte(word18>>8),
byte(word18>>16),
byte(word18>>24),
byte(word19),
byte(word19>>8),
byte(word19>>16)};
byte efuse_crc_wifi = byte(word16),
efuse_crc_bt = byte(word18);
byte wifi_crc_cal = byte(crc16(wifi_crc_data,6)),
bt_crc_cal = byte(crc16(bt_crc_data,6));
if (wifi_crc_cal!=efuse_crc_wifi) {
printf("WIFI CRC ERROR.");
return false;
}
if (bt_crc_cal != efuse_crc_bt) {
printf("BT CRC ERROR");
return false;
}
}return true;
default: return false;
}
}
/* GPIO Change */
void ESP::HSPI_INIT() {
switch (chip) {
case esp::ESP32: {
printf("hspi by gpio");
write_reg(0x60008474,0xfc,0xffffffff);//hold spi sig output
//hspi gpio init
uint32 val=read_reg(0x60009030);
write_reg(0x60009030, 2<<12|val, 0xFFFFFFFF);
val=read_reg(0x60009034);
write_reg(0x60009034, 2<<12|val, 0xFFFFFFFF);
val=read_reg(0x60009038);
write_reg(0x60009038, 2<<12|val, 0xFFFFFFFF);
val=read_reg(0x6000903c);
write_reg(0x6000903c, 2<<12|val, 0xFFFFFFFF);
val=read_reg(0x60009040);
write_reg(0x60009040, 2<<12|val, 0xFFFFFFFF);
val=read_reg(0x60009048);
write_reg(0x60009048, 2<<12|val, 0xFFFFFFFF);
//gpio enable
val=read_reg(0x60004020);
write_reg(0x60004020, 0xf014|val, 0xFFFFFFFF);
//gpio out enable
write_reg(0x600041a4, 5<<24|2<<8|1, 0xFFFFFFFF);
write_reg(0x6000419c, 3, 0xFFFFFFFF);
write_reg(0x60004198, 4<<16, 0xFFFFFFFF);
//gpio input enable
write_reg(0x600041c0, 0x3f, 0xFFFFFFFF);
write_reg(0x60004130, 2<<24|4<<18|13<<12|12<<6|14, 0xFFFFFFFF);
write_reg(0x60004134, 15, 0xFFFFFFFF);
}break;
default: printf("Support ESP%s LATER",info1?info1->name:"??");
break;
}
}
bool ESP::write_flash_cb(void*cbd,const Dumpfile&df) {
return ((ESP*)cbd)->write_flash_cb(df);
}
bool ESP::write_flash_cb(const Dumpfile&df) {
byte compress = stub_active ? DEFL_ADD : 0;
if (chip==esp::ESP8266) compress=0; // not capable handling compressed data
const byte*data=df.data();
if (compress && !(df.flags&1<<4)) {
const_cast<Dumpfile&>(df).usize=df.size;
size_t csize;
data=reinterpret_cast<const byte*>(Deflate::mem_to_heap(data,df.usize,
csize,Deflate::WRITE_ZLIB_HEADER));
if (!data) return false;
const_cast<Dumpfile&>(df).size=(uint32)csize;
}
if (!df.size) return true;
uint32 blocksize=compress?FLASH_BLOCK>>2:FLASH_BLOCK;
BeginInfo bi={
df.usize,
div_ceil(df.size,blocksize),
blocksize,
df.addr};
printf("Erasing flash...");
DWORD t=GetTickCount();
if (!flash_begin(bi,compress)) return false;
// Schleife über FLASH_BLOCK-Chunks
DataInfo di={bi.sblk};
for (uint32 i=0; i<df.size; i+=di.sblk,di.iblk++) {
putchar('\r'); tty.x=0;
tty.x+=printf("Writing at %#x... (%d %%)",bi.addr + di.iblk * FLASH_BLOCK, 100 * (di.iblk + 1) / bi.nblk);
fflush(stdout);
// if (di.sblk>df.size-i) di.sblk=df.size-i;
// Pad the last block (das macht gefälligst der Lader!)
if (!flash_block(di,data+i,compress)) return false;
bi.addr+=uint32(di.sblk*(uint64)df.usize/df.size); // Adresse etwa, nur zur Anzeige
}
t=GetTickCount()-t;
putchar('\r');
println("Wrote %d bytes at %#x in %u ms (%u kbit/s)",bi.size,df.addr,t,bi.size*8/(t?t:1));
if (compress && !(df.flags&1<<4)) free(const_cast<byte*>(data)); // Komprimierte Daten löschen
return true;
}
bool ESP::verboseDownloadCb(void*cbd,const Dumpfile&df) {
return reinterpret_cast<ESP*>(cbd)->verboseDownloadCb(df);
}
bool ESP::verboseDownloadCb(const Dumpfile&df) {
printf("Downloading %d bytes at %08x",df.size,df.addr);
fflush(stdout);
BeginInfo bi={
df.size,
div_ceil(df.size,RAM_BLOCK),
RAM_BLOCK,
df.addr};
if (!bi.size) return true;
if (!mem_begin(bi)) return false;
DataInfo di={bi.sblk};
for (uint32 i=0; i<bi.size; i+=di.sblk,di.iblk++) {
if (di.sblk>bi.size-i) di.sblk=bi.size-i;
if (!mem_block(di,df.data()+i)) return false;
printf(".");
}
println("done!");
return true;
}
ESP::~ESP() {
CloseHandle(_port);
}
Detected encoding: ANSI (CP1252) | 4
|
|