Skip to content

Commit

Permalink
Almost functional OTA support
Browse files Browse the repository at this point in the history
ota_basic example can receive new image via TCP.

However - writing to flash with interrupts disabled causes data loss,
and the TCP flow is very slow to recover. Linux sender quickly ramps up
RTT timer to very long retry intervals, crippling performance &
throughput.

Running the update without the flash writes causes the data to be
received quickly, so this is definitely an issue with the time taken for
the erase cycle.

Progress towards #10
  • Loading branch information
projectgus committed Jul 29, 2015
1 parent 3797cf5 commit 147257e
Show file tree
Hide file tree
Showing 10 changed files with 449 additions and 0 deletions.
1 change: 1 addition & 0 deletions common.mk
Expand Up @@ -230,6 +230,7 @@ $$($(1)_OBJ_DIR)%.o: $$($(1)_REAL_ROOT)%.s $$($(1)_MAKEFILE) $(wildcard $(ROOT)*
# for missing explicitly named source files
$$($(1)_AR): $$($(1)_OBJ_FILES) $$($(1)_SRC_FILES)
$(vecho) "AR $$@"
$(Q) mkdir -p $$(dir $$@)
$(Q) $(AR) cru $$@ $$^

COMPONENT_ARS += $$($(1)_AR)
Expand Down
5 changes: 5 additions & 0 deletions examples/ota_basic/Makefile
@@ -0,0 +1,5 @@
PROGRAM=ota_basic
OTA=1
EXTRA_COMPONENTS=extras/rboot-ota
include ../../common.mk

90 changes: 90 additions & 0 deletions examples/ota_basic/ota_basic.c
@@ -0,0 +1,90 @@
/* A very simple OTA example
*
* Binds a TCP socket, reads an image from it and then flashes live.
*
* This lets you flash from the command line via netcat.
*
* NOT SUITABLE TO PUT ON THE INTERNET OR INTO A PRODUCTION ENVIRONMENT!!!!
*/
#include "espressif/esp_common.h"
#include "espressif/sdk_private.h"
#include "FreeRTOS.h"
#include "task.h"
#include "esp8266.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);

struct sdk_station_config config = {
.ssid = WIFI_SSID,
.password = WIFI_PASS,
};
sdk_wifi_set_opmode(STATION_MODE);
sdk_wifi_station_set_config(&config);

xTaskCreate(simpleOTATask, (signed char *)"simpleOTATask", 512, NULL, 2, NULL);
}
9 changes: 9 additions & 0 deletions examples/ota_basic/ota_basic.def
@@ -0,0 +1,9 @@
cpu xtensa

# Show up to this many raw bytes of code/data
show bytes 4

# ELF file
# See example.def if you want to load raw binaries instead
load build/ota_basic.out elf

7 changes: 7 additions & 0 deletions examples/ota_basic/test.c
@@ -0,0 +1,7 @@
extern void wDev_ProcessFiq(void);

void call_wdev(void)
{
wDev_ProcessFiq();
}

9 changes: 9 additions & 0 deletions extras/rboot-ota/component.mk
@@ -0,0 +1,9 @@
# Component makefile for extras/rboot-ota

INC_DIRS += $(ROOT)extras/rboot-ota

# args for passing into compile rule generation
extras/rboot-ota_INC_DIR = $(ROOT)extras/rboot-ota
extras/rboot-ota_SRC_DIR = $(ROOT)extras/rboot-ota

$(eval $(call component_compile_rules,extras/rboot-ota))
14 changes: 14 additions & 0 deletions extras/rboot-ota/rboot-config.h
@@ -0,0 +1,14 @@
#ifndef _RBOOT_CONFIG_H
/* rboot configuration parameters.
Must match the config in the compiler bootloader rboot.h. Values below are the rboot.h defaults.
Override rboot parameters by editing this file, or (much better
alternative) copy rboot-config.h to your program directory or
program/include/ directory, then edit it to override these values.
*/

#define RBOOT_MAX_ROMS 4
//#define RBOOT_CONFIG_CHECKSUM

#endif
203 changes: 203 additions & 0 deletions extras/rboot-ota/rboot-ota.c
@@ -0,0 +1,203 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <espressif/spi_flash.h>
#include <espressif/esp_system.h>

#include <lwip/sockets.h>

#include <FreeRTOS.h>
#include <task.h>

#define SECTOR_SIZE 0x1000
#define BOOT_CONFIG_SECTOR 1

#include "rboot-ota.h"

#define ROM_MAGIC_OLD 0xe9
#define ROM_MAGIC_NEW 0xea


// get the rboot config
rboot_config_t rboot_get_config() {
rboot_config_t conf;
sdk_spi_flash_read(BOOT_CONFIG_SECTOR * SECTOR_SIZE, (uint32_t*)&conf, sizeof(rboot_config_t));
return conf;
}

// write the rboot config
// preserves contents of rest of sector, so rest
// of sector can be used to store user data
// updates checksum automatically, if enabled
bool rboot_set_config(rboot_config_t *conf) {
uint8_t *buffer;
#ifdef RBOOT_CONFIG_CHKSUM
uint8_t chksum;
uint8_t *ptr;
#endif

buffer = (uint8_t*)malloc(SECTOR_SIZE);
if (!buffer) {
printf("Failed to allocate sector buffer\r\n");
return false;
}

#ifdef BOOT_CONFIG_CHKSUM
chksum = CHKSUM_INIT;
for (ptr = (uint8_t*)conf; ptr < &conf->chksum; ptr++) {
chksum ^= *ptr;
}
conf->chksum = chksum;
#endif

sdk_spi_flash_read(BOOT_CONFIG_SECTOR * SECTOR_SIZE, (uint32_t*)buffer, SECTOR_SIZE);
memcpy(buffer, conf, sizeof(rboot_config_t));
vPortEnterCritical();
sdk_spi_flash_erase_sector(BOOT_CONFIG_SECTOR);
vPortExitCritical();
taskYIELD();
vPortEnterCritical();
sdk_spi_flash_write(BOOT_CONFIG_SECTOR * SECTOR_SIZE, (uint32_t*)buffer, SECTOR_SIZE);
vPortExitCritical();
free(buffer);
return true;
}

// get current boot rom
uint8_t rboot_get_current_rom() {
rboot_config_t conf;
conf = rboot_get_config();
return conf.current_rom;
}

// set current boot rom
bool rboot_set_current_rom(uint8_t rom) {
rboot_config_t conf;
conf = rboot_get_config();
if (rom >= conf.count) return false;
conf.current_rom = rom;
return rboot_set_config(&conf);
}

int rboot_ota_update(int fd, int slot, bool reboot_now)
{
rboot_config_t conf;
conf = rboot_get_config();

if(slot == -1) {
slot = (conf.current_rom + 1) % RBOOT_MAX_ROMS;
}

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;
}

printf("New image going into sector %d @ 0x%lx\r\n", slot, conf.roms[slot]);

/* 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 = sector_buf[1];
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)
{
if(left_section == 0) {
/* No more bytes in this section, read the next section header */

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);
}

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(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++;
}
}

/* 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();
}

cleanup:
free(sector_buf);
return slot;
}

0 comments on commit 147257e

Please sign in to comment.