Emergency data storage

Overview

The emergency data storage (EMDS) library provides persistent storage functionality designed to prevent the wear and tear of the flash memory. Its intended use is for storing of data undergoing frequent changes during runtime.

Implementation

The CONFIG_EMDS Kconfig option enables the emergency data storage.

The application must initialize the pre-allocated flash area by using the emds_init() function. The CONFIG_EMDS_SECTOR_COUNT option defines how many sectors should be used to store data.

The allocated storage space must be larger than the combined data stored by the application. Allocating a larger flash area will demand more resources, but also increase the life expectancy of the flash. The chosen size should reflect the amount of data stored, the available flash resources, and how the application calls the emds_store() function. In general, it should not be necessary to allocate a large flash area, since only a limited set of data should be stored to ensure swift completion of writing the flash on shutdown.

The memory location that is going to be stored must be added on initialization. All memory areas must be provided through entries containing an ID, data pointer, and data length, using the emds_entry_add() function and the EMDS_STATIC_ENTRY_DEFINE macro. Entries to be stored when the emergency data storage is triggered need their own unique IDs that are not changed after a reboot.

When all entries are added, the emds_load() function restores the entries into the memory areas from the flash.

After restoring the previous data, the application must run the emds_prepare() function to prepare the flash area for receiving new entries. If the remaining empty flash area is smaller than the required data size, the flash area will be automatically erased to increase the available flash area.

The storage is done in deterministic time, so it is possible to know how long it takes to store all registered entries. However, this is chip-dependent, so it is important to measure the time. The Nordic Semiconductor Infocenter contains chip information and datasheet, and timing values can be found under the “Electrical specification” for the Non-volatile memory controller. The following Kconfig options can be configured:

When configuring these values, consider the time for erase when doing garbage collection in NVS. If partial erase is not enabled or supported, the time of a complete sector erase has to be included in the CONFIG_EMDS_FLASH_TIME_BASE_OVERHEAD_US. When partial erase is enabled and supported by the hardware, include the time it takes for the scheduler to trigger, which is depending on the time defined in CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE_MS. When changing the CONFIG_EMDS_FLASH_TIME_BASE_OVERHEAD_US option, it is important that the worst time is considered.

The application must call the emds_store() function to store all entries. This can only be done once, before the emds_prepare() function must be called again. When invoked, the emds_store() function stores all the registered entries. Invocation of this call should be performed when the application detects loss of power, or when a reboot is triggered.

Note

Before calling the emds_store() function, the application should try shutting down the application-specific features that consume a lot of power. Shutting down these features may prolong the time the CPU is alive, and improve the storage time. For example, if Bluetooth is used, disabling Bluetooth before shutdown will save power, and stopping the MPSL scheduler will shorten the total time required to complete the store operation.

The emds_is_ready() function can be called to check if EMDS is prepared to store the data.

Once the data storage has completed, a callback is called if provided in emds_init(). This callback notifies the application that the data storage has completed, and can be used to reboot the CPU or execute another function that is needed.

After completion of emds_store(), the emds_is_ready() function call will return error, since it can no longer guarantee that the data will fit into the flash area.

The above described process is summarized in a message sequence diagram.

msc {
hscale = "1.3";
Application, EMDS;
--- [ label = "Application initialization started" ];
Application=>EMDS         [ label = "emds_init(emds_store_cb_t)" ];
--- [ label = "Initialization of all functionality that does emds_entry_add()" ];
Application=>EMDS         [ label = "emds_entry_add(1)" ];
Application=>EMDS         [ label = "emds_entry_add(2)" ];
...;
Application=>EMDS         [ label = "emds_entry_add(n)" ];
--- [ label = "All emds_entry_add() executed" ];
Application=>EMDS         [ label = "emds_load()" ];
Application=>EMDS         [ label = "emds_prepare()" ];
--- [ label = "Application initialization ended" ];
...;
Application->Application  [ label = "Interrupt calling emds_store()" ];
Application=>EMDS         [ label = "emds_store()" ];
Application<<=EMDS        [ label = "emds_store_cb_t callback" ];
Application->Application [ label = "Reboot/halt" ];
}

Requirements

To prevent frequent writes to flash memory, the EMDS library can write data to flash only when the device is shutting down. EMDS restores the application data to RAM at reboot.

EMDS can store data within a guaranteed time, based on the amount of data being stored. EMDS can be used to store data in memory in situations of critical power shortage, for example before the device battery is depleted. It is important that the hardware has the appropriate functionality to sustain power long enough for the storage to be completed before the power source is fully discharged.

Configuration

To initialize the emergency data storage, complete the following steps:

  1. Enable the CONFIG_EMDS Kconfig option.

  2. Include the emds/emds.h file in your main.c file.

  3. Create the callback function emds_store_cb_t() that can execute functions after storage has completed. This is optional.

  4. Call the emds_init() function.

  5. Add RAM areas that shall be loaded/stored through emds_entry_add() calls.

  6. Call emds_load().

  7. Call emds_prepare().

  8. Create interrupt or other functionality that will call emds_store().

Application integration

When using EMDS in an application, you need to know the worst case scenario for how long power is required to be available. This knowledge makes it possible for you to make good design choices ensuring enough backup power to reach this time requirement.

The easiest way of computing an estimate of the time required to store all entries, in a worst case scenario, is to call the emds_store_time_get() function. This function returns a worst-case storage time estimate in microseconds (µs) for a given application. For this to work, Kconfig options CONFIG_EMDS_FLASH_TIME_BASE_OVERHEAD_US, CONFIG_EMDS_FLASH_TIME_ENTRY_OVERHEAD_US and CONFIG_EMDS_FLASH_TIME_WRITE_ONE_WORD_US need to be set as described in the Implementation section. The emds_store_time_get() function estimates the required worst-case time to store \(n\) entries using the following formula:

\[t_\text{store} = t_\text{base} + \sum_{i = 1}^n \left(t_\text{entry} + t_\text{word}\left(\left\lceil\frac{s_\text{ate}}{s_\text{block}}\right\rceil + \left\lceil\frac{s_i}{s_\text{block}}\right\rceil \right)\right)\]

where \(t_\text{base}\) is the value specified by CONFIG_EMDS_FLASH_TIME_BASE_OVERHEAD_US, \(t_\text{entry}\) is the value specified by CONFIG_EMDS_FLASH_TIME_ENTRY_OVERHEAD_US and \(t_\text{word}\) is the value specified by CONFIG_EMDS_FLASH_TIME_WRITE_ONE_WORD_US. \(s_i\) is the size of the \(i\)th entry in bytes and \(s_\text{block}\) is the number of bytes in one word of flash. These can be found by looking at datasheets, driver documentation, and the configuration of the application. \(s_\text{ate}\) is the size of the allocation table entry used by the EMDS flash module, which is 8 B.

Example of time estimation

Using the formula from the previous section, you can estimate the time required to store all entries for the Bluetooth Mesh: Light fixture sample running on the nRF52840. The following values can be inserted into the formula:

  • Set \(t_\text{base}\) = 9000 µs. This is the worst case overhead when a store is triggered in the middle of an erase on nRF52840 with CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE enabled in the sample by default, and should be adjusted when using other configurations.

  • Set \(t_\text{entry}\) = 300 µs and \(t_\text{word}\) = 41 µs. Note: These values are valid only for this specific chip and configuration, and should be computed for the specific configuration whenever using EMDS.

  • The sample uses two entries, one for the RPL with 255 entries (\(s_i\) = 2040 B) and one for the lightness state (\(s_i\) = 3 B).

  • The flash write block size \(s_\text{block}\) in this case is 4 B, and the ATE size \(s_\text{ate}\) is 8 B.

This gives the following formula to compute estimated storage time:

\[\begin{split}\begin{aligned} t_\text{store} = 9000\text{ µs} &+ \left( 300\text{ µs} + 41\text{ µs} \times \left( \left\lceil\frac{8\text{ B}}{4\text{ B}}\right\rceil + \left\lceil\frac{2040\text{ B}}{4\text{ B}}\right\rceil \right) \right) \\ &+ \left( 300\text{ µs} + 41\text{ µs} \times \left( \left\lceil\frac{8\text{ B}}{4\text{ B}}\right\rceil + \left\lceil\frac{3\text{ B}}{4\text{ B}}\right\rceil \right) \right) \\ &= 30715\text{ µs} \end{aligned}\end{split}\]

Calling the emds_store_time_get() function in the sample automatically computes the result of the formula and returns 30715.

Limitations

The power-fail comparator for the nRF528xx cannot be used with EMDS, as it will prevent the NVMC from performing write operations to flash.

Dependencies

The emergency data storage is dependent on these Kconfig options:

API documentation

Header file: include/emds/emds.h
Source file: subsys/emds/emds.c
group emds

Emergency Data Storage API.

Defines

EMDS_STATIC_ENTRY_DEFINE(_name, _id, _data, _len)

Define a static entry for emergency data storage items.

This creates a variable name prepended by emds.

Parameters:
  • _name – The entry name.

  • _id – Unique ID for the entry. This value and not an overlap with any other value.

  • _data – Data pointer to be stored at emergency data store.

  • _len – Length of data to be stored at emergency data store.

Typedefs

typedef void (*emds_store_cb_t)(void)

Callback for application commands when storing has been executed.

This can be used to perform other actions or execute busy-waiting until the power supply is discharged. It will be run by emds_store, and called from the same context the emds_store is called from. Interrupts are unlocked before triggering this callback. Therefore, this callback may not be reached before available backup power runs out. If the application needs interrupts to be locked during this callback, application can do so by calling irq_lock before calling emds_store.

Functions

int emds_init(emds_store_cb_t cb)

Initialize the emergency data storage.

Initializes the emergency data storage. This needs to be called before adding entries to the emds_dynamic_entry and loading data.

Parameters:
  • cb – Callback to notify application that the store operation has been executed or NULL if no notification is needed.

Return values:
  • 0 – Success

  • -ERRNO – errno code if error

int emds_entry_add(struct emds_dynamic_entry *entry)

Add entry to be saved/restored when emergency data storage is called.

Adds the entry to the dynamic entry list. When the emds_store function is called, takes the given data pointer and stores the data to the emergency data storage.

Note

EMDS does not make a local copy of the dynamic entry structure.

Parameters:
  • entry – Entry to add to list and load data into.

Return values:
  • 0 – Success

  • -ERRNO – errno code if error

int emds_store(void)

Start the emergency data storage process.

Triggers the process of storing all data registered to be stored. All data registered either through emds_entry_add function or the EMDS_STATIC_ENTRY_DEFINE macro is stored. It locks all interrupts until the write is finished. Once the data storage is completed, the data should not be changed, and the device should be halted. The device must not be allowed to reboot when operating on a backup supply, since reboot will trigger data load from the EMDS storage and clear the storage area. The reboot should only be allowed when the main power supply is available.

This is a time-critical operation and will be processed as fast as possible. To achieve better time predictability, this function must be called from an interrupt context with the highest priority.

When writing to flash, it bypasses the flash driver. Therefore, when running with MPSL, make sure to uninitialize the MPSL before this function is called. Otherwise, an assertion may be triggered by the exit of the function.

Return values:
  • 0 – Success

  • -ERRNO – errno code if error

int emds_load(void)

Load all static data from the emergency data storage.

This function needs to be called after the static entries are added, as they are used to select the data to be loaded. The function also needs to be called before the emds_prepare function which will delete all the previously stored data.

Return values:
  • 0 – Success

  • -ERRNO – errno code if error

int emds_clear(void)

Clear flash area from the emergency data storage.

This function clears the flash area for all previously stored data.

Return values:
  • 0 – Success

  • -ERRNO – errno code if error

int emds_prepare(void)

Prepare flash area for the next emergency data storage.

This function prepares the flash area for the next emergency data storage. It deletes the current entries if there is enough space for the next emergency data storage, and clears the flash storage if there is not enough space for the next storage. This has to be done after all the dynamic entries are added. After this has been called emergency data storage should be ready to store.

Return values:
  • 0 – Success

  • -ERRNO – errno code if error

uint32_t emds_store_time_get(void)

Estimate the time needed to store the registered data.

Estimate how much time it takes to store all dynamic and static data registered in the entries. This value is dependent on the chip used, and should be checked against the chip datasheet.

Returns:

Time needed to store all data (in microseconds).

uint32_t emds_store_size_get(void)

Calculate the size needed to store the registered data.

Calculates the size it takes to store all dynamic and static data registered in the entries.

Returns:

Byte size that is needed to store all data.

bool emds_is_ready(void)

Check if the store operation can be run.

When the data store operation has completed, the store operation is no longer ready and this function will then return false.

Returns:

True if the store operation can be started, otherwise false.

struct emds_entry
#include <emds.h>

Information about entries used to store data in the emergency data storage.

Public Members

uint16_t id

Unique ID for each static and dynamic entry.

uint8_t *data

Pointer to data that will be stored.

size_t len

Length of data that will be stored.

struct emds_dynamic_entry
#include <emds.h>

Entries with a node element, used for dynamic entries. These are registered using a call to emds_entry_add.