Skip to content

Commit 6259acc

Browse files
committed
nano_nora: core split + recovery mode rework
Use an absolute address in SPIRAM to store the magic tokens, almost at the end of the memory, to avoid the markers from being overwritten on any kind of sketch and core combination. Also, only start the recovery once if a valid binary is present in the Flash, by immediately setting that for the next boot when recovery starts.
1 parent eb8cffd commit 6259acc

File tree

6 files changed

+266
-199
lines changed

6 files changed

+266
-199
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#include "Arduino.h"
2+
3+
#include <esp32-hal-tinyusb.h>
4+
#include <esp_system.h>
5+
6+
// defines an "Update" object accessed only by this translation unit
7+
// (also, the object requires MD5Builder internally)
8+
namespace {
9+
// ignore '{anonymous}::MD5Builder::...() defined but not used' warnings
10+
#pragma GCC diagnostic push
11+
#pragma GCC diagnostic ignored "-Wunused-function"
12+
#include "../../libraries/Update/src/Updater.cpp"
13+
#include "../../cores/esp32/MD5Builder.cpp"
14+
#pragma GCC diagnostic pop
15+
}
16+
17+
#define ALT_COUNT 1
18+
19+
//--------------------------------------------------------------------+
20+
// DFU callbacks
21+
// Note: alt is used as the partition number, in order to support multiple partitions like FLASH, EEPROM, etc.
22+
//--------------------------------------------------------------------+
23+
24+
uint16_t load_dfu_ota_descriptor(uint8_t * dst, uint8_t * itf)
25+
{
26+
#define DFU_ATTRS (DFU_ATTR_CAN_DOWNLOAD | DFU_ATTR_CAN_UPLOAD | DFU_ATTR_MANIFESTATION_TOLERANT)
27+
28+
uint8_t str_index = tinyusb_add_string_descriptor("Arduino DFU");
29+
uint8_t descriptor[TUD_DFU_DESC_LEN(ALT_COUNT)] = {
30+
// Interface number, string index, attributes, detach timeout, transfer size */
31+
TUD_DFU_DESCRIPTOR(*itf, ALT_COUNT, str_index, DFU_ATTRS, 100, CFG_TUD_DFU_XFER_BUFSIZE),
32+
};
33+
*itf+=1;
34+
memcpy(dst, descriptor, TUD_DFU_DESC_LEN(ALT_COUNT));
35+
return TUD_DFU_DESC_LEN(ALT_COUNT);
36+
}
37+
38+
// Invoked right before tud_dfu_download_cb() (state=DFU_DNBUSY) or tud_dfu_manifest_cb() (state=DFU_MANIFEST)
39+
// Application return timeout in milliseconds (bwPollTimeout) for the next download/manifest operation.
40+
// During this period, USB host won't try to communicate with us.
41+
uint32_t tud_dfu_get_timeout_cb(uint8_t alt, uint8_t state)
42+
{
43+
if ( state == DFU_DNBUSY )
44+
{
45+
// longest delay for Flash writing
46+
return 10;
47+
}
48+
else if (state == DFU_MANIFEST)
49+
{
50+
// time for esp32_ota_set_boot_partition to check final image
51+
return 100;
52+
}
53+
54+
return 0;
55+
}
56+
57+
// Invoked when received DFU_DNLOAD (wLength>0) following by DFU_GETSTATUS (state=DFU_DNBUSY) requests
58+
// This callback could be returned before flashing op is complete (async).
59+
// Once finished flashing, application must call tud_dfu_finish_flashing()
60+
void tud_dfu_download_cb(uint8_t alt, uint16_t block_num, uint8_t const* data, uint16_t length)
61+
{
62+
if (!Update.isRunning())
63+
{
64+
// this is the first data block, start update if possible
65+
if (!Update.begin())
66+
{
67+
tud_dfu_finish_flashing(DFU_STATUS_ERR_TARGET);
68+
return;
69+
}
70+
}
71+
72+
// write a block of data to Flash
73+
// XXX: Update API is needlessly non-const
74+
size_t written = Update.write(const_cast<uint8_t*>(data), length);
75+
tud_dfu_finish_flashing((written == length) ? DFU_STATUS_OK : DFU_STATUS_ERR_WRITE);
76+
}
77+
78+
// Invoked when download process is complete, received DFU_DNLOAD (wLength=0) following by DFU_GETSTATUS (state=Manifest)
79+
// Application can do checksum, or actual flashing if buffered entire image previously.
80+
// Once finished flashing, application must call tud_dfu_finish_flashing()
81+
void tud_dfu_manifest_cb(uint8_t alt)
82+
{
83+
(void) alt;
84+
bool ok = Update.end(true);
85+
86+
// flashing op for manifest is complete
87+
tud_dfu_finish_flashing(ok? DFU_STATUS_OK : DFU_STATUS_ERR_VERIFY);
88+
}
89+
90+
// Invoked when received DFU_UPLOAD request
91+
// Application must populate data with up to length bytes and
92+
// Return the number of written bytes
93+
uint16_t tud_dfu_upload_cb(uint8_t alt, uint16_t block_num, uint8_t* data, uint16_t length)
94+
{
95+
(void) alt;
96+
(void) block_num;
97+
(void) data;
98+
(void) length;
99+
100+
// not implemented
101+
return 0;
102+
}
103+
104+
// Invoked when the Host has terminated a download or upload transfer
105+
void tud_dfu_abort_cb(uint8_t alt)
106+
{
107+
(void) alt;
108+
// ignore
109+
}
110+
111+
// Invoked when a DFU_DETACH request is received
112+
void tud_dfu_detach_cb(void)
113+
{
114+
// done, reboot
115+
esp_restart();
116+
}
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#include <string.h>
2+
3+
#include <esp_system.h>
4+
#include <esp32s3/rom/cache.h>
5+
6+
#include "double_tap.h"
7+
8+
#define NUM_TOKENS 3
9+
static const uint32_t MAGIC_TOKENS[NUM_TOKENS] = {
10+
0xf01681de, 0xbd729b29, 0xd359be7a,
11+
};
12+
13+
static void *magic_area;
14+
static uint32_t backup_area[NUM_TOKENS];
15+
16+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
17+
// Current IDF does not map external RAM to a fixed address.
18+
// The actual VMA depends on other enabled devices, so the precise
19+
// location must be discovered.
20+
#include <esp_psram.h>
21+
#include <esp_private/esp_psram_extram.h>
22+
static uintptr_t get_extram_data_high(void) {
23+
// get a pointer into SRAM area (only the address is useful)
24+
void *psram_ptr = heap_caps_malloc(16, MALLOC_CAP_SPIRAM);
25+
heap_caps_free(psram_ptr);
26+
27+
// keep moving backwards until leaving PSRAM area
28+
uintptr_t psram_base_addr = (uintptr_t) psram_ptr;
29+
psram_base_addr &= ~(CONFIG_MMU_PAGE_SIZE - 1); // align to start of page
30+
while (esp_psram_check_ptr_addr((void *) psram_base_addr)) {
31+
psram_base_addr -= CONFIG_MMU_PAGE_SIZE;
32+
}
33+
34+
// offset is one page from start of PSRAM
35+
return psram_base_addr + CONFIG_MMU_PAGE_SIZE + esp_psram_get_size();
36+
}
37+
#else
38+
#include <soc/soc.h>
39+
#define get_extram_data_high() ((uintptr_t) SOC_EXTRAM_DATA_HIGH)
40+
#endif
41+
42+
43+
void double_tap_init(void) {
44+
// magic location block ends 0x20 bytes from end of PSRAM
45+
magic_area = (void *) (get_extram_data_high() - 0x20 - sizeof(MAGIC_TOKENS));
46+
}
47+
48+
void double_tap_mark() {
49+
memcpy(backup_area, magic_area, sizeof(MAGIC_TOKENS));
50+
memcpy(magic_area, MAGIC_TOKENS, sizeof(MAGIC_TOKENS));
51+
Cache_WriteBack_Addr((uintptr_t) magic_area, sizeof(MAGIC_TOKENS));
52+
}
53+
54+
void double_tap_invalidate() {
55+
if (memcmp(backup_area, MAGIC_TOKENS, sizeof(MAGIC_TOKENS))) {
56+
// different contents: restore backup
57+
memcpy(magic_area, backup_area, sizeof(MAGIC_TOKENS));
58+
} else {
59+
// clear memory
60+
memset(magic_area, 0, sizeof(MAGIC_TOKENS));
61+
}
62+
Cache_WriteBack_Addr((uintptr_t) magic_area, sizeof(MAGIC_TOKENS));
63+
}
64+
65+
bool double_tap_check_match() {
66+
return (memcmp(magic_area, MAGIC_TOKENS, sizeof(MAGIC_TOKENS)) == 0);
67+
}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#ifndef __DOUBLE_TAP_H__
2+
#define __DOUBLE_TAP_H__
3+
4+
#include <stdint.h>
5+
#include <stdbool.h>
6+
7+
#ifdef __cplusplus
8+
extern "C" {
9+
#endif
10+
11+
void double_tap_init(void);
12+
void double_tap_mark(void);
13+
void double_tap_invalidate(void);
14+
bool double_tap_check_match(void);
15+
16+
#ifdef __cplusplus
17+
}
18+
#endif
19+
20+
#endif /* __DOUBLE_TAP_H__ */

variants/arduino_nano_nora/extra/nora_recovery/nora_recovery.ino

+20-13
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ void pulse_led() {
2727
#include <esp_partition.h>
2828
#include <esp_flash_partitions.h>
2929
#include <esp_image_format.h>
30-
bool load_previous_firmware() {
31-
// if user flashed this recovery sketch to an OTA partition, stay here
30+
const esp_partition_t *find_previous_firmware() {
3231
extern bool _recovery_active;
33-
if (!_recovery_active)
34-
return false;
32+
if (!_recovery_active) {
33+
// user flashed this recovery sketch to an OTA partition
34+
// stay here and wait for a proper firmware
35+
return NULL;
36+
}
3537

3638
// booting from factory partition, look for a valid OTA image
3739
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, NULL);
@@ -42,24 +44,29 @@ bool load_previous_firmware() {
4244
esp_image_metadata_t meta;
4345
if (esp_image_verify(ESP_IMAGE_VERIFY_SILENT, &candidate, &meta) == ESP_OK) {
4446
// found, use it
45-
esp_ota_set_boot_partition(part);
46-
return true;
47+
return part;
4748
}
4849
}
4950
}
5051

51-
return false;
52+
return NULL;
5253
}
5354

55+
const esp_partition_t *user_part = NULL;
56+
5457
void setup() {
58+
user_part = find_previous_firmware();
59+
if (user_part)
60+
esp_ota_set_boot_partition(user_part);
61+
5562
extern bool _recovery_marker_found;
56-
if (!_recovery_marker_found) {
57-
// marker not found, probable cold start
63+
if (!_recovery_marker_found && user_part) {
64+
// recovery marker not found, probable cold start
5865
// try starting previous firmware immediately
59-
if (load_previous_firmware())
60-
esp_restart();
66+
esp_restart();
6167
}
6268

69+
// recovery marker found, or nothing else to load
6370
printf("Recovery firmware started, waiting for USB\r\n");
6471
}
6572

@@ -79,8 +86,8 @@ void loop() {
7986
if (elapsed_ms > USB_TIMEOUT_MS) {
8087
elapsed_ms = 0;
8188
// timed out, try loading previous firmware
82-
if (load_previous_firmware()) {
83-
// found a valid FW image, load it
89+
if (user_part) {
90+
// there was a valid FW image, load it
8491
analogWrite(LED_GREEN, 255);
8592
printf("Leaving recovery firmware\r\n");
8693
delay(200);
Binary file not shown.

0 commit comments

Comments
 (0)