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"); |  | ||||||
|                 slot = -1; |  | ||||||
|                 goto cleanup; |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             if(in_new_header && total_read > 8) |         if(header.length+offset > end_offset) { | ||||||
|             { |             break; /* sanity check: will reading section take us off end of expected flashregion? */ | ||||||
|                 /* 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; |         if(!is_new_header) { | ||||||
|         int r = read(fd, sector_buf+offs, bytes_to_read); |             /* Add individual data of the section to the checksum. */ | ||||||
|         if(r < 0) { |             char chunk[16]; | ||||||
|             printf("Failed to read from fd\r\n"); |             for(int i = 0; i < header.length; i++) { | ||||||
|             slot = -1; |                 if(i % sizeof(chunk) == 0) | ||||||
|             goto cleanup; |                     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)) { |         offset += header.length; | ||||||
|             /* sector buffer is full, or we're at the very end, so write sector to flash */ |         /* pad section to 4 byte align */ | ||||||
|             printf("Sector buffer full. Erasing sector 0x%02x\r\n", sector); |         offset = (offset+3) & ~3; | ||||||
|             sdk_spi_flash_erase_sector(sector); | 
 | ||||||
|             printf("Erase done.\r\n"); |         remaining_sections--; | ||||||
|             //sdk_spi_flash_write(sector * SECTOR_SIZE, (uint32_t*)sector_buf, SECTOR_SIZE);
 | 
 | ||||||
|             printf("Flash done.\r\n"); |         if(is_new_header) { | ||||||
|             offs = 0; |             /* pad to a 16 byte offset */ | ||||||
|             sector++; |             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(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…
	
	Add table
		Add a link
		
	
		Reference in a new issue