1 // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 #include <string.h>
15 #include "esp_partition.h"
16 #include "esp_log.h"
17 #include "esp_core_dump_types.h"
18 #include "core_dump_checksum.h"
19 #include "esp_flash_internal.h"
20 #include "esp_flash_encrypt.h"
21 #include "esp_rom_crc.h"
22
23 #define BLANK_COREDUMP_SIZE 0xFFFFFFFF
24
25 const static DRAM_ATTR char TAG[] __attribute__((unused)) = "esp_core_dump_flash";
26
27 #if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
28
29 typedef struct _core_dump_partition_t
30 {
31 /* Core dump partition start. */
32 uint32_t start;
33 /* Core dump partition size. */
34 uint32_t size;
35 /* Flag set to true if the partition is encrypted. */
36 bool encrypted;
37 } core_dump_partition_t;
38
39 typedef uint32_t core_dump_crc_t;
40
41 typedef struct _core_dump_flash_config_t
42 {
43 /* Core dump partition config. */
44 core_dump_partition_t partition;
45 /* CRC of core dump partition config. */
46 core_dump_crc_t partition_config_crc;
47 } core_dump_flash_config_t;
48
49 /* Core dump flash data. */
50 static core_dump_flash_config_t s_core_flash_config;
51
52 #ifdef CONFIG_SPI_FLASH_USE_LEGACY_IMPL
53 #define ESP_COREDUMP_FLASH_WRITE(_off_, _data_, _len_) spi_flash_write(_off_, _data_, _len_)
54 #define ESP_COREDUMP_FLASH_WRITE_ENCRYPTED(_off_, _data_, _len_) spi_flash_write_encrypted(_off_, _data_, _len_)
55 #define ESP_COREDUMP_FLASH_ERASE(_off_, _len_) spi_flash_erase_range(_off_, _len_)
56 #else
57 #define ESP_COREDUMP_FLASH_WRITE(_off_, _data_, _len_) esp_flash_write(esp_flash_default_chip, _data_, _off_, _len_)
58 #define ESP_COREDUMP_FLASH_WRITE_ENCRYPTED(_off_, _data_, _len_) esp_flash_write_encrypted(esp_flash_default_chip, _off_, _data_, _len_)
59 #define ESP_COREDUMP_FLASH_ERASE(_off_, _len_) esp_flash_erase_region(esp_flash_default_chip, _off_, _len_)
60 #endif
61
62 esp_err_t esp_core_dump_image_check(void);
63 static esp_err_t esp_core_dump_partition_and_size_get(const esp_partition_t **partition, uint32_t* size);
64
esp_core_dump_flash_custom_write(uint32_t address,const void * buffer,uint32_t length)65 static esp_err_t esp_core_dump_flash_custom_write(uint32_t address, const void *buffer, uint32_t length)
66 {
67 esp_err_t err = ESP_OK;
68
69 if (esp_flash_encryption_enabled() && s_core_flash_config.partition.encrypted) {
70 err = ESP_COREDUMP_FLASH_WRITE_ENCRYPTED(address, buffer, length);
71 } else {
72 err = ESP_COREDUMP_FLASH_WRITE(address, buffer, length);
73 }
74
75 return err;
76 }
77
esp_core_dump_calc_flash_config_crc(void)78 static inline core_dump_crc_t esp_core_dump_calc_flash_config_crc(void)
79 {
80 return esp_rom_crc32_le(0, (uint8_t const *)&s_core_flash_config.partition, sizeof(s_core_flash_config.partition));
81 }
82
esp_core_dump_flash_init(void)83 void esp_core_dump_flash_init(void)
84 {
85 const esp_partition_t *core_part = NULL;
86
87 /* Look for the core dump partition on the flash. */
88 ESP_COREDUMP_LOGI("Init core dump to flash");
89 core_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_COREDUMP, NULL);
90 if (!core_part) {
91 ESP_COREDUMP_LOGE("No core dump partition found!");
92 return;
93 }
94 ESP_COREDUMP_LOGI("Found partition '%s' @ %x %d bytes", core_part->label, core_part->address, core_part->size);
95 s_core_flash_config.partition.start = core_part->address;
96 s_core_flash_config.partition.size = core_part->size;
97 s_core_flash_config.partition.encrypted = core_part->encrypted;
98 s_core_flash_config.partition_config_crc = esp_core_dump_calc_flash_config_crc();
99 }
100
esp_core_dump_flash_write_data(core_dump_write_data_t * priv,uint8_t * data,uint32_t data_size)101 static esp_err_t esp_core_dump_flash_write_data(core_dump_write_data_t* priv, uint8_t* data, uint32_t data_size)
102 {
103 core_dump_write_data_t *wr_data = (core_dump_write_data_t *)priv;
104 esp_err_t err = ESP_OK;
105 uint32_t written = 0;
106 uint32_t wr_sz = 0;
107
108 /* Make sure that the partition is large enough to hold the data. */
109 ESP_COREDUMP_ASSERT((wr_data->off + data_size) < s_core_flash_config.partition.size);
110
111 if (wr_data->cached_bytes) {
112 /* Some bytes are in the cache, let's continue filling the cache
113 * with the data received as parameter. Let's calculate the maximum
114 * amount of bytes we can still fill the cache with. */
115 if ((COREDUMP_CACHE_SIZE - wr_data->cached_bytes) > data_size)
116 wr_sz = data_size;
117 else
118 wr_sz = COREDUMP_CACHE_SIZE - wr_data->cached_bytes;
119
120 /* Append wr_sz bytes from data parameter to the cache. */
121 memcpy(&wr_data->cached_data[wr_data->cached_bytes], data, wr_sz);
122 wr_data->cached_bytes += wr_sz;
123
124 if (wr_data->cached_bytes == COREDUMP_CACHE_SIZE) {
125 /* The cache is full, we can flush it to the flash. */
126 err = esp_core_dump_flash_custom_write(s_core_flash_config.partition.start + wr_data->off,
127 wr_data->cached_data,
128 COREDUMP_CACHE_SIZE);
129 if (err != ESP_OK) {
130 ESP_COREDUMP_LOGE("Failed to write cached data to flash (%d)!", err);
131 return err;
132 }
133 /* The offset of the next data that will be written onto the flash
134 * can now be increased. */
135 wr_data->off += COREDUMP_CACHE_SIZE;
136
137 /* Update checksum with the newly written data on the flash. */
138 esp_core_dump_checksum_update(wr_data->checksum_ctx, &wr_data->cached_data, COREDUMP_CACHE_SIZE);
139
140 /* Reset cache from the next use. */
141 wr_data->cached_bytes = 0;
142 memset(wr_data->cached_data, 0, COREDUMP_CACHE_SIZE);
143 }
144
145 written += wr_sz;
146 data_size -= wr_sz;
147 }
148
149 /* Figure out how many bytes we can write onto the flash directly, without
150 * using the cache. In our case the cache size is a multiple of the flash's
151 * minimum writing block size, so we will use it for our calculation.
152 * For example, if COREDUMP_CACHE_SIZE equals 32, here are interesting
153 * values:
154 * +---------+-----------------------+
155 * | | data_size |
156 * +---------+---+----+----+----+----+
157 * | | 0 | 31 | 32 | 40 | 64 |
158 * +---------+---+----+----+----+----+
159 * | (blocks | 0 | 0 | 1 | 1 | 2) |
160 * +---------+---+----+----+----+----+
161 * | wr_sz | 0 | 0 | 32 | 32 | 64 |
162 * +---------+---+----+----+----+----+
163 */
164 wr_sz = (data_size / COREDUMP_CACHE_SIZE) * COREDUMP_CACHE_SIZE;
165 if (wr_sz) {
166 /* Write the contiguous amount of bytes to the flash,
167 * without using the cache */
168 err = esp_core_dump_flash_custom_write(s_core_flash_config.partition.start + wr_data->off, data + written, wr_sz);
169
170 if (err != ESP_OK) {
171 ESP_COREDUMP_LOGE("Failed to write data to flash (%d)!", err);
172 return err;
173 }
174
175 /* Update the checksum with the newly written bytes */
176 esp_core_dump_checksum_update(wr_data->checksum_ctx, data + written, wr_sz);
177 wr_data->off += wr_sz;
178 written += wr_sz;
179 data_size -= wr_sz;
180 }
181
182 if (data_size > 0) {
183 /* There still some bytes from the data parameter that need to be sent,
184 * append it to cache in order to write them later. (i.e. when there
185 * will be enough bytes to fill the cache) */
186 memcpy(&wr_data->cached_data, data + written, data_size);
187 wr_data->cached_bytes = data_size;
188 }
189
190 return ESP_OK;
191 }
192
esp_core_dump_flash_write_prepare(core_dump_write_data_t * priv,uint32_t * data_len)193 static esp_err_t esp_core_dump_flash_write_prepare(core_dump_write_data_t *priv, uint32_t *data_len)
194 {
195 core_dump_write_data_t *wr_data = (core_dump_write_data_t *)priv;
196 esp_err_t err = ESP_OK;
197 uint32_t sec_num = 0;
198 uint32_t cs_len = 0;
199
200 /* Get the length, in bytes, of the checksum. */
201 cs_len = esp_core_dump_checksum_size();
202
203 /* At the end of the core dump file, a padding may be added, according to the
204 * cache size. We must take that padding into account. */
205 uint32_t padding = 0;
206 const uint32_t modulo = *data_len % COREDUMP_CACHE_SIZE;
207 if (modulo != 0) {
208 /* The data length is not a multiple of the cache size,
209 * so there will be a padding. */
210 padding = COREDUMP_CACHE_SIZE - modulo;
211 }
212
213 /* Now we can check whether we have enough space in our core dump parition
214 * or not. */
215 if ((*data_len + padding + cs_len) > s_core_flash_config.partition.size) {
216 ESP_COREDUMP_LOGE("Not enough space to save core dump!");
217 return ESP_ERR_NO_MEM;
218 }
219
220 /* We have enough space in the partition, add the padding and the checksum
221 * in the core dump file calculation. */
222 *data_len += padding + cs_len;
223
224 memset(wr_data, 0, sizeof(core_dump_write_data_t));
225
226 /* In order to erase the right amount of data in the flash, we have to
227 * calculate how many SPI flash sectors will be needed by the core dump
228 * file. */
229 sec_num = *data_len / SPI_FLASH_SEC_SIZE;
230 if (*data_len % SPI_FLASH_SEC_SIZE) {
231 sec_num++;
232 }
233
234 /* Erase the amount of sectors needed. */
235 ESP_COREDUMP_LOGI("Erase flash %d bytes @ 0x%x", sec_num * SPI_FLASH_SEC_SIZE, s_core_flash_config.partition.start + 0);
236 ESP_COREDUMP_ASSERT(sec_num * SPI_FLASH_SEC_SIZE <= s_core_flash_config.partition.size);
237 err = ESP_COREDUMP_FLASH_ERASE(s_core_flash_config.partition.start + 0, sec_num * SPI_FLASH_SEC_SIZE);
238 if (err != ESP_OK) {
239 ESP_COREDUMP_LOGE("Failed to erase flash (%d)!", err);
240 }
241
242 return err;
243 }
244
esp_core_dump_flash_write_start(core_dump_write_data_t * priv)245 static esp_err_t esp_core_dump_flash_write_start(core_dump_write_data_t* priv)
246 {
247 core_dump_write_data_t *wr_data = (core_dump_write_data_t *)priv;
248 esp_core_dump_checksum_init(&wr_data->checksum_ctx);
249 return ESP_OK;
250 }
251
esp_core_dump_flash_write_end(core_dump_write_data_t * priv)252 static esp_err_t esp_core_dump_flash_write_end(core_dump_write_data_t* priv)
253 {
254 esp_err_t err = ESP_OK;
255 core_dump_checksum_bytes checksum = NULL;
256 uint32_t cs_len = 0;
257 core_dump_write_data_t *wr_data = (core_dump_write_data_t *)priv;
258
259 /* Get the size, in bytes of the checksum. */
260 cs_len = esp_core_dump_checksum_size();
261
262 /* Flush cached bytes, including the zero padding at the end (if any). */
263 if (wr_data->cached_bytes) {
264 err = esp_core_dump_flash_custom_write(s_core_flash_config.partition.start + wr_data->off,
265 wr_data->cached_data,
266 COREDUMP_CACHE_SIZE);
267
268 if (err != ESP_OK) {
269 ESP_COREDUMP_LOGE("Failed to flush cached data to flash (%d)!", err);
270 return err;
271 }
272
273 /* Update the checksum with the data written, including the padding. */
274 esp_core_dump_checksum_update(wr_data->checksum_ctx, wr_data->cached_data, COREDUMP_CACHE_SIZE);
275 wr_data->off += COREDUMP_CACHE_SIZE;
276 wr_data->cached_bytes = 0;
277 }
278
279 /* All data have been written to the flash, the cache is now empty, we can
280 * terminate the checksum calculation. */
281 esp_core_dump_checksum_finish(wr_data->checksum_ctx, &checksum);
282
283 /* Use the cache to write the checksum if its size doesn't match the requirements.
284 * (e.g. its size is not a multiple of 32) */
285 if (cs_len < COREDUMP_CACHE_SIZE) {
286 /* Copy the checksum into the cache. */
287 memcpy(wr_data->cached_data, checksum, cs_len);
288
289 /* Fill the rest of the cache with zeros. */
290 memset(wr_data->cached_data + cs_len, 0, COREDUMP_CACHE_SIZE - cs_len);
291
292 /* Finally, write the checksum on the flash, using the cache. */
293 err = esp_core_dump_flash_custom_write(s_core_flash_config.partition.start + wr_data->off,
294 wr_data->cached_data,
295 COREDUMP_CACHE_SIZE);
296 } else {
297 /* In that case, the length of the checksum must be a multiple of 16. */
298 ESP_COREDUMP_ASSERT(cs_len % 16 == 0);
299 err = esp_core_dump_flash_custom_write(s_core_flash_config.partition.start + wr_data->off, checksum, cs_len);
300 }
301
302 if (err != ESP_OK) {
303 ESP_COREDUMP_LOGE("Failed to flush cached data to flash (%d)!", err);
304 return err;
305 }
306 wr_data->off += cs_len;
307 ESP_COREDUMP_LOGI("Write end offset 0x%x, check sum length %d", wr_data->off, cs_len);
308
309 return err;
310 }
311
esp_core_dump_to_flash(panic_info_t * info)312 void esp_core_dump_to_flash(panic_info_t *info)
313 {
314 static core_dump_write_config_t wr_cfg = { 0 };
315 static core_dump_write_data_t wr_data = { 0 };
316
317 /* Check core dump partition configuration. */
318 core_dump_crc_t crc = esp_core_dump_calc_flash_config_crc();
319 if (s_core_flash_config.partition_config_crc != crc) {
320 ESP_COREDUMP_LOGE("Core dump flash config is corrupted! CRC=0x%x instead of 0x%x", crc, s_core_flash_config.partition_config_crc);
321 return;
322 }
323
324 /* Make sure that the partition can at least hold the data length. */
325 if (s_core_flash_config.partition.start == 0 || s_core_flash_config.partition.size < sizeof(uint32_t)) {
326 ESP_COREDUMP_LOGE("Invalid flash partition config!");
327 return;
328 }
329
330 /* Initialize non-OS flash access critical section. */
331 spi_flash_guard_set(&g_flash_guard_no_os_ops);
332 esp_flash_app_disable_protect(true);
333
334 /* Register the callbacks that will be called later by the generic part. */
335 wr_cfg.prepare = esp_core_dump_flash_write_prepare;
336 wr_cfg.start = esp_core_dump_flash_write_start;
337 wr_cfg.end = esp_core_dump_flash_write_end;
338 wr_cfg.write = (esp_core_dump_flash_write_data_t) esp_core_dump_flash_write_data;
339 wr_cfg.priv = &wr_data;
340
341 ESP_COREDUMP_LOGI("Save core dump to flash...");
342 esp_core_dump_write(info, &wr_cfg);
343 ESP_COREDUMP_LOGI("Core dump has been saved to flash.");
344 }
345
esp_core_dump_init(void)346 void esp_core_dump_init(void)
347 {
348 esp_core_dump_flash_init();
349
350 #if CONFIG_ESP_COREDUMP_CHECK_BOOT
351 const esp_partition_t *partition = 0;
352 uint32_t size = 0;
353
354 if (esp_core_dump_image_check() == ESP_OK
355 && esp_core_dump_partition_and_size_get(&partition, &size) == ESP_OK) {
356 ESP_COREDUMP_LOGI("Found core dump %d bytes in flash @ 0x%x", size, partition->address);
357 }
358 #endif
359 }
360
esp_core_dump_image_check(void)361 esp_err_t esp_core_dump_image_check(void)
362 {
363 esp_err_t err = ESP_OK;
364 const esp_partition_t *core_part = NULL;
365 core_dump_write_data_t wr_data = { 0 };
366 uint32_t size = 0;
367 uint32_t total_size = 0;
368 uint32_t offset = 0;
369 const uint32_t checksum_size = esp_core_dump_checksum_size();
370 core_dump_checksum_bytes checksum_calc = NULL;
371 /* Initialize the checksum we have to read from the flash to the biggest
372 * size we can have for a checksum. */
373 uint8_t checksum_read[COREDUMP_CHECKSUM_MAX_LEN] = { 0 };
374
375 /* Assert that we won't have any problems with our checksum size. */
376 ESP_COREDUMP_DEBUG_ASSERT(checksum_size <= COREDUMP_CHECKSUM_MAX_LEN);
377
378 /* Retrieve the partition and size. */
379 err = esp_core_dump_partition_and_size_get(&core_part, &total_size);
380 if (err != ESP_OK) {
381 return err;
382 }
383
384 /* The final checksum, from the image, doesn't take part into the checksum
385 * calculation, so subtract it from the bytes we are going to read. */
386 size = total_size - checksum_size ;
387
388 /* Initiate the checksum calculation for the coredump in the flash. */
389 esp_core_dump_checksum_init(&wr_data.checksum_ctx);
390
391 while (size > 0) {
392 /* Use the cache in core_dump_write_data_t structure to read the
393 * partition. */
394 const uint32_t toread = (size < COREDUMP_CACHE_SIZE) ? size : COREDUMP_CACHE_SIZE;
395
396 /* Read the content of the flash. */
397 err = esp_partition_read(core_part, offset, wr_data.cached_data, toread);
398 if (err != ESP_OK) {
399 ESP_COREDUMP_LOGE("Failed to read data from core dump (%d)!", err);
400 return err;
401 }
402
403 /* Update the checksum according to what was just read. */
404 esp_core_dump_checksum_update(wr_data.checksum_ctx, wr_data.cached_data, toread);
405
406 /* Move the offset forward and decrease the remaining size. */
407 offset += toread;
408 size -= toread;
409 }
410
411 /* The coredump has been totally read, finish the checksum calculation. */
412 esp_core_dump_checksum_finish(wr_data.checksum_ctx, &checksum_calc);
413
414 /* Read the checksum from the flash and compare to the one just
415 * calculated. */
416 err = esp_partition_read(core_part, total_size - checksum_size, checksum_read, checksum_size);
417 if (err != ESP_OK) {
418 ESP_COREDUMP_LOGE("Failed to read checksum from core dump (%d)!", err);
419 return err;
420 }
421
422 /* Compare the checksum read from the flash and the one just calculated. */
423 if (memcmp(checksum_calc, checksum_read, checksum_size) != 0) {
424 ESP_COREDUMP_LOGE("Core dump data check failed:");
425 esp_core_dump_print_checksum("Calculated checksum", checksum_calc);
426 esp_core_dump_print_checksum("Image checksum", checksum_read);
427 return ESP_ERR_INVALID_CRC;
428 } else {
429 ESP_COREDUMP_LOGI("Core dump data checksum is correct");
430 }
431
432 return ESP_OK;
433 }
434
435 #endif
436
esp_core_dump_image_erase(void)437 esp_err_t esp_core_dump_image_erase(void)
438 {
439 /* If flash is encrypted, we can only write blocks of 16 bytes, let's always
440 * write a 16-byte buffer. */
441 uint32_t helper[4] = { BLANK_COREDUMP_SIZE };
442 _Static_assert(sizeof(helper) % 16 == 0, "esp_partition_write() needs multiple of 16 bytes long buffers");
443
444 /* Find the partition that could potentially contain a (previous) core dump. */
445 const esp_partition_t *core_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
446 ESP_PARTITION_SUBTYPE_DATA_COREDUMP,
447 NULL);
448 if (!core_part) {
449 ESP_LOGE(TAG, "No core dump partition found!");
450 return ESP_ERR_NOT_FOUND;
451 }
452 if (core_part->size < sizeof(uint32_t)) {
453 ESP_LOGE(TAG, "Too small core dump partition!");
454 return ESP_ERR_INVALID_SIZE;
455 }
456
457 esp_err_t err = ESP_OK;
458 err = esp_partition_erase_range(core_part, 0, core_part->size);
459 if (err != ESP_OK) {
460 ESP_LOGE(TAG, "Failed to erase core dump partition (%d)!", err);
461 return err;
462 }
463
464 err = esp_partition_write(core_part, 0, helper, sizeof(helper));
465 if (err != ESP_OK) {
466 ESP_LOGE(TAG, "Failed to write core dump partition size (%d)!", err);
467 }
468
469 return err;
470 }
471
esp_core_dump_partition_and_size_get(const esp_partition_t ** partition,uint32_t * size)472 static esp_err_t esp_core_dump_partition_and_size_get(const esp_partition_t **partition, uint32_t* size)
473 {
474 uint32_t core_size = 0;
475 const esp_partition_t *core_part = NULL;
476
477 /* Check the arguments, at least one should be provided. */
478 if (partition == NULL && size == NULL) {
479 return ESP_ERR_INVALID_ARG;
480 }
481
482 /* Find the partition that could potentially contain a (previous) core dump. */
483 core_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
484 ESP_PARTITION_SUBTYPE_DATA_COREDUMP,
485 NULL);
486 if (core_part == NULL) {
487 ESP_COREDUMP_LOGE("No core dump partition found!");
488 return ESP_ERR_NOT_FOUND;
489 }
490 if (core_part->size < sizeof(uint32_t)) {
491 ESP_COREDUMP_LOGE("Too small core dump partition!");
492 return ESP_ERR_INVALID_SIZE;
493 }
494
495 /* The partition has been found, get its first uint32_t value, which
496 * describes the core dump file size. */
497 esp_err_t err = esp_partition_read(core_part, 0, &core_size, sizeof(uint32_t));
498 if (err != ESP_OK) {
499 ESP_COREDUMP_LOGE("Failed to read core dump data size (%d)!", err);
500 return err;
501 }
502
503 /* Verify that the size read from the flash is not corrupted. */
504 if (core_size == 0xFFFFFFFF) {
505 ESP_COREDUMP_LOGD("Blank core dump partition!");
506 return ESP_ERR_INVALID_SIZE;
507 }
508
509 if ((core_size < sizeof(uint32_t)) || (core_size > core_part->size)) {
510 ESP_COREDUMP_LOGE("Incorrect size of core dump image: %d", core_size);
511 return ESP_ERR_INVALID_SIZE;
512 }
513
514 /* Return the values if needed. */
515 if (partition != NULL) {
516 *partition = core_part;
517 }
518
519 if (size != NULL) {
520 *size = core_size;
521 }
522
523 return ESP_OK;
524 }
525
esp_core_dump_image_get(size_t * out_addr,size_t * out_size)526 esp_err_t esp_core_dump_image_get(size_t* out_addr, size_t *out_size)
527 {
528 esp_err_t err = ESP_OK;
529 uint32_t size = 0;
530 const esp_partition_t *core_part = NULL;
531
532 /* Check the validity of the parameters. */
533 if (out_addr == NULL || out_size == NULL) {
534 return ESP_ERR_INVALID_ARG;
535 }
536
537 /* Retrieve the partition and size. */
538 err = esp_core_dump_partition_and_size_get(&core_part, &size);
539
540 if (err != ESP_OK) {
541 return err;
542 }
543
544 /* Save the address. */
545 *out_addr = core_part->address;
546
547 /* Save the size read. */
548 *out_size = size;
549
550 return ESP_OK;
551 }
552