REDAC HybridController
Firmware for LUCIDAC/REDAC Teensy
Loading...
Searching...
No Matches
flasher.cpp
Go to the documentation of this file.
1//******************************************************************************
2// Flash write/erase functions (TLC/T3x/T4x/TMM), LMEM cache functions for T3.6
3//******************************************************************************
4// WARNING: you can destroy your MCU with flash erase or write!
5// This code may or may not protect you from that.
6//
7// Original by Niels A. Moseley, 2015.
8// Modifications for OTA updates by Jon Zeeff, Deb Hollenback
9// Paul Stoffregen's T4.x flash routines from Teensy4 core added by Jon Zeeff
10// Frank Boesing's T3.x flash routines adapted for OTA by Joe Pasquariello
11// Largely adapted and rewritten for the Anabrid REDAC infrastructure by SvenK
12// This code is released into the public domain.
13//******************************************************************************
14
15#include <Arduino.h> // Serial, etc. (if used)
16
17#ifdef ARDUINO
18#include <malloc.h> // malloc(), free()
19#include <stdint.h> // uint32_t, etc.
20#include <string.h> // memset()
21
22#include "ota/flasher.h"
23#include "utils/etl_base64.h"
24#include "utils/logging.h"
25
26// [<------- code ------->][<--------- buffer --------->][<-- FLASH_RESERVE -->]
27// [<------------------------------ FLASH_SIZE ------------------------------->]
28// ^FLASH_BASE_ADDR
29
30// teensy is __IMXRT1062__
31#define FLASH_ID "fw_teensy41" // target ID (in code)
32#define FLASH_ID_LEN (11) // target ID (in code)
33#define FLASH_SIZE (0x800000) // 8MB
34#define FLASH_SECTOR_SIZE (0x1000) // 4KB sector size
35#define FLASH_WRITE_SIZE (4) // 4-byte/32-bit writes
36// teensy has 256KB reserve top of flash for EEPROM+LED blink restore program
37// however, we make another big 256KB reserve because of LittleFS storage below.
38#define FLASH_RESERVE (80 * FLASH_SECTOR_SIZE)
39#define FLASH_BASE_ADDR (0x60000000) // code starts here
40
41#define IN_FLASH(a) ((a) >= FLASH_BASE_ADDR && (a) < FLASH_BASE_ADDR + FLASH_SIZE)
42
43// reboot is the same for all ARM devices
44#define CPU_RESTART_ADDR ((uint32_t *)0xE000ED0C)
45#define CPU_RESTART_VAL (0x5FA0004)
46#define REBOOT (*CPU_RESTART_ADDR = CPU_RESTART_VAL)
47
49
50#define RAMFUNC __attribute__((section(".fastrun"), noinline, noclone, optimize("Os")))
51
52RAMFUNC int flash_sector_not_erased(uint32_t address);
53
54// from cores\Teensy4\eeprom.c -- use these functions at your own risk!!!
55extern "C" {
56void eepromemu_flash_write(void *addr, const void *data, uint32_t len);
60}
61
62// functions used to move code from buffer to program flash (must be in RAM)
63RAMFUNC void flash_move(uint32_t dst, uint32_t src, uint32_t size);
64
65// functions that can be in flash
66void firmware_buffer_free(uint32_t buffer_addr, uint32_t buffer_size);
67int flash_write_block(uint32_t addr, char *data, uint32_t count);
69
71
72// compute addr/size for firmware buffer and return NO/RAM/FLASH_BUFFER_TYPE
73// buffer will begin at first sector ABOVE code and below FLASH_RESERVE
74// start at bottom of FLASH_RESERVE and work down until non-erased flash found
75void firmware_buffer_init(uint32_t *buffer_addr, uint32_t *buffer_size) {
76 *buffer_addr = FLASH_BASE_ADDR + FLASH_SIZE - FLASH_RESERVE - 4;
77 LOGMEV("Search starting at 0x%0X", *buffer_addr);
78 while (*buffer_addr > 0 && *((uint32_t *)*buffer_addr) == 0xFFFFFFFF)
79 *buffer_addr -= 4;
80 *buffer_addr += 4; // first address above code
81 LOGMEV("Found first non-erased flash byte at 0x%0X", *buffer_addr);
82
83 // increase buffer_addr to next sector boundary (if not on a sector boundary)
84 if ((*buffer_addr % FLASH_SECTOR_SIZE) > 0)
85 *buffer_addr += FLASH_SECTOR_SIZE - (*buffer_addr % FLASH_SECTOR_SIZE);
86 *buffer_size = FLASH_BASE_ADDR - *buffer_addr + FLASH_SIZE - FLASH_RESERVE;
87}
88
89/*
90 * TODO: It is currently very important that any started upload is either completed or aborted.
91 * Abandoned uploads lead to leftovers in the FLASH which hinder the current buffer detection scheme
92 * to identify them as available.
93 *
94 * It is quite easy to do this: Just use utils::flashimagelen() and erase everything
95 * until the FLASH_RESERVE. That should probably be a measure to take if the buffer init
96 * fails.
97 *
98 */
99
101 // TODO: This function should look for leftovers of abandoned file transfers, see above.
103}
104
106
107void firmware_buffer_free(uint32_t buffer_addr, uint32_t buffer_size) {
108 flash_erase_block(buffer_addr, buffer_size);
109}
110
111//******************************************************************************
112// flash_sector_not_erased() returns 0 if erased and !0 (error) if NOT erased
113//******************************************************************************
114RAMFUNC int flash_sector_not_erased(uint32_t address) {
115 uint32_t *sector = (uint32_t *)(address & ~(FLASH_SECTOR_SIZE - 1));
116 for (int i = 0; i < FLASH_SECTOR_SIZE / 4; i++) {
117 if (*sector++ != 0xFFFFFFFF)
118 return 1; // NOT erased
119 }
120 return 0; // erased
121}
122
123//******************************************************************************
124// move from source to destination (flash), erasing destination sectors as we go
125// DANGER: this is critical and cannot be interrupted, else T3.x can be damaged
126//******************************************************************************
127RAMFUNC void flash_move(uint32_t dst, uint32_t src, uint32_t size) {
128 uint32_t offset = 0, error = 0, addr;
129
130 // set global flag leave_interrupts_disabled = 1 to prevent the T3.x flash
131 // write and erase functions from re-enabling interrupts when they complete
133
134 // move size bytes containing new program from source to destination
135 while (offset < size && error == 0) {
136
137 addr = dst + offset;
138
139 // if new sector, erase, then immediately write FSEC/FOPT if in this sector
140 // this is the ONLY place that FSEC values are written, so it's the only
141 // place where calls to KINETIS flash write functions have aFSEC = oFSEC = 1
142 if ((addr & (FLASH_SECTOR_SIZE - 1)) == 0) {
143 if (flash_sector_not_erased(addr)) {
144#if defined(__IMXRT1062__)
145 eepromemu_flash_erase_sector((void *)addr);
146#elif (FLASH_WRITE_SIZE == 4)
147 error |= flash_erase_sector(addr, 1);
148 if (addr == (0x40C & ~(FLASH_SECTOR_SIZE - 1)))
149 error |= flash_word(0x40C, 0xfffff9de, 1, 1);
150#elif (FLASH_WRITE_SIZE == 8)
151 error |= flash_erase_sector(addr, 1);
152 if (addr == (0x408 & ~(FLASH_SECTOR_SIZE - 1)))
153 error |= flash_phrase(0x408, 0xfffff9deffffffff, 1, 1);
154#endif
155 }
156 }
157
158// for KINETIS, these writes may be to the sector containing FSEC, but the
159// FSEC location was written by the code above, so use aFSEC=1, oFSEC=0
160#if defined(__IMXRT1062__)
161 // for T4.x, data address passed to flash_write() must be in RAM
162 uint32_t value = *(uint32_t *)(src + offset);
163 eepromemu_flash_write((void *)addr, &value, 4);
164#elif (FLASH_WRITE_SIZE == 4)
165 error |= flash_word(addr, *(uint32_t *)(src + offset), 1, 0);
166#elif (FLASH_WRITE_SIZE == 8)
167 error |= flash_phrase(addr, *(uint64_t *)(src + offset), 1, 0);
168#endif
169
170 offset += FLASH_WRITE_SIZE;
171 }
172
173 // move is complete. if the source buffer (src) is in FLASH, erase the buffer
174 // by erasing all sectors from top of new program to bottom of FLASH_RESERVE,
175 // which leaves FLASH in same state as if code was loaded using TeensyDuino.
176 // For KINETIS, this erase cannot include FSEC, so erase uses aFSEC=0.
177 if (IN_FLASH(src)) {
178 while (offset < (FLASH_SIZE - FLASH_RESERVE) && error == 0) {
179 addr = dst + offset;
180 if ((addr & (FLASH_SECTOR_SIZE - 1)) == 0) {
181 if (flash_sector_not_erased(addr)) {
182#if defined(__IMXRT1062__)
183 eepromemu_flash_erase_sector((void *)addr);
184#else
185 error |= flash_erase_sector(addr, 0);
186#endif
187 }
188 }
189 offset += FLASH_WRITE_SIZE;
190 }
191 }
192
193 // for T3.x, at least, must REBOOT here (via macro) because original code has
194 // been erased and overwritten, so return address is no longer valid
195 REBOOT;
196 // wait here until REBOOT actually happens
197 for (;;) {
198 }
199}
200
201//******************************************************************************
202// flash_erase_block() erase sectors from (start) to (start + size)
203//******************************************************************************
205 int error = 0;
206 uint32_t address = start;
207 while (address < (start + size) && error == 0) {
208 if ((address & (FLASH_SECTOR_SIZE - 1)) == 0) {
209 if (flash_sector_not_erased(address)) {
210#if defined(__IMXRT1062__)
211 eepromemu_flash_erase_sector((void *)address);
212#elif defined(KINETISK) || defined(KINETISL)
213 error = flash_erase_sector(address, 0);
214#endif
215 }
216 }
217 address += FLASH_SECTOR_SIZE;
218 }
219 return (error);
220}
221
222//******************************************************************************
223// take a 32-bit aligned array of 32-bit values and write it to erased flash
224//******************************************************************************
225FLASHMEM int flash_write_block(uint32_t addr, uint8_t *data, uint32_t count) {
226 if (!IN_FLASH(addr))
227 return 4; // sanity check
228// static (aligned) variables to guarantee 32-bit or 64-bit-aligned writes
229#if (FLASH_WRITE_SIZE == 4) // #if 4-byte writes
230 static uint32_t buf __attribute__((aligned(4))); // 4-byte buffer
231#elif (FLASH_WRITE_SIZE == 8) // #elif 8-byte writes
232 static uint64_t buf __attribute__((aligned(8))); // 8-byte buffer
233#endif //
234 static uint32_t buf_count = 0; // bytes in buffer
235 static uint32_t next_addr = 0; // expected address
236
237 int ret = 0; // return value
238 uint32_t data_i = 0; // index to data array
239
240 if ((addr % 4) != 0 || (count % 4) != 0) { // if not 32-bit aligned
241 return 1; // "flash_block align error\n" // return error code 1
242 }
243
244 if (buf_count > 0 && addr != next_addr) { // if unexpected address
245 return 2; // "unexpected address\n" // return error code 2
246 }
247 next_addr = addr + count; // compute next address
248 addr -= buf_count; // address of data[0]
249
250 while (data_i < count) { // while more data
251 ((uint8_t *)&buf)[buf_count++] = data[data_i++]; // copy a byte to buf
252 if (buf_count < FLASH_WRITE_SIZE) { // if buf not complete
253 continue; // continue while()
254 } //
255#if defined(__IMXRT1062__) // #if T4.x 4-byte
256 eepromemu_flash_write((void *)addr, (void *)&buf, 4); // flash_write()
257#elif (FLASH_WRITE_SIZE == 4) // #elif T3.x 4-byte
258 ret = flash_word(addr, buf, 0, 0); // flash_word()
259#elif (FLASH_WRITE_SIZE == 8) // #elif T3.x 8-byte
260 ret = flash_phrase(addr, buf, 0, 0); // flash_phrase()
261#endif
262 if (ret != 0) { // if write error
263 return 3; // "flash write error %d\n" // error code
264 }
265 buf_count = 0; // re-init buf count
266 addr += FLASH_WRITE_SIZE; // advance address
267 }
268 return 0; // return success
269}
270
271//******************************************************************************
272// Actual user frontend code
273//******************************************************************************
274
275constexpr static int success = 0;
276#define return_err(code, msg) \
277 { \
278 msg_out["error"] = msg; \
279 return code; \
280 }
281#define return_errf(code, msg, ...) \
282 { \
283 char msgbuf[1000]; \
284 snprintf(msgbuf, sizeof(msgbuf), msg, __VA_ARGS__); \
285 msg_out["error"] = msgbuf; \
286 return code; \
287 }
288
289// no of base64 symbols to transfer in a single chunk. Note that this unit is limited by the
290// maximum JSON Documentsize anyway, which is currently 4096 bytes.
291// Note: number_of_bytes = max_chunk_size * 3/4 and we require number_of_bytes%4 == 0.
292// TODO: Communicate this somewhere centrally
293constexpr size_t static json_overhead = 0xff; // for {'bla'}
294constexpr size_t static base64_chunk_size = 4096 / 2; //- json_overhead;
295constexpr size_t static bin_chunk_size = base64_chunk_size * 3 / 4;
296
297// OTA Flashing allows anybody on the network to run code on the microcontroller. This must
298// not be enabled in unsafe networks without any user access checking. The following define flag
299// based code is a minimal guard against attacks, uses have to *opt-in* whether they want to
300// enable this feature or not.
301#ifdef ANABRID_ENABLE_OTA_FLASHING
302#define RETURN_IF_FLASHING_NOT_ENABLED
303#else
304#define RETURN_IF_FLASHING_NOT_ENABLED return_err(0, "OTA Flashing is disabled in this firmware build")
305#endif
306
307FLASHMEM int loader::FirmwareFlasher::init(JsonObjectConst msg_in, JsonObject &msg_out) {
309 if (upgrade)
310 return_err(1, "Firmware upgrade already running. Cancel it before doing another one");
311
312 upgrade = new loader::FirmwareBuffer();
313 upgrade->name = msg_in["name"].as<std::string>();
314 upgrade->imagelen = msg_in["imagelen"];
315 upgrade->upstream_hash = utils::parse_sha256(msg_in["upstream_hash"]);
316 upgrade->bytes_transmitted = 0;
317
318 if (upgrade->imagelen > upgrade->buffer_size) {
319 delete_upgrade();
321 2,
322 "New firmware image too large for OTA upgrade process. Image size: %zu bytes > Buffer size: %ld bytes",
323 upgrade->imagelen, upgrade->buffer_size);
324 }
325
326 // Tell the client a suitable chunk size (in base64-encoded).
327 // The JsonDocuments in main.cpp have length 4096.
328 msg_out["bin_chunk_size"] = bin_chunk_size;
329 msg_out["encoding"] = "binary-base64";
330 return success;
331}
332
333FLASHMEM int loader::FirmwareFlasher::stream(JsonObjectConst msg_in, JsonObject &msg_out) {
335 if (!upgrade)
336 return_err(1, "No firmware upgrade running.");
337 if (upgrade->transfer_completed())
338 return_err(2, "Transfer already completed");
339
340 uint8_t decoded_buffer[bin_chunk_size] __attribute__((aligned(4)));
341 auto base64_payload = msg_in["payload"].as<std::string>();
342 auto payload_base64_size = base64_payload.size(); // / 4 * 3;
343 if (!payload_base64_size)
344 return_err(3, "Missing payload.");
345 if ((payload_base64_size % 4) != 0)
346 return_err(4, "Payload has not correct length to be base64-encoded.");
347 // return_err("Chunk not 32-bit aligned. Expecting buffer size multiples of 4 bytes.");
348 auto payload_bin_size =
349 etl::base64::decode(base64_payload.c_str(), base64_payload.length(), decoded_buffer, bin_chunk_size);
350 if (!payload_bin_size)
351 return_err(2, "Could not decode base64-encoded payload.");
352
353 LOGV("payload_base64_size=%d, payload_bin_size=%d", payload_base64_size, payload_bin_size);
354
355 if (payload_bin_size > bin_chunk_size)
356 return_errf(6, "Chunk size too large. Given %zu bytes > buffer size %zu bytes", payload_bin_size,
358 if (upgrade->bytes_transmitted + payload_bin_size > upgrade->imagelen)
359 return_errf(7, "Chunk size too large, yields to image size %zu bytes but only %zu announced.",
360 upgrade->bytes_transmitted + payload_bin_size, upgrade->imagelen);
361 if ((payload_bin_size % 4) != 0)
362 return_err(8, "Chunk not 32-bit aligned. Expecting buffer size multiples of 4 bytes.");
363
364 LOGV("Loaded %zu bytes to RAM, ready for flash writing", payload_bin_size);
365
366 // The write bases on the assumption that the buffer is completely erased.
367 int write_error =
368 flash_write_block(upgrade->buffer_addr + upgrade->bytes_transmitted, decoded_buffer, payload_bin_size);
369 if (write_error)
370 return_errf(50 + write_error, "flash_write_block() error %02X", write_error);
371
372 upgrade->bytes_transmitted += payload_bin_size;
373 return success;
374}
375
376FLASHMEM void loader::FirmwareFlasher::status(JsonObject &msg_out) {
377#ifndef ANABRID_ENABLE_OTA_FLASHING
378 msg_out["disabled"] =
379 "OTA flashing is disabled due to absence of firmware build flag ANABRID_ENABLE_OTA_FLASHING";
380 return;
381#endif
382 msg_out["is_upgrade_running"] = bool(upgrade);
383 if (upgrade) {
384 msg_out["name"] = upgrade->name;
385 msg_out["size"] = upgrade->imagelen;
386 msg_out["upstream_hash"] = utils::sha256_to_string(upgrade->upstream_hash);
387 msg_out["bytes_transmitted"] = upgrade->bytes_transmitted;
388 msg_out["transfer_completed"] = upgrade->transfer_completed();
389
390 if (upgrade->transfer_completed()) {
391 auto actual_hash = utils::hash_sha256((uint8_t *)upgrade->buffer_addr, upgrade->imagelen);
392 msg_out["hash_correct"] = actual_hash == upgrade->upstream_hash;
393 // the following is mostly for debugging
394 msg_out["actual_hash"] = utils::sha256_to_string(actual_hash);
395 }
396
397 // just to always have this information, cf below
398 msg_out["buffer_addr"] = upgrade->buffer_addr;
399 msg_out["buffer_size"] = upgrade->buffer_size;
400 } else {
401 // inform about upgrade capabilities
402 uint32_t potential_buffer_addr, potential_buffer_size;
403 firmware_buffer_init(&potential_buffer_addr,
404 &potential_buffer_size); // is non-destructive = w/o side effects
405 msg_out["buffer_addr"] = potential_buffer_addr;
406 msg_out["buffer_size"] = potential_buffer_size;
407 }
408}
409
410FLASHMEM int loader::FirmwareFlasher::abort(JsonObjectConst msg_in, JsonObject &msg_out) {
412 if (!upgrade)
413 return_err(1, "No upgrade running which I could abort");
414 delete_upgrade();
415 return true;
416}
417
418FLASHMEM int loader::FirmwareFlasher::complete(JsonObjectConst msg_in, JsonObject &msg_out) {
420 if (!upgrade->transfer_completed())
421 return_err(1, "Require transfer to be completed");
422
423 LOG_ALWAYS("Checking new firmware image hash...");
424 auto actual_hash = utils::hash_sha256((uint8_t *)upgrade->buffer_addr, upgrade->imagelen);
425 if (actual_hash != upgrade->upstream_hash)
426 return_err(2, "Corrupted upload data, hash mismatch. Please try again (start new init)");
427
428 LOG_ALWAYS("Moving new firmware image into place and rebooting...");
429
430 // move new program from buffer to flash, free buffer, and reboot
431 flash_move(FLASH_BASE_ADDR, upgrade->buffer_addr, upgrade->imagelen);
432
433 // will not return from flash_move(). This is actually never reached.
434 // return statement only to surpress compiler warnings.
435 return success;
436}
437
438#endif /* ARDUINO */
static ETL_CONSTEXPR14 etl::enable_if< etl::is_integral< T >::value &&(etl::integral_limits< T >::bits==8U), size_t >::type decode(const char *input, size_t input_length, T *output, size_t output_length)
Decode from Base64 from and to pointer/length.
Definition etl_base64.h:293
#define FLASH_BASE_ADDR
Definition flasher.cpp:39
void eepromemu_flash_erase_64K_block(void *addr)
uint32_t
Definition flasher.cpp:195
uint32_t src
Definition flasher.cpp:63
#define return_err(code, msg)
Definition flasher.cpp:276
void eepromemu_flash_erase_sector(void *addr)
constexpr static size_t bin_chunk_size
Definition flasher.cpp:295
void firmware_buffer_init(uint32_t *buffer_addr, uint32_t *buffer_size)
Definition flasher.cpp:75
#define return_errf(code, msg,...)
Definition flasher.cpp:281
constexpr static size_t json_overhead
Definition flasher.cpp:293
uint32_t uint32_t size
Definition flasher.cpp:63
void firmware_buffer_free(uint32_t buffer_addr, uint32_t buffer_size)
Definition flasher.cpp:107
#define RETURN_IF_FLASHING_NOT_ENABLED
Definition flasher.cpp:304
#define IN_FLASH(a)
Definition flasher.cpp:41
#define FLASH_RESERVE
Definition flasher.cpp:38
int flash_erase_block(uint32_t address, uint32_t size)
Definition flasher.cpp:204
#define REBOOT
Definition flasher.cpp:46
#define FLASH_WRITE_SIZE
Definition flasher.cpp:35
void eepromemu_flash_erase_32K_block(void *addr)
static constexpr int success
Definition flasher.cpp:275
static int leave_interrupts_disabled
Definition flasher.cpp:70
int flash_write_block(uint32_t addr, char *data, uint32_t count)
__attribute__((section(".fastrun"), noinline, noclone, optimize("Os"))) int flash_sector_not_erased(uint32_t address)
Definition flasher.cpp:114
constexpr static size_t base64_chunk_size
Definition flasher.cpp:294
#define FLASH_SIZE
Definition flasher.cpp:33
#define FLASH_SECTOR_SIZE
Definition flasher.cpp:34
void eepromemu_flash_write(void *addr, const void *data, uint32_t len)
#define RAMFUNC
Definition flasher.cpp:50
int init(JsonObjectConst msg_in, JsonObject &msg_out)
Definition flasher.cpp:307
int complete(JsonObjectConst msg_in, JsonObject &msg_out)
Definition flasher.cpp:418
void status(JsonObject &msg_out)
Definition flasher.cpp:376
int stream(JsonObjectConst msg_in, JsonObject &msg_out)
Definition flasher.cpp:333
int abort(JsonObjectConst msg_in, JsonObject &msg_out)
Definition flasher.cpp:410
#define LOGV(message,...)
Definition logging.h:91
#define LOGMEV(message,...)
Definition logging.h:93
#define LOG_ALWAYS(message)
Definition logging.h:38
void reboot()
Just a little generic Teensy reboot routine.
Definition flasher.cpp:48
sha256_t parse_sha256(const std::string &hash)
Definition dcp.h:54
FLASHMEM void hash_sha256(const uint8_t *msg, size_t msg_len, uint8_t *out_hash)
Computes the SHA256 sum of an arbitrary message (large memory segment).
Definition dcp.cpp:631
std::string sha256_to_string(const utils::sha256_t &hash)
Definition dcp.h:33
The Flasher-X over-the-air firmware upgrade process is a small code snippet originally dating from ht...
Definition flasher.h:30
uint32_t buffer_addr
buffer on flash for new image
Definition flasher.h:38
uint32_t buffer_size
identical to imagelen but increaed to next sector boundary
Definition flasher.h:39