/* Some flash handling cgi routines. Used for reading the existing flash and updating the ESPFS image. */ /* * ---------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * Jeroen Domburg wrote this file. As long as you retain * this notice you can do whatever you want with this stuff. If we meet some day, * and you think this stuff is worth it, you can buy me a beer in return. * ---------------------------------------------------------------------------- */ #include #include "cgiflash.h" #include "espfs.h" #include "cgiflash.h" #include "espfs.h" //#include #include "cgiflash.h" #include "espfs.h" #ifndef UPGRADE_FLAG_FINISH #define UPGRADE_FLAG_FINISH 0x02 #endif // Check that the header of the firmware blob looks like actual firmware... static int ICACHE_FLASH_ATTR checkBinHeader(void *buf) { uint8_t *cd = (uint8_t *)buf; if (cd[0] != 0xEA) return 0; if (cd[1] != 4 || cd[2] > 3 || cd[3] > 0x40) return 0; if (((uint16_t *)buf)[3] != 0x4010) return 0; if (((uint32_t *)buf)[2] != 0) return 0; return 1; } static int ICACHE_FLASH_ATTR checkEspfsHeader(void *buf) { if (memcmp(buf, "ESfs", 4)!=0) return 0; return 1; } // Cgi to query which firmware needs to be uploaded next httpd_cgi_state ICACHE_FLASH_ATTR cgiGetFirmwareNext(HttpdConnData *connData) { if (connData->conn==NULL) { //Connection aborted. Clean up. return HTTPD_CGI_DONE; } uint8 id = system_upgrade_userbin_check(); httpdStartResponse(connData, 200); httpdHeader(connData, "Content-Type", "text/plain"); httpdHeader(connData, "Content-Length", "9"); httpdEndHeaders(connData); char *next = id == 1 ? "user1.bin" : "user2.bin"; httpdSend(connData, next, -1); dbg("Next firmware: %s (got %d)", next, id); return HTTPD_CGI_DONE; } //Cgi that reads the SPI flash. Assumes 512KByte flash. //ToDo: Figure out real flash size somehow? httpd_cgi_state ICACHE_FLASH_ATTR cgiReadFlash(HttpdConnData *connData) { int *pos=(int *)&connData->cgiData; if (connData->conn==NULL) { //Connection aborted. Clean up. return HTTPD_CGI_DONE; } if (*pos==0) { info("Start flash download."); httpdStartResponse(connData, 200); httpdHeader(connData, "Content-Type", "application/bin"); httpdEndHeaders(connData); *pos=0x40200000; return HTTPD_CGI_MORE; } //Send 1K of flash per call. We will get called again if we haven't sent 512K yet. httpdSend(connData, (char*)(*pos), 1024); *pos+=1024; if (*pos>=0x40200000+(512*1024)) return HTTPD_CGI_DONE; else return HTTPD_CGI_MORE; } //Cgi that allows the firmware to be replaced via http POST This takes //a direct POST from e.g. Curl or a Javascript AJAX call with either the //firmware given by cgiGetFirmwareNext or an OTA upgrade image. //Because we don't have the buffer to allocate an entire sector but will //have to buffer some data because the post buffer may be misaligned, we //write SPI data in pages. The page size is a software thing, not //a hardware one. #define PAGELEN 64 #define FLST_START 0 #define FLST_WRITE 1 #define FLST_SKIP 2 #define FLST_DONE 3 #define FLST_ERROR 4 #define FILETYPE_ESPFS 0 #define FILETYPE_FLASH 1 #define FILETYPE_OTA 2 typedef struct { int state; int filetype; int flashPos; char pageData[PAGELEN]; int pagePos; int address; int len; int skip; char *err; } UploadState; typedef struct __attribute__((packed)) { char magic[4]; char tag[28]; int32_t len1; int32_t len2; } OtaHeader; httpd_cgi_state ICACHE_FLASH_ATTR cgiUploadFirmware(HttpdConnData *connData) { CgiUploadFlashDef *def=(CgiUploadFlashDef*)connData->cgiArg; UploadState *state=(UploadState *)connData->cgiData; int len; char buff[128]; if (connData->conn==NULL) { //Connection aborted. Clean up. if (state!=NULL) free(state); return HTTPD_CGI_DONE; } if (state==NULL) { //First call. Allocate and initialize state variable. info("Firmware upload cgi start."); state=malloc(sizeof(UploadState)); if (state==NULL) { error("Can't allocate firmware upload struct!"); return HTTPD_CGI_DONE; } memset(state, 0, sizeof(UploadState)); state->state=FLST_START; connData->cgiData=state; state->err="Premature end"; } char *data=connData->post->buff; int dataLen=connData->post->buffLen; while (dataLen!=0) { if (state->state==FLST_START) { //First call. Assume the header of whatever we're uploading already is in the POST buffer. if (def->type==CGIFLASH_TYPE_FW && memcmp(data, "EHUG", 4)==0) { //Type is combined flash1/flash2 file OtaHeader *h=(OtaHeader*)data; strncpy(buff, h->tag, 27); buff[27]=0; if (strcmp(buff, def->tagName)!=0) { warn("OTA tag mismatch! Current=`%s` uploaded=`%s`.", def->tagName, buff); len=httpdFindArg(connData->getArgs, "force", buff, sizeof(buff)); if (len!=-1 && atoi(buff)) { info("Forcing firmware flash."); } else { state->err="Firmware not intended for this device!\n"; state->state=FLST_ERROR; } } if (state->state!=FLST_ERROR && connData->post->len > def->fwSize*2+sizeof(OtaHeader)) { state->err="Firmware image too large"; state->state=FLST_ERROR; } if (state->state!=FLST_ERROR) { //Flash header seems okay. dataLen-=sizeof(OtaHeader); //skip header when parsing data data+=sizeof(OtaHeader); if (system_upgrade_userbin_check()==1) { info("Flashing user1.bin from ota image"); state->len=h->len1; state->skip=h->len2; state->state=FLST_WRITE; state->address=def->fw1Pos; } else { info("Flashing user2.bin from ota image"); state->len=h->len2; state->skip=h->len1; state->state=FLST_SKIP; state->address=def->fw2Pos; } } } else if (def->type==CGIFLASH_TYPE_FW && checkBinHeader(connData->post->buff)) { if (connData->post->len > def->fwSize) { state->err="Firmware image too large"; state->state=FLST_ERROR; } else { state->len=connData->post->len; state->address=def->fw1Pos; state->state=FLST_WRITE; } } else if (def->type==CGIFLASH_TYPE_ESPFS && checkEspfsHeader(connData->post->buff)) { if (connData->post->len > def->fwSize) { state->err="Firmware image too large"; state->state=FLST_ERROR; } else { state->len=connData->post->len; state->address=def->fw1Pos; state->state=FLST_WRITE; } } else { state->err="Invalid flash image type!"; state->state=FLST_ERROR; error("Did not recognize flash image type!"); } } else if (state->state==FLST_SKIP) { //Skip bytes without doing anything with them if (state->skip>dataLen) { //Skip entire buffer state->skip-=dataLen; dataLen=0; } else { //Only skip part of buffer dataLen-=state->skip; data+=state->skip; state->skip=0; if (state->len) state->state=FLST_WRITE; else state->state=FLST_DONE; } } else if (state->state==FLST_WRITE) { //Copy bytes to page buffer, and if page buffer is full, flash the data. //First, calculate the amount of bytes we need to finish the page buffer. int lenLeft=PAGELEN-state->pagePos; if (state->lenlen; //last buffer can be a cut-off one //See if we need to write the page. if (dataLenpageData[state->pagePos], data, dataLen); state->pagePos+=dataLen; state->len-=dataLen; dataLen=0; } else { //Finish page; take data we need from post buffer memcpy(&state->pageData[state->pagePos], data, lenLeft); data+=lenLeft; dataLen-=lenLeft; state->pagePos+=lenLeft; state->len-=lenLeft; //Erase sector, if needed if ((state->address&(SPI_FLASH_SEC_SIZE-1))==0) { spi_flash_erase_sector(state->address/SPI_FLASH_SEC_SIZE); } //Write page //httpd_printf("Writing %d bytes of data to SPI pos 0x%x...\n", state->pagePos, state->address); spi_flash_write(state->address, (uint32 *)state->pageData, state->pagePos); state->address+=PAGELEN; state->pagePos=0; if (state->len==0) { //Done. if (state->skip) state->state=FLST_SKIP; else state->state=FLST_DONE; } } } else if (state->state==FLST_DONE) { warn("Huh? %d bogus bytes received after data received.", dataLen); //Ignore those bytes. dataLen=0; } else if (state->state==FLST_ERROR) { //Just eat up any bytes we receive. dataLen=0; } } if (connData->post->len==connData->post->received) { //We're done! Format a response. info("Upload done. Sending response."); httpdStartResponse(connData, state->state==FLST_ERROR?400:200); httpdHeader(connData, "Content-Type", "text/plain"); httpdEndHeaders(connData); if (state->state!=FLST_DONE) { error("Error msg: %s", state->err); httpdSend(connData, "Firmware image error:", -1); httpdSend(connData, state->err, -1); httpdSend(connData, "\n", -1); } free(state); return HTTPD_CGI_DONE; } return HTTPD_CGI_MORE; } static os_timer_t resetTimer; static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) { system_upgrade_flag_set(UPGRADE_FLAG_FINISH); system_upgrade_reboot(); } // Handle request to reboot into the new firmware httpd_cgi_state ICACHE_FLASH_ATTR cgiRebootFirmware(HttpdConnData *connData) { if (connData->conn==NULL) { //Connection aborted. Clean up. return HTTPD_CGI_DONE; } // TODO: sanity-check that the 'next' partition actually contains something that looks like // valid firmware //Do reboot in a timer callback so we still have time to send the response. os_timer_disarm(&resetTimer); os_timer_setfn(&resetTimer, resetTimerCb, NULL); os_timer_arm(&resetTimer, 200, 0); httpdStartResponse(connData, 200); httpdHeader(connData, "Content-Type", "text/plain"); httpdEndHeaders(connData); httpdSend(connData, "Rebooting...", -1); return HTTPD_CGI_DONE; }