Working TFTP server based OTA updates
Tested with 16MBit flash configuration, two rboot update slots. Closes #10
This commit is contained in:
parent
6887a8119a
commit
19b8383069
8 changed files with 586 additions and 176 deletions
21
core/include/esp/rom.h
Normal file
21
core/include/esp/rom.h
Normal 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
|
|
@ -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
326
extras/rboot-ota/ota-tftp.c
Normal 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);
|
||||||
|
}
|
33
extras/rboot-ota/ota-tftp.h
Normal file
33
extras/rboot-ota/ota-tftp.h
Normal 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
|
78
extras/rboot-ota/rboot-cache.S
Normal file
78
extras/rboot-ota/rboot-cache.S
Normal 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
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
Loading…
Reference in a new issue