Hello everyone,
I'm working in an application where I need to store a string in flash so I can keep that value like if it where settings when doing a firmware update so the app can read those values and use them to configure some stuff inside my app. But I noticed an issue when trying to initialize the fds_init when updating the app using a boot loader, the app can run fds_init if I don't use the boot loader.
Do you know if the boot loader can affect the FDS file system In a way that can cause a crash or affecting some interrupts that the wait_for_fds_ready is stuck waiting after fds_init causing a hard fault crashing the app.
I'm having an odd issue using the ads library, if I load the app the first time with the programmer the app works just fine, but if I use the boot loader it will crash the call to fds_init.
<!DOCTYPE Linker_Placement_File> <Root name="Flash Section Placement"> <MemorySegment name="FLASH" start="$(FLASH_PH_START)" size="$(FLASH_PH_SIZE)"> <ProgramSection load="no" name=".reserved_flash" start="$(FLASH_PH_START)" size="$(FLASH_START)-$(FLASH_PH_START)" /> <ProgramSection alignment="0x100" load="Yes" name=".vectors" start="$(FLASH_START)" /> <ProgramSection alignment="4" load="Yes" name=".init" /> <ProgramSection alignment="4" load="Yes" name=".init_rodata" /> <ProgramSection alignment="4" load="Yes" name=".text" /> <ProgramSection alignment="4" keep="Yes" load="Yes" name=".sdh_soc_observers" inputsections="*(SORT(.sdh_soc_observers*))" address_symbol="__start_sdh_soc_observers" end_symbol="__stop_sdh_soc_observers" /> <ProgramSection alignment="4" keep="Yes" load="Yes" name=".sdh_ble_observers" inputsections="*(SORT(.sdh_ble_observers*))" address_symbol="__start_sdh_ble_observers" end_symbol="__stop_sdh_ble_observers" /> <ProgramSection alignment="4" keep="Yes" load="Yes" name=".log_const_data" inputsections="*(SORT(.log_const_data*))" address_symbol="__start_log_const_data" end_symbol="__stop_log_const_data" /> <ProgramSection alignment="4" keep="Yes" load="Yes" name=".nrf_balloc" inputsections="*(.nrf_balloc*)" address_symbol="__start_nrf_balloc" end_symbol="__stop_nrf_balloc" /> <ProgramSection alignment="4" keep="Yes" load="Yes" name=".sdh_state_observers" inputsections="*(SORT(.sdh_state_observers*))" address_symbol="__start_sdh_state_observers" end_symbol="__stop_sdh_state_observers" /> <ProgramSection alignment="4" keep="Yes" load="Yes" name=".sdh_stack_observers" inputsections="*(SORT(.sdh_stack_observers*))" address_symbol="__start_sdh_stack_observers" end_symbol="__stop_sdh_stack_observers" /> <ProgramSection alignment="4" keep="Yes" load="Yes" name=".sdh_req_observers" inputsections="*(SORT(.sdh_req_observers*))" address_symbol="__start_sdh_req_observers" end_symbol="__stop_sdh_req_observers" /> <ProgramSection alignment="4" keep="Yes" load="Yes" name=".nrf_queue" inputsections="*(.nrf_queue*)" address_symbol="__start_nrf_queue" end_symbol="__stop_nrf_queue" /> <ProgramSection alignment="4" keep="Yes" load="Yes" name=".cli_command" inputsections="*(.cli_command*)" address_symbol="__start_cli_command" end_symbol="__stop_cli_command" /> <ProgramSection alignment="4" keep="Yes" load="Yes" name=".crypto_data" inputsections="*(SORT(.crypto_data*))" address_symbol="__start_crypto_data" end_symbol="__stop_crypto_data" /> <ProgramSection alignment="4" keep="Yes" load="Yes" name=".pwr_mgmt_data" inputsections="*(SORT(.pwr_mgmt_data*))" address_symbol="__start_pwr_mgmt_data" end_symbol="__stop_pwr_mgmt_data" /> <ProgramSection alignment="4" keep="Yes" load="No" name=".nrf_sections" address_symbol="__start_nrf_sections" /> <ProgramSection alignment="4" keep="Yes" load="Yes" name=".log_dynamic_data" inputsections="*(SORT(.log_dynamic_data*))" runin=".log_dynamic_data_run"/> <ProgramSection alignment="4" keep="Yes" load="Yes" name=".fs_data" inputsections="*(.fs_data*)" runin=".fs_data_run"/> <ProgramSection alignment="4" keep="Yes" load="Yes" name=".cli_sorted_cmd_ptrs" inputsections="*(.cli_sorted_cmd_ptrs*)" runin=".cli_sorted_cmd_ptrs_run"/> <ProgramSection alignment="4" load="Yes" name=".dtors" /> <ProgramSection alignment="4" load="Yes" name=".ctors" /> <ProgramSection alignment="4" load="Yes" name=".rodata" /> <ProgramSection alignment="4" load="Yes" name=".ARM.exidx" address_symbol="__exidx_start" end_symbol="__exidx_end" /> <ProgramSection alignment="4" load="Yes" runin=".fast_run" name=".fast" /> <ProgramSection alignment="4" load="Yes" runin=".data_run" name=".data" /> <ProgramSection alignment="4" load="Yes" runin=".tdata_run" name=".tdata" /> </MemorySegment> <MemorySegment name="RAM" start="$(RAM_PH_START)" size="$(RAM_PH_SIZE)"> <ProgramSection load="no" name=".reserved_ram" start="$(RAM_PH_START)" size="$(RAM_START)-$(RAM_PH_START)" /> <ProgramSection alignment="0x100" load="No" name=".vectors_ram" start="$(RAM_START)" address_symbol="__app_ram_start__"/> <ProgramSection alignment="4" keep="Yes" load="No" name=".nrf_sections_run" address_symbol="__start_nrf_sections_run" /> <ProgramSection alignment="4" keep="Yes" load="No" name=".log_dynamic_data_run" address_symbol="__start_log_dynamic_data" end_symbol="__stop_log_dynamic_data" /> <ProgramSection alignment="4" keep="Yes" load="No" name=".fs_data_run" address_symbol="__start_fs_data" end_symbol="__stop_fs_data" /> <ProgramSection alignment="4" keep="Yes" load="No" name=".cli_sorted_cmd_ptrs_run" address_symbol="__start_cli_sorted_cmd_ptrs" end_symbol="__stop_cli_sorted_cmd_ptrs" /> <ProgramSection alignment="4" keep="Yes" load="No" name=".nrf_sections_run_end" address_symbol="__end_nrf_sections_run" /> <ProgramSection alignment="4" load="No" name=".fast_run" /> <ProgramSection alignment="4" load="No" name=".data_run" /> <ProgramSection alignment="4" load="No" name=".tdata_run" /> <ProgramSection alignment="4" load="No" name=".bss" /> <ProgramSection alignment="4" load="No" name=".tbss" /> <ProgramSection alignment="4" load="No" name=".non_init" /> <ProgramSection alignment="4" size="__HEAPSIZE__" load="No" name=".heap" /> <ProgramSection alignment="8" size="__STACKSIZE__" load="No" place_from_segment_end="Yes" name=".stack" address_symbol="__StackLimit" end_symbol="__StackTop"/> <ProgramSection alignment="8" size="__STACKSIZE_PROCESS__" load="No" name=".stack_process" /> </MemorySegment> </Root>
It is crashing something after the fds_init and before wait_for_fds_ready.
The data I am trying to store is just a simple string I based the code on the fstorage app.
#include "flashconfig.h" #include <stdint.h> #include <stdbool.h> #include "fds.h" #include "nrf_log.h" #include "nrf_log_ctrl.h" #include <string.h> #include "SoftwareSerial.h" /* File ID and Key used for the configuration record. */ #define CONFIG_FILE (0xF010) #define CONFIG_REC_KEY (0x7010) extern char const *fds_err_str[]; /* A dummy structure to save in flash. */ typedef struct { uint32_t boot_count; char device_name[16]; bool config1_on; bool config2_on; } configuration_t; /* Array to map FDS return values to strings. */ char const *fds_err_str[] = { "FDS_SUCCESS", "FDS_ERR_OPERATION_TIMEOUT", "FDS_ERR_NOT_INITIALIZED", "FDS_ERR_UNALIGNED_ADDR", "FDS_ERR_INVALID_ARG", "FDS_ERR_NULL_ARG", "FDS_ERR_NO_OPEN_RECORDS", "FDS_ERR_NO_SPACE_IN_FLASH", "FDS_ERR_NO_SPACE_IN_QUEUES", "FDS_ERR_RECORD_TOO_LARGE", "FDS_ERR_NOT_FOUND", "FDS_ERR_NO_PAGES", "FDS_ERR_USER_LIMIT_REACHED", "FDS_ERR_CRC_CHECK_FAILED", "FDS_ERR_BUSY", "FDS_ERR_INTERNAL", }; /* Array to map FDS events to strings. */ static char const *fds_evt_str[] = { "FDS_EVT_INIT", "FDS_EVT_WRITE", "FDS_EVT_UPDATE", "FDS_EVT_DEL_RECORD", "FDS_EVT_DEL_FILE", "FDS_EVT_GC", }; /* Dummy configuration data. */ static configuration_t m_dummy_cfg = { .config1_on = false, .config2_on = true, .boot_count = 0x0, .device_name = "dummy", }; /* A record containing dummy configuration data. */ static fds_record_t const m_dummy_record = { .file_id = CONFIG_FILE, .key = CONFIG_REC_KEY, .data.p_data = &m_dummy_cfg, /* The length of a record is always expressed in 4-byte units (words). */ .data.length_words = (sizeof(m_dummy_cfg) + 3) / sizeof(uint32_t), }; /* Keep track of the progress of a delete_all operation. */ static struct { bool delete_next; //!< Delete next record. bool pending; //!< Waiting for an fds FDS_EVT_DEL_RECORD event, to delete the next record. } m_delete_all; /* Flag to check fds initialization. */ static bool volatile m_fds_initialized; static void fds_evt_handler(fds_evt_t const *p_evt) { NRF_LOG_INFO("Event: %s received (%s)", fds_evt_str[p_evt->id], fds_err_str[p_evt->result]); NRF_LOG_FLUSH(); switch (p_evt->id) { case FDS_EVT_INIT: if (p_evt->result == FDS_SUCCESS) { m_fds_initialized = true; } break; case FDS_EVT_WRITE: { if (p_evt->result == FDS_SUCCESS) { NRF_LOG_INFO("Record ID:\t0x%04x", p_evt->write.record_id); NRF_LOG_INFO("File ID:\t0x%04x", p_evt->write.file_id); NRF_LOG_INFO("Record key:\t0x%04x", p_evt->write.record_key); NRF_LOG_FLUSH(); } } break; case FDS_EVT_DEL_RECORD: { if (p_evt->result == FDS_SUCCESS) { NRF_LOG_INFO("Record ID:\t0x%04x", p_evt->del.record_id); NRF_LOG_INFO("File ID:\t0x%04x", p_evt->del.file_id); NRF_LOG_INFO("Record key:\t0x%04x", p_evt->del.record_key); NRF_LOG_FLUSH(); } m_delete_all.pending = false; } break; default: break; } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /**@brief Sleep until an event is received. */ static void power_manage(void) { #ifdef SOFTDEVICE_PRESENT (void)sd_app_evt_wait(); #else __WFE(); #endif } /**@brief Wait for fds to initialize. */ static void wait_for_fds_ready(void) { while (!m_fds_initialized) { power_manage(); } } static void record_write(uint32_t fid, uint32_t key, void const *p_data, uint32_t len) { fds_record_t const rec = { .file_id = fid, .key = key, .data.p_data = p_data, .data.length_words = (len + 3) / sizeof(uint32_t)}; NRF_LOG_INFO("writing record to flash...\r\n" "file: 0x%x, key: 0x%x, \"%s\", len: %u bytes\r\n", fid, key, p_data, len); ret_code_t rc = fds_record_write(NULL, &rec); if (rc != FDS_SUCCESS) { NRF_LOG_INFO( "error: fds_record_write() returned %s.\r\n", fds_err_str[rc]); } } static void record_update(configuration_t const *p_cfg) { fds_record_desc_t desc = {0}; fds_find_token_t ftok = {0}; if (fds_record_find(CONFIG_FILE, CONFIG_REC_KEY, &desc, &ftok) == FDS_SUCCESS) { fds_record_t const rec = { .file_id = CONFIG_FILE, .key = CONFIG_REC_KEY, .data.p_data = p_cfg, .data.length_words = (sizeof(configuration_t) + 3) / sizeof(uint32_t)}; ret_code_t rc = fds_record_update(&desc, &rec); if (rc != FDS_SUCCESS) { NRF_LOG_INFO( "error: fds_record_update() returned %s.\r\n", fds_err_str[rc]); NRF_LOG_FLUSH(); } } else { NRF_LOG_INFO("error: could not find config file.\r\n"); NRF_LOG_FLUSH(); } } static void record_delete(uint32_t fid, uint32_t key) { fds_find_token_t tok = {0}; fds_record_desc_t desc = {0}; NRF_LOG_INFO( "deleting record...\r\n" "file: 0x%x, key: 0x%x\r\n", fid, key); NRF_LOG_FLUSH(); if (fds_record_find(fid, key, &desc, &tok) == FDS_SUCCESS) { ret_code_t rc = fds_record_delete(&desc); if (rc != FDS_SUCCESS) { NRF_LOG_INFO( "error: fds_record_delete() returned %s.\r\n", fds_err_str[rc]); NRF_LOG_FLUSH(); return; } NRF_LOG_INFO("record id: 0x%x\r\n", desc.record_id); NRF_LOG_FLUSH(); } else { NRF_LOG_INFO("error: record not found!\r\n"); NRF_LOG_FLUSH(); } } bool record_delete_next(void) { fds_find_token_t tok = {0}; fds_record_desc_t desc = {0}; if (fds_record_iterate(&desc, &tok) == FDS_SUCCESS) { ret_code_t rc = fds_record_delete(&desc); if (rc != FDS_SUCCESS) { return false; } return true; } else { /* No records left to delete. */ return false; } } /**@brief Begin deleting all records, one by one. */ void delete_all_begin(void) { m_delete_all.delete_next = true; } /**@brief Process a delete all command. * * Delete records, one by one, until no records are left. */ void delete_all_process(void) { if (m_delete_all.delete_next & !m_delete_all.pending) { NRF_LOG_INFO("Deleting next record."); NRF_LOG_FLUSH(); m_delete_all.delete_next = record_delete_next(); if (!m_delete_all.delete_next) { NRF_LOG_INFO("No records left to delete."); NRF_LOG_FLUSH(); } } } // run garbage collection static void gc_cmd() { ret_code_t rc = fds_gc(); switch (rc) { case FDS_SUCCESS: NRF_LOG_INFO( "file system is now clean\r\n"); NRF_LOG_FLUSH(); break; default: NRF_LOG_INFO( "error: garbage collection returned %s\r\n", fds_err_str[rc]); NRF_LOG_FLUSH(); break; } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void flashconfig_begin() { ret_code_t rc; NRF_LOG_INFO("FDS example started.") NRF_LOG_FLUSH(); SoftwareSerial_printf("FDS example started.\r\n"); /* Register first to receive an event when initialization is complete. */ (void)fds_register(fds_evt_handler); NRF_LOG_INFO("Initializing fds..."); NRF_LOG_FLUSH(); SoftwareSerial_printf("Initializing fds...\r\n"); rc = fds_init(); APP_ERROR_CHECK(rc); /* Wait for fds to initialize. */ wait_for_fds_ready(); NRF_LOG_INFO("Reading flash usage statistics..."); NRF_LOG_FLUSH(); SoftwareSerial_printf("Reading flash usage statistics...\r\n"); fds_stat_t stat = {0}; rc = fds_stat(&stat); APP_ERROR_CHECK(rc); NRF_LOG_INFO("Found %d valid records.", stat.valid_records); NRF_LOG_INFO("Found %d dirty records (ready to be garbage collected).", stat.dirty_records); NRF_LOG_FLUSH(); SoftwareSerial_printf("Found %d valid records.\r\n", stat.valid_records); SoftwareSerial_printf("Found %d dirty records (ready to be garbage collected).\r\n", stat.dirty_records); if (stat.dirty_records > 0) { gc_cmd(); wait_for_fds_ready(); } fds_record_desc_t desc = {0}; fds_find_token_t tok = {0}; rc = fds_record_find(CONFIG_FILE, CONFIG_REC_KEY, &desc, &tok); if (rc != FDS_SUCCESS) { /* System config not found; write a new one. */ NRF_LOG_INFO("Writing config file..."); NRF_LOG_FLUSH(); SoftwareSerial_printf("Writing config file...\r\n"); rc = fds_record_write(&desc, &m_dummy_record); APP_ERROR_CHECK(rc); } } bool flashconfig_setESN(char *newESN) { bool result=false; ret_code_t rc; fds_record_desc_t desc = {0}; fds_find_token_t tok = {0}; rc = fds_record_find(CONFIG_FILE, CONFIG_REC_KEY, &desc, &tok); if (rc == FDS_SUCCESS) { /* A config file is in flash. Let's update it. */ fds_flash_record_t config = {0}; /* Open the record and read its contents. */ rc = fds_record_open(&desc, &config); APP_ERROR_CHECK(rc); //wait_for_fds_ready(); /* Copy the configuration from flash into m_dummy_cfg. */ memcpy(&m_dummy_cfg, config.p_data, sizeof(configuration_t)); char mytmp[16]={0}; memcpy(mytmp,newESN,sizeof(mytmp)); NRF_LOG_INFO("Comparing %s vs %s", m_dummy_cfg.device_name, mytmp); NRF_LOG_FLUSH(); SoftwareSerial_printf("Comparing %s vs %s\r\n", m_dummy_cfg.device_name, mytmp); /* Update ESN field. */ int rescmp = strcmp(m_dummy_cfg.device_name, mytmp); if(rescmp!=0) { memset(m_dummy_cfg.device_name, 0, sizeof(m_dummy_cfg.device_name)); memcpy(m_dummy_cfg.device_name, mytmp, strlen(mytmp)); result = true; NRF_LOG_INFO("The ESN is different, updating ESN %s.", m_dummy_cfg.device_name); NRF_LOG_FLUSH(); SoftwareSerial_printf("The ESN is different, updating ESN %s.\r\n", m_dummy_cfg.device_name); } else { NRF_LOG_INFO("The ESN %s is already the same we have.", m_dummy_cfg.device_name); NRF_LOG_FLUSH(); SoftwareSerial_printf("The ESN %s is already the same we have.\r\n", m_dummy_cfg.device_name); result = false; } /* Close the record when done reading. */ rc = fds_record_close(&desc); APP_ERROR_CHECK(rc); /* Update ESN record. */ if(rescmp!=0) { /* Write the updated record to flash. */ rc = fds_record_update(&desc, &m_dummy_record); APP_ERROR_CHECK(rc); NRF_LOG_INFO("FDS UPDATED"); NRF_LOG_FLUSH(); SoftwareSerial_printf("FDS UPDATED\r\n"); } } return result; } char *flashconfig_getESN() { ret_code_t rc; fds_record_desc_t desc = {0}; fds_find_token_t tok = {0}; rc = fds_record_find(CONFIG_FILE, CONFIG_REC_KEY, &desc, &tok); if (rc == FDS_SUCCESS) { /* A config file is in flash. Let's update it. */ fds_flash_record_t config = {0}; /* Open the record and read its contents. */ rc = fds_record_open(&desc, &config); APP_ERROR_CHECK(rc); /* Copy the configuration from flash into m_dummy_cfg. */ memcpy(&m_dummy_cfg, config.p_data, sizeof(configuration_t)); /* Close the record when done reading. */ rc = fds_record_close(&desc); APP_ERROR_CHECK(rc); } return m_dummy_cfg.device_name; }
I think the issue is related to something like you must initialize ble_init before using FDS or so, it's what I was researching as the ble_init is also doing some initialization for the sofdevice too and that is causing the wait_for_fds_ready to never exit.
main() => peer_manager_init() => pm_init() => pds_init() => fds_init()
The issue of the fds_init is happening when using the bootloader, the original boot loader from nordic.
So I don't know if there is an issue with some corrupted records or an interrupt ire table that is not correctly assigned when we use the boot loader and run the app.
if I use the debugger from the boot loader side it will crash in app_start.
If I use the debugger in the app it will crash in the flash init after the boot loader starts the app, I used a serial console to output the log and verify this.
Is there an alternative way to store an string in flash that will be available during boot loader app updates? like the flash write example?