diff --git a/core/include/esp/rom.h b/core/include/esp/rom.h new file mode 100644 index 0000000..0fbf4bd --- /dev/null +++ b/core/include/esp/rom.h @@ -0,0 +1,21 @@ +/* "Boot ROM" function signatures + + Note that a lot of the ROM functions used in the IoT SDK aren't + referenced from the Espressif RTOS SDK, and are probably incompatible. + */ +#ifndef _ESP_ROM_H +#define _ESP_ROM_H + +#include + +void Cache_Read_Disable(void); + +/* http://esp8266-re.foogod.com/wiki/Cache_Read_Enable + + Note: when compiling non-OTA we use the ROM version of this + function, but for OTA we use the version in extras/rboot-ota that + maps the correct flash page for OTA support. + */ +void Cache_Read_Enable(uint32_t odd_even, uint32_t mb_count, uint32_t no_idea); + +#endif diff --git a/examples/ota_basic/ota_basic.c b/examples/ota_basic/ota_basic.c index 3691661..69a061f 100644 --- a/examples/ota_basic/ota_basic.c +++ b/examples/ota_basic/ota_basic.c @@ -11,73 +11,26 @@ #include "FreeRTOS.h" #include "task.h" #include "esp8266.h" + +#include "ota-tftp.h" #include "rboot-ota.h" -#include "lwip/err.h" -#include "lwip/sockets.h" -#include "lwip/sys.h" -#include "lwip/netdb.h" -#include "lwip/dns.h" - -#define FWPORT 12550 - #if !defined(WIFI_SSID) || !defined(WIFI_PASS) #error "Please define macros WIFI_SSID & WIFI_PASS (here, or better in a local.h file at root level or in program dir." #endif -void simpleOTATask(void *pvParameters) -{ - printf("Listening for firmware on port %d...\r\n", FWPORT); - int s = socket(AF_INET, SOCK_STREAM, 0); - if(s < 0) { - printf("... Failed to allocate socket.\r\n"); - return; - } - - struct sockaddr_in s_addr = { - .sin_family = AF_INET, - .sin_addr = { - .s_addr = INADDR_ANY, - }, - .sin_port = htons(FWPORT), - }; - if(bind(s, (struct sockaddr *) &s_addr, sizeof(s_addr)) < 0) - { - printf("... Failed to bind.\r\n"); - return; - } - - if(listen(s, 0) == -1) { - printf("... Failed to listen.\r\n"); - return; - } - - int client; - while((client = accept(s, NULL, NULL)) != 0) - { - printf("Got new socket. Trying OTA update...\r\n"); - - int slot = rboot_ota_update(client, -1, false); - close(client); - if(slot < 0) { - printf("OTA update failed. :(.\r\n"); - continue; - } - printf("OTA succeeded at slot %d!\r\n", slot); - //rboot_set_current_rom(slot); - //sdk_system_restart(); - } - printf("Failed to accept.\r\n"); - close(s); - return; -} - void user_init(void) { sdk_uart_div_modify(0, UART_CLK_FREQ / 115200); - printf("OTA Basic demo. Currently running on slot %d / %d.\r\n", - rboot_get_current_rom(), RBOOT_MAX_ROMS); + rboot_config_t conf = rboot_get_config(); + printf("\r\n\r\nOTA Basic demo.\r\nCurrently running on flash slot %d / %d.\r\n\r\n", + conf.current_rom, conf.count); + + printf("Image addresses in flash:\r\n"); + for(int i = 0; i +#include +#include + +#include "lwip/err.h" +#include "lwip/api.h" +#include "lwip/sys.h" +#include "lwip/netdb.h" +#include "lwip/dns.h" +#include "lwip/mem.h" + +#include +#include +#include + +#include "ota-tftp.h" +#include "rboot-ota.h" + +#define TFTP_FIRMWARE_FILE "firmware.bin" +#define TFTP_OCTET_MODE "octet" /* non-case-sensitive */ + +#define TFTP_OP_WRQ 2 +#define TFTP_OP_DATA 3 +#define TFTP_OP_ACK 4 +#define TFTP_OP_ERROR 5 +#define TFTP_OP_OACK 6 + +#define TFTP_ERR_FILENOTFOUND 1 +#define TFTP_ERR_FULL 3 +#define TFTP_ERR_ILLEGAL 4 +#define TFTP_ERR_BADID 5 + +#define MAX_IMAGE_SIZE 0x100000 /*1MB images max at the moment */ + +static void tftp_task(void *port_p); +static char *tftp_get_field(int field, struct netbuf *netbuf); +static err_t tftp_receive_data(struct netconn *nc, size_t write_offs, size_t limit_offs, size_t *received_len); +static err_t tftp_send_ack(struct netconn *nc, int block); +static void tftp_send_error(struct netconn *nc, int err_code, const char *err_msg); + +void ota_tftp_init_server(int listen_port) +{ + xTaskCreate(tftp_task, (signed char *)"tftpOTATask", 512, (void *)listen_port, 2, NULL); +} + +static void tftp_task(void *listen_port) +{ + struct netconn *nc = netconn_new (NETCONN_UDP); + if(!nc) { + printf("OTA TFTP: Failed to allocate socket.\r\n"); + return; + } + + netconn_bind(nc, IP_ADDR_ANY, (int)listen_port); + + /* We expect a WRQ packet with filename "firmware.bin" and "octet" mode, + */ + while(1) + { + /* wait as long as needed for a WRQ packet */ + netconn_set_recvtimeout(nc, 0); + struct netbuf *netbuf; + err_t err = netconn_recv(nc, &netbuf); + if(err != ERR_OK) { + printf("OTA TFTP Error: Failed to receive TFTP initial packet. err=%d\r\n", err); + continue; + } + uint16_t len = netbuf_len(netbuf); + if(len < 6) { + printf("OTA TFTP Error: Packet too short for a valid WRQ\r\n"); + netbuf_delete(netbuf); + continue; + } + + uint16_t opcode = netbuf_read_u16_n(netbuf, 0); + if(opcode != TFTP_OP_WRQ) { + printf("OTA TFTP Error: Invalid opcode 0x%04x didn't match WRQ\r\n", opcode); + netbuf_delete(netbuf); + continue; + } + + /* check filename */ + char *filename = tftp_get_field(0, netbuf); + if(!filename || strcmp(filename, TFTP_FIRMWARE_FILE)) { + tftp_send_error(nc, TFTP_ERR_FILENOTFOUND, "File must be firmware.bin"); + free(filename); + netbuf_delete(netbuf); + continue; + } + free(filename); + + /* check mode */ + char *mode = tftp_get_field(1, netbuf); + if(!mode || strcmp("octet", mode)) { + tftp_send_error(nc, TFTP_ERR_ILLEGAL, "Mode must be octet/binary"); + free(mode); + netbuf_delete(netbuf); + continue; + } + free(mode); + + /* establish a connection back to the sender from this netbuf */ + netconn_connect(nc, netbuf_fromaddr(netbuf), netbuf_fromport(netbuf)); + netbuf_delete(netbuf); + + /* Find next free slot - this requires flash unmapping so best done when no packets in flight */ + rboot_config_t conf; + conf = rboot_get_config(); + int slot = (conf.current_rom + 1) % conf.count; + + if(slot == conf.current_rom) { + tftp_send_error(nc, TFTP_ERR_ILLEGAL, "Only one OTA slot!"); + netconn_disconnect(nc); + continue; + } + + /* ACK the WRQ */ + int ack_err = tftp_send_ack(nc, 0); + if(ack_err != 0) { + printf("OTA TFTP initial ACK failed\r\n"); + netconn_disconnect(nc); + continue; + } + + /* Finished WRQ phase, start TFTP data transfer */ + size_t received_len; + int recv_err = tftp_receive_data(nc, conf.roms[slot], conf.roms[slot]+MAX_IMAGE_SIZE, &received_len); + + netconn_disconnect(nc); + printf("OTA TFTP receive data result %d bytes %d\r\n", recv_err, received_len); + if(recv_err == ERR_OK) { + printf("OTA TFTP result valid. Changing slot to %d\r\n", slot); + vPortEnterCritical(); + if(!rboot_set_current_rom(slot)) { + printf("OTA TFTP failed to set new rboot slot\r\n"); + } + sdk_system_restart(); + } + } +} + +/* Return numbered field in a TFTP RRQ/WRQ packet + + Uses dest_buf (length dest_len) for temporary storage, so dest_len must be + at least as long as the longest valid/expected field. + + Result is either NULL if an error occurs, or a newly malloced string that the + caller needs to free(). + */ +static char *tftp_get_field(int field, struct netbuf *netbuf) +{ + int offs = 2; + int field_offs = 2; + int field_len = 0; + /* Starting past the opcode, skip all previous fields then find start/len of ours */ + while(field >= 0 && offs < netbuf_len(netbuf)) { + char c = netbuf_read_u8(netbuf, offs++); + if(field == 0) { + field_len++; + } + if(c == 0) { + field--; + if(field == 0) + field_offs = offs; + } + } + + if(field != -1) + return NULL; + + char * result = malloc(field_len); + netbuf_copy_partial(netbuf, result, field_len, field_offs); + return result; +} + +static err_t tftp_receive_data(struct netconn *nc, size_t write_offs, size_t limit_offs, size_t *received_len) +{ + *received_len = 0; + const int DATA_PACKET_SZ = 512 + 4; /*( packet size plus header */ + uint32_t start_offs = write_offs; + int block = 1; + + struct netbuf *netbuf; + + while(1) + { + netconn_set_recvtimeout(nc, 10000); + err_t err = netconn_recv(nc, &netbuf); + if(err == ERR_TIMEOUT) { + tftp_send_error(nc, TFTP_ERR_ILLEGAL, "Timeout"); + return ERR_TIMEOUT; + } + else if(err != ERR_OK) { + tftp_send_error(nc, TFTP_ERR_ILLEGAL, "Failed to receive packet"); + return err; + } + + uint16_t opcode = netbuf_read_u16_n(netbuf, 0); + if(opcode != TFTP_OP_DATA) { + tftp_send_error(nc, TFTP_ERR_ILLEGAL, "Unknown opcode"); + netbuf_delete(netbuf); + return ERR_VAL; + } + + uint16_t client_block = netbuf_read_u16_n(netbuf, 2); + if(client_block != block) { + netbuf_delete(netbuf); + if(client_block == block-1) { + /* duplicate block, means our ack got lost */ + tftp_send_ack(nc, block-1); + continue; + } + else { + tftp_send_error(nc, TFTP_ERR_ILLEGAL, "Block# out of order"); + return ERR_VAL; + } + } + + if(write_offs % SECTOR_SIZE == 0) { + sdk_spi_flash_erase_sector(write_offs / SECTOR_SIZE); + } + + /* One UDP packet can be more than one netbuf segment, so iterate all the + segments in the netbuf and write them to flash + */ + int offset = 0; + int len = netbuf_len(netbuf); + + if(write_offs + len >= limit_offs) { + tftp_send_error(nc, TFTP_ERR_FULL, "Image too large"); + return ERR_VAL; + } + + bool first_chunk = true; + do + { + uint16_t chunk_len; + uint32_t *chunk; + netbuf_data(netbuf, (void **)&chunk, &chunk_len); + if(first_chunk) { + chunk++; /* skip the 4 byte TFTP header */ + chunk_len -= 4; /* assuming this netbuf chunk is at least 4 bytes! */ + first_chunk = false; + } + if(chunk_len && ((uint32_t)chunk % 4)) { + /* sdk_spi_flash_write requires a word aligned + buffer, so if the UDP payload is unaligned + (common) then we copy the first word to the stack + and write that to flash, then move the rest of the + buffer internally to sit on an aligned offset. + + Assuming chunk_len is always a multiple of 4 bytes. + */ + uint32_t first_word; + memcpy(&first_word, chunk, 4); + sdk_spi_flash_write(write_offs+offset, &first_word, 4); + memmove(LWIP_MEM_ALIGN(chunk),&chunk[1],chunk_len-4); + chunk = LWIP_MEM_ALIGN(chunk); + offset += 4; + chunk_len -= 4; + } + sdk_spi_flash_write(write_offs+offset, chunk, chunk_len); + offset += chunk_len; + } while(netbuf_next(netbuf) >= 0); + + netbuf_delete(netbuf); + + *received_len += len - 4; + + if(len < DATA_PACKET_SZ) { + /* This was the last block, but verify the image before we ACK + it so the client gets an indication if things were successful. + */ + const char *err = "Unknown validation error"; + if(!rboot_verify_image(start_offs, *received_len, &err)) { + tftp_send_error(nc, TFTP_ERR_ILLEGAL, err); + return ERR_VAL; + } + } + + err_t ack_err = tftp_send_ack(nc, block); + if(ack_err != ERR_OK) { + printf("OTA TFTP failed to send ACK.\r\n"); + return ack_err; + } + + if(len < DATA_PACKET_SZ) { + return ERR_OK; + } + + block++; + write_offs += 512; + } +} + +static err_t tftp_send_ack(struct netconn *nc, int block) +{ + /* Send ACK */ + struct netbuf *resp = netbuf_new(); + uint16_t *ack_buf = (uint16_t *)netbuf_alloc(resp, 4); + ack_buf[0] = htons(TFTP_OP_ACK); + ack_buf[1] = htons(block); + err_t ack_err = netconn_send(nc, resp); + netbuf_delete(resp); + return ack_err; +} + +static void tftp_send_error(struct netconn *nc, int err_code, const char *err_msg) +{ + printf("OTA TFTP Error: %s\r\n", err_msg); + struct netbuf *err = netbuf_new(); + uint16_t *err_buf = (uint16_t *)netbuf_alloc(err, 4+strlen(err_msg)+1); + err_buf[0] = htons(TFTP_OP_ERROR); + err_buf[1] = htons(err_code); + strcpy((char *)&err_buf[2], err_msg); + netconn_send(nc, err); + netbuf_delete(err); +} diff --git a/extras/rboot-ota/ota-tftp.h b/extras/rboot-ota/ota-tftp.h new file mode 100644 index 0000000..d27511b --- /dev/null +++ b/extras/rboot-ota/ota-tftp.h @@ -0,0 +1,33 @@ +#ifndef _OTA_TFTP_H +#define _OTA_TFTP_H +/* TFTP Server OTA Support + * + * To use, call ota_tftp_init_server() which will start the TFTP server task + * and keep it running until a valid image is sent. + * + * The server expects to see a valid image sent with filename "filename.bin" + * and will switch "slots" and reboot if a valid image is received. + * + * Note that the server will allow you to flash an "OTA" update that doesn't + * support OTA itself, and possibly brick the esp requiring serial upload. + * + * Example client comment: + * tftp -m octet ESP_IP -c put firmware/myprogram.bin firmware.bin + * + * TFTP protocol implemented as per RFC1350: + * https://tools.ietf.org/html/rfc1350 + * + * IMPORTANT: TFTP is not a secure protocol. + * Only allow TFTP OTA updates on trusted networks. + * + */ + +/* Start a FreeRTOS task to wait to receive an OTA update from a TFTP client. + * + * listen_port is the UDP port number to receive TFTP connections on (default TFTP port is 69) + */ +void ota_tftp_init_server(int listen_port); + +#define TFTP_PORT 69 + +#endif diff --git a/extras/rboot-ota/rboot-cache.S b/extras/rboot-ota/rboot-cache.S new file mode 100644 index 0000000..2363a98 --- /dev/null +++ b/extras/rboot-ota/rboot-cache.S @@ -0,0 +1,78 @@ +/* OTA-compatible Cache_Read_Enable function + * + * This gets called from the SDK when it wants to enable the flash mapping. + * + * In recent SDK versions it's been replaced with a Cache_Read_Enable_New + * function that takes note of OTA stuff. + * + * For esp-open-rtos we just replace the ROM function with this wrapper. + * + * + * Part of esp-open-rtos + * Copyright (C) 2015 Superhouse Automation Pty Ltd + * BSD Licensed as described in the file LICENSE +*/ +#ifdef OTA + +#define RBOOT_CONFIG_BASE (0x40200000 + 0x1000) +#define RBOOT_ROMS_OFFS 0x8 /* offset of rboot_config_t.roms array in config */ + +#define RBOOT_MEGABYTE_DEFAULT 0x80 + + .section .data + .global rboot_megabyte +rboot_megabyte: + .byte RBOOT_MEGABYTE_DEFAULT + + .section .data + .local cache_return_save + .align 4 +cache_return_save: + .word 0 + + .text + .section .iram1.text, "x" + .literal_position + .align 4 + .global Cache_Read_Enable + .type Cache_Read_Enable, @function /* it's not really a function, but treat it like one */ +Cache_Read_Enable: + movi a2, cache_return_save + s32i a0, a2, 0 /* save a0 here */ + movi a2, rboot_megabyte + l8ui a2, a2, 0 + bnei a2, RBOOT_MEGABYTE_DEFAULT, .Lalready_initialised + + /* map the first megabyte of flash */ + movi a2, 0 + movi a3, 0 + movi a4, 1 + call0 rom_Cache_Read_Enable + + movi a3, RBOOT_CONFIG_BASE + l32i a2, a3, 0 /* 32-bit flash read */ + extui a2, a2, 24, 8 /* 3rd byte is 'current' field of config */ + slli a2, a2, 2 /* Left shift by two, becomes offset into rom array */ + add a4, a2, a3 /* Add the base config address */ + l32i a4, a4, RBOOT_ROMS_OFFS /* Read from the ROM array */ + extui a4, a4, 20, 8 /* now a4 is number of megabytes */ + + /* save to rboot_megabyte */ + movi a3, rboot_megabyte + s8i a4, a3, 0 + + /* re-disable cache? */ + call0 Cache_Read_Disable + +.Lalready_initialised: + movi a4, rboot_megabyte + l32i a4, a4, 0 + extui a2, a4, 0, 1 /* a2 is now lsb of a4 (odd/even) */ + srli a3, a4, 1 /* a3 is half value of mb */ + movi a4, 1 + call0 rom_Cache_Read_Enable + movi a0, cache_return_save /* restore a0 return address */ + l32i a0, a0, 0 + ret.n + +#endif diff --git a/extras/rboot-ota/rboot-ota.c b/extras/rboot-ota/rboot-ota.c index 46c5712..c8b27c4 100644 --- a/extras/rboot-ota/rboot-ota.c +++ b/extras/rboot-ota/rboot-ota.c @@ -7,18 +7,32 @@ #include #include -#include - #include #include -#define SECTOR_SIZE 0x1000 -#define BOOT_CONFIG_SECTOR 1 - #include "rboot-ota.h" #define ROM_MAGIC_OLD 0xe9 #define ROM_MAGIC_NEW 0xea +#define CHECKSUM_INIT 0xef + +#if 0 +#define RBOOT_DEBUG(f_, ...) printf((f_), __VA_ARGS__) +#else +#define RBOOT_DEBUG(f_, ...) +#endif + +typedef struct __attribute__((packed)) { + uint8_t magic; + uint8_t section_count; + uint8_t val[2]; /* flash size & speed when placed @ offset 0, I think ignored otherwise */ + uint32_t entrypoint; +} image_header_t; + +typedef struct __attribute__((packed)) { + uint32_t load_addr; + uint32_t length; +} section_header_t; // get the rboot config @@ -41,7 +55,7 @@ bool rboot_set_config(rboot_config_t *conf) { buffer = (uint8_t*)malloc(SECTOR_SIZE); if (!buffer) { - printf("Failed to allocate sector buffer\r\n"); + printf("rboot_set_config: Failed to allocate sector buffer\r\n"); return false; } @@ -82,122 +96,105 @@ bool rboot_set_current_rom(uint8_t rom) { return rboot_set_config(&conf); } -int rboot_ota_update(int fd, int slot, bool reboot_now) +// Check that a valid-looking rboot image is found at this offset on the flash, and +// takes up 'expected_length' bytes. +bool rboot_verify_image(uint32_t offset, uint32_t expected_length, const char **error_message) { - rboot_config_t conf; - conf = rboot_get_config(); - - if(slot == -1) { - slot = (conf.current_rom + 1) % RBOOT_MAX_ROMS; + char *error = NULL; + if(offset % 4) { + error = "Unaligned flash offset"; + goto fail; } - size_t sector = conf.roms[slot] / SECTOR_SIZE; - uint8_t *sector_buf = (uint8_t *)malloc(SECTOR_SIZE); - if(!sector_buf) { - printf("Failed to malloc sector buffer\r\n"); - return 0; + uint32_t end_offset = offset + expected_length; + image_header_t image_header; + sdk_spi_flash_read(offset, (uint32_t *)&image_header, sizeof(image_header_t)); + offset += sizeof(image_header_t); + + if(image_header.magic != ROM_MAGIC_OLD && image_header.magic != ROM_MAGIC_NEW) { + error = "Missing initial magic"; + goto fail; } - printf("New image going into sector %d @ 0x%lx\r\n", slot, conf.roms[slot]); + bool is_new_header = (image_header.magic == ROM_MAGIC_NEW); /* a v1.2/rboot header, so expect a v1.1 header after the initial section */ - /* Read bootloader header */ - int r = read(fd, sector_buf, 8); - if(r != 8 || (sector_buf[0] != ROM_MAGIC_OLD && sector_buf[0] != ROM_MAGIC_NEW)) { - printf("Failed to read ESP bootloader header r=%d magic=%d.\r\n", r, sector_buf[0]); - slot = -1; - goto cleanup; - } - /* if we saw a v1.2 header, we can expect a v1.1 header after the first section */ - bool in_new_header = (sector_buf[0] == ROM_MAGIC_NEW); + int remaining_sections = image_header.section_count; - int remaining_sections = sector_buf[1]; - size_t offs = 8; - size_t total_read = 8; - size_t left_section = 0; /* Number of bytes left in this section */ + uint8_t checksum = CHECKSUM_INIT; - while(remaining_sections > 0 || left_section > 0) + while(remaining_sections > 0 && offset < end_offset) { - if(left_section == 0) { - /* No more bytes in this section, read the next section header */ + /* read section header */ + section_header_t header; + sdk_spi_flash_read(offset, (uint32_t *)&header, sizeof(section_header_t)); + RBOOT_DEBUG("Found section @ 0x%08lx length 0x%08lx load 0x%08lx padded 0x%08lx\r\n", offset, header.length, header.load_addr, header.length); + offset += sizeof(section_header_t); - if(offs + (in_new_header ? 16 : 8) > SECTOR_SIZE) { - printf("PANIC: reading section header overflows sector boundary. FIXME.\r\n"); - slot = -1; - goto cleanup; - } - if(in_new_header && total_read > 8) - { - /* expecting an "old" style 8 byte image header here, following irom0 */ - int r = read(fd, sector_buf+offs, 8); - if(r != 8 || sector_buf[offs] != ROM_MAGIC_OLD) { - printf("Failed to read second flash header r=%d magic=0x%02x.\r\n", r, sector_buf[offs]); - slot = -1; - goto cleanup; - } - /* treat this number as the reliable number of remaining sections */ - remaining_sections = sector_buf[offs+1]; - in_new_header = false; - } - - int r = read(fd, sector_buf+offs, 8); - if(r != 8) { - printf("Failed to read section header.\r\n"); - slot = -1; - goto cleanup; - } - offs += 8; - total_read += 8; - left_section = *((uint32_t *)(sector_buf+offs-4)); - - /* account for padding from the reported section size out to the alignment barrier */ - size_t section_align = in_new_header ? 16 : 4; - left_section = (left_section+section_align-1) & ~(section_align-1); - - remaining_sections--; - printf("New section @ 0x%x length 0x%x align 0x%x remaining 0x%x\r\n", total_read, left_section, section_align, remaining_sections); + if(header.length+offset > end_offset) { + break; /* sanity check: will reading section take us off end of expected flashregion? */ } - size_t bytes_to_read = left_section > (SECTOR_SIZE-offs) ? (SECTOR_SIZE-offs) : left_section; - int r = read(fd, sector_buf+offs, bytes_to_read); - if(r < 0) { - printf("Failed to read from fd\r\n"); - slot = -1; - goto cleanup; + if(!is_new_header) { + /* Add individual data of the section to the checksum. */ + char chunk[16]; + for(int i = 0; i < header.length; i++) { + if(i % sizeof(chunk) == 0) + sdk_spi_flash_read(offset+i, (uint32_t *)chunk, sizeof(chunk)); + checksum ^= chunk[i % sizeof(chunk)]; + } } - if(r == 0) { - printf("EOF before end of image remaining_sections=%d this_section=%d\r\n", - remaining_sections, left_section); - slot = -1; - goto cleanup; - } - offs += r; - total_read += r; - left_section -= r; - if(offs == SECTOR_SIZE || (left_section == 0 && remaining_sections == 0)) { - /* sector buffer is full, or we're at the very end, so write sector to flash */ - printf("Sector buffer full. Erasing sector 0x%02x\r\n", sector); - sdk_spi_flash_erase_sector(sector); - printf("Erase done.\r\n"); - //sdk_spi_flash_write(sector * SECTOR_SIZE, (uint32_t*)sector_buf, SECTOR_SIZE); - printf("Flash done.\r\n"); - offs = 0; - sector++; + offset += header.length; + /* pad section to 4 byte align */ + offset = (offset+3) & ~3; + + remaining_sections--; + + if(is_new_header) { + /* pad to a 16 byte offset */ + offset = (offset+15) & ~15; + + /* expect a v1.1 header here at start of "real" sections */ + sdk_spi_flash_read(offset, (uint32_t *)&image_header, sizeof(image_header_t)); + offset += sizeof(image_header_t); + if(image_header.magic != ROM_MAGIC_OLD) { + error = "Bad second magic"; + goto fail; + } + remaining_sections = image_header.section_count; + is_new_header = false; } } - /* Done reading image from fd and writing it out */ - if(reboot_now) - { - close(fd); - conf.current_rom = slot; - rboot_set_config(&conf); - sdk_system_restart(); + if(remaining_sections > 0) { + error = "Image truncated"; + goto fail; } - cleanup: - free(sector_buf); - return slot; + /* add a byte for the image checksum (actually comes after the padding) */ + offset++; + /* pad the image length to a 16 byte boundary */ + offset = (offset+15) & ~15; + + uint8_t read_checksum; + sdk_spi_flash_read(offset-1, (uint32_t *)&read_checksum, 1); /* not sure if this will work */ + if(read_checksum != checksum) { + error = "Invalid checksum"; + goto fail; + } + + if(offset != end_offset) { + error = "Wrong length"; + goto fail; + } + + RBOOT_DEBUG("rboot_verify_image: verified expected 0x%08lx bytes.\r\n", expected_length); + return true; + + fail: + if(error_message) + *error_message = error; + printf("%s: %s\r\n", __func__, error); + return false; } - diff --git a/extras/rboot-ota/rboot-ota.h b/extras/rboot-ota/rboot-ota.h index 078fe9a..5586a30 100644 --- a/extras/rboot-ota/rboot-ota.h +++ b/extras/rboot-ota/rboot-ota.h @@ -1,5 +1,5 @@ #ifndef __RBOOT_OTA_H__ -#define __RBOOTOTA_H__ +#define __RBOOT_OTA_H__ /* rboot-ota client API * * Ported from https://github.com/raburton/esp8266/ to esp-open-rtos @@ -8,6 +8,7 @@ * Copyright (c) 2015 Richard A Burton & SuperHouse Pty Ltd */ #include +#include #include /* rboot config block structure (stored in flash offset 0x1000) @@ -30,6 +31,9 @@ typedef struct { } rboot_config_t; +#define SECTOR_SIZE 0x1000 +#define BOOT_CONFIG_SECTOR 1 + // timeout for the initial connect (in ms) #define OTA_CONNECT_TIMEOUT 10000 @@ -42,21 +46,10 @@ typedef struct { #define FLASH_BY_ADDR 0xff -/* Perform an OTA update. - * - * * 'fd' is the file descriptor to read the OTA image from. - * * 'slot' is the slot to update, or -1 to automatically update next slot. - * * 'reboot_now' means to reboot to the new slot immediately - * (if true, function won't return if successful). - * - * Returns '0' if the update fails. Returns the newly loaded slot if - * reboot_now is false, and the update succeeds so the next restart - * will hit the new image. - */ -int rboot_ota_update(int fd, int slot, bool reboot_now); rboot_config_t rboot_get_config(); bool rboot_set_config(rboot_config_t *conf); uint8_t rboot_get_current_rom(); bool rboot_set_current_rom(uint8_t rom); +bool rboot_verify_image(uint32_t offset, uint32_t expected_length, const char **error_message); #endif diff --git a/ld/eagle.rom.addr.v6.ld b/ld/eagle.rom.addr.v6.ld index 9a062e5..efb6a5a 100644 --- a/ld/eagle.rom.addr.v6.ld +++ b/ld/eagle.rom.addr.v6.ld @@ -8,9 +8,18 @@ PROVIDE ( Wait_SPI_Idle = 0x4000448c ); PROVIDE ( Enable_QMode = 0x400044c0 ); PROVIDE ( Disable_QMode = 0x40004508 ); -PROVIDE ( Cache_Read_Enable = 0x40004678 ); +PROVIDE ( rom_Cache_Read_Enable = 0x40004678 ); PROVIDE ( Cache_Read_Disable = 0x400047f0 ); +#ifndef OTA +/* If not building an OTA image for boot, can use the default (simple) + cache enable function from ROM. + + Otherwise, we need an OTA-aware one (see extras/rboot-ota/rboot-cache.S) +*/ +Cache_Read_Enable = rom_Cache_Read_Enable; +#endif + PROVIDE ( lldesc_build_chain = 0x40004f40 ); PROVIDE ( lldesc_num2link = 0x40005050 ); PROVIDE ( lldesc_set_owner = 0x4000507c );