Working TFTP server based OTA updates

Tested with 16MBit flash configuration, two rboot update slots.

Closes #10
This commit is contained in:
Angus Gratton 2015-08-04 14:50:50 +10:00
parent 6887a8119a
commit 19b8383069
8 changed files with 586 additions and 176 deletions

21
core/include/esp/rom.h Normal file
View file

@ -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 <stdint.h>
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

View file

@ -11,73 +11,26 @@
#include "FreeRTOS.h" #include "FreeRTOS.h"
#include "task.h" #include "task.h"
#include "esp8266.h" #include "esp8266.h"
#include "ota-tftp.h"
#include "rboot-ota.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) #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." #error "Please define macros WIFI_SSID & WIFI_PASS (here, or better in a local.h file at root level or in program dir."
#endif #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) void user_init(void)
{ {
sdk_uart_div_modify(0, UART_CLK_FREQ / 115200); sdk_uart_div_modify(0, UART_CLK_FREQ / 115200);
printf("OTA Basic demo. Currently running on slot %d / %d.\r\n", rboot_config_t conf = rboot_get_config();
rboot_get_current_rom(), RBOOT_MAX_ROMS); 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 <conf.count; i++) {
printf("%c%d: offset 0x%08lx\r\n", i == conf.current_rom ? '*':' ', i, conf.roms[i]);
}
struct sdk_station_config config = { struct sdk_station_config config = {
.ssid = WIFI_SSID, .ssid = WIFI_SSID,
@ -86,5 +39,5 @@ void user_init(void)
sdk_wifi_set_opmode(STATION_MODE); sdk_wifi_set_opmode(STATION_MODE);
sdk_wifi_station_set_config(&config); sdk_wifi_station_set_config(&config);
xTaskCreate(simpleOTATask, (signed char *)"simpleOTATask", 512, NULL, 2, NULL); ota_tftp_init_server(TFTP_PORT);
} }

326
extras/rboot-ota/ota-tftp.c Normal file
View file

@ -0,0 +1,326 @@
/* TFTP Server OTA support
*
* For details of use see ota-tftp.h
*
* Part of esp-open-rtos
* Copyright (C) 2015 Superhouse Automation Pty Ltd
* BSD Licensed as described in the file LICENSE
*/
#include <FreeRTOS.h>
#include <string.h>
#include <strings.h>
#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 <netbuf_helpers.h>
#include <espressif/spi_flash.h>
#include <espressif/esp_system.h>
#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);
}

View file

@ -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

View file

@ -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

View file

@ -7,18 +7,32 @@
#include <espressif/spi_flash.h> #include <espressif/spi_flash.h>
#include <espressif/esp_system.h> #include <espressif/esp_system.h>
#include <lwip/sockets.h>
#include <FreeRTOS.h> #include <FreeRTOS.h>
#include <task.h> #include <task.h>
#define SECTOR_SIZE 0x1000
#define BOOT_CONFIG_SECTOR 1
#include "rboot-ota.h" #include "rboot-ota.h"
#define ROM_MAGIC_OLD 0xe9 #define ROM_MAGIC_OLD 0xe9
#define ROM_MAGIC_NEW 0xea #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 // get the rboot config
@ -41,7 +55,7 @@ bool rboot_set_config(rboot_config_t *conf) {
buffer = (uint8_t*)malloc(SECTOR_SIZE); buffer = (uint8_t*)malloc(SECTOR_SIZE);
if (!buffer) { if (!buffer) {
printf("Failed to allocate sector buffer\r\n"); printf("rboot_set_config: Failed to allocate sector buffer\r\n");
return false; return false;
} }
@ -82,122 +96,105 @@ bool rboot_set_current_rom(uint8_t rom) {
return rboot_set_config(&conf); 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; char *error = NULL;
conf = rboot_get_config(); if(offset % 4) {
error = "Unaligned flash offset";
if(slot == -1) { goto fail;
slot = (conf.current_rom + 1) % RBOOT_MAX_ROMS;
} }
size_t sector = conf.roms[slot] / SECTOR_SIZE; uint32_t end_offset = offset + expected_length;
uint8_t *sector_buf = (uint8_t *)malloc(SECTOR_SIZE); image_header_t image_header;
if(!sector_buf) { sdk_spi_flash_read(offset, (uint32_t *)&image_header, sizeof(image_header_t));
printf("Failed to malloc sector buffer\r\n"); offset += sizeof(image_header_t);
return 0;
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 remaining_sections = image_header.section_count;
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 = sector_buf[1]; uint8_t checksum = CHECKSUM_INIT;
size_t offs = 8;
size_t total_read = 8;
size_t left_section = 0; /* Number of bytes left in this section */
while(remaining_sections > 0 || left_section > 0) while(remaining_sections > 0 && offset < end_offset)
{ {
if(left_section == 0) { /* read section header */
/* No more bytes in this section, read the next 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"); if(header.length+offset > end_offset) {
slot = -1; break; /* sanity check: will reading section take us off end of expected flashregion? */
goto cleanup;
} }
if(in_new_header && total_read > 8) if(!is_new_header) {
{ /* Add individual data of the section to the checksum. */
/* expecting an "old" style 8 byte image header here, following irom0 */ char chunk[16];
int r = read(fd, sector_buf+offs, 8); for(int i = 0; i < header.length; i++) {
if(r != 8 || sector_buf[offs] != ROM_MAGIC_OLD) { if(i % sizeof(chunk) == 0)
printf("Failed to read second flash header r=%d magic=0x%02x.\r\n", r, sector_buf[offs]); sdk_spi_flash_read(offset+i, (uint32_t *)chunk, sizeof(chunk));
slot = -1; checksum ^= chunk[i % sizeof(chunk)];
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); offset += header.length;
if(r != 8) { /* pad section to 4 byte align */
printf("Failed to read section header.\r\n"); offset = (offset+3) & ~3;
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--; 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);
}
size_t bytes_to_read = left_section > (SECTOR_SIZE-offs) ? (SECTOR_SIZE-offs) : left_section; if(is_new_header) {
int r = read(fd, sector_buf+offs, bytes_to_read); /* pad to a 16 byte offset */
if(r < 0) { offset = (offset+15) & ~15;
printf("Failed to read from fd\r\n");
slot = -1;
goto cleanup;
}
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)) { /* expect a v1.1 header here at start of "real" sections */
/* sector buffer is full, or we're at the very end, so write sector to flash */ sdk_spi_flash_read(offset, (uint32_t *)&image_header, sizeof(image_header_t));
printf("Sector buffer full. Erasing sector 0x%02x\r\n", sector); offset += sizeof(image_header_t);
sdk_spi_flash_erase_sector(sector); if(image_header.magic != ROM_MAGIC_OLD) {
printf("Erase done.\r\n"); error = "Bad second magic";
//sdk_spi_flash_write(sector * SECTOR_SIZE, (uint32_t*)sector_buf, SECTOR_SIZE); goto fail;
printf("Flash done.\r\n"); }
offs = 0; remaining_sections = image_header.section_count;
sector++; is_new_header = false;
} }
} }
/* Done reading image from fd and writing it out */ if(remaining_sections > 0) {
if(reboot_now) error = "Image truncated";
{ goto fail;
close(fd);
conf.current_rom = slot;
rboot_set_config(&conf);
sdk_system_restart();
} }
cleanup: /* add a byte for the image checksum (actually comes after the padding) */
free(sector_buf); offset++;
return slot; /* 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;
}

View file

@ -1,5 +1,5 @@
#ifndef __RBOOT_OTA_H__ #ifndef __RBOOT_OTA_H__
#define __RBOOTOTA_H__ #define __RBOOT_OTA_H__
/* rboot-ota client API /* rboot-ota client API
* *
* Ported from https://github.com/raburton/esp8266/ to esp-open-rtos * Ported from https://github.com/raburton/esp8266/ to esp-open-rtos
@ -8,6 +8,7 @@
* Copyright (c) 2015 Richard A Burton & SuperHouse Pty Ltd * Copyright (c) 2015 Richard A Burton & SuperHouse Pty Ltd
*/ */
#include <stdint.h> #include <stdint.h>
#include <stdbool.h>
#include <rboot-config.h> #include <rboot-config.h>
/* rboot config block structure (stored in flash offset 0x1000) /* rboot config block structure (stored in flash offset 0x1000)
@ -30,6 +31,9 @@ typedef struct {
} rboot_config_t; } rboot_config_t;
#define SECTOR_SIZE 0x1000
#define BOOT_CONFIG_SECTOR 1
// timeout for the initial connect (in ms) // timeout for the initial connect (in ms)
#define OTA_CONNECT_TIMEOUT 10000 #define OTA_CONNECT_TIMEOUT 10000
@ -42,21 +46,10 @@ typedef struct {
#define FLASH_BY_ADDR 0xff #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(); rboot_config_t rboot_get_config();
bool rboot_set_config(rboot_config_t *conf); bool rboot_set_config(rboot_config_t *conf);
uint8_t rboot_get_current_rom(); uint8_t rboot_get_current_rom();
bool rboot_set_current_rom(uint8_t 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 #endif

View file

@ -8,9 +8,18 @@ PROVIDE ( Wait_SPI_Idle = 0x4000448c );
PROVIDE ( Enable_QMode = 0x400044c0 ); PROVIDE ( Enable_QMode = 0x400044c0 );
PROVIDE ( Disable_QMode = 0x40004508 ); PROVIDE ( Disable_QMode = 0x40004508 );
PROVIDE ( Cache_Read_Enable = 0x40004678 ); PROVIDE ( rom_Cache_Read_Enable = 0x40004678 );
PROVIDE ( Cache_Read_Disable = 0x400047f0 ); 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_build_chain = 0x40004f40 );
PROVIDE ( lldesc_num2link = 0x40005050 ); PROVIDE ( lldesc_num2link = 0x40005050 );
PROVIDE ( lldesc_set_owner = 0x4000507c ); PROVIDE ( lldesc_set_owner = 0x4000507c );