Open Model Railroad Network (OpenMRN)
Loading...
Searching...
No Matches
Esp32BootloaderHal.hxx
Go to the documentation of this file.
1
39#ifndef _FREERTOS_DRIVERS_ESP32_ESP32BOOTLOADERHAL_HXX_
40#define _FREERTOS_DRIVERS_ESP32_ESP32BOOTLOADERHAL_HXX_
41
42#include "sdkconfig.h"
43
44#ifndef BOOTLOADER_LOG_LEVEL
45#define BOOTLOADER_LOG_LEVEL VERBOSE
46#endif // BOOTLOADER_LOG_LEVEL
47
48#ifndef BOOTLOADER_TWAI_LOG_LEVEL
49#define BOOTLOADER_TWAI_LOG_LEVEL VERBOSE
50#endif // BOOTLOADER_TWAI_LOG_LEVEL
51
52// Enable streaming support for the bootloader
53#define BOOTLOADER_STREAM
54#ifndef WRITE_BUFFER_SIZE
55// Set the buffer size to half of the sector size to minimize the flash writes.
56#define WRITE_BUFFER_SIZE (CONFIG_WL_SECTOR_SIZE / 2)
57#endif // WRITE_BUFFER_SIZE
58
59#include <driver/twai.h>
60
61#include <esp_app_format.h>
62#include <esp_chip_info.h>
63#include <esp_ota_ops.h>
64#if defined(CONFIG_IDF_TARGET_ESP32)
65#include <esp32/rom/rtc.h>
66#elif defined(CONFIG_IDF_TARGET_ESP32S2)
67#include <esp32s2/rom/rtc.h>
68#elif defined(CONFIG_IDF_TARGET_ESP32S3)
69#include <esp32s3/rom/rtc.h>
70#elif defined(CONFIG_IDF_TARGET_ESP32C3)
71#include <esp32c3/rom/rtc.h>
72#else
73#error Unknown/Unsupported ESP32 variant.
74#endif
75#include <stdint.h>
76
78#include "openlcb/Defs.hxx"
79#include "utils/constants.hxx"
80#include "utils/Hub.hxx"
81
84{
86 esp_chip_id_t chip_id;
87
93
95 uint64_t node_id;
96
98 esp_partition_t *current;
99
101 esp_partition_t *target;
102
104 esp_ota_handle_t ota_handle;
105
109
111 gpio_num_t tx_pin;
112
114 gpio_num_t rx_pin;
115};
116
119
121static constexpr BaseType_t MAX_TWAI_WAIT_RX = pdMS_TO_TICKS(250);
122
124static constexpr BaseType_t MAX_TWAI_WAIT_TX = pdMS_TO_TICKS(0);
125
130static uint32_t RTC_NOINIT_ATTR bootloader_request;
131
134static constexpr uint32_t RTC_BOOL_TRUE = 0x92e01a42;
135
138static constexpr uint32_t RTC_BOOL_FALSE = 0;
139
140extern "C"
141{
142
145{
146 // TWAI driver timing configuration, 125kbps.
147 twai_timing_config_t t_config = TWAI_TIMING_CONFIG_125KBITS();
148
149 // TWAI driver filter configuration, accept all frames.
150 twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
151
152 // TWAI driver general configuration.
153 twai_general_config_t g_config =
154 TWAI_GENERAL_CONFIG_DEFAULT(esp_bl_state.tx_pin, esp_bl_state.rx_pin,
155 TWAI_MODE_NORMAL);
156 g_config.tx_queue_len = config_can_tx_buffer_size();
157 g_config.rx_queue_len = config_can_rx_buffer_size();
158
159 LOG(BOOTLOADER_LOG_LEVEL, "[Bootloader] Configuring TWAI driver");
160 ESP_ERROR_CHECK(twai_driver_install(&g_config, &t_config, &f_config));
161 LOG(BOOTLOADER_LOG_LEVEL, "[Bootloader] Starting TWAI driver");
162 ESP_ERROR_CHECK(twai_start());
164}
165
170{
171 LOG(BOOTLOADER_LOG_LEVEL, "[Bootloader] application_entry");
172
173 // reset the RTC persistent variable to not enter bootloader on next
174 // restart.
176
177 // restart the esp32 since we do not have a way to rerun app_main.
178 esp_restart();
179}
180
185{
186 LOG(BOOTLOADER_LOG_LEVEL, "[Bootloader] Reboot requested");
187}
188
193bool read_can_frame(struct can_frame *frame)
194{
195 twai_message_t rx_msg;
196 memset(&rx_msg, 0, sizeof(twai_message_t));
197 if (twai_receive(&rx_msg, MAX_TWAI_WAIT) == ESP_OK)
198 {
199 LOG(BOOTLOADER_TWAI_LOG_LEVEL, "[Bootloader] CAN_RX");
200 frame->can_id = rx_msg.identifier;
201 frame->can_dlc = rx_msg.data_length_code;
202 frame->can_err = 0;
203 frame->can_eff = rx_msg.extd;
204 frame->can_rtr = rx_msg.rtr;
205 memcpy(frame->data, rx_msg.data, frame->can_dlc);
206 return true;
207 }
208 return false;
209}
210
215bool try_send_can_frame(const struct can_frame &frame)
216{
217 twai_message_t tx_msg;
218 memset(&tx_msg, 0, sizeof(twai_message_t));
219 tx_msg.identifier = frame.can_id;
220 tx_msg.data_length_code = frame.can_dlc;
221 tx_msg.extd = frame.can_eff;
222 tx_msg.rtr = frame.can_rtr;
223 memcpy(tx_msg.data, frame.data, frame.can_dlc);
224 if (twai_transmit(&tx_msg, MAX_TWAI_WAIT_TX) == ESP_OK)
225 {
226 LOG(BOOTLOADER_TWAI_LOG_LEVEL, "[Bootloader] CAN_TX");
227 return true;
228 }
229 return false;
230}
231
242void get_flash_boundaries(const void **flash_min, const void **flash_max,
243 const struct app_header **app_header)
244{
245 LOG(BOOTLOADER_LOG_LEVEL,
246 "[Bootloader] get_flash_boundaries(%d, %" PRIu32 ")",
248 *((uint32_t *)flash_min) = 0;
249 *((uint32_t *)flash_max) = esp_bl_state.app_header.app_size;
251}
252
259 const void *address, const void **page_start, uint32_t *page_length_bytes)
260{
261 uint32_t value = (uint32_t)address;
262 value &= ~(CONFIG_WL_SECTOR_SIZE - 1);
263 *page_start = (const void *)value;
264 *page_length_bytes = CONFIG_WL_SECTOR_SIZE;
265 LOG(BOOTLOADER_LOG_LEVEL,
266 "[Bootloader] get_flash_page_info(%" PRIu32 ", %" PRIu32")",
267 value, *page_length_bytes);
268}
269
276void erase_flash_page(const void *address)
277{
278 // NO OP as this is handled automatically as part of esp_ota_write.
279 LOG(BOOTLOADER_LOG_LEVEL, "[Bootloader] Erase: %" PRIu32,
280 (uint32_t)address);
281}
282
295void write_flash(const void *address, const void *data, uint32_t size_bytes)
296{
297 uint32_t addr = (uint32_t)address;
298 LOG(VERBOSE, "[Bootloader] Write: %" PRIu32 ", %" PRIu32, addr,
299 size_bytes);
300
301 // The first part of the received binary should have the image header,
302 // segment header and app description. These are used as a first pass
303 // validation of the received data to ensure it is a valid firmware.
304 if (addr == 0)
305 {
306 // Mapping of known ESP32 chip id values.
307 const char * const CHIP_ID_NAMES[] =
308 {
309 // Note: this must be kept in sync with esp_chip_id_t.
310 "ESP32", // 0x00 ESP_CHIP_ID_ESP32
311 "INVALID", // 0x01 invalid (placeholder)
312 "ESP32-S2", // 0x02 ESP_CHIP_ID_ESP32S2
313 "INVALID", // 0x03 invalid (placeholder)
314 "INVALID", // 0x04 invalid (placeholder)
315 "ESP32-C3", // 0x05 ESP_CHIP_ID_ESP32C3
316 "INVALID", // 0x06 invalid (placeholder)
317 "INVALID", // 0x07 invalid (placeholder)
318 "INVALID", // 0x08 invalid (placeholder)
319 "ESP32-S3", // 0x09 ESP_CHIP_ID_ESP32S3
320 "ESP32-H2", // 0x0A ESP_CHIP_ID_ESP32H2
321 "INVALID", // 0x0B invalid (placeholder)
322 "ESP32-C2", // 0x0C ESP_CHIP_ID_ESP32C2
323 };
324
325 bool should_abort = false;
326 esp_image_header_t *image_header = (esp_image_header_t *)data;
327 // If the image magic is correct we can proceed with validating the
328 // basic details of the image.
329 if (image_header->magic == ESP_IMAGE_HEADER_MAGIC)
330 {
331 LOG(BOOTLOADER_LOG_LEVEL, "[Bootloader] Chip ID: %s / %x (%x)",
332 CHIP_ID_NAMES[image_header->chip_id],
333 image_header->chip_id, esp_bl_state.chip_id);
334 // validate the image magic byte and chip type to
335 // ensure it matches the currently running chip.
336 if (image_header->chip_id != ESP_CHIP_ID_INVALID &&
337 image_header->chip_id == esp_bl_state.chip_id)
338 {
339 // start the OTA process at this point, if we have had a
340 // previous failure this will reset the OTA process so we can
341 // start fresh.
342 esp_err_t err = ESP_ERROR_CHECK_WITHOUT_ABORT(
343 esp_ota_begin(esp_bl_state.target, OTA_SIZE_UNKNOWN,
345 should_abort = (err != ESP_OK);
346 }
347 else
348 {
349 LOG_ERROR("[Bootloader] Firmware does not appear to be valid "
350 "or is for a different chip (%s - %x vs %s - %x).",
351 CHIP_ID_NAMES[image_header->chip_id],
352 image_header->chip_id,
353 CHIP_ID_NAMES[esp_bl_state.chip_id],
355 should_abort = true;
356 }
357 }
358 else
359 {
360 LOG_ERROR("[Bootloader] Image magic is incorrect: %d vs %d!",
361 image_header->magic, ESP_IMAGE_HEADER_MAGIC);
362 should_abort = true;
363 }
364
365 // It would be ideal to abort the firmware upload at this point but the
366 // bootloader HAL does not offer a way to abort the transfer so instead
367 // reboot the node.
368 if (should_abort || esp_bl_state.ota_handle == 0)
369 {
370 // reset the RTC persistent variable to not enter bootloader on
371 // next restart.
373
374 // cleanup the OTA handle since we are aborting.
375 if (esp_bl_state.ota_handle != 0)
376 {
377 // abort the OTA operation, we do not validate the return code
378 // other than logging the error (if any) as this method only
379 // cleans up the ota_handle reference within the OTA system and
380 // does not impact future usage.
381 ESP_ERROR_CHECK_WITHOUT_ABORT(
382 esp_ota_abort(esp_bl_state.ota_handle));
383 }
384
385 // invalidate ota_handle in case the esp32 does not reboot before
386 // the next time address zero comes up again.
388 esp_restart();
389 }
390 }
391 bootloader_led(LED_WRITING, true);
392 bootloader_led(LED_ACTIVE, false);
393 ESP_ERROR_CHECK(
394 esp_ota_write(esp_bl_state.ota_handle, data, size_bytes));
395 bootloader_led(LED_WRITING, false);
396 bootloader_led(LED_ACTIVE, true);
397}
398
406uint16_t flash_complete(void)
407{
408 LOG(INFO, "[Bootloader] Finalizing firmware update");
409 esp_err_t res = esp_ota_end(esp_bl_state.ota_handle);
410 if (res != ESP_OK)
411 {
412 LOG_ERROR("[Bootloader] Firmware update failed: %s (%04x), aborting!",
413 esp_err_to_name(res), res);
415 }
416 LOG(INFO,
417 "[Bootloader] Firmware appears valid, updating the next boot "
418 "partition to %s.", esp_bl_state.target->label);
419 res = esp_ota_set_boot_partition(esp_bl_state.target);
420 if (res != ESP_OK)
421 {
422 LOG_ERROR("[Bootloader] Failed to update the boot partition %s (%04x)!",
423 esp_err_to_name(res), res);
425 }
426 return openlcb::Defs::ERROR_CODE_OK;
427}
428
437void checksum_data(const void* data, uint32_t size, uint32_t* checksum)
438{
439 LOG(BOOTLOADER_LOG_LEVEL, "[Bootloader] checksum_data(%" PRIu32 ")", size);
440 // Force the checksum to be zero since it is not currently used on the
441 // ESP32. The startup of the node may validate the built-in SHA256 and
442 // fallback to previous application binary if the SHA256 validation fails.
443 memset(checksum, 0, sizeof(uint32_t) * CHECKSUM_COUNT);
444}
445
449uint16_t nmranet_alias(void)
450{
451 LOG(BOOTLOADER_LOG_LEVEL, "[Bootloader] nmranet_alias");
452 // let the bootloader generate it based on nmranet_nodeid().
453 return 0;
454}
455
459uint64_t nmranet_nodeid(void)
460{
461 LOG(BOOTLOADER_LOG_LEVEL, "[Bootloader] nmranet_nodeid");
462 return esp_bl_state.node_id;
463}
464
465} // extern "C"
466
485void esp32_bootloader_init(uint8_t reset_reason)
486{
487 // If this is the first power up of the node we need to reset the flag
488 // since it will not be initialized automatically.
489 if (reset_reason == POWERON_RESET)
490 {
492 }
493}
494
501void esp32_bootloader_run(uint64_t id, gpio_num_t rx, gpio_num_t tx,
502 bool reboot_on_exit = true)
503{
504 memset(&esp_bl_state, 0, sizeof(Esp32BootloaderState));
505
507 esp_bl_state.tx_pin = tx;
508 esp_bl_state.rx_pin = rx;
509 esp_bl_state.chip_id = ESP_CHIP_ID_INVALID;
510
511 // Extract the currently running chip details so we can use it to confirm
512 // the received firmware is for this chip.
513 esp_chip_info_t chip_info;
514 esp_chip_info(&chip_info);
515 switch (chip_info.model)
516 {
517 case CHIP_ESP32:
518 esp_bl_state.chip_id = ESP_CHIP_ID_ESP32;
519 break;
520 case CHIP_ESP32S2:
521 esp_bl_state.chip_id = ESP_CHIP_ID_ESP32S2;
522 break;
523 case CHIP_ESP32S3:
524 esp_bl_state.chip_id = ESP_CHIP_ID_ESP32S3;
525 break;
526 case CHIP_ESP32C3:
527 esp_bl_state.chip_id = ESP_CHIP_ID_ESP32C3;
528 break;
529 default:
530 LOG(FATAL, "[Bootloader] Unknown/Unsupported Chip ID: %x",
531 chip_info.model);
532 }
533
534 // Initialize the app header details based on the currently running
535 // partition.
536 esp_bl_state.current = (esp_partition_t *)esp_ota_get_running_partition();
538
539 // Find the next OTA partition and confirm it is not the currently running
540 // partition.
542 (esp_partition_t *)esp_ota_get_next_update_partition(NULL);
543 if (esp_bl_state.target != nullptr &&
545 {
546 LOG(INFO, "[Bootloader] Preparing to receive firmware");
547 LOG(INFO, "[Bootloader] Current partition: %s",
548 esp_bl_state.current->label);
549 LOG(INFO, "[Bootloader] Target partition: %s",
550 esp_bl_state.target->label);
551
552 // since we have the target partition identified, start the bootloader.
553 LOG(BOOTLOADER_LOG_LEVEL, "[Bootloader] calling bootloader_entry");
555 }
556 else
557 {
558 LOG_ERROR("[Bootloader] Unable to locate next OTA partition!");
559 }
560
562 {
563 LOG(BOOTLOADER_LOG_LEVEL, "[Bootloader] Stopping TWAI driver");
564 ESP_ERROR_CHECK(twai_stop());
565 LOG(BOOTLOADER_LOG_LEVEL, "[Bootloader] Disabling TWAI driver");
566 ESP_ERROR_CHECK(twai_driver_uninstall());
567 }
568
569 // reset the RTC persistent variable to not enter bootloader on next
570 // restart.
572
573 if (reboot_on_exit)
574 {
575 // If we reach here we should restart the node.
576 LOG(INFO, "[Bootloader] Restarting!");
577 esp_restart();
578 }
579}
580
581#endif // _FREERTOS_DRIVERS_ESP32_ESP32BOOTLOADERHAL_HXX_
void bootloader_entry()
Main entry point for MCU-based bootloaders. Never returns.
bool try_send_can_frame(const struct can_frame &frame)
Callback from the bootloader to transmit a single CAN frame.
static constexpr BaseType_t MAX_TWAI_WAIT_RX
Maximum time to wait for a TWAI frame to be received before giving up.
void get_flash_boundaries(const void **flash_min, const void **flash_max, const struct app_header **app_header)
Callback from the bootloader to retrieve flash boundaries.
void esp32_bootloader_init(uint8_t reset_reason)
Initializes the ESP32 Bootloader.
void bootloader_reboot(void)
Callback from the bootloader when a reboot should be triggered.
static constexpr uint32_t RTC_BOOL_TRUE
Value to be assigned to bootloader_request when the bootloader should run instead of normal node oper...
void application_entry(void)
Callback from the bootloader for entering the application.
static constexpr uint32_t RTC_BOOL_FALSE
Default value to assign to bootloader_request when the ESP32-C3 starts the first time or when the boo...
void erase_flash_page(const void *address)
Callback from the bootloader to erase a flash page.
void get_flash_page_info(const void *address, const void **page_start, uint32_t *page_length_bytes)
Callback from the bootloader to retrieve flash page information.
static constexpr BaseType_t MAX_TWAI_WAIT_TX
Maximum time to wait for a TWAI frame to be transmitted before giving up.
void esp32_bootloader_run(uint64_t id, gpio_num_t rx, gpio_num_t tx, bool reboot_on_exit=true)
Runs the ESP32 Bootloader.
uint16_t flash_complete(void)
Callback from the bootloader to indicate that the full firmware file has been received.
void checksum_data(const void *data, uint32_t size, uint32_t *checksum)
Callback from the bootloader to calculate the checksum of a data block.
static uint32_t RTC_NOINIT_ATTR bootloader_request
Flag used to indicate that we have been requested to enter the bootloader instead of normal node oper...
void bootloader_hw_init(void)
Callback from the bootloader to configure and start the TWAI hardware.
bool read_can_frame(struct can_frame *frame)
Callback from the bootloader to read a single CAN frame.
uint16_t nmranet_alias(void)
Callback from the bootloader to obtain the pre-defined alias to use.
uint64_t nmranet_nodeid(void)
Callback from the bootloader to obtain the node-id to use.
static Esp32BootloaderState esp_bl_state
Bootloader configuration data.
void write_flash(const void *address, const void *data, uint32_t size_bytes)
Callback from the bootloader to write to flash.
void bootloader_led(enum BootloaderLed led, bool value)
Sets status LEDs.
#define CHECKSUM_COUNT
Number of 32-bit words in one checksum data.
#define LOG(level, message...)
Conditionally write a message to the logging output.
Definition logging.h:99
static const int VERBOSE
Loglevel that is usually not printed, reporting debugging information.
Definition logging.h:59
static const int INFO
Loglevel that is printed by default, reporting some status information.
Definition logging.h:57
static const int FATAL
Loglevel that kills the current process.
Definition logging.h:51
#define LOG_ERROR(message...)
Shorthand for LOG(LEVEL_ERROR, message...). See LOG.
Definition logging.h:124
ESP32 Bootloader internal data.
esp_partition_t * current
Partition where the firmware is currently running from.
gpio_num_t rx_pin
GPIO pin connected to the CAN transceiver RX pin.
uint64_t node_id
Node ID to use for the bootloader.
esp_ota_handle_t ota_handle
OTA handle used to track the firmware update progress.
gpio_num_t tx_pin
GPIO pin connected to the CAN transceiver TX pin.
esp_partition_t * target
Partition where the new firmware should be written to.
esp_chip_id_t chip_id
Chip identifier for the currently running firmware.
struct app_header app_header
Currently running application header information.
bool twai_initialized
Internal flag to indicate that we have initialized the TWAI peripheral and should deinit it before ex...
Static definitions of the application.
uint32_t app_size
The total length of the application binary.
@ ERROR_FIRMWARE_CSUM
The firmware written has failed checksum (temporary error).