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 "protocol/transport.h"
16
17
18#include <Arduino.h> // Serial, etc. (if used)
19
20#include <malloc.h> // malloc(), free()
21#include <stdint.h> // uint32_t, etc.
22#include <string.h> // memset()
23
24#include <cstring>
25#include <imxrt.h> // SCB_AIRCR
26#include <ota/flasher.h>
27#include <utils/etl_base64.h>
28#include <utils/logging.h>
29#include <utils/reboot.h>
30
31#include "QNEthernet.h"
32#include "redac/calibration.h"
33#include "utils/timer.h"
34
35void flash_write(void *addr, const void *data, uint32_t len);
36void flash_erase_sector(void *addr);
37void flash_erase_32K_block(void *addr);
38void flash_erase_64K_block(void *addr);
39
40#define CPU_RESTART_VAL (0x5FA0004)
41#define REBOOT (SCB_AIRCR = CPU_RESTART_VAL)
42
43namespace loader {
44
45// ── nanopb response helpers ───────────────────────────────────────────────
46
47FASTRUN
48static void make_ack(pb_UpdateResponse &res) {
49 res.which_kind = pb_UpdateResponse_ack_tag;
50 res.kind.ack.chunk_size = CHUNK_SIZE;
51}
52
53FASTRUN
54static void make_success(pb_UpdateResponse &res) { res.which_kind = pb_UpdateResponse_success_tag; }
55
56FASTRUN
57static void make_failure(pb_UpdateResponse &res, const char *reason) {
58 res.which_kind = pb_UpdateResponse_failure_tag;
59 strncpy(res.kind.failure.reason, reason, sizeof(res.kind.failure.reason) - 1);
60 res.kind.failure.reason[sizeof(res.kind.failure.reason) - 1] = '\0';
61}
62
63// ── State ─────────────────────────────────────────────────────────────────
64
65FASTRUN
66void Updater::reset_state() {
67 m_active = false;
68 m_expected_size = 0;
69 memset(m_expected_hash.data(), 0, sizeof(m_expected_hash));
70}
71
72// ── Erase helper (runs from flash — staging is not the app slot) ──────────
73
74FASTRUN
75__attribute__((always_inline, unused))
76static inline void flush(void *addr, uint32_t size)
77{
78 uint32_t location = (uint32_t)addr & 0xFFFFFFE0;
79 uint32_t end_addr = (uint32_t)addr + size;
80 asm volatile("": : :"memory");
81 asm("dsb");
82 do {
83 SCB_CACHE_DCCIMVAC = location;
84 location += 32;
85 } while (location < end_addr);
86 asm("dsb");
87 asm("isb");
88}
89
90FASTRUN
91void erase_region(uint8_t *base, uint32_t size) {
92 uint32_t offset = 0;
93 while (offset < size) {
94 const uint32_t remaining = size - offset;
95 auto *cur = base + offset;
96 const uintptr_t addr = reinterpret_cast<uintptr_t>(cur);
97
98 if (remaining >= BLOCK_64K && (addr % BLOCK_64K == 0)) {
100 offset += BLOCK_64K;
101 } else if (remaining >= BLOCK_32K && (addr % BLOCK_32K == 0)) {
103 offset += BLOCK_32K;
104 } else {
106 offset += SECTOR_SIZE;
107 }
108 }
109}
110
111// ── SHA-256 verification ──────────────────────────────────────────────────
112
113FASTRUN
114bool Updater::verify_hash() const {
115 auto *staging = reinterpret_cast<uint32_t *>(STAGING_START_ADDR);
116 utils::sha256_t actual_hash;
117 utils::hash_sha256((const uint8_t*)staging, m_expected_size, actual_hash.data());
118 return actual_hash == m_expected_hash;
119}
120
121// ── RAM-resident commit: erase app slot, copy staging → app, reboot ───────
122//
123// CRITICAL: This function must live in RAM. It erases the flash region it
124// was loaded from (APP_START_ADDR), so any XIP fetch of its code would fault.
125// The linker script must place .ramfunc in DTCM (0x2000_0000).
126//
127
128FASTRUN
129void flash_write_32(uint32_t *dst, const uint32_t *src, uint32_t size){
130 for (auto idx = 0; idx < size / 4; ++idx) {
131 alignas(4) uint32_t value = src[idx];
132 flash_write(dst + idx, &value, 4);
133 }
134}
135
136FASTRUN
137void flash_copy(uint32_t *dst, const uint32_t *src, uint32_t size){
138 erase_region((uint8_t*)dst, size);
139 flash_write_32(dst, src, size);
140}
141
142
143// ══════════════════════════════════════════════════════════════════════════
144// Public API
145// ══════════════════════════════════════════════════════════════════════════
146
147// begin — validate size, erase staging, store metadata, ack chunk size
148FASTRUN
149void Updater::begin(const pb_UpdateBegin &req, pb_UpdateResponse &res) {
150 if (m_active) {
151 // Implicitly abort previous session
152 reset_state();
153 }
154
155 // ── Validate size ────────────────────────────────────────────────────
156 if (req.size == 0 || req.size > MAX_FW_SIZE)
157 return make_failure(res, "firmware size out of range");
158
159 if (req.hash.size != 32)
160 return make_failure(res, "invalid hash length");
161
162 m_expected_size = static_cast<uint32_t>(req.size);
163
164 // ── Erase staging area ───────────────────────────────────────────────
165 // This is safe: staging is a different flash region from the running app.
166 auto *staging = reinterpret_cast<uint8_t *>(STAGING_START_ADDR);
167 // Round up to next full sector boundary for clean erase
168 __disable_irq();
169 erase_region(staging, m_expected_size);
170 __enable_irq();
171
172 // ── Store hash ──────────────────────────────────────────────
173 memcpy(m_expected_hash.data(), req.hash.bytes, 32);
174 m_active = true;
175
176 LOGV("BEGIN UPLOAD of %d bytes", m_expected_size);
177 make_ack(res);
178}
179
180// write — validate and write one chunk to staging area
181FASTRUN
182void Updater::write(const pb_UpdateWrite &req, pb_UpdateResponse &res) {
183 if (!m_active)
184 return make_failure(res, "upload not active");
185
186 auto offset = static_cast<size_t>(req.offset);
187 size_t data_len = req.data.size;
188 auto src = req.data.bytes;
189
190 // ── Validate chunk ───────────────────────────────────────────────────
191 if (data_len == 0)
192 return make_failure(res, "empty chunk size");
193
194 if (offset + data_len > m_expected_size)
195 return make_failure(res, "chunk exceeds declared firmware size");
196
197 std::memcpy(m_buffer, src, data_len);
198
199 // ── Write to staging ─────────────────────────────────────────────────
200 auto *src_buffer = reinterpret_cast<uint32_t *>(m_buffer);
201 auto *dst = reinterpret_cast<uint32_t *>(STAGING_START_ADDR + offset);
202
203 __disable_irq();
204 flash_write_32(dst, src_buffer, data_len);
205 __enable_irq();
206
207 make_success(res);
208}
209
210// end — verify hash, then commit staging → app and reboot
211FASTRUN
212void Updater::commit(const pb_UpdateCommit & /*req*/, pb_UpdateResponse &res, const std::function<void()> &&success) {
213 if (!m_active)
214 return make_failure(res, "upload not active");
215
216 // ── Verify SHA-256 over staged firmware ──────────────────────────────
217 if (!verify_hash())
218 return make_failure(res, "hash mismatch");
219
220 success();
221 __disable_irq();
222 auto *app = reinterpret_cast<uint32_t *>(APP_START_ADDR);
223 auto *staging = reinterpret_cast<uint32_t *>(STAGING_START_ADDR);
224 flash_copy(app, staging, m_expected_size);
225 REBOOT;
226 for (;;) {
227 }
228}
229
230// abort — discard staged data, reset state
231FASTRUN
232void Updater::abort(const pb_UpdateAbort & /*req*/, pb_UpdateResponse &res) {
233 if (!m_active)
234 return make_failure(res, "upload not active");
235
236 reset_state();
237 make_success(res);
238}
239
240// abort — discard staged data, reset state
241FASTRUN
242void Updater::verify(const pb_UpdateVerify & /*req*/, pb_UpdateResponse &res) {
243 if (!m_active)
244 return make_failure(res, "upload not active");
245
246 if (verify_hash()) {
247 make_success(res);
248 }else {
249 make_failure(res, "hash mismatch");
250 }
251}
252
253} // namespace loader
void flash_write(void *addr, const void *data, uint32_t len)
Definition eeprom.cpp:99
void flash_erase_64K_block(void *addr)
Definition eeprom.cpp:187
void flash_erase_32K_block(void *addr)
Definition eeprom.cpp:163
void flash_erase_sector(void *addr)
Definition eeprom.cpp:139
#define REBOOT
Definition flasher.cpp:41
void flash_erase_64K_block(void *addr)
Definition eeprom.cpp:187
void flash_write(void *addr, const void *data, uint32_t len)
Definition eeprom.cpp:99
void flash_erase_sector(void *addr)
Definition eeprom.cpp:139
void flash_erase_32K_block(void *addr)
Definition eeprom.cpp:163
static FASTRUN void make_success(pb_UpdateResponse &res)
Definition flasher.cpp:54
static FASTRUN void make_ack(pb_UpdateResponse &res)
Definition flasher.cpp:48
FASTRUN void erase_region(uint8_t *base, uint32_t size)
Definition flasher.cpp:91
FASTRUN __attribute__((always_inline, unused)) static inline void flush(void *addr
static FASTRUN void make_failure(pb_UpdateResponse &res, const char *reason)
Definition flasher.cpp:57
FASTRUN void flash_copy(uint32_t *dst, const uint32_t *src, uint32_t size)
Definition flasher.cpp:137
uint32_t end_addr
Definition flasher.cpp:79
FASTRUN void flash_write_32(uint32_t *dst, const uint32_t *src, uint32_t size)
Definition flasher.cpp:129
FASTRUN uint32_t size
Definition flasher.cpp:77
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:630