Set blackbox file handler to NULL after closing file
[inav.git] / src / main / config / config_eeprom.c
blob6d11c1804d35ca1704bc1e260490c20e2be72d4e
1 /*
2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
18 #include <stdbool.h>
19 #include <stdint.h>
20 #include <string.h>
21 #include <stddef.h>
23 #include "platform.h"
25 #include "build/build_config.h"
27 #include "common/crc.h"
28 #include "common/utils.h"
30 #include "config/config_eeprom.h"
31 #include "config/config_streamer.h"
32 #include "config/parameter_group.h"
34 #include "drivers/system.h"
35 #include "drivers/flash.h"
37 #include "fc/config.h"
39 #if defined(CONFIG_IN_FILE)
40 void config_streamer_impl_unlock(void);
41 #endif
43 static uint16_t eepromConfigSize;
45 typedef enum {
46 CR_CLASSICATION_SYSTEM = 0,
47 CR_CLASSICATION_PROFILE1 = 1,
48 CR_CLASSICATION_PROFILE2 = 2,
49 CR_CLASSICATION_PROFILE3 = 3,
50 CR_CLASSICATION_PROFILE_LAST = CR_CLASSICATION_PROFILE3,
51 } configRecordFlags_e;
53 #define CR_CLASSIFICATION_MASK (0x3)
55 // Header for the saved copy.
56 typedef struct {
57 uint8_t format;
58 } PG_PACKED configHeader_t;
60 // Header for each stored PG.
61 typedef struct {
62 // split up.
63 uint16_t size;
64 pgn_t pgn;
65 uint8_t version;
67 // lower 2 bits used to indicate system or profile number, see CR_CLASSIFICATION_MASK
68 uint8_t flags;
70 uint8_t pg[];
71 } PG_PACKED configRecord_t;
73 // Footer for the saved copy.
74 typedef struct {
75 uint16_t terminator;
76 } PG_PACKED configFooter_t;
77 // checksum is appended just after footer. It is not included in footer to make checksum calculation consistent
79 // Used to check the compiler packing at build time.
80 typedef struct {
81 uint8_t byte;
82 uint32_t word;
83 } PG_PACKED packingTest_t;
85 #if defined(CONFIG_IN_EXTERNAL_FLASH)
86 bool loadEEPROMFromExternalFlash(void)
88 const flashPartition_t *flashPartition = flashPartitionFindByType(FLASH_PARTITION_TYPE_CONFIG);
89 const flashGeometry_t *flashGeometry = flashGetGeometry();
91 uint32_t flashStartAddress = flashPartition->startSector * flashGeometry->sectorSize;
93 uint32_t totalBytesRead = 0;
94 int bytesRead = 0;
96 bool success = false;
98 do {
99 bytesRead = flashReadBytes(flashStartAddress + totalBytesRead, &eepromData[totalBytesRead], EEPROM_SIZE - totalBytesRead);
100 if (bytesRead > 0) {
101 totalBytesRead += bytesRead;
102 success = (totalBytesRead == EEPROM_SIZE);
104 } while (!success && bytesRead > 0);
106 return success;
108 #endif /* defined(CONFIG_IN_EXTERNAL_FLASH) */
110 void initEEPROM(void)
112 // Verify that this architecture packs as expected.
113 BUILD_BUG_ON(offsetof(packingTest_t, byte) != 0);
114 BUILD_BUG_ON(offsetof(packingTest_t, word) != 1);
115 BUILD_BUG_ON(sizeof(packingTest_t) != 5);
117 BUILD_BUG_ON(sizeof(configHeader_t) != 1);
118 BUILD_BUG_ON(sizeof(configFooter_t) != 2);
119 BUILD_BUG_ON(sizeof(configRecord_t) != 6);
121 #if defined(CONFIG_IN_EXTERNAL_FLASH)
122 bool eepromLoaded = loadEEPROMFromExternalFlash();
123 if (!eepromLoaded) {
124 // Flash read failed - just die now
125 failureMode(FAILURE_FLASH_READ_FAILED);
127 #elif defined(CONFIG_IN_FILE)
128 config_streamer_impl_unlock();
129 #endif
132 // Scan the EEPROM config. Returns true if the config is valid.
133 bool isEEPROMContentValid(void)
135 const uint8_t *p = &__config_start;
136 const configHeader_t *header = (const configHeader_t *)p;
138 if (header->format != EEPROM_CONF_VERSION) {
139 return false;
141 uint16_t crc = crc16_ccitt_update(0, header, sizeof(*header));
142 p += sizeof(*header);
144 for (;;) {
145 const configRecord_t *record = (const configRecord_t *)p;
147 if (record->size == 0) {
148 // Found the end. Stop scanning.
149 break;
152 if (p + sizeof(*record) >= &__config_end) {
153 // Too big. Further checking for size doesn't make sense
154 return false;
157 if (p + record->size >= &__config_end || record->size < sizeof(*record)) {
158 // Too big or too small.
159 return false;
162 crc = crc16_ccitt_update(crc, p, record->size);
164 p += record->size;
167 const configFooter_t *footer = (const configFooter_t *)p;
168 crc = crc16_ccitt_update(crc, footer, sizeof(*footer));
169 p += sizeof(*footer);
170 const uint16_t checkSum = *(uint16_t *)p;
171 p += sizeof(checkSum);
172 eepromConfigSize = p - &__config_start;
173 return crc == checkSum;
176 uint16_t getEEPROMConfigSize(void)
178 return eepromConfigSize;
181 // find config record for reg + classification (profile info) in EEPROM
182 // return NULL when record is not found
183 // this function assumes that EEPROM content is valid
184 static const configRecord_t *findEEPROM(const pgRegistry_t *reg, configRecordFlags_e classification)
186 const uint8_t *p = &__config_start;
187 p += sizeof(configHeader_t); // skip header
188 while (true) {
189 const configRecord_t *record = (const configRecord_t *)p;
190 // Ensure that the record header fits into config memory, otherwise accessing size and flags may cause a hardfault.
191 if (p + sizeof(*record) >= &__config_end) {
192 break;
195 // Check that record header makes sense
196 if (record->size == 0 || p + record->size >= &__config_end || record->size < sizeof(*record)) {
197 break;
200 // Check if this is the record we're looking for (check for size)
201 if (pgN(reg) == record->pgn && (record->flags & CR_CLASSIFICATION_MASK) == classification) {
202 return record;
205 p += record->size;
207 // record not found
208 return NULL;
211 // Initialize all PG records from EEPROM.
212 // This functions processes all PGs sequentially, scanning EEPROM for each one. This is suboptimal,
213 // but each PG is loaded/initialized exactly once and in defined order.
214 bool loadEEPROM(void)
216 PG_FOREACH(reg) {
217 configRecordFlags_e cls_start, cls_end;
218 if (pgIsSystem(reg)) {
219 cls_start = CR_CLASSICATION_SYSTEM;
220 cls_end = CR_CLASSICATION_SYSTEM;
221 } else {
222 cls_start = CR_CLASSICATION_PROFILE1;
223 cls_end = CR_CLASSICATION_PROFILE_LAST;
225 for (configRecordFlags_e cls = cls_start; cls <= cls_end; cls++) {
226 int profileIndex = cls - cls_start;
227 const configRecord_t *rec = findEEPROM(reg, cls);
228 if (rec) {
229 // config from EEPROM is available, use it to initialize PG. pgLoad will handle version mismatch
230 pgLoad(reg, profileIndex, rec->pg, rec->size - offsetof(configRecord_t, pg), rec->version);
231 } else {
232 pgReset(reg, profileIndex);
236 return true;
239 static bool writeSettingsToEEPROM(void)
241 config_streamer_t streamer;
242 config_streamer_init(&streamer);
244 config_streamer_start(&streamer, (uintptr_t)&__config_start, &__config_end - &__config_start);
246 configHeader_t header = {
247 .format = EEPROM_CONF_VERSION,
250 if (config_streamer_write(&streamer, (uint8_t *)&header, sizeof(header)) < 0) {
251 return false;
253 uint16_t crc = crc16_ccitt_update(0, (uint8_t *)&header, sizeof(header));
254 PG_FOREACH(reg) {
255 const uint16_t regSize = pgSize(reg);
256 configRecord_t record = {
257 .size = sizeof(configRecord_t) + regSize,
258 .pgn = pgN(reg),
259 .version = pgVersion(reg),
260 .flags = 0
263 if (pgIsSystem(reg)) {
264 // write the only instance
265 record.flags |= CR_CLASSICATION_SYSTEM;
266 if (config_streamer_write(&streamer, (uint8_t *)&record, sizeof(record)) < 0) {
267 return false;
269 crc = crc16_ccitt_update(crc, (uint8_t *)&record, sizeof(record));
270 if (config_streamer_write(&streamer, reg->address, regSize) < 0) {
271 return false;
273 crc = crc16_ccitt_update(crc, reg->address, regSize);
274 } else {
275 // write one instance for each profile
276 for (uint8_t profileIndex = 0; profileIndex < MAX_PROFILE_COUNT; profileIndex++) {
277 record.flags = 0;
279 record.flags |= ((profileIndex + 1) & CR_CLASSIFICATION_MASK);
280 if (config_streamer_write(&streamer, (uint8_t *)&record, sizeof(record)) < 0) {
281 return false;
283 crc = crc16_ccitt_update(crc, (uint8_t *)&record, sizeof(record));
284 const uint8_t *address = reg->address + (regSize * profileIndex);
285 if (config_streamer_write(&streamer, address, regSize) < 0) {
286 return false;
288 crc = crc16_ccitt_update(crc, address, regSize);
293 configFooter_t footer = {
294 .terminator = 0,
297 if (config_streamer_write(&streamer, (uint8_t *)&footer, sizeof(footer)) < 0) {
298 return false;
300 crc = crc16_ccitt_update(crc, (uint8_t *)&footer, sizeof(footer));
302 // append checksum now
303 if (config_streamer_write(&streamer, (uint8_t *)&crc, sizeof(crc)) < 0) {
304 return false;
307 if (config_streamer_flush(&streamer) < 0) {
308 return false;
311 bool success = config_streamer_finish(&streamer) == 0;
313 return success;
316 void writeConfigToEEPROM(void)
318 bool success = false;
319 // write it
320 for (int attempt = 0; attempt < 3 && !success; attempt++) {
321 if (writeSettingsToEEPROM()) {
322 success = true;
323 #ifdef CONFIG_IN_EXTERNAL_FLASH
324 // copy it back from flash to the in-memory buffer.
325 success = loadEEPROMFromExternalFlash();
326 #endif
330 if (success && isEEPROMContentValid()) {
331 return;
334 // Flash write failed - just die now
335 failureMode(FAILURE_FLASH_WRITE_FAILED);