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 "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 <conf.count; i++) { | ||||
|         printf("%c%d: offset 0x%08lx\r\n", i == conf.current_rom ? '*':' ', i, conf.roms[i]); | ||||
|     } | ||||
| 
 | ||||
|     struct sdk_station_config config = { | ||||
|         .ssid = WIFI_SSID, | ||||
|  | @ -86,5 +39,5 @@ void user_init(void) | |||
|     sdk_wifi_set_opmode(STATION_MODE); | ||||
|     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/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 | ||||
| #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; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <rboot-config.h> | ||||
| 
 | ||||
| /* 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 | ||||
|  |  | |||
|  | @ -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 ); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue