diff --git a/doc/nrf/libraries/others/emds.rst b/doc/nrf/libraries/others/emds.rst index 6582606ba54d..b9f80e4cbb93 100644 --- a/doc/nrf/libraries/others/emds.rst +++ b/doc/nrf/libraries/others/emds.rst @@ -14,43 +14,62 @@ Implementation ************** The :kconfig:option:`CONFIG_EMDS` Kconfig option enables the emergency data storage. -The application must initialize the pre-allocated storage area by using the :c:func:`emds_init` function. -The :kconfig:option:`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. +The EMDS uses the persistent memory in form of storage partitions, which can be either flash or RRAM. +The static partition manager file :file:`pm.yml.emds` is required for defining the storage partitions. +You must align the storage partitions to the write block size, which is 4 bytes for flash and 16 bytes for RRAM. +The EMDS mandates at least two storage partitions to allow restoring of the last known copy of data. +The application must initialize storage partitions by using the :c:func:`emds_init` function. + +The partition storage space for each partition must be larger than the combined data stored by the application. +To find out how much storage space is needed, call the :c:func:`emds_store_size_get` function. +Additionally, consider the size of the metadata, which is 32 bytes for each snapshot. Allocating a larger storage area will demand more resources, but also increase the life expectancy of the persistent memory. The chosen size should reflect the amount of data stored, the available persistent memory and how the application calls the :c:func:`emds_store` function. -In general, it should not be necessary to allocate a large storage area, since only a limited set of data should be stored to ensure swift completion of writing the data 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 :c:func:`emds_entry_add` function and the :c:macro:`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 :c:func:`emds_load` function restores the entries into the memory areas from the persistent memory. - -After restoring the previous data, the application must run the :c:func:`emds_prepare` function to prepare the storage area for receiving new entries. -If the remaining empty storage area is smaller than the required data size, the storage area will be automatically erased to increase the available storage area. +The entries are stored in the partition in form of snapshots. +The snapshot is a packed copy of all entries that are registered with the EMDS library for storage in the data area of the partition. +Each snapshot is described by the metadata that is stored in the metadata area of the partition. + +After initialization is completed by registering all EMDS entries, the application calls the :c:func:`emds_load` function. +This starts a partition scanning procedure to find the latest snapshot. +As a part of loading procedure, the EMDS scans the metadata area of both partitions. +The :kconfig:option:`CONFIG_EMDS_SCANNING_FAILURES` Kconfig option configures the depth of the scanning procedure. +The depth is the number of times the scanning procedure can fail in a row before it stops, because most likely, there is no stored metadata left. +The scanning procedure is performed by reading the metadata area of the partition and checking the integrity of the metadata. +The procedure gathers the most recent snapshots (set by :kconfig:option:`CONFIG_EMDS_MAX_CANDIDATES`) from the partition metadata area. +After the procedure is completed, the EMDS checks data integrity on the gathered candidates, starting from the latest one stored on the partition. +If the data integrity checks pass, the entries are restored into the memory areas from the persistent memory. +If candidates are not found or the data integrity check does not pass, the function returns an error. +The :c:func:`emds_load` function returns an error code if it runs on the empty partitions. + +After restoring the previous data, the application must call the :c:func:`emds_prepare` function to prepare the storage area for receiving new entries. +Initially, the EMDS library tries to allocate the storage area for the new entries in the partition that has the most recent snapshot to use the maximum partition capacity. +However, if the partition is full, the EMDS tries to allocate the storage area in the other partition. +If the other partition is full, the EMDS erases the entire partition to get space for a new snapshot, depending on the type of persistent memory. +Devices with flash memory require an explicit erase, whereas devices with RRAM do not need an explicit erase. +The EMDS library handles this internally. +The EMDS never erases the partition that has the most recent snapshot found during the partitions scanning procedure. 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. Find timing values under the "Electrical specification" section for the non-volatile memory controller in the Product Specification for the relevant SoC or the SiP you are using. For example, for the nRF52840 SiP, see the `nRF52840 Product Specification`_ page. +The data is stored by chunks of 16 bytes. +The storing time is determined by the chunk preparation time and the flash writing time, and depends on both the number of stored data bytes (both data and metadata) as well as the number of chunks. -The following Kconfig options can be configured: +The following (non-public) Kconfig options are needed for the time estimation: * :kconfig:option:`CONFIG_EMDS_FLASH_TIME_WRITE_ONE_WORD_US` -* :kconfig:option:`CONFIG_EMDS_FLASH_TIME_ENTRY_OVERHEAD_US` -* :kconfig:option:`CONFIG_EMDS_FLASH_TIME_BASE_OVERHEAD_US` +* :kconfig:option:`CONFIG_EMDS_CHUNK_PREPARATION_TIME_US` -When configuring these values, consider the time for erase when doing garbage collection in NVS using flash. -If partial erase is not enabled or supported, the time of a complete sector erase has to be included in the :kconfig:option:`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 :kconfig:option:`CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE_MS`. -When changing the :kconfig:option:`CONFIG_EMDS_FLASH_TIME_BASE_OVERHEAD_US` option, it is important that the worst time is considered. -When configuring these values using RRAM, you do not need to consider garbage collection in the same way as for flash, as it can be written to regardless of value. -However, avoid the ERASEALL functionality, because that can increase the time before the EMDS store functions are called. +You can tune these options to influence the estimation of the writing time (see :c:func:`emds_store_time_get`), but they do not change the actual time needed for storing the snapshot. +It is recommended to consider the worst case scenarios when adjusting these options. The application must call the :c:func:`emds_store` function to store all entries. -This can only be done once, before the :c:func:`emds_prepare` function must be called again. +This can only be done once, before the :c:func:`emds_load` and :c:func:`emds_prepare` functions must be called again. When invoked, the :c:func:`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. @@ -120,17 +139,21 @@ This knowledge makes it possible for you to make good design choices ensuring en The easiest way of computing an estimate of the time required to store all entries, in a worst case scenario, is to call the :c:func:`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 :kconfig:option:`CONFIG_EMDS_FLASH_TIME_BASE_OVERHEAD_US`, :kconfig:option:`CONFIG_EMDS_FLASH_TIME_ENTRY_OVERHEAD_US` and :kconfig:option:`CONFIG_EMDS_FLASH_TIME_WRITE_ONE_WORD_US` need to be set as described in the `Implementation`_ section. +To make this work, you need to determine and set the Kconfig options :kconfig:option:`CONFIG_EMDS_FLASH_TIME_WRITE_ONE_WORD_US` and :kconfig:option:`CONFIG_EMDS_CHUNK_PREPARATION_TIME_US` as described in the `Implementation`_ section for your platform. The :c:func:`emds_store_time_get` function estimates the required worst-case time to store :math:`n` entries using the following formula: .. math:: - 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) + t_\text{store} = t_\text{word}\lceil\frac{s_\text{meta}}{s_\text{word}}\rceil + t_\text{word} \sum_{i = 1}^n (\left\lceil\frac{s_i}{s_\text{word}}\right\rceil) + t_\text{chunk} \sum_{i = 1}^n (\left\lceil\frac{s_i}{s_\text{chunk}}\right\rceil) + +where -where :math:`t_\text{base}` is the value specified by :kconfig:option:`CONFIG_EMDS_FLASH_TIME_BASE_OVERHEAD_US`, :math:`t_\text{entry}` is the value specified by :kconfig:option:`CONFIG_EMDS_FLASH_TIME_ENTRY_OVERHEAD_US` and :math:`t_\text{word}` is the value specified by :kconfig:option:`CONFIG_EMDS_FLASH_TIME_WRITE_ONE_WORD_US`. -:math:`s_i` is the size of the :math:`i`\ th entry in bytes and :math:`s_\text{block}` is the number of bytes in one word (4 bytes) of flash or the write-buffer size (16 bytes) of RRAM. -These can be found by looking at datasheets, driver documentation, and the configuration of the application. -:math:`s_\text{ate}` is the size of the allocation table entry used by the EMDS, which is 8 B. +:math:`t_\text{word}` is the value specified by :kconfig:option:`CONFIG_EMDS_FLASH_TIME_WRITE_ONE_WORD_US`. +:math:`t_\text{chunk}` is the value specified by :kconfig:option:`CONFIG_EMDS_CHUNK_PREPARATION_TIME_US`. +:math:`s_i` is the size of the :math:`i`\ th entry in bytes(entry data length + 4 bytes entry header). +:math:`s_\text{meta}` is the size of the snapshot metadata (32 bytes). +:math:`s_\text{block}` is the number of bytes in one word (4 bytes). +:math:`s_\text{chunk}` is the number of bytes in one chunk (16 bytes). Example of time estimation ========================== @@ -138,22 +161,21 @@ Example of time estimation Using the formula from the previous section, you can estimate the time required to store all entries for the :ref:`bluetooth_mesh_light_lc` sample running on the nRF52840. The following values can be inserted into the formula: -* Set :math:`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 :kconfig:option:`CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE` enabled in the sample by default, and should be adjusted when using other configurations. -* Set :math:`t_\text{entry}` = 300 µs and :math:`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 (:math:`s_i` = 2040 B) and one for the lightness state (:math:`s_i` = 3 B). -* The flash write block size :math:`s_\text{block}` in this case is 4 B, and the ATE size :math:`s_\text{ate}` is 8 B. +* Set :math:`t_\text{chunk}` = 31 µs and :math:`t_\text{word}` = 41 µs. + 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 (:math:`s_i` = 2040 + 4 B) and one for the lightness state (:math:`s_i` = 3 + 4 B). +* The flash write block size :math:`s_\text{block}` in this case is 4 B. +* The chunk size :math:`s_\text{chunk}` is 16 B. +* The size of the snapshot metadata :math:`s_\text{meta}` is 32 B. This gives the following formula to compute estimated storage time: .. math:: \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} + t_\text{store} = 41{ µs}(\frac{32{ B}}{4{ B}}) + 41{ µs}(\frac{2044{ B} + 7{ B}}{4{ B}}) + 31{ µs}(\frac{2044{ B} + 7{ B}}{16{ B}}) = 25360\text{ µs} \end{aligned} -Calling the :c:func:`emds_store_time_get` function in the sample automatically computes the result of the formula and returns 30715. +Calling the :c:func:`emds_store_time_get` function in the sample automatically computes the result of the formula and returns 25360. Limitations *********** diff --git a/include/emds/emds.h b/include/emds/emds.h index 442dba433094..45aa91ad0f5b 100644 --- a/include/emds/emds.h +++ b/include/emds/emds.h @@ -144,17 +144,22 @@ int emds_store(void); * previously stored data. * * @retval 0 Success - * @retval -ERRNO errno code if error + * @retval -ECANCELED errno code if it was called before @ref emds_init + * @retval -ENOENT errno code if no valid snapshot was found in any partition + * @retval -EIO errno code if error during reading data */ int emds_load(void); /** - * @brief Clear flash area from the emergency data storage. + * @brief Clear flash areas allocated for the emergency data storage. * - * This function clears the flash area for all previously stored data. + * This function erases all the flash areas allocated for the emergency data. + * Calling of this API is optional for emergency data storage working algorithms, + * since EMDS erases the flash areas before storing data if needed. * * @retval 0 Success - * @retval -ERRNO errno code if error + * @retval -ECANCELED errno code if it was called before @ref emds_init + * @retval -EIO errno code if error occurs during erasure. */ int emds_clear(void); @@ -169,7 +174,8 @@ int emds_clear(void); * store. * * @retval 0 Success - * @retval -ERRNO errno code if error + * @retval -ECANCELED errno code if it was called before @ref emds_init and @ref emds_load + * @retval -ENOENT errno code if no valid snapshot was found in any partition */ int emds_prepare(void); @@ -180,9 +186,13 @@ int emds_prepare(void); * registered in the entries. This value is dependent on the chip used, and * should be checked against the chip datasheet. * - * @return Time needed to store all data (in microseconds). + * @param store_time_us Pointer to a variable where the estimated time (in microseconds) + * will be stored. + * + * @return 0 on success. + * @retval -ECANCELED if the function was called before @ref emds_init. */ -uint32_t emds_store_time_get(void); +int emds_store_time_get(uint32_t *store_time_us); /** * @brief Calculate the size needed to store the registered data. @@ -190,9 +200,12 @@ uint32_t emds_store_time_get(void); * Calculates the size it takes to store all dynamic and static data registered * in the entries. * - * @return Byte size that is needed to store all data. + * @param store_size Pointer to a variable where the size will be stored. + * + * @return 0 on success. + * @retval -ECANCELED if the function was called before @ref emds_init. */ -uint32_t emds_store_size_get(void); +int emds_store_size_get(size_t *store_size); /** * @brief Check if the store operation can be run. diff --git a/samples/bluetooth/mesh/light_ctrl/overlay-emds.conf b/samples/bluetooth/mesh/light_ctrl/overlay-emds.conf index a4ee7947fdd4..21154e7b8be0 100644 --- a/samples/bluetooth/mesh/light_ctrl/overlay-emds.conf +++ b/samples/bluetooth/mesh/light_ctrl/overlay-emds.conf @@ -11,3 +11,5 @@ CONFIG_BT_MESH_RPL_STORAGE_MODE_EMDS=y CONFIG_DYNAMIC_INTERRUPTS=y CONFIG_DYNAMIC_DIRECT_INTERRUPTS=y CONFIG_MPSL_DYNAMIC_INTERRUPTS=y + +CONFIG_PM_PARTITION_SIZE_EMDS_STORAGE=0x4000 diff --git a/samples/bluetooth/mesh/light_ctrl/sample.yaml b/samples/bluetooth/mesh/light_ctrl/sample.yaml index ef800ccb1a05..6caa880596bc 100644 --- a/samples/bluetooth/mesh/light_ctrl/sample.yaml +++ b/samples/bluetooth/mesh/light_ctrl/sample.yaml @@ -38,7 +38,6 @@ tests: extra_args: OVERLAY_CONFIG="overlay-emds.conf" build_only: true integration_platforms: - - nrf52dk/nrf52832 - nrf52840dk/nrf52840 - nrf52833dk/nrf52833 - nrf5340dk/nrf5340/cpuapp @@ -49,7 +48,6 @@ tests: - nrf54l15dk/nrf54l10/cpuapp - nrf54l15dk/nrf54l05/cpuapp platform_allow: - - nrf52dk/nrf52832 - nrf52840dk/nrf52840 - nrf5340dk/nrf5340/cpuapp - nrf5340dk/nrf5340/cpuapp/ns diff --git a/samples/bluetooth/mesh/light_ctrl/src/main.c b/samples/bluetooth/mesh/light_ctrl/src/main.c index 4a6f27c77ecc..e82a4768258d 100644 --- a/samples/bluetooth/mesh/light_ctrl/src/main.c +++ b/samples/bluetooth/mesh/light_ctrl/src/main.c @@ -112,9 +112,13 @@ static void bt_ready(int err) #ifdef CONFIG_EMDS err = emds_load(); - if (err) { + if (err && err != -ENOENT) { printk("Restore of emds data failed (err %d)\n", err); return; + } else if (err == -ENOENT) { + printk("No valid emds data found, starting fresh\n"); + } else { + printk("Restored emds data successfully\n"); } err = emds_prepare(); diff --git a/subsys/emds/Kconfig b/subsys/emds/Kconfig index 48f9426b9934..9e1deda2d94b 100644 --- a/subsys/emds/Kconfig +++ b/subsys/emds/Kconfig @@ -5,87 +5,64 @@ # menuconfig EMDS - bool "Emergency Data Storage [EXPERIMENTAL]" - select EXPERIMENTAL + bool "Emergency Data Storage" + select CRC + select CRC32_K_4_2_TABLE_256 depends on PARTITION_MANAGER_ENABLED depends on FLASH_MAP - depends on CRC - default n help - Enable Emergency Data Storage + Enable Emergency Data Storage subsystem. if EMDS -config EMDS_SECTOR_COUNT - int "Sector count of the emergency data storage area" - default 1 +config EMDS_SCANNING_FAILURES + int "Maximum number of scanning failures" + default 2 help - Number of sectors used for the emergency data storage area + Maximum number of allowed metadata scanning failures on partition + before giving up. The option should be tradeoff between + performance and reliability. If the value is too low, the system + may not take into account all the candidates, and may not select + the best one for recovery. If the value is too high, the system + may take too long to scan the partition, and may not be able to + recover in time. The value should be set to the number of + expected failures during the scanning process. -config EMDS_THREAD_STACK_SIZE - int "Stack size for the emergency data storage thread" - default 500 +config EMDS_MAX_CANDIDATES + int "Maximum number of snapshot candidates" + default 3 help - Size of the stack that is initialized by the emergency data storage - -config EMDS_THREAD_PRIORITY - int "Priority for the emergency data storage thread" - default 0 - help - Cooperative priority for the emergency data storage thread. This will - be used through K_PRIO_COOP(x), that means higher value gives lower - priority. + Maximum number of snapshot candidates to keep track within + the partition to select the best one for recovery. config EMDS_FLASH_TIME_WRITE_ONE_WORD_US - int "Time to write block size into flash/rram" - default 107 if SOC_SERIES_NRF54LX - default 43 if SOC_SERIES_NRF53X + int + default 41 if SOC_NRF52840 default 43 if SOC_NRF52833 - default 338 if SOC_NRF52832 - default 41 + default 43 if SOC_SERIES_NRF53X + default 28 if SOC_SERIES_NRF54LX help - Max time to write one block into non-volatile storage (in microseconds). - The block size is dependent on non-volatile storage type and is 4 - bytes for flash and 16 bytes for RRAM. The value is dependent on the + Max time to write one word into non-volatile storage (in microseconds). + The word size is 4 bytes. The value is dependent on the chip used, and should be checked against the chip's datasheet. - For RRAM-based flash drivers, data is written in blocks of 16 bytes - over buffered writing. This value is used to calculate the time to - write one 16-byte block. + For RRAM-based flash driver the average time to write a 32-bit word + in a stream of sequentially address ordered writes was taken. -config EMDS_FLASH_TIME_ENTRY_OVERHEAD_US - int "Time to schedule write of one entry" - default 300 - help - Max time to prepare the write of each entry (in microseconds). + Note that despite the typical time for streamed writing of a 32-bit word + for RRAM is 22 microseconds the actual time may vary. It is used a bit bigger + value here to compensate dispersion around the typical time. -config EMDS_FLASH_TIME_BASE_OVERHEAD_US - int "Time to schedule the store process" - default 1000 if SETTINGS && SOC_FLASH_NRF_RRAM - default 85000 if SETTINGS && !SOC_FLASH_NRF_PARTIAL_ERASE - default 9000 if SETTINGS && SOC_FLASH_NRF_PARTIAL_ERASE - default 500 +config EMDS_CHUNK_PREPARATION_TIME_US + int + default 31 if SOC_NRF52840 + default 31 if SOC_NRF52833 + default 31 if SOC_SERIES_NRF53X + default 8 if SOC_SERIES_NRF54LX help - Max time to prepare the store process (in microseconds). If used - together with SETTINGS this value has to be at least as long as the - time to erase one page (ERASEPAGE). If used together with - SOC_FLASH_NRF_PARTIAL_ERASE this value should be - (SOC_FLASH_NRF_PARTIAL_ERASE_MS * 1000 * 3). This is to account for - worst case scenario, before starting storing entries. This value - is dependent on the chip used, and should be checked against the chip - datasheet. - For RRAM-based persistent memory driver, use ERASEPROTECT and disregard the SOC_FLASH_NRF_PARTIAL_ERASE_MS parameter. - -if SOC_FLASH_NRF_RRAM - -config EMDS_RRAM_WRITE_BUFFER_SIZE - int "Internal write-buffer size" - default 1 - range 1 32 - help - Number of 128-bit words. - Maximum buffer size can be configured to the value of 32 (128-bit words). - -endif # SOC_FLASH_NRF_RRAM + Time that is required to prepare a chunk for storing. + It includes creation chunk from entries, crc calculation and + prologue/epilogue time of participated functions. + Time is approximate and depends on entry sizes and number of entries. module = EMDS module-str = emergency data storage diff --git a/subsys/emds/emds.c b/subsys/emds/emds.c index 69c00ee6d3ed..fc4315af68e0 100644 --- a/subsys/emds/emds.c +++ b/subsys/emds/emds.c @@ -1,116 +1,165 @@ /* - * Copyright (c) 2022 Nordic Semiconductor ASA + * Copyright (c) 2025 Nordic Semiconductor ASA * * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ #include -#include -#include -#include #include "emds_flash.h" +#include +#include + #include LOG_MODULE_REGISTER(emds, CONFIG_EMDS_LOG_LEVEL); -static bool emds_ready; -static bool emds_initialized; - -static sys_slist_t emds_dynamic_entries; -static struct emds_fs emds_flash; -static emds_store_cb_t app_store_cb; +#if NRF52_ERRATA_242_PRESENT +#include +/* Disable POFWARN by writing POFCON before a write or erase operation. + * Do not attempt to write or erase if EVENTS_POFWARN is already asserted. + */ +static bool pofcon_enabled; -static int emds_fs_init(void) +static int suspend_pofwarn(void) { - int rc; - const struct flash_area *fa; - uint32_t sector_cnt = 1; - struct flash_sector hw_fs; - size_t emds_sector_size; - size_t emds_storage_size = 0; - uint16_t cnt = 0; - - rc = flash_area_open(FIXED_PARTITION_ID(emds_storage), &fa); - if (rc) { - return rc; + if (!nrf52_errata_242()) { + return 0; } - rc = flash_area_get_sectors(FIXED_PARTITION_ID(emds_storage), §or_cnt, - &hw_fs); - if (rc == -ENODEV) { - return rc; - } else if (rc != 0 && rc != -ENOMEM) { - k_panic(); - } + bool enabled; + nrf_power_pof_thr_t pof_thr; - emds_sector_size = hw_fs.fs_size; + pof_thr = nrf_power_pofcon_get(NRF_POWER, &enabled); - if (emds_sector_size > UINT16_MAX) { - return -EDOM; - } + if (enabled) { + nrf_power_pofcon_set(NRF_POWER, false, pof_thr); - while (cnt < CONFIG_EMDS_SECTOR_COUNT) { - emds_storage_size += emds_sector_size; - if (emds_storage_size > fa->fa_size) { - break; + /* This check need to be reworked once POFWARN event will be + * served by zephyr. + */ + if (nrf_power_event_check(NRF_POWER, NRF_POWER_EVENT_POFWARN)) { + nrf_power_pofcon_set(NRF_POWER, true, pof_thr); + return -ECANCELED; } - cnt++; + + pofcon_enabled = enabled; } - emds_flash.sector_size = emds_sector_size; - emds_flash.sector_cnt = cnt; - emds_flash.offset = fa->fa_off; - emds_flash.flash_dev = fa->fa_dev; + return 0; +} + +static void restore_pofwarn(void) +{ + nrf_power_pof_thr_t pof_thr; - return emds_flash_init(&emds_flash); + if (pofcon_enabled) { + pof_thr = nrf_power_pofcon_get(NRF_POWER, NULL); + + nrf_power_pofcon_set(NRF_POWER, true, pof_thr); + pofcon_enabled = false; + } } +#define SUSPEND_POFWARN() suspend_pofwarn() +#define RESUME_POFWARN() restore_pofwarn() +#else +#define SUSPEND_POFWARN() 0 +#define RESUME_POFWARN() +#endif /* NRF52_ERRATA_242_PRESENT */ -static int emds_entries_size(uint32_t *size) -{ - size_t block_size = emds_flash.flash_params->write_block_size; - int entries = 0; +#define PARTITIONS_NUM_MAX 2 +#define CHUNK_SIZE 16 - *size = 0; +enum emds_state { + EMDS_STATE_NOT_INITIALIZED, + EMDS_STATE_INITIALIZED, + EMDS_STATE_SYNCHRONIZED, + EMDS_STATE_READY, +}; - STRUCT_SECTION_FOREACH(emds_entry, ch) { - *size += DIV_ROUND_UP(ch->len, block_size) * block_size; - *size += DIV_ROUND_UP(emds_flash.ate_size, block_size) * block_size; - entries++; +static enum emds_state emds_state = EMDS_STATE_NOT_INITIALIZED; +static struct emds_snapshot_candidate freshest_snapshot; +static struct emds_snapshot_candidate allocated_snapshot; + +static sys_slist_t emds_dynamic_entries; +static struct emds_partition partition[PARTITIONS_NUM_MAX]; +static emds_store_cb_t app_store_cb; + +static void emds_print_init_info(void) +{ + LOG_DBG("EMDS initialized with the following partitions:"); + for (int i = 0; i < PARTITIONS_NUM_MAX; i++) { + LOG_DBG("Flash device: %s", partition[i].fa->fa_dev->name); + LOG_DBG("Partition %d: ID=%d, Offset=0x%04lx, Size=0x%04zx", i, + partition[i].fa->fa_id, partition[i].fa->fa_off, partition[i].fa->fa_size); + LOG_DBG("Flash parameters: Write block size=%zu, Erase value=%zu", + partition[i].fp->write_block_size, partition[i].fp->erase_value); + LOG_DBG("%s", flash_params_get_erase_cap(partition[i].fp) & FLASH_ERASE_C_EXPLICIT + ? "Explicit erase available" + : "Explicit erase not available"); } +} - struct emds_dynamic_entry *ch; +static int emds_partition_init(const uint8_t id, struct emds_partition *partition) +{ + int rc; - SYS_SLIST_FOR_EACH_CONTAINER(&emds_dynamic_entries, ch, node) { - *size += DIV_ROUND_UP(ch->entry.len, block_size) * block_size; - *size += DIV_ROUND_UP(emds_flash.ate_size, block_size) * block_size; - entries++; + rc = flash_area_open(id, &partition->fa); + if (rc) { + LOG_ERR("Failed to open EMDS flash storage: %d", rc); + return rc; } - return entries; + rc = emds_flash_init(partition); + if (rc) { + LOG_ERR("Failed to initialize EMDS flash storage: %d", rc); + return rc; + } + + return 0; } int emds_init(emds_store_cb_t cb) { + const uint8_t id[] = {FIXED_PARTITION_ID(emds_partition_0), + FIXED_PARTITION_ID(emds_partition_1)}; int rc; - if (emds_initialized) { + if (emds_state != EMDS_STATE_NOT_INITIALIZED) { LOG_DBG("Already initialized"); return -EALREADY; } - rc = emds_fs_init(); - if (rc) { - return rc; + if (!FIXED_PARTITION_EXISTS(emds_partition_0) || + !FIXED_PARTITION_EXISTS(emds_partition_1)) { + LOG_ERR("EMDS partitions not found: %d, %d", id[0], id[1]); + return -ENODEV; } - sys_slist_init(&emds_dynamic_entries); + for (int i = 0; i < PARTITIONS_NUM_MAX; i++) { + rc = emds_partition_init(id[i], &partition[i]); + if (rc) { + return rc; + } + } - app_store_cb = cb; + if (REGIONS_OVERLAP(partition[0].fa->fa_off, partition[0].fa->fa_size, + partition[1].fa->fa_off, partition[1].fa->fa_size)) { + LOG_ERR("Flash areas overlap"); + return -EINVAL; + } + + if (partition[0].fp->write_block_size != partition[1].fp->write_block_size) { + LOG_ERR("Write block sizes of partitions do not match"); + return -EINVAL; + } - emds_initialized = true; + emds_print_init_info(); + sys_slist_init(&emds_dynamic_entries); + app_store_cb = cb; + emds_state = EMDS_STATE_INITIALIZED; LOG_DBG("Emergency Data Storage initialized."); return 0; @@ -120,10 +169,16 @@ int emds_entry_add(struct emds_dynamic_entry *entry) { struct emds_dynamic_entry *ch; - if (!emds_initialized) { + if (emds_state != EMDS_STATE_INITIALIZED) { return -ECANCELED; } + STRUCT_SECTION_FOREACH(emds_entry, static_entry) { + if (static_entry->id == entry->entry.id) { + return -EINVAL; + } + } + SYS_SLIST_FOR_EACH_CONTAINER(&emds_dynamic_entries, ch, node) { if (ch->entry.id == entry->entry.id) { return -EINVAL; @@ -132,59 +187,122 @@ int emds_entry_add(struct emds_dynamic_entry *entry) sys_slist_append(&emds_dynamic_entries, &entry->node); - emds_ready = false; - return 0; } -int emds_store(void) +static int emds_entries_size(size_t *size) { - uint32_t store_key; + int entries = 0; + + *size = 0; + + STRUCT_SECTION_FOREACH(emds_entry, ch) { + *size += ch->len + sizeof(struct emds_data_entry); + entries++; + } - if (!emds_ready) { + struct emds_dynamic_entry *ch; + + SYS_SLIST_FOR_EACH_CONTAINER(&emds_dynamic_entries, ch, node) { + *size += ch->entry.len + sizeof(struct emds_data_entry); + entries++; + } + + return entries; +} + +int emds_store_size_get(size_t *store_size) +{ + if (emds_state == EMDS_STATE_NOT_INITIALIZED) { return -ECANCELED; } - /* Lock all interrupts */ - store_key = irq_lock(); + (void)emds_entries_size(store_size); + + return 0; +} + +bool emds_is_ready(void) +{ + return emds_state == EMDS_STATE_READY; +} + +int emds_store_time_get(uint32_t *store_time) +{ + size_t store_size = 0; + size_t words; + int chunk_handling; + int rc; - /* Start the emergency data storage process. */ - LOG_DBG("Emergency Data Storeage released"); + rc = emds_store_size_get(&store_size); + if (rc) { + return rc; + } + + words = DIV_ROUND_UP(store_size, 4); + words += DIV_ROUND_UP(sizeof(struct emds_snapshot_metadata), 4); + chunk_handling = DIV_ROUND_UP(store_size, CHUNK_SIZE); + + *store_time = words * CONFIG_EMDS_FLASH_TIME_WRITE_ONE_WORD_US; + *store_time += chunk_handling * CONFIG_EMDS_CHUNK_PREPARATION_TIME_US; + + return 0; +} +static uint8_t *emds_entry_memory_get(struct emds_data_entry *entry) +{ STRUCT_SECTION_FOREACH(emds_entry, ch) { - ssize_t len = emds_flash_write(&emds_flash, - ch->id, ch->data, ch->len); - if (len < 0) { - LOG_ERR("Write static entry: (%d) error (%d)", - ch->id, len); - } else if (len != ch->len) { - LOG_ERR("Write static entry: (%d) failed (%d:%d)", - ch->id, ch->len, len); + if (ch->id == entry->id) { + entry->length = MIN(ch->len, entry->length); + return ch->data; } } struct emds_dynamic_entry *ch; SYS_SLIST_FOR_EACH_CONTAINER(&emds_dynamic_entries, ch, node) { - ssize_t len = emds_flash_write(&emds_flash, - ch->entry.id, ch->entry.data, ch->entry.len); - if (len < 0) { - LOG_ERR("Write dynamic entry: (%d) error (%d).", - ch->entry.id, len); - } - if (len != ch->entry.len) { - LOG_ERR("Write dynamic entry: (%d) failed (%d:%d).", - ch->entry.id, ch->entry.len, len); + if (ch->entry.id == entry->id) { + entry->length = MIN(ch->entry.len, entry->length); + return ch->entry.data; } } - emds_ready = false; + LOG_WRN("Entry with ID %u not found", entry->id); + return NULL; +} - /* Unlock all interrupts */ - irq_unlock(store_key); +static int emds_read_data(const struct flash_area *fa, struct emds_snapshot_metadata *metadata) +{ + struct emds_data_entry entry; + off_t data_off = metadata->data_instance_off; + int32_t data_len = metadata->data_instance_len; + int32_t flash_entry_data_len; + uint8_t *data_buf; + int rc; - if (app_store_cb) { - app_store_cb(); + while (data_len > 0) { + rc = flash_area_read(fa, data_off, &entry, sizeof(entry)); + if (rc) { + LOG_ERR("Failed to read data entry: %d", rc); + return -EIO; + } + + data_off += sizeof(entry); + data_len -= sizeof(entry); + flash_entry_data_len = entry.length; + + data_buf = emds_entry_memory_get(&entry); + + if (data_buf) { + rc = flash_area_read(fa, data_off, data_buf, entry.length); + if (rc) { + LOG_ERR("Failed to read data for entry ID %u: %d", entry.id, rc); + return -EIO; + } + } + + data_off += flash_entry_data_len; + data_len -= flash_entry_data_len; } return 0; @@ -192,113 +310,244 @@ int emds_store(void) int emds_load(void) { - struct emds_dynamic_entry *ch; + struct emds_snapshot_candidate candidate = {0}; - if (!emds_initialized) { + if (emds_state == EMDS_STATE_NOT_INITIALIZED) { return -ECANCELED; } - SYS_SLIST_FOR_EACH_CONTAINER(&emds_dynamic_entries, ch, node) { - ssize_t len = emds_flash_read(&emds_flash, - ch->entry.id, ch->entry.data, - ch->entry.len); - - if (len < 0) { - if (len != -ENXIO) { - LOG_ERR("Read dynamic entry: (%d) error (%d)", - ch->entry.id, len); - } - } else if (len != ch->entry.len) { - LOG_WRN("Read dynamic entry: (%d) did not match (%d:%d).", - ch->entry.id, ch->entry.len, len); + for (int i = 0; i < PARTITIONS_NUM_MAX; i++) { + if (emds_flash_scan_partition(&partition[i], &candidate)) { + LOG_ERR("Failed to scan partition: %d", i); + continue; } - } - - STRUCT_SECTION_FOREACH(emds_entry, ch) { - ssize_t len = emds_flash_read(&emds_flash, - ch->id, ch->data, ch->len); - if (len < 0) { - if (len != -ENXIO) { - LOG_ERR("Read static entry: (%d) error (%d)", - ch->id, len); - } - } else if (len != ch->len) { - LOG_WRN("Read static entry: (%d) entry did not match (%d:%d)", - ch->id, ch->len, len); + if (freshest_snapshot.metadata.fresh_cnt < candidate.metadata.fresh_cnt) { + freshest_snapshot = candidate; + freshest_snapshot.partition_index = i; } } - return 0; -} + emds_state = EMDS_STATE_SYNCHRONIZED; -int emds_clear(void) -{ - if (!emds_initialized) { - return -ECANCELED; + if (freshest_snapshot.metadata.fresh_cnt == 0) { + LOG_WRN("No valid snapshot found in any partition"); + return -ENOENT; } - return emds_flash_clear(&emds_flash); + LOG_DBG("Found freshest snapshot in partition %d with fresh_cnt %u", + freshest_snapshot.partition_index, freshest_snapshot.metadata.fresh_cnt); + + return emds_read_data(partition[freshest_snapshot.partition_index].fa, + &freshest_snapshot.metadata); } int emds_prepare(void) { - uint32_t size; - int rc; + size_t data_size; + bool erase_enabled = false; + int idx = 0; + int freshest_partition_idx = -1; + int rc = 0; - if (!emds_initialized) { + if (emds_state != EMDS_STATE_SYNCHRONIZED) { return -ECANCELED; } - (void)emds_entries_size(&size); + /* Returned status is not checked since initialization state is checked above */ + (void)emds_store_size_get(&data_size); + + allocated_snapshot.metadata.fresh_cnt = freshest_snapshot.metadata.fresh_cnt + 1; + + /* First try to allocate snapshot in the same partition where freshest snapshot exists */ + if (freshest_snapshot.metadata.fresh_cnt > 0) { + freshest_partition_idx = freshest_snapshot.partition_index; + rc = emds_flash_allocate_snapshot(&partition[freshest_partition_idx], + &freshest_snapshot, &allocated_snapshot, + data_size); + if (rc == 0) { + allocated_snapshot.partition_index = freshest_partition_idx; + emds_state = EMDS_STATE_READY; + return 0; + } + rc = 0; + } - rc = emds_flash_prepare(&emds_flash, size); - if (rc) { - return rc; + do { + if (idx != freshest_partition_idx) { + if (erase_enabled) { + LOG_DBG("Erase partition %d", idx); + rc = emds_flash_erase_partition(&partition[idx]); + if (rc) { + LOG_ERR("Failed to erase partition %d: %d", idx, rc); + break; + } + } + + rc = emds_flash_allocate_snapshot(&partition[idx], NULL, + &allocated_snapshot, data_size); + if (rc == 0) { + allocated_snapshot.partition_index = idx; + emds_state = EMDS_STATE_READY; + return 0; + } + } + + idx++; + + if ((idx == PARTITIONS_NUM_MAX) && !erase_enabled && rc == -EADDRINUSE) { + erase_enabled = true; + idx = 0; + } + } while (idx < PARTITIONS_NUM_MAX); + + return -ENOENT; +} + +static void data_stream_pack(uint8_t *in, uint8_t *out, size_t *wp, size_t *rp, size_t len) +{ + size_t size = MIN(CHUNK_SIZE - *wp, len - *rp); + + memcpy(out + *wp, in + *rp, size); + *rp += size; + *wp += size; +} + +static void data_to_stream(const struct emds_partition *partition, off_t *data_off, uint8_t *in, + uint8_t *out, size_t *wp, size_t len) +{ + size_t rp = 0; + + while (rp != len) { + data_stream_pack(in, out, wp, &rp, len); + if (*wp == CHUNK_SIZE) { + allocated_snapshot.metadata.snapshot_crc = crc32_k_4_2_update( + allocated_snapshot.metadata.snapshot_crc, out, *wp); + emds_flash_write_data(partition, *data_off, out, *wp); + *data_off += *wp; + *wp = 0; + } } +} - emds_ready = true; +static void entry_to_stream(const struct emds_partition *partition, off_t *data_off, uint8_t *out, + size_t *wp, struct emds_entry *entry) +{ + struct emds_data_entry data_entry = { + .id = entry->id, + .length = entry->len, + }; + + LOG_DBG("Storing entry ID %u, length %u", entry->id, entry->len); + data_to_stream(partition, data_off, (uint8_t *)&data_entry, out, wp, sizeof(data_entry)); + data_to_stream(partition, data_off, entry->data, out, wp, entry->len); +} - return 0; +static void stream_fflush(const struct emds_partition *partition, off_t *data_off, uint8_t *out, + size_t *wp) +{ + if (*wp > 0) { + allocated_snapshot.metadata.snapshot_crc = + crc32_k_4_2_update(allocated_snapshot.metadata.snapshot_crc, out, *wp); + emds_flash_write_data(partition, *data_off, out, *wp); + *data_off += *wp; + *wp = 0; + } } -uint32_t emds_store_time_get(void) +int emds_store(void) { - size_t block_size = emds_flash.flash_params->write_block_size; - uint32_t store_time_us = CONFIG_EMDS_FLASH_TIME_BASE_OVERHEAD_US; + uint32_t store_key; + uint8_t data_chunk[CHUNK_SIZE]; + size_t wp = 0; + off_t data_off = allocated_snapshot.metadata.data_instance_off; + int idx = allocated_snapshot.partition_index; + int rc = 0; + if (emds_state != EMDS_STATE_READY) { + return -ECANCELED; + } + + /* Lock all interrupts */ + store_key = irq_lock(); + + if (SUSPEND_POFWARN()) { + rc = -ECANCELED; + goto unlock_and_exit; + } + + if (flash_params_get_erase_cap(partition[idx].fp) & FLASH_ERASE_C_EXPLICIT) { + LOG_DBG("Writing metadata on offset: 0x%4lx, address : 0x%4lx", + allocated_snapshot.metadata_off, + allocated_snapshot.metadata_off + partition[idx].fa->fa_off); + emds_flash_write_data(&partition[idx], allocated_snapshot.metadata_off, + &allocated_snapshot.metadata, + offsetof(struct emds_snapshot_metadata, snapshot_crc)); + } STRUCT_SECTION_FOREACH(emds_entry, ch) { - store_time_us += DIV_ROUND_UP(ch->len, block_size) * - CONFIG_EMDS_FLASH_TIME_WRITE_ONE_WORD_US - + DIV_ROUND_UP(emds_flash.ate_size, block_size) * - CONFIG_EMDS_FLASH_TIME_WRITE_ONE_WORD_US - + CONFIG_EMDS_FLASH_TIME_ENTRY_OVERHEAD_US; + entry_to_stream(&partition[idx], &data_off, data_chunk, &wp, ch); } struct emds_dynamic_entry *ch; SYS_SLIST_FOR_EACH_CONTAINER(&emds_dynamic_entries, ch, node) { - store_time_us += DIV_ROUND_UP(ch->entry.len, block_size) * - CONFIG_EMDS_FLASH_TIME_WRITE_ONE_WORD_US - + DIV_ROUND_UP(emds_flash.ate_size, block_size) * - CONFIG_EMDS_FLASH_TIME_WRITE_ONE_WORD_US - + CONFIG_EMDS_FLASH_TIME_ENTRY_OVERHEAD_US; + entry_to_stream(&partition[idx], &data_off, data_chunk, &wp, &ch->entry); } - return store_time_us; -} + stream_fflush(&partition[idx], &data_off, data_chunk, &wp); + + if (flash_params_get_erase_cap(partition[idx].fp) & FLASH_ERASE_C_EXPLICIT) { + LOG_DBG("Writing snapshot crc on offset: 0x%4lx, crc : 0x%4x", + allocated_snapshot.metadata_off + + offsetof(struct emds_snapshot_metadata, snapshot_crc), + allocated_snapshot.metadata.snapshot_crc); + emds_flash_write_data(&partition[idx], + allocated_snapshot.metadata_off + + offsetof(struct emds_snapshot_metadata, snapshot_crc), + &allocated_snapshot.metadata.snapshot_crc, sizeof(uint32_t)); + } else { + LOG_DBG("Writing metadata on offset: 0x%4lx, address : 0x%4lx, crc : 0x%4x", + allocated_snapshot.metadata_off, + allocated_snapshot.metadata_off + partition[idx].fa->fa_off, + allocated_snapshot.metadata.snapshot_crc); + emds_flash_write_data(&partition[idx], allocated_snapshot.metadata_off, + &allocated_snapshot.metadata, + offsetof(struct emds_snapshot_metadata, reserved)); + } -uint32_t emds_store_size_get(void) -{ - uint32_t store_size; +unlock_and_exit: + emds_state = EMDS_STATE_INITIALIZED; + RESUME_POFWARN(); + /* Unlock all interrupts */ + irq_unlock(store_key); - (void)emds_entries_size(&store_size); + if (app_store_cb && rc == 0) { + app_store_cb(); + } - return store_size; + return rc; } -bool emds_is_ready(void) +int emds_clear(void) { - return emds_ready; + bool failed = false; + int rc; + + if (emds_state == EMDS_STATE_NOT_INITIALIZED) { + return -ECANCELED; + } + + emds_state = EMDS_STATE_INITIALIZED; + memset(&freshest_snapshot, 0, sizeof(freshest_snapshot)); + memset(&allocated_snapshot, 0, sizeof(allocated_snapshot)); + for (int i = 0; i < PARTITIONS_NUM_MAX; i++) { + rc = emds_flash_erase_partition(&partition[i]); + if (rc) { + LOG_ERR("Failed to erase partition %d error: %d", i, rc); + failed = true; + } + } + + return failed ? -EIO : 0; } diff --git a/subsys/emds/emds_flash.c b/subsys/emds/emds_flash.c index 46abea37f024..1c20b8773395 100644 --- a/subsys/emds/emds_flash.c +++ b/subsys/emds/emds_flash.c @@ -1,613 +1,378 @@ /* - * Copyright (c) 2022 Nordic Semiconductor ASA + * Copyright (c) 2025 Nordic Semiconductor ASA * * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ +#include "emds_flash.h" + #include -#include -#include -#include #include + #include -#include "emds_flash.h" -#include +LOG_MODULE_REGISTER(emds_flash, CONFIG_EMDS_LOG_LEVEL); #if defined CONFIG_SOC_FLASH_NRF_RRAM #include #include -#define RRAM DT_INST(0, soc_nv_flash) -#define RRAM_START DT_REG_ADDR(RRAM) -#define RRAM_SIZE DT_REG_SIZE(RRAM) -#define EMDS_FLASH_BLOCK_SIZE DT_PROP(RRAM, write_block_size) -#define WRITE_BUFFER_SIZE CONFIG_EMDS_RRAM_WRITE_BUFFER_SIZE /* in 128-bits words */ -#define WRITE_LINE_SIZE 16 /* In bytes, one line is 128 bits. */ -#define WRITE_BUFFER_SIZE_IN_BYTES (WRITE_BUFFER_SIZE * WRITE_LINE_SIZE) #else #include -#define FLASH DT_INST(0, soc_nv_flash) -#define EMDS_FLASH_BLOCK_SIZE DT_PROP(FLASH, write_block_size) #endif -LOG_MODULE_REGISTER(emds_flash, CONFIG_EMDS_LOG_LEVEL); - -#define ADDR_OFFS_MASK 0x0000FFFF - -/* Allocation Table Entry */ -struct emds_ate { - uint16_t id; /* data id */ - uint16_t offset; /* data offset within sector */ - uint16_t len; /* data len within sector */ - uint8_t crc8_data; /* crc8 check of the data entry */ - uint8_t crc8; /* crc8 check of the ate entry */ -} __packed; - -enum ate_type { - ATE_TYPE_VALID = BIT(0), - ATE_TYPE_INVALIDATED = BIT(1), - ATE_TYPE_ERASED = BIT(2), - ATE_TYPE_UNKNOWN = BIT(3) -}; - -BUILD_ASSERT(offsetof(struct emds_ate, crc8) == sizeof(struct emds_ate) - sizeof(uint8_t), - "crc8 must be the last member"); - -#define SOC_NV_FLASH_NODE DT_INST(0, soc_nv_flash) - -#if NRF52_ERRATA_242_PRESENT -#include -/* Disable POFWARN by writing POFCON before a write or erase operation. - * Do not attempt to write or erase if EVENTS_POFWARN is already asserted. - */ -static bool pofcon_enabled; - -static int suspend_pofwarn(void) -{ - if (!nrf52_errata_242()) { - return 0; - } - - bool enabled; - nrf_power_pof_thr_t pof_thr; - - pof_thr = nrf_power_pofcon_get(NRF_POWER, &enabled); - - if (enabled) { - nrf_power_pofcon_set(NRF_POWER, false, pof_thr); - - /* This check need to be reworked once POFWARN event will be - * served by zephyr. - */ - if (nrf_power_event_check(NRF_POWER, NRF_POWER_EVENT_POFWARN)) { - nrf_power_pofcon_set(NRF_POWER, true, pof_thr); - return -ECANCELED; - } - } - - return 0; -} +#define SOC_NV_FLASH_NODE DT_INST(0, soc_nv_flash) +/* "EMDS" in ASCII */ +#define EMDS_SNAPSHOT_METADATA_MARKER 0x4D444553 -static void restore_pofwarn(void) +static void cand_list_init(sys_slist_t *cand_list, struct emds_snapshot_candidate *cand_buf) { - nrf_power_pof_thr_t pof_thr; - - if (pofcon_enabled) { - pof_thr = nrf_power_pofcon_get(NRF_POWER, NULL); - - nrf_power_pofcon_set(NRF_POWER, true, pof_thr); - pofcon_enabled = false; + sys_slist_init(cand_list); + for (int i = 0; i < CONFIG_EMDS_MAX_CANDIDATES; i++) { + sys_slist_append(cand_list, &cand_buf[i].node); } } -#define SUSPEND_POFWARN() suspend_pofwarn() -#define RESUME_POFWARN() restore_pofwarn() -#else -#define SUSPEND_POFWARN() 0 -#define RESUME_POFWARN() -#endif /* NRF52_ERRATA_242_PRESENT */ - -static inline bool is_aligned_32(uint32_t data) +static void cand_put(sys_slist_t *cand_list, off_t cand_off, struct emds_snapshot_metadata *cand) { - return (data & 0x3) ? false : true; -} - -static inline bool is_within_bounds(off_t addr, size_t len, off_t boundary_start, - size_t boundary_size) -{ - return (addr >= boundary_start && - (addr < (boundary_start + boundary_size)) && - (len <= (boundary_start + boundary_size - addr))); -} + sys_snode_t *cand_node = sys_slist_peek_tail(cand_list); + sys_snode_t *head = sys_slist_peek_head(cand_list); + sys_snode_t *curr = head; + sys_snode_t *prev = curr; + struct emds_snapshot_candidate *curr_ctx; + struct emds_snapshot_candidate *placeholder = + CONTAINER_OF(cand_node, struct emds_snapshot_candidate, node); + + if (cand->fresh_cnt < placeholder->metadata.fresh_cnt) { + return; /* Ignore candidates that are not fresher than the placeholder */ + } + + LOG_DBG("Found snapshot at offset 0x%04lx with fresh_cnt %u. Metadata offset: 0x%04lx", + cand->data_instance_off, cand->fresh_cnt, cand_off); + + sys_slist_find_and_remove(cand_list, cand_node); + placeholder->metadata = *cand; + placeholder->metadata_off = cand_off; + + do { + curr_ctx = CONTAINER_OF(curr, struct emds_snapshot_candidate, node); + if (placeholder->metadata.fresh_cnt > curr_ctx->metadata.fresh_cnt) { + if (curr == head) { + sys_slist_prepend(cand_list, cand_node); + } else { + sys_slist_insert(cand_list, prev, cand_node); + } + return; + } + prev = curr; + } while ((curr = sys_slist_peek_next(curr))); -static inline bool is_regular_addr_valid(off_t addr, size_t len) -{ -#if defined CONFIG_SOC_FLASH_NRF_RRAM - return is_within_bounds(addr, len, RRAM_START, RRAM_START + RRAM_SIZE); -#else - return is_within_bounds(addr, len, 0, nrfx_nvmc_flash_size_get()); -#endif + sys_slist_append(cand_list, cand_node); } -static void nvmc_wait_ready(void) +static bool cand_snapshot_crc_check(const struct emds_partition *partition, + struct emds_snapshot_metadata *metadata) { -#if defined CONFIG_SOC_FLASH_NRF_RRAM - while (!nrf_rramc_ready_check(NRF_RRAMC)) { - } -#else - while (!nrfx_nvmc_write_done_check()) { - } -#endif -} + const struct flash_area *fa = partition->fa; + uint8_t data_chunk[16]; + size_t chunk_size; + uint32_t crc = 0; + size_t data_length = metadata->data_instance_len; + off_t data_off = metadata->data_instance_off; + int rc; -#if defined CONFIG_SOC_FLASH_NRF_RRAM -static void commit_changes(size_t len) -{ - if (nrf_rramc_empty_buffer_check(NRF_RRAMC)) { - /* The internal write-buffer has been committed to RRAM and is now empty. */ - return; - } + while (data_length > 0) { + chunk_size = MIN(data_length, sizeof(data_chunk)); + rc = flash_area_read(fa, data_off, data_chunk, chunk_size); + if (rc) { + LOG_ERR("Failed to read data chunk: %d", rc); + return false; + } - if ((len % WRITE_BUFFER_SIZE_IN_BYTES) == 0) { - /* Our last operation was buffer size-aligned, so we're done. */ - return; + crc = crc32_k_4_2_update(crc, data_chunk, chunk_size); + data_off += chunk_size; + data_length -= chunk_size; } - nrf_rramc_task_trigger(NRF_RRAMC, NRF_RRAMC_TASK_COMMIT_WRITEBUF); - - barrier_dmem_fence_full(); + return crc == metadata->snapshot_crc; } -#endif -static int flash_direct_write(const struct device *dev, off_t offset, const void *data, size_t len) +static bool metadata_iterator(off_t *read_off, int cur_failures) { - uint32_t flash_addr = offset; - - ARG_UNUSED(dev); - - if (!is_regular_addr_valid(flash_addr, len)) { - return -EINVAL; - } - - if (!is_aligned_32(flash_addr)) { - return -EINVAL; + *read_off -= sizeof(struct emds_snapshot_metadata); + if (*read_off <= 0) { + return false; } - if (len % sizeof(uint32_t)) { - return -EINVAL; - } - - flash_addr += DT_REG_ADDR(SOC_NV_FLASH_NODE); - - nvmc_wait_ready(); - - if (SUSPEND_POFWARN()) { - return -ECANCELED; - } - -#if defined CONFIG_SOC_FLASH_NRF_RRAM - nrf_rramc_config_t config = {.mode_write = true, .write_buff_size = WRITE_BUFFER_SIZE}; - - nrf_rramc_config_set(NRF_RRAMC, &config); - memcpy((void *)flash_addr, data, len); - - barrier_dmem_fence_full(); /* Barrier following our last write. */ - commit_changes(len); - - config.mode_write = false; - nrf_rramc_config_set(NRF_RRAMC, &config); -#else - uint32_t data_addr = (uint32_t)data; - - while (len >= sizeof(uint32_t)) { - nrfx_nvmc_word_write(flash_addr, UNALIGNED_GET((uint32_t *)data_addr)); - - flash_addr += sizeof(uint32_t); - data_addr += sizeof(uint32_t); - len -= sizeof(uint32_t); - } -#endif - - RESUME_POFWARN(); - - nvmc_wait_ready(); - - return 0; + return cur_failures <= CONFIG_EMDS_SCANNING_FAILURES; } -static size_t align_size(struct emds_fs *fs, size_t len) +int emds_flash_init(struct emds_partition *partition) { - uint8_t write_block_size = fs->flash_params->write_block_size; + const struct flash_area *fa = partition->fa; - if (write_block_size <= 1U) { - return len; + if (!fa->fa_dev) { + LOG_ERR("No valid flash device found"); + return -ENXIO; } - return (len + (write_block_size - 1U)) & ~(write_block_size - 1U); -} - -static int ate_wrt(struct emds_fs *fs, const struct emds_ate *entry) -{ - size_t ate_size = align_size(fs, sizeof(struct emds_ate)); - if (ate_size % fs->flash_params->write_block_size) { + partition->fp = flash_get_parameters(fa->fa_dev); + if (partition->fp == NULL) { + LOG_ERR("Could not obtain flash parameters"); return -EINVAL; } - int rc = flash_direct_write(fs->flash_dev, fs->ate_wra, entry, sizeof(struct emds_ate)); + if (flash_params_get_erase_cap(partition->fp) & FLASH_ERASE_C_EXPLICIT) { + struct flash_pages_info info; + int rc; - if (rc) { - return rc; - } - - fs->ate_wra -= ate_size; - return 0; -} - -static int data_wrt(struct emds_fs *fs, const void *data, size_t len) -{ - const uint8_t *data8 = (const uint8_t *)data; - int rc; - off_t offset; - size_t blen; - size_t temp_len = len; - uint8_t buf[EMDS_FLASH_BLOCK_SIZE]; - - if (!temp_len) { - /* Nothing to write, avoid changing the flash protection */ - return 0; - } - - offset = fs->offset; - offset += fs->data_wra_offset & ADDR_OFFS_MASK; - - blen = temp_len & ~(fs->flash_params->write_block_size - 1U); - /* Writes multiples of 4 bytes to flash */ - if (blen > 0) { - rc = flash_direct_write(fs->flash_dev, offset, data8, blen); - if (rc) { - return rc; - } - - temp_len -= blen; - offset += blen; - data8 += blen; - } - - if (temp_len) { - (void)memcpy(buf, data8, temp_len); - (void)memset(buf + temp_len, fs->flash_params->erase_value, - fs->flash_params->write_block_size - temp_len); - rc = flash_direct_write(fs->flash_dev, offset, buf, - fs->flash_params->write_block_size); + rc = flash_get_page_info_by_offs(fa->fa_dev, fa->fa_off, &info); if (rc) { - return rc; + LOG_ERR("Unable to get page info"); + return -EINVAL; } - } - - fs->data_wra_offset += align_size(fs, len); - return 0; -} -static int check_erased(struct emds_fs *fs, uint32_t addr, size_t len) -{ - size_t bytes_to_cmp; - uint8_t cmp[EMDS_FLASH_BLOCK_SIZE]; - uint8_t buf[EMDS_FLASH_BLOCK_SIZE]; - - (void)memset(cmp, 0xff, EMDS_FLASH_BLOCK_SIZE); - while (len) { - bytes_to_cmp = MIN(EMDS_FLASH_BLOCK_SIZE, len); - if (flash_read(fs->flash_dev, addr, buf, bytes_to_cmp)) { - return -EIO; + if (fa->fa_off != info.start_offset || fa->fa_size % info.size) { + LOG_ERR("Flash area offset or size is not aligned to page size"); + return -EINVAL; } - - if (memcmp(cmp, buf, bytes_to_cmp)) { + } else { + if (fa->fa_off % partition->fp->write_block_size || + fa->fa_size % partition->fp->write_block_size) { + LOG_ERR("Flash area offset or size is not aligned to write block size"); return -EINVAL; } - - len -= bytes_to_cmp; - addr += bytes_to_cmp; - } - return 0; -} - -static int is_ate_valid(const struct emds_ate *entry) -{ - return entry->crc8 == crc8_ccitt(0xff, entry, offsetof(struct emds_ate, crc8)); -} - -static int entry_wrt(struct emds_fs *fs, uint16_t id, const void *data, size_t len) -{ - int rc; - struct emds_ate entry; - - entry.id = id; - entry.offset = fs->data_wra_offset; - entry.len = (uint16_t)len; - entry.crc8_data = crc8_ccitt(0xff, data, len); - entry.crc8 = crc8_ccitt(0xff, &entry, offsetof(struct emds_ate, crc8)); - rc = data_wrt(fs, data, len); - if (rc) { - return rc; - } - - rc = ate_wrt(fs, &entry); - if (rc) { - return rc; } return 0; } -static enum ate_type ate_check(struct emds_fs *fs, uint32_t addr, struct emds_ate *entry) +int emds_flash_scan_partition(const struct emds_partition *partition, + struct emds_snapshot_candidate *candidate) { - uint8_t read_buf[fs->ate_size]; - int rc = flash_read(fs->flash_dev, addr, read_buf, fs->ate_size); - - if (rc) { - return ATE_TYPE_UNKNOWN; - } - - memset(entry, 0, sizeof(struct emds_ate)); - if (!memcmp(entry, read_buf, sizeof(struct emds_ate))) { - return ATE_TYPE_INVALIDATED; - } - - memset(entry, fs->flash_params->erase_value, sizeof(struct emds_ate)); - if (!memcmp(entry, read_buf, sizeof(struct emds_ate))) { - return ATE_TYPE_ERASED; - } - - memcpy(entry, read_buf, sizeof(struct emds_ate)); - if (is_ate_valid(entry)) { - return ATE_TYPE_VALID; - } + struct emds_snapshot_metadata cache = {0}; + struct emds_snapshot_candidate cand_buf[CONFIG_EMDS_MAX_CANDIDATES] = {0}; + sys_slist_t cand_list; + sys_snode_t *cand_node; + const struct flash_area *fa = partition->fa; + off_t read_off = fa->fa_size - sizeof(cache); + int failures = 0; + uint32_t crc; + int rc; - return ATE_TYPE_UNKNOWN; -} + cand_list_init(&cand_list, cand_buf); -static int ate_last_recover(struct emds_fs *fs) -{ - struct emds_ate end_ate; - enum ate_type type = 0; - uint8_t expect_field = 0xFF; - - fs->ate_wra = fs->offset + fs->sector_cnt * fs->sector_size - fs->ate_size; - fs->data_wra_offset = 0; - while (type != ATE_TYPE_ERASED) { - /* Ate wra has reached the start of the data area */ - if (fs->ate_wra < fs->offset) { - fs->force_erase = true; - fs->ate_wra = fs->offset; - fs->data_wra_offset = 0; - return 0; + do { + rc = flash_area_read(fa, read_off, &cache, sizeof(cache)); + if (rc) { + LOG_ERR("Failed to read snapshot metadata: %d", rc); + return rc; } - type = ate_check(fs, fs->ate_wra, &end_ate); + if (cache.marker != EMDS_SNAPSHOT_METADATA_MARKER) { + failures++; + LOG_DBG("Snapshot metadata marker mismatch at address 0x%04lx", + fa->fa_off + read_off); + continue; + } - /* If an unexpected entry type occurs we force erase on next prepare */ - if (!(type & expect_field)) { - fs->force_erase = true; + crc = crc32_k_4_2_update(0, (const unsigned char *)&cache, + offsetof(struct emds_snapshot_metadata, metadata_crc)); + if (crc != cache.metadata_crc) { + failures++; + LOG_DBG("Snapshot metadata CRC mismatch at address 0x%04lx", + fa->fa_off + read_off); + continue; } - switch (type) { - case ATE_TYPE_VALID: - fs->data_wra_offset = align_size(fs, end_ate.offset + end_ate.len); - fs->ate_wra -= fs->ate_size; - expect_field = ATE_TYPE_VALID | ATE_TYPE_ERASED; - break; + cand_put(&cand_list, read_off, &cache); + } while (metadata_iterator(&read_off, failures)); - case ATE_TYPE_INVALIDATED: - expect_field = ATE_TYPE_VALID | ATE_TYPE_INVALIDATED | ATE_TYPE_ERASED; - fs->ate_wra -= fs->ate_size; - break; + /* At this point we have the sorted linked list of candidates. + * Candidates have been sorted from the freshest at the head to the oldest snapshot. + */ + while ((cand_node = sys_slist_get(&cand_list))) { + struct emds_snapshot_candidate *cand = + CONTAINER_OF(cand_node, struct emds_snapshot_candidate, node); - case ATE_TYPE_UNKNOWN: - fs->force_erase = true; - fs->ate_wra -= fs->ate_size; + if (cand->metadata.fresh_cnt == 0) { break; + } - case ATE_TYPE_ERASED: - /* fall through*/ - default: + if (cand_snapshot_crc_check(partition, &cand->metadata)) { + *candidate = *cand; + LOG_DBG("Found valid snapshot at address 0x%04lx with length %u with " + "fresh_cnt %u", + fa->fa_off + cand->metadata.data_instance_off, + cand->metadata.data_instance_len, cand->metadata.fresh_cnt); break; } - } - /* Verify that flash area between ate and data pointer is writeable */ - if (check_erased(fs, fs->offset + fs->data_wra_offset, - fs->ate_wra - (fs->offset + fs->data_wra_offset) + fs->ate_size)) { - fs->force_erase = true; + LOG_DBG("Snapshot CRC mismatch at address 0x%04lx", + fa->fa_off + cand->metadata.data_instance_off); } return 0; } -static int old_entries_invalidate(struct emds_fs *fs) +int emds_flash_allocate_snapshot(const struct emds_partition *partition, + const struct emds_snapshot_candidate *freshest_snapshot, + struct emds_snapshot_candidate *allocated_snapshot, + size_t data_size) { - int rc = 0; - uint8_t inval_buf[fs->ate_size]; - uint32_t addr = fs->ate_wra + fs->ate_size; - - memset(inval_buf, 0, sizeof(inval_buf)); - while (addr <= (fs->offset + fs->sector_cnt * fs->sector_size) - fs->ate_size) { - rc = flash_write(fs->flash_dev, addr, inval_buf, sizeof(inval_buf)); - if (rc) { - return rc; - } + const struct flash_area *fa = partition->fa; + const struct flash_parameters *fp = partition->fp; + off_t metadata_off = freshest_snapshot ? freshest_snapshot->metadata_off : fa->fa_size; + off_t data_off = freshest_snapshot + ? ROUND_UP(freshest_snapshot->metadata.data_instance_off + + freshest_snapshot->metadata.data_instance_len, + fp->write_block_size) + : 0; + size_t aligned_data_size = ROUND_UP(data_size, fp->write_block_size); + int rc; - addr += fs->ate_size; - } + metadata_off -= sizeof(struct emds_snapshot_metadata); - return 0; -} - -int emds_flash_init(struct emds_fs *fs) -{ - if (fs->is_initialized) { - return -EACCES; + if (aligned_data_size + + ROUND_UP(sizeof(struct emds_snapshot_metadata), fp->write_block_size) > + fa->fa_size) { + LOG_ERR("Invalid data size: %zu", data_size); + return -EINVAL; } - k_mutex_init(&fs->emds_lock); - if (!fs->flash_dev) { - LOG_ERR("No valid flash device found"); - return -ENXIO; + if (REGIONS_OVERLAP(metadata_off, sizeof(struct emds_snapshot_metadata), data_off, + aligned_data_size) || metadata_off <= data_off) { + LOG_WRN("Metadata area overlaps with data area"); + return -ENOMEM; } - fs->flash_params = flash_get_parameters(fs->flash_dev); - if (fs->flash_params == NULL) { - LOG_ERR("Could not obtain flash parameters"); - return -EINVAL; - } + if (flash_params_get_erase_cap(partition->fp) & FLASH_ERASE_C_EXPLICIT) { + uint8_t cmp[sizeof(struct emds_snapshot_metadata)]; + uint8_t cache[sizeof(struct emds_snapshot_metadata)]; - if (fs->flash_params->write_block_size > EMDS_FLASH_BLOCK_SIZE || - fs->flash_params->write_block_size == 0) { - LOG_ERR("Unsupported write block size: %i", fs->flash_params->write_block_size); - return -EINVAL; - } + memset(cmp, fp->erase_value, sizeof(cmp)); - struct flash_pages_info info; - int rc = flash_get_page_info_by_offs(fs->flash_dev, fs->offset, &info); + rc = flash_area_read(fa, metadata_off, cache, + sizeof(struct emds_snapshot_metadata)); + if (rc) { + LOG_ERR("Failed to read snapshot metadata memory: %d", rc); + return rc; + } - if (rc) { - LOG_ERR("Unable to get page info"); - return -EINVAL; - } + if (memcmp(cmp, cache, sizeof(struct emds_snapshot_metadata))) { + LOG_WRN("Metadata area at address: 0x%04lx is not empty", + fa->fa_off + metadata_off); + return -EADDRINUSE; + } - if (!fs->sector_size || fs->sector_size % info.size) { - LOG_ERR("Invalid sector size"); - return -EINVAL; - } + /* Check area of the first entry only. If it is empty then everything behind this is + * empty. If area is busy then emds does not know border where it is empty. Need to + * erase partition. + */ + rc = flash_area_read(fa, data_off, cache, sizeof(struct emds_data_entry)); + if (rc) { + LOG_ERR("Failed to read the first entry memory: %d", rc); + return rc; + } - if (fs->sector_cnt < 1) { - LOG_ERR("Configuration error - sector count"); - return -EINVAL; + if (memcmp(cmp, cache, sizeof(struct emds_data_entry))) { + LOG_WRN("Data area at address: 0x%04lx is not empty", + fa->fa_off + data_off); + return -EADDRINUSE; + } } - k_mutex_lock(&fs->emds_lock, K_FOREVER); - fs->ate_size = align_size(fs, sizeof(struct emds_ate)); + allocated_snapshot->metadata_off = metadata_off; + allocated_snapshot->metadata.marker = EMDS_SNAPSHOT_METADATA_MARKER; + allocated_snapshot->metadata.data_instance_off = data_off; + allocated_snapshot->metadata.data_instance_len = data_size; + allocated_snapshot->metadata.metadata_crc = + crc32_k_4_2_update(0, (const unsigned char *)&allocated_snapshot->metadata, + offsetof(struct emds_snapshot_metadata, metadata_crc)); + allocated_snapshot->metadata.snapshot_crc = 0; - rc = ate_last_recover(fs); - k_mutex_unlock(&fs->emds_lock); - if (rc) { - return rc; - } + LOG_DBG("Allocating snapshot at address 0x%04lx with length %u and fresh_cnt %u", + fa->fa_off + data_off, data_size, allocated_snapshot->metadata.fresh_cnt); + LOG_DBG("Metadata address: 0x%04lx, crc: 0x%08x", fa->fa_off + metadata_off, + allocated_snapshot->metadata.metadata_crc); - fs->is_initialized = true; return 0; } -int emds_flash_clear(struct emds_fs *fs) +static void nvmc_wait_ready(void) { - int rc = flash_erase(fs->flash_dev, fs->offset, fs->sector_size * fs->sector_cnt); - - if (rc) { - return rc; +#if defined CONFIG_SOC_FLASH_NRF_RRAM + while (!nrf_rramc_ready_check(NRF_RRAMC)) { } - - rc = check_erased(fs, fs->offset, fs->sector_size * fs->sector_cnt); - if (rc) { - return -ENXIO; +#else + while (!nrfx_nvmc_write_done_check()) { } - ate_last_recover(fs); - return rc; +#endif } -ssize_t emds_flash_write(struct emds_fs *fs, uint16_t id, const void *data, size_t len) +#if defined CONFIG_SOC_FLASH_NRF_RRAM +static void commit_changes(const struct emds_partition *partition, size_t len) { - if (!fs->is_initialized || !fs->is_prepeared) { - LOG_ERR("EMDS flash not initialized or not ready for write"); - return -EACCES; - } - - if (fs->ate_size + align_size(fs, len) > emds_flash_free_space_get(fs)) { - return -ENOMEM; + if (nrf_rramc_empty_buffer_check(NRF_RRAMC)) { + /* The internal write-buffer has been committed to RRAM and is now empty. */ + return; } - if (len == 0) { - return 0; + if ((len % partition->fp->write_block_size) == 0) { + /* Our last operation was buffer size-aligned, so we're done. */ + return; } - int rc = entry_wrt(fs, id, data, len); - - if (rc) { - return rc; - } + nrf_rramc_task_trigger(NRF_RRAMC, NRF_RRAMC_TASK_COMMIT_WRITEBUF); - return len; + barrier_dmem_fence_full(); } +#endif -ssize_t emds_flash_read(struct emds_fs *fs, uint16_t id, void *data, size_t len) +void emds_flash_write_data(const struct emds_partition *partition, off_t data_off, void *data_chunk, + size_t data_size) { - if (!fs->is_initialized) { - LOG_ERR("EMDS flash not initialized"); - return -EACCES; - } + uint32_t flash_addr = data_off + partition->fa->fa_off; - int rc; - uint32_t wlk_addr = fs->ate_wra; - struct emds_ate wlk_ate; - - while (true) { - rc = flash_read(fs->flash_dev, wlk_addr, &wlk_ate, sizeof(struct emds_ate)); - if (rc) { - return rc; - } + flash_addr += DT_REG_ADDR(SOC_NV_FLASH_NODE); - if ((wlk_ate.id == id) && (is_ate_valid(&wlk_ate))) { - break; - } + nvmc_wait_ready(); - wlk_addr += fs->ate_size; - if (wlk_addr >= fs->offset + fs->sector_cnt * fs->sector_size) { - return -ENXIO; - } - } +#if defined CONFIG_SOC_FLASH_NRF_RRAM + /* 1 word of 128bits length */ + nrf_rramc_config_t config = {.mode_write = true, .write_buff_size = 1}; - if (len < wlk_ate.len) { - return -ENOMEM; - } + nrf_rramc_config_set(NRF_RRAMC, &config); + memcpy((void *)flash_addr, data_chunk, data_size); - rc = flash_read(fs->flash_dev, fs->offset + wlk_ate.offset, data, wlk_ate.len); - if (rc) { - return rc; - } + barrier_dmem_fence_full(); /* Barrier following our last write. */ + commit_changes(partition, data_size); - if (wlk_ate.crc8_data != crc8_ccitt(0xff, data, wlk_ate.len)) { - return -EFAULT; - } + config.mode_write = false; + nrf_rramc_config_set(NRF_RRAMC, &config); +#else + uint32_t data_addr = (uint32_t)data_chunk; - return wlk_ate.len; -} + data_size = ROUND_UP(data_size, sizeof(uint32_t)); -int emds_flash_prepare(struct emds_fs *fs, int byte_size) -{ - if (!fs->is_initialized) { - LOG_ERR("EMDS flash not initialized"); - return -EACCES; - } + while (data_size >= sizeof(uint32_t)) { + nrfx_nvmc_word_write(flash_addr, UNALIGNED_GET((uint32_t *)data_addr)); - if (byte_size > (fs->sector_cnt * fs->sector_size) - fs->ate_size) { - return -ENOMEM; + flash_addr += sizeof(uint32_t); + data_addr += sizeof(uint32_t); + data_size -= sizeof(uint32_t); } +#endif + nvmc_wait_ready(); +} - int rc = old_entries_invalidate(fs); +int emds_flash_erase_partition(const struct emds_partition *partition) +{ + const struct flash_area *fa = partition->fa; + int rc; + rc = flash_area_flatten(fa, 0, fa->fa_size); if (rc) { return rc; } - if (fs->force_erase || (byte_size > emds_flash_free_space_get(fs))) { - emds_flash_clear(fs); - fs->force_erase = false; - } - - fs->is_prepeared = true; return 0; } - -ssize_t emds_flash_free_space_get(struct emds_fs *fs) -{ - ssize_t space = fs->ate_wra - (fs->data_wra_offset + fs->offset); - - return (space > fs->ate_size) ? space : 0; -} diff --git a/subsys/emds/emds_flash.h b/subsys/emds/emds_flash.h index 6fc8ff39dd39..710ca3bc449c 100644 --- a/subsys/emds/emds_flash.h +++ b/subsys/emds/emds_flash.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Nordic Semiconductor ASA + * Copyright (c) 2025 Nordic Semiconductor ASA * * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ @@ -7,126 +7,172 @@ #ifndef EMDS_FLASH_H__ #define EMDS_FLASH_H__ -#include -#include -#include +#include +#include #ifdef __cplusplus extern "C" { #endif /** - * @brief Emergency data storage file system structure - * - * @param offset File system offset from start of flash - * @param ate_wra Allocation table entry write address. Addresses are stored as uint32_t: - * high 2 bytes correspond to the sector, low 2 bytes are the offset in the sector - * @param ate_size Size of allocation table entry - * @param data_wra_offset Data write address offset - * @param sector_size File system is split into sectors, each sector must be multiple of pagesize - * @param sector_cnt Number of sectors in the file systems - * @param is_initialized Initialized flag - * @param is_prepeared Prepared for write flag - * @param emds_lock Mutex - * @param flash_dev Pointer to flash device runtime structure - * @param flash_params Pointer to flash memory parameters structure - * @param force_erase Force erase flag + * @brief Check if two regions overlap + * + * This macro checks if two memory regions overlap. + * + * @param a Start address of the first region. + * @param len_a Length of the first region. + * @param b Start address of the second region. + * @param len_b Length of the second region. + * + * @return true if regions overlap, false otherwise. */ -struct emds_fs { - off_t offset; - uint32_t ate_wra; - size_t ate_size; - uint32_t data_wra_offset; - uint16_t sector_size; - uint16_t sector_cnt; - bool is_initialized; - bool is_prepeared; - struct k_mutex emds_lock; - const struct device *flash_dev; - const struct flash_parameters *flash_params; - bool force_erase; -}; +#define REGIONS_OVERLAP(a, len_a, b, len_b) (((a) < ((b) + (len_b))) && ((b) < ((a) + (len_a)))) /** - * @brief Initialize emergency data storage flash. + * @brief Emergency data storage partition descriptor * - * @param fs Pointer to file system + * This structure describes a partition in the emergency data storage. + * It contains the flash area and its parameters. * - * @retval 0 on success or negative error code + * @param fa Flash area for the partition. + * @param fp Flash parameters for the partition. */ -int emds_flash_init(struct emds_fs *fs); +struct emds_partition { + const struct flash_area *fa; + const struct flash_parameters *fp; +}; /** - * @brief Clears the emergency data storage file system from flash. + * @brief Emergency data storage data entry structure * - * @param fs Pointer to file system - * - * @note Calling this function will make any subsequent read attempts fail. Be sure to - * restore all necessary entries before using this function. - * - * @retval 0 on success or negative error code + * @param id Unique data identifier. + * @param length Data length. + * @param data Zero length array for data reference. + */ +struct emds_data_entry { + uint16_t id; + uint16_t length; + uint8_t data[]; +} __packed; + +/** + * @brief Emergency data storage metadata structure + * + * This structure is used to store metadata about the emergency data storage. + * It contains information about the data instance area, its length, and CRCs + * for both the metadata and the snapshot area. + * + * EMDS storage API can be called maximum of 4B times, due to width of the fresh_cnt, + * in the lifetime of devices. This will never happen in the lifetime of the device + * as flash endurance will run out much before this count is reached. + * + * @param marker Constant value to follow the end of the table. + * @param fresh_cnt Increment counter for every data instance. + * @param data_instance_off The start offset of the data instance area. + * @param data_instance_len The data instance area length. + * @param metadata_crc The metadata structure CRC. + * @param snapshot_crc The snapshot area CRC. */ -int emds_flash_clear(struct emds_fs *fs); +struct emds_snapshot_metadata { + uint32_t marker; + uint32_t fresh_cnt; + off_t data_instance_off; + uint32_t data_instance_len; + uint32_t metadata_crc; + uint32_t snapshot_crc; + uint32_t reserved[2]; +} __packed; /** - * @brief Write an entry to the EMDS file system. + * @brief Emergency data storage snapshot candidate structure * - * @param fs Pointer to file system - * @param id Id of the entry to be written - * @param data Pointer to the data to be written - * @param len Number of bytes to be written + * This structure is used to hold a candidate snapshot during the scanning + * of the emergency data storage partition. It contains the metadata address + * and the metadata itself. * - * @return Number of bytes written. On success, it will be equal to the number of bytes requested - * to be written. When a rewrite of the same data already stored is attempted, nothing is written - * to flash, thus 0 is returned. On error, returns negative value of errno.h defined error codes. + * @param node Node for the system list. + * @param partition_index Belonged partition index. + * @param metadata_off Offset of the metadata within the partition. + * @param metadata The snapshot metadata. */ -ssize_t emds_flash_write(struct emds_fs *fs, uint16_t id, const void *data, size_t len); +struct emds_snapshot_candidate { + sys_snode_t node; + int partition_index; + off_t metadata_off; + struct emds_snapshot_metadata metadata; +}; /** - * @brief Read an entry from the EMDS file system. - * - * Read an entry from the file system. + * @brief Initialize the emergency data storage flash partition. * - * @param fs Pointer to file system - * @param id Id of the entry to be read - * @param data Pointer to data buffer - * @param len Number of bytes in data buffer + * @param partition Pointer to the emergency data storage partition structure. * - * @return Number of bytes read. On success, it will be equal to the number of bytes requested - * to be read. When the return value is larger than the number of bytes requested to read this - * indicates not all bytes were read, and more data is available. On error, returns negative - * value of errno.h defined error codes. + * @retval 0 on success. + * @retval -ENXIO if no valid flash device is found. + * @retval -EINVAL if unable to obtain flash parameters or wrong partition format. */ -ssize_t emds_flash_read(struct emds_fs *fs, uint16_t id, void *data, size_t len); +int emds_flash_init(struct emds_partition *partition); /** - * @brief Prepare EMDS file system for next write events. - * - * This function should be called at the moment when the user has restored the desired data - * entries from flash. It will invalidate all prior entries, and potentially clear the flash - * area. + * @brief Scan the emergency data storage partition for valid snapshots. * - * @note Calling this function will make any subsequent read attempts fail. Be sure to - * restore all necessary entries before using this function. + * This function scans the specified partition for valid snapshots and populates + * the candidate structure with the found snapshot metadata with the biggest + * fresh_cnt value and valid metadata and snapshot crc values. * - * @note Should only be called once. + * @param partition Pointer to the emergency data storage partition structure. + * @param candidate Pointer to the emergency data storage snapshot candidate structure * - * @param fs Pointer to file system - * @param byte_size Total number of bytes + * @retval 0 on success. + * @retval -EINVAL if the partition is not initialized or if an error occurs during reading. + */ +int emds_flash_scan_partition(const struct emds_partition *partition, + struct emds_snapshot_candidate *candidate); + +/** * @brief Allocate a new snapshot in the emergency data storage partition. + * + * This function allocates a new snapshot in the specified partition based on the + * freshest snapshot found in the partition. + * Note that since emds flash functionality operates with partitions by pointer, + * it does not have information about the partition index and the freshest counter and + * leaves them not initialized. + * + * @param partition Pointer to the emergency data storage partition structure. + * @param freshest_snapshot Pointer to the freshest snapshot candidate structure. + * @param allocated_snapshot Pointer to the emergency data storage snapshot candidate structure + * that will be filled with the allocated snapshot metadata. + * @param data_size The size of the data to be allocated including the size of the metadata. + * + * @retval 0 on success. + * @retval -EINVAL if the data size is invalid. + * @retval -EADDRINUSE if the metadata or data area is not empty. + * @retval -ENOMEM if the metadata area overlaps with the data area. + */ +int emds_flash_allocate_snapshot(const struct emds_partition *partition, + const struct emds_snapshot_candidate *freshest_snapshot, + struct emds_snapshot_candidate *allocated_snapshot, + size_t data_size); + +/** * @brief Write data to the emergency data storage partition. * - * @retval 0 on success or negative error code + * @param partition Pointer to the emergency data storage partition structure. + * @param data_off Offset in the partition where the data chunk should be written. + * @param data_chunk Pointer to data chunk. + * @param data_size Size of the data chunk. */ -int emds_flash_prepare(struct emds_fs *fs, int byte_size); +void emds_flash_write_data(const struct emds_partition *partition, off_t data_off, void *data_chunk, + size_t data_size); /** - * @brief Get remaining raw space on the flash device. + * @brief Erase the specified emergency data storage partition. * - * @param fs Pointer to file system + * This function erases the entire partition, preparing it for new data storage. * - * @retval Remaining free space of the flash device in bytes + * @param partition Pointer to the emergency data storage partition structure. + * + * @retval 0 on success, negative errno code on fail. */ -ssize_t emds_flash_free_space_get(struct emds_fs *fs); - +int emds_flash_erase_partition(const struct emds_partition *partition); #ifdef __cplusplus } diff --git a/subsys/partition_manager/pm.yml.emds b/subsys/partition_manager/pm.yml.emds index fc9115002104..fda6e9cd27a3 100644 --- a/subsys/partition_manager/pm.yml.emds +++ b/subsys/partition_manager/pm.yml.emds @@ -1,6 +1,15 @@ #include -emds_storage: +emds_partition_0: + placement: + before: [end] +#ifdef CONFIG_BUILD_WITH_TFM + align: {start: CONFIG_NRF_TRUSTZONE_FLASH_REGION_SIZE} +#endif + size: CONFIG_PM_PARTITION_SIZE_EMDS_STORAGE + inside: [nonsecure_storage] + +emds_partition_1: placement: before: [end] #ifdef CONFIG_BUILD_WITH_TFM diff --git a/tests/subsys/emds/emds_api/prj.conf b/tests/subsys/emds/emds_api/prj.conf index a50d0dadd90a..0283c87066bb 100644 --- a/tests/subsys/emds/emds_api/prj.conf +++ b/tests/subsys/emds/emds_api/prj.conf @@ -8,7 +8,6 @@ CONFIG_ZTEST=y CONFIG_FLASH=y CONFIG_FLASH_MAP=y -CONFIG_CRC=y CONFIG_EMDS=y CONFIG_REBOOT=y CONFIG_SETTINGS=y diff --git a/tests/subsys/emds/emds_api/src/main.c b/tests/subsys/emds/emds_api/src/main.c index 7839eb123868..006b71a9cec4 100644 --- a/tests/subsys/emds/emds_api/src/main.c +++ b/tests/subsys/emds/emds_api/src/main.c @@ -18,23 +18,6 @@ #include #endif -#if defined CONFIG_SOC_FLASH_NRF_RRAM -#define RRAM DT_INST(0, soc_nv_flash) -#define EMDS_FLASH_BLOCK_SIZE DT_PROP(RRAM, write_block_size) -#else -#define FLASH DT_INST(0, soc_nv_flash) -#define EMDS_FLASH_BLOCK_SIZE DT_PROP(FLASH, write_block_size) -#endif - -/* Allocation Table Entry */ -struct test_ate { - uint16_t id; /* data id */ - uint16_t offset; /* data offset within sector */ - uint16_t len; /* data len within sector */ - uint8_t crc8_data; /* crc8 check of the entry */ - uint8_t crc8; /* crc8 check of the entry */ -} __packed; - enum test_states { EMDS_TS_EMPTY_FLASH, EMDS_TS_STORE_DATA, @@ -45,6 +28,7 @@ enum test_states { static int iteration; +/* test scenario */ static enum test_states state[] = { EMDS_TS_EMPTY_FLASH, EMDS_TS_STORE_DATA, @@ -56,10 +40,10 @@ static enum test_states state[] = { EMDS_TS_CLEAR_FLASH, }; -static const uint8_t expect_d_data[3][10] = { - { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - {11, 12, 13, 14, 15, 16, 17, 18, 19, 20}, - {21, 22, 23, 24, 25, 26, 27, 28, 29, 30}, +static const uint8_t expect_d_data[3][3][10] = { + {{[0 ... 9] = 0x01}, {[0 ... 9] = 0x02}, {[0 ... 9] = 0x03}}, + {{[0 ... 9] = 0x11}, {[0 ... 9] = 0x12}, {[0 ... 9] = 0x13}}, + {{[0 ... 9] = 0x21}, {[0 ... 9] = 0x22}, {[0 ... 9] = 0x23}} }; static uint8_t d_data[3][10]; @@ -69,7 +53,11 @@ static struct emds_dynamic_entry d_entries[3] = { {{0x1003, &d_data[2][0], 10}}, }; -static const uint8_t expect_s_data[1024] = { 0xCC }; +static const uint8_t expect_s_data[3][1024] = { + {[0 ... 1023] = 0xAA}, + {[0 ... 1023] = 0xBB}, + {[0 ... 1023] = 0xCC}, +}; static uint8_t s_data[1024]; EMDS_STATIC_ENTRY_DEFINE(s_entry, 0x100, s_data, sizeof(s_data)); @@ -99,11 +87,6 @@ static void app_store_cb(void) #endif } -static inline size_t align_size(size_t len) -{ - return (len + (EMDS_FLASH_BLOCK_SIZE - 1U)) & ~(EMDS_FLASH_BLOCK_SIZE - 1U); -} - /** Mocks ******************************************/ @@ -129,23 +112,20 @@ static void init(void) static void add_d_entries(void) { int err; - int ate_size = align_size(sizeof(struct test_ate)); - uint32_t store_expected = - DIV_ROUND_UP(sizeof(s_data), EMDS_FLASH_BLOCK_SIZE) * EMDS_FLASH_BLOCK_SIZE + - ate_size; + uint32_t store_expected = sizeof(s_data) + sizeof(struct emds_data_entry); + uint32_t store_used; for (int i = 0; i < ARRAY_SIZE(d_entries); i++) { err = emds_entry_add(&d_entries[i]); - store_expected += DIV_ROUND_UP(d_entries[i].entry.len, EMDS_FLASH_BLOCK_SIZE) * - EMDS_FLASH_BLOCK_SIZE; - store_expected += ate_size; + store_expected += d_entries[i].entry.len + sizeof(struct emds_data_entry); zassert_equal(err, 0, "Add entry failed"); err = emds_entry_add(&d_entries[i]); zassert_equal(err, -EINVAL, "Entry duplicated"); } - uint32_t store_used = emds_store_size_get(); + err = emds_store_size_get(&store_used); + zassert_equal(err, 0, "Getting store size failed"); zassert_equal(store_used, store_expected, "Wrong storage size: expected: %i, got: %i", store_expected, store_used); @@ -159,7 +139,7 @@ static void load_empty_flash(void) memcpy(d_data, test_d_data, sizeof(d_data)); memcpy(s_data, test_s_data, sizeof(s_data)); - zassert_equal(emds_load(), 0, "Load failed"); + zassert_equal(emds_load(), -ENOENT, "Load failed"); zassert_mem_equal(d_data, test_d_data, sizeof(d_data), "Data has changed"); @@ -167,16 +147,16 @@ static void load_empty_flash(void) "Data has changed"); } -static void load_flash(void) +static void load_flash(int idx) { memset(d_data, 0, sizeof(d_data)); memset(s_data, 0, sizeof(s_data)); zassert_equal(emds_load(), 0, "Load failed"); - zassert_mem_equal(d_data, expect_d_data, sizeof(expect_d_data), + zassert_mem_equal(d_data, &expect_d_data[idx][0][0], sizeof(d_data), "Data has changed"); - zassert_mem_equal(s_data, expect_s_data, sizeof(expect_s_data), + zassert_mem_equal(s_data, &expect_s_data[idx][0], sizeof(s_data), "Data has changed"); } @@ -191,12 +171,12 @@ static void prepare(void) zassert_true(emds_is_ready(), "EMDS should be ready"); } -static void store(void) +static void store(int idx) { zassert_true(emds_is_ready(), "Store should be ready to execute"); - memcpy(d_data, expect_d_data, sizeof(expect_d_data)); - memcpy(s_data, expect_s_data, sizeof(expect_s_data)); + memcpy(d_data, &expect_d_data[idx][0][0], sizeof(d_data)); + memcpy(s_data, &expect_s_data[idx][0], sizeof(s_data)); #if defined(CONFIG_BT) && !defined(CONFIG_BT_LL_SW_SPLIT) /* Disable bluetooth and mpsl scheduler if bluetooth is enabled. */ @@ -208,16 +188,17 @@ static void store(void) zassert_equal(emds_store(), 0, "Store failed"); - k_yield(); int64_t store_time_ticks = k_uptime_ticks() - start_tic; zassert_false(emds_is_ready(), "Store not completed"); - uint64_t store_time_us = k_ticks_to_us_ceil64(store_time_ticks); + uint32_t store_time_us = k_ticks_to_us_near32(store_time_ticks); + + uint32_t estimate_store_time_us = 0; - uint32_t estimate_store_time_us = emds_store_time_get(); + zassert_equal(emds_store_time_get(&estimate_store_time_us), 0, "Getting store time failed"); - printf("Store time: Actual %lldus, Worst case: %dus\n", + printf("Store time: Actual %dus, Worst case: %dus\n", store_time_us, estimate_store_time_us); zassert_true((store_time_us < estimate_store_time_us), "Store takes to long time"); @@ -307,45 +288,42 @@ ZTEST(empty_flash, test_empty_flash) { load_empty_flash(); prepare(); - store(); - load_flash(); + store(0); + load_flash(0); } ZTEST(store_data, test_store_data) { - load_flash(); + load_flash(0); prepare(); - store(); - load_flash(); + store(1); + load_flash(1); } ZTEST(clear_flash, test_clear_flash) { - load_flash(); - prepare(); - store(); + load_flash(0); clear(); load_empty_flash(); } ZTEST(no_store, test_no_store) { - load_flash(); + clear(); + load_empty_flash(); prepare(); load_empty_flash(); } ZTEST(several_store, test_several_store) { - load_flash(); + load_flash(1); prepare(); - load_empty_flash(); - store(); - load_flash(); + store(2); + load_flash(2); prepare(); - load_empty_flash(); - store(); - load_flash(); + store(0); + load_flash(0); } ZTEST_SUITE(_setup, pragma_always, NULL, NULL, NULL, NULL); diff --git a/tests/subsys/emds/emds_flash/prj.conf b/tests/subsys/emds/emds_flash/prj.conf index 924c26635894..b902b8539f83 100644 --- a/tests/subsys/emds/emds_flash/prj.conf +++ b/tests/subsys/emds/emds_flash/prj.conf @@ -10,4 +10,4 @@ CONFIG_FLASH_MAP=y CONFIG_DK_LIBRARY=y CONFIG_EMDS=y CONFIG_BT=y -CONFIG_CRC=y +CONFIG_ENTROPY_GENERATOR=y diff --git a/tests/subsys/emds/emds_flash/src/main.c b/tests/subsys/emds/emds_flash/src/main.c index 5083819830be..9159363b7efd 100644 --- a/tests/subsys/emds/emds_flash/src/main.c +++ b/tests/subsys/emds/emds_flash/src/main.c @@ -1,15 +1,17 @@ /* - * Copyright (c) 2022 Nordic Semiconductor ASA + * Copyright (c) 2025 Nordic Semiconductor ASA * * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ +#include + #include -#include -#include -#include -#include +#include #include +#include +#include +#include #include #include @@ -21,661 +23,564 @@ #if defined CONFIG_SOC_FLASH_NRF_RRAM #define RRAM DT_INST(0, soc_nv_flash) #define EMDS_FLASH_BLOCK_SIZE DT_PROP(RRAM, write_block_size) -#define EXPECTED_STORE_TIME_BLOCK_SIZE (400) +/* 32bits word in buffered stream is written typically 22us */ +/* 16 bytes should take ideally 88us (really takes 62 - 128us) */ +#define EXPECTED_STORE_TIME_BLOCK_SIZE (150) +/* 1024 bytes should take ideally 5632us (really takes 5664 - 5824us) */ #define EXPECTED_STORE_TIME_1024 (6200) #else #define FLASH DT_INST(0, soc_nv_flash) #define EMDS_FLASH_BLOCK_SIZE DT_PROP(FLASH, write_block_size) -#define EXPECTED_STORE_TIME_BLOCK_SIZE (200) -#define EXPECTED_STORE_TIME_1024 (13000) +/* 32bits word is written maximum 41us (from datasheet) */ +#define EXPECTED_STORE_TIME_BLOCK_SIZE (41) +/* 1024 bytes should take ideally 10496us (really takes about 11000us) */ +#define EXPECTED_STORE_TIME_1024 (11500) #endif -#define ADDR_OFFS_MASK 0x0000FFFF - -static struct { - const struct device *fd; - uint32_t offset; - uint32_t size; - uint32_t ate_idx_start; - uint32_t data_wra_offset; -} m_test_fd; - -/* Allocation Table Entry */ -struct test_ate { - uint16_t id; /* data id */ - uint16_t offset; /* data offset within sector */ - uint16_t len; /* data len within sector */ - uint8_t crc8_data; /* crc8 check of the entry */ - uint8_t crc8; /* crc8 check of the entry */ -} __packed; - -static struct emds_fs ctx; -static uint16_t m_sec_size; -static uint16_t m_sec_cnt; -static const struct flash_area *m_fa; +#define EMDS_SNAPSHOT_METADATA_MARKER 0x4D444553 +#define PARTITIONS_NUM_MAX 2 -/** Local functions ***********************************************************/ +static struct emds_partition partition[PARTITIONS_NUM_MAX]; -static int flash_block_cmp(uint32_t addr, const void *data, size_t len) +/** Local functions ***********************************************************/ +static void *emds_flash_setup(void) { - const uint8_t *data8 = (const uint8_t *)data; - int rc; - size_t bytes_to_cmp, block_size; - uint8_t buf[32]; - - block_size = sizeof(buf) & ~(4 - 1U); - - while (len) { - bytes_to_cmp = MIN(block_size, len); - rc = flash_read(m_test_fd.fd, addr, buf, bytes_to_cmp); - if (rc) { - return rc; - } - rc = memcmp(data8, buf, bytes_to_cmp); - if (rc) { - return -ENOTSUP; - } - - len -= bytes_to_cmp; - addr += bytes_to_cmp; - data8 += bytes_to_cmp; + const uint8_t id[] = {FIXED_PARTITION_ID(emds_partition_0), + FIXED_PARTITION_ID(emds_partition_1)}; + + for (int i = 0; i < ARRAY_SIZE(id); i++) { + zassert_ok(flash_area_open(id[i], &partition[i].fa), "Failed to open flash area %d", + id[i]); + zassert_ok(emds_flash_init(&partition[i]), "Failed to initialize flash area %d", + id[i]); } - return 0; + return NULL; } -static int flash_cmp_const(uint32_t addr, uint8_t value, size_t len) +static void emds_flash_partitions_erase(void *fixture) { - int rc; - size_t bytes_to_cmp, block_size; - uint8_t cmp[32]; - - block_size = sizeof(cmp) & ~(4 - 1U); - - memset(cmp, value, block_size); - while (len) { - bytes_to_cmp = MIN(block_size, len); - rc = flash_block_cmp(addr, cmp, bytes_to_cmp); + (void)fixture; - if (rc) { - return rc; - } - len -= bytes_to_cmp; - addr += bytes_to_cmp; + for (int i = 0; i < PARTITIONS_NUM_MAX; i++) { + zassert_ok(emds_flash_erase_partition(&partition[i]), + "Failed to erase partition %d", i); } - return 0; } -static inline size_t align_size(size_t len) +static struct emds_snapshot_metadata * +snapshot_make(const struct emds_partition *partition, + struct emds_snapshot_metadata *previous_snapshot, off_t metadata_off, + bool is_md_crc_correct, bool is_d_crc_correct, uint32_t fresh_cnt) { - return (len + (EMDS_FLASH_BLOCK_SIZE - 1U)) & ~(EMDS_FLASH_BLOCK_SIZE - 1U); -} + static struct emds_snapshot_metadata metadata; + uint8_t data[128 + sizeof(struct emds_data_entry)] = {0}; + uint8_t data_len = sys_rand32_get() % 128 + sizeof(struct emds_data_entry) + 1; -static int data_write(const void *data, size_t len) -{ - const uint8_t *data8 = (const uint8_t *)data; - int rc = 0; - off_t offset; - size_t blen; - size_t temp_len = len; - uint8_t buf[32]; - - if (!temp_len) { - /* Nothing to write, avoid changing the flash protection */ - return 0; + for (int i = 0; i < data_len; i++) { + data[i] = sys_rand32_get() % 256; } - offset = m_test_fd.offset; - offset += m_test_fd.data_wra_offset & ADDR_OFFS_MASK; - - blen = temp_len & ~(4 - 1U); - - /* Writes multiples of 4 bytes to flash */ - if (blen > 0) { - rc = flash_write(m_test_fd.fd, offset, data8, blen); - if (rc) { - /* flash write error */ - goto end; - } - temp_len -= blen; - offset += blen; - data8 += blen; + if (previous_snapshot) { + metadata.data_instance_off = previous_snapshot->data_instance_off + + ROUND_UP(previous_snapshot->data_instance_len, + partition->fp->write_block_size); + } else { + memset(&metadata, 0, sizeof(metadata)); + metadata.marker = EMDS_SNAPSHOT_METADATA_MARKER; } - - /* Writes remaining (len < 4) bytes to flash */ - /* Should NEVER be used for ATE!? */ - if (temp_len) { - memcpy(buf, data8, temp_len); - memset(buf + temp_len, 0, 4 - temp_len); - - rc = flash_write(m_test_fd.fd, offset, buf, 4); + metadata.data_instance_len = data_len; + metadata.fresh_cnt = fresh_cnt; + + if (REGIONS_OVERLAP(metadata_off, sizeof(struct emds_snapshot_metadata), + metadata.data_instance_off, + ROUND_UP(data_len, partition->fp->write_block_size)) || + metadata_off <= metadata.data_instance_off) { + return NULL; } -end: - m_test_fd.data_wra_offset += align_size(len); - return rc; - -} - -static void *fs_init(void) -{ - int rc; - uint32_t sector_cnt = 1; - struct flash_sector hw_flash_sector; - size_t emds_flash_size = 0; - uint16_t cnt = 0; - - rc = flash_area_open(FIXED_PARTITION_ID(emds_storage), &m_fa); - __ASSERT(rc == 0, "Failed opening flash area (err:%d)", rc); - - rc = flash_area_get_sectors(FIXED_PARTITION_ID(emds_storage), §or_cnt, - &hw_flash_sector); - - __ASSERT(rc == 0, "Failed when getting sector information (err:%d", rc); - __ASSERT(hw_flash_sector.fs_size <= UINT16_MAX, "fs_size (%u) exceeds UINT16_MAX", - hw_flash_sector.fs_size); - - emds_flash_size += hw_flash_sector.fs_size; - if (emds_flash_size <= m_fa->fa_size) { - cnt++; + metadata.metadata_crc = + crc32_k_4_2_update(0, (const unsigned char *)&metadata, + offsetof(struct emds_snapshot_metadata, metadata_crc)); + if (!is_md_crc_correct) { + metadata.metadata_crc++; } - m_sec_size = hw_flash_sector.fs_size; - m_sec_cnt = cnt; - ctx.offset = m_fa->fa_off; - ctx.sector_cnt = m_sec_cnt; - ctx.sector_size = m_sec_size; + metadata.snapshot_crc = crc32_k_4_2_update(0, data, data_len); + if (!is_d_crc_correct) { + metadata.snapshot_crc++; + } - m_test_fd.fd = m_fa->fa_dev; - m_test_fd.offset = m_fa->fa_off; - m_test_fd.size = hw_flash_sector.fs_size; - m_test_fd.ate_idx_start = - m_fa->fa_off + hw_flash_sector.fs_size - align_size(sizeof(struct test_ate)); - m_test_fd.data_wra_offset = 0; + zassert_ok(flash_area_write(partition->fa, metadata_off, &metadata, sizeof(metadata)), + "Failed to write metadata to flash area"); - return NULL; -} + data_len = ROUND_UP(data_len, sizeof(uint32_t)); + zassert_ok(flash_area_write(partition->fa, metadata.data_instance_off, data, data_len), + "Failed to write data to flash area"); -static int flash_clear(void) -{ - m_test_fd.data_wra_offset = 0; - return flash_erase(m_test_fd.fd, m_test_fd.offset, m_test_fd.size); -} - -static int entry_write(uint32_t ate_wra, uint16_t id, const void *data, size_t len) -{ - struct test_ate entry = { - .id = id, - .offset = m_test_fd.data_wra_offset, - .len = (uint16_t)len, - .crc8_data = crc8_ccitt(0xff, data, len), - .crc8 = crc8_ccitt(0xff, &entry, offsetof(struct test_ate, crc8)), - }; - - data_write(data, len); - return flash_write(m_test_fd.fd, ate_wra, &entry, sizeof(struct test_ate)); -} - -static int ate_invalidate_write(uint32_t ate_wra) -{ - uint8_t invalid[8]; - - memset(invalid, 0, sizeof(invalid)); - - return flash_write(m_test_fd.fd, ate_wra, invalid, sizeof(invalid)); + return &metadata; } +/** End Local functions *******************************************************/ -static int ate_corrupt_write(uint32_t ate_wra) +/* Test checks scanning the empty partition */ +ZTEST(emds_flash, test_scanning_empty_partition) { - uint8_t corrupt[8]; + struct emds_snapshot_candidate candidate; - memset(corrupt, 69, sizeof(corrupt)); - - return flash_write(m_test_fd.fd, ate_wra, corrupt, sizeof(corrupt)); + candidate.metadata.fresh_cnt = 0; + zassert_ok(emds_flash_scan_partition(&partition[0], &candidate), + "Failed to scan partition 0"); + zassert_equal(candidate.metadata.fresh_cnt, 0, + "Fresh count has been changed for empty partition"); } -static int corrupt_write_all(void) +/* Test checks that the partition scanning behaves equally for both partitions */ +ZTEST(emds_flash, test_scanning_partitions_equality) { - uint8_t corrupt[32]; - - memset(corrupt, 69, sizeof(corrupt)); + struct emds_snapshot_candidate candidate; + struct emds_snapshot_metadata *metadata = NULL; + off_t metadata_off = partition[0].fa->fa_size; + uint32_t fresh_cnt = 0; + + candidate.metadata.fresh_cnt = 0; + for (int i = 0; i < CONFIG_EMDS_MAX_CANDIDATES; i++) { + metadata_off -= sizeof(struct emds_snapshot_metadata); + fresh_cnt++; + metadata = + snapshot_make(&partition[0], metadata, metadata_off, true, true, fresh_cnt); + zassert_not_null(metadata, "Failed to create snapshot on partition 0"); + } - for (uint32_t i = m_test_fd.offset; i < m_test_fd.offset + m_test_fd.size; i += 32) { - flash_write(m_test_fd.fd, i, corrupt, sizeof(corrupt)); + metadata = NULL; + metadata_off = partition[1].fa->fa_size; + for (int i = 0; i < CONFIG_EMDS_MAX_CANDIDATES; i++) { + metadata_off -= sizeof(struct emds_snapshot_metadata); + fresh_cnt++; + metadata = + snapshot_make(&partition[1], metadata, metadata_off, true, true, fresh_cnt); + zassert_not_null(metadata, "Failed to create snapshot on partition 1"); } - return 0; + zassert_ok(emds_flash_scan_partition(&partition[0], &candidate), + "Failed to scan partition 0"); + zassert_equal(candidate.metadata_off, + partition[0].fa->fa_size - + CONFIG_EMDS_MAX_CANDIDATES * sizeof(struct emds_snapshot_metadata), + "Metadata offset mismatch for partition 0"); + zassert_equal(candidate.metadata.fresh_cnt, CONFIG_EMDS_MAX_CANDIDATES, + "Fresh count mismatch for partition 0"); + + zassert_ok(emds_flash_scan_partition(&partition[1], &candidate), + "Failed to scan partition 1"); + zassert_equal(candidate.metadata_off, + partition[1].fa->fa_size - + CONFIG_EMDS_MAX_CANDIDATES * sizeof(struct emds_snapshot_metadata), + "Metadata offset mismatch for partition 1"); + zassert_equal(candidate.metadata.fresh_cnt, CONFIG_EMDS_MAX_CANDIDATES * 2, + "Fresh count mismatch for partition 1"); } -static void device_reset(void) +/* Test checks that emds scans partitions on full deepness. */ +ZTEST(emds_flash, test_scanning_partitions_deepness) { - memset(&ctx, 0, sizeof(ctx)); - ctx.sector_cnt = m_sec_cnt; - ctx.sector_size = m_sec_size; - ctx.offset = m_fa->fa_off; - ctx.flash_dev = m_fa->fa_dev; + struct emds_snapshot_candidate candidate; + int partition_index = sys_rand32_get() % PARTITIONS_NUM_MAX; + struct emds_snapshot_metadata *metadata = NULL; + off_t metadata_off = partition[partition_index].fa->fa_size; + uint32_t fresh_cnt = 0; + + candidate.metadata.fresh_cnt = 0; + do { + metadata_off -= sizeof(struct emds_snapshot_metadata); + fresh_cnt++; + metadata = snapshot_make(&partition[partition_index], metadata, metadata_off, true, + true, fresh_cnt); + } while (metadata != NULL); + fresh_cnt--; + + zassert_ok(emds_flash_scan_partition(&partition[partition_index], &candidate), + "Failed to scan partition %d", partition_index); + zassert_equal(candidate.metadata.fresh_cnt, fresh_cnt, "Fresh count mismatch"); } -/** End Local functions *******************************************************/ - -ZTEST(emds_flash_tests, test_initialize) +/* Test checks that emds always finds the freshest snapshot. */ +ZTEST(emds_flash, test_scanning_partitions_freshness) { - /* Check that device inits on first attempt, and rejects init on consecutive attempts */ - flash_clear(); - device_reset(); + struct emds_snapshot_candidate candidate; + int partition_index = sys_rand32_get() % PARTITIONS_NUM_MAX; + struct emds_snapshot_metadata *metadata = NULL; + off_t metadata_off = partition[partition_index].fa->fa_size; + uint32_t fresh_cnt = 0; + uint32_t fresh_cnt_max = 0; + + candidate.metadata.fresh_cnt = 0; + do { + metadata_off -= sizeof(struct emds_snapshot_metadata); + fresh_cnt = sys_rand32_get() % 100 + 1; + metadata = snapshot_make(&partition[partition_index], metadata, metadata_off, true, + true, fresh_cnt); + if (fresh_cnt > fresh_cnt_max && metadata != NULL) { + fresh_cnt_max = fresh_cnt; + } + } while (metadata != NULL); - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - zassert_true(emds_flash_init(&ctx), "Should not init more than once"); + zassert_ok(emds_flash_scan_partition(&partition[partition_index], &candidate), + "Failed to scan partition %d", partition_index); + zassert_equal(candidate.metadata.fresh_cnt, fresh_cnt_max, "Fresh count mismatch"); } -ZTEST(emds_flash_tests, test_rd_wr_simple) +/* Test checks that emds ignores snapshot with wrong crc (both data and metadata). */ +ZTEST(emds_flash, test_scanning_partitions_wrong_crc) { - /* Init the device, performs prepare and does a write and read. - * Verifies that entry is valid - */ - uint8_t data_in[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; - uint8_t data_out[8] = { 0 }; - - flash_clear(); - device_reset(); - - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - zassert_false(emds_flash_prepare(&ctx, sizeof(data_in) + ctx.ate_size), "Prepare failed"); - zassert_false(emds_flash_write(&ctx, 1, data_in, sizeof(data_in)) < 0, "Error when write"); - zassert_false(emds_flash_read(&ctx, 1, data_out, sizeof(data_out)) < 0, "Error when read"); - zassert_false(memcmp(data_out, data_in, sizeof(data_out)), "Retrived wrong value"); -} + struct emds_snapshot_candidate candidate; + int partition_index = sys_rand32_get() % PARTITIONS_NUM_MAX; + struct emds_snapshot_metadata *metadata = NULL; + off_t metadata_off = partition[partition_index].fa->fa_size; + uint32_t fresh_cnt = 1; + + candidate.metadata.fresh_cnt = 0; + metadata_off -= sizeof(struct emds_snapshot_metadata); + metadata = snapshot_make(&partition[partition_index], metadata, metadata_off, true, true, + fresh_cnt); + zassert_not_null(metadata, "Failed to create snapshot on partition %d", partition_index); + + for (int i = 0; i < 2; i++) { + metadata_off -= sizeof(struct emds_snapshot_metadata); + fresh_cnt++; + metadata = snapshot_make(&partition[partition_index], metadata, metadata_off, false, + true, fresh_cnt); + zassert_not_null(metadata, "Failed to create snapshot on partition %d", + partition_index); + } -ZTEST(emds_flash_tests, test_flash_recovery) -{ - char data_in1[9] = "Deadbeef"; - char data_in2[8] = "Beafded"; - char data_in3[13] = "Deadbeefface"; - char data_out[32] = {0}; - uint32_t idx; - - flash_clear(); - device_reset(); - - /* Entries: - * #1: Empty - * #2: Empty - * #3: Empty - * #4: Empty - * #5: Empty - * #6: Empty - * ... - * Expect: Normal behavior - */ - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - zassert_false(ctx.force_erase, "Expected false"); - zassert_equal(m_test_fd.ate_idx_start, ctx.ate_wra, "%X not equal to %X", - m_test_fd.ate_idx_start, ctx.ate_wra); - device_reset(); - - /* Entries: - * #1: Valid - * #2: Empty - * #3: Empty - * #4: Empty - * #5: Empty - * #6: Empty - * ... - * Expect: Normal behavior - */ - idx = m_test_fd.ate_idx_start; - entry_write(idx, 1, data_in1, sizeof(data_in1)); - idx -= align_size(sizeof(struct test_ate)); - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - zassert_true(emds_flash_read(&ctx, 1, data_out, sizeof(data_in1)) > 0, "Could not read"); - (void)emds_flash_read(&ctx, 1, data_out, sizeof(data_in1)); - zassert_false(ctx.force_erase, "Expected false"); - zassert_equal(idx, ctx.ate_wra, "Addr not equal"); - zassert_false(memcmp(data_in1, data_out, sizeof(data_in1)), "Not same data"); - device_reset(); - - /* Entries: - * #1: Inval - * #2: Valid - * #3: Empty - * #4: Empty - * #5: Empty - * #6: Empty - * ... - * Expect: Normal behavior - */ - idx = m_test_fd.ate_idx_start; - ate_invalidate_write(idx); - idx -= align_size(sizeof(struct test_ate)); - entry_write(idx, 2, data_in2, sizeof(data_in2)); - idx -= align_size(sizeof(struct test_ate)); - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - zassert_false(ctx.force_erase, "Expected false"); - zassert_equal(idx, ctx.ate_wra, "Addr not equal"); - zassert_true(emds_flash_read(&ctx, 2, data_out, sizeof(data_in2)) > 0, "Could not read"); - zassert_false(memcmp(data_in2, data_out, sizeof(data_in2)), "Not same data"); - device_reset(); - - /* Entries: - * #1: Inval - * #2: Valid - * #3: Inval - * #4: Valid - * #5: Empty - * #6: Empty - * ... - * Expect: Unexpected behavior - */ - ate_invalidate_write(idx); - idx -= align_size(sizeof(struct test_ate)); - entry_write(idx, 3, data_in3, sizeof(data_in3)); - idx -= align_size(sizeof(struct test_ate)); - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - zassert_true(ctx.force_erase, "Expected true"); - zassert_equal(idx, ctx.ate_wra, "Addr not equal"); - zassert_true(emds_flash_read(&ctx, 2, data_out, sizeof(data_in2)) > 0, "Could not read"); - zassert_false(memcmp(data_in2, data_out, sizeof(data_in2)), "Not same data"); - zassert_true(emds_flash_read(&ctx, 3, data_out, sizeof(data_in3)) > 0, "Could not read"); - zassert_false(memcmp(data_in3, data_out, sizeof(data_in3)), "Not same data"); - device_reset(); - - /* Entries: - * #1: Inval - * #2: Inval - * #3: Inval - * #4: Valid - * #5: Corrupt - * #6: Empty - * ... - * Expect: Unexpected behavior. - * - Erase flag raised. - * - Should be able to recover valid entry - */ - idx = m_test_fd.ate_idx_start; - for (size_t i = 0; i < 3; i++) { - ate_invalidate_write(idx); - idx -= align_size(sizeof(struct test_ate)); + for (int i = 0; i < 2; i++) { + metadata_off -= sizeof(struct emds_snapshot_metadata); + fresh_cnt++; + metadata = snapshot_make(&partition[partition_index], metadata, metadata_off, true, + false, fresh_cnt); + zassert_not_null(metadata, "Failed to create snapshot on partition %d", + partition_index); } - idx -= align_size(sizeof(struct test_ate)); - ate_corrupt_write(idx); - idx -= align_size(sizeof(struct test_ate)); - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - zassert_true(ctx.force_erase, "Expected true"); - zassert_equal(idx, ctx.ate_wra, "Addr not equal"); - zassert_true(emds_flash_read(&ctx, 3, data_out, sizeof(data_in3)) > 0, "Could not read"); - zassert_false(memcmp(data_in3, data_out, sizeof(data_in3)), "Not same data"); + zassert_ok(emds_flash_scan_partition(&partition[partition_index], &candidate), + "Failed to scan partition %d", partition_index); + zassert_equal(candidate.metadata.fresh_cnt, 1, "Fresh count mismatch"); } -ZTEST(emds_flash_tests, test_flash_recovery_corner_case) +/* Test checks that emds scanning has limitation according to KConfig options. */ +ZTEST(emds_flash, test_scanning_limitations) { - flash_clear(); - device_reset(); - - /* Fill flash with garbage. Expect 0 space left and erase on prepare */ - corrupt_write_all(); - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - zassert_true(ctx.force_erase, "Force erase should be true"); - zassert_equal(0, emds_flash_free_space_get(&ctx), "Expected no free space"); - device_reset(); -} - - -ZTEST(emds_flash_tests, test_invalidate_on_prepare) -{ - char data_in[9] = "Deadbeef"; - char data_out[9] = {0}; - - flash_clear(); - device_reset(); - - uint32_t idx = m_test_fd.ate_idx_start; - - for (size_t i = 0; i < 5; i++) { - entry_write(idx, i, data_in, sizeof(data_in)); - idx -= align_size(sizeof(struct test_ate)); + struct emds_snapshot_candidate candidate; + int partition_index = sys_rand32_get() % PARTITIONS_NUM_MAX; + struct emds_snapshot_metadata *metadata = NULL; + off_t metadata_off = partition[partition_index].fa->fa_size; + uint32_t fresh_cnt = 1; + + candidate.metadata.fresh_cnt = 0; + + metadata_off -= sizeof(struct emds_snapshot_metadata); + metadata = snapshot_make(&partition[partition_index], metadata, metadata_off, true, true, + fresh_cnt); + zassert_not_null(metadata, "Failed to create snapshot on partition %d", partition_index); + + for (int i = 0; i < CONFIG_EMDS_MAX_CANDIDATES; i++) { + metadata_off -= sizeof(struct emds_snapshot_metadata); + fresh_cnt++; + metadata = snapshot_make(&partition[partition_index], metadata, metadata_off, true, + false, fresh_cnt); + zassert_not_null(metadata, "Failed to create snapshot on partition %d", + partition_index); } - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - zassert_false(ctx.force_erase, "Force erase should be false"); - zassert_equal(idx, ctx.ate_wra, "Addr not equal"); - - for (size_t i = 0; i < 5; i++) { - zassert_true(emds_flash_read(&ctx, i, data_out, sizeof(data_in)) > 0, - "Could not read"); - zassert_false(memcmp(data_in, data_out, sizeof(data_in)), "Not same data"); - memset(data_out, 0, sizeof(data_out)); + for (int i = 0; i <= CONFIG_EMDS_SCANNING_FAILURES; i++) { + metadata_off -= sizeof(struct emds_snapshot_metadata); + fresh_cnt++; + metadata = snapshot_make(&partition[partition_index], metadata, metadata_off, false, + true, fresh_cnt); + zassert_not_null(metadata, "Failed to create snapshot on partition %d", + partition_index); } - zassert_false(emds_flash_prepare(&ctx, 0), "Error when preparing"); - - device_reset(); - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - - /* Expect the free storage integrity to fail due to no valid ATE and invalid presence. - * This occurs only if the user does not store any new entry after emds_flash_prepare - */ - zassert_true(ctx.force_erase, "Expected true"); - for (size_t i = 0; i < 5; i++) { - zassert_false(emds_flash_read(&ctx, i, data_out, sizeof(data_in)) > 0, - "Should not be able to read"); - } - zassert_false(emds_flash_prepare(&ctx, 0), "Error when preparing"); + fresh_cnt++; + metadata_off -= sizeof(struct emds_snapshot_metadata); + metadata = snapshot_make(&partition[partition_index], metadata, metadata_off, true, true, + fresh_cnt); + zassert_not_null(metadata, "Failed to create snapshot on partition %d", partition_index); - /* Reset again without writing */ - device_reset(); - - /* If the device yet again runs emds_flash_prepare without writing the flash should - * be in a clean state - */ - zassert_false(ctx.force_erase, "Force erase should be false"); + zassert_ok(emds_flash_scan_partition(&partition[partition_index], &candidate), + "Failed to scan partition %d", partition_index); + zassert_equal(candidate.metadata.fresh_cnt, 0, "Fresh count mismatch"); } -ZTEST(emds_flash_tests, test_clear_on_strange_flash) +/* Test checks allocation snapshot on the empty partition */ +ZTEST(emds_flash, test_allocation_empty_partition) { - flash_clear(); - device_reset(); - - /* Entries: - * #1: Corrupt - * #2: Empty - * #3: Empty - * ... - * Expect: Unexpected behavior - */ - ate_corrupt_write(m_test_fd.ate_idx_start); - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - zassert_true(ctx.force_erase, "Force erase should be true"); - zassert_false(emds_flash_prepare(&ctx, 0), "Error when preparing"); - zassert_false(flash_cmp_const(m_test_fd.offset, 0xff, m_test_fd.size), "Flash not cleared"); + int partition_index = sys_rand32_get() % PARTITIONS_NUM_MAX; + struct emds_snapshot_candidate allocated_snapshot = { + .partition_index = partition_index, + .metadata.fresh_cnt = 1 + }; + + zassert_ok(emds_flash_allocate_snapshot(&partition[partition_index], NULL, + &allocated_snapshot, 100), + "Failed to allocate snapshot on partition %d", partition_index); + + zassert_equal(allocated_snapshot.partition_index, partition_index, + "Partition index is not equal to the requested one"); + zassert_equal(allocated_snapshot.metadata_off, + partition[partition_index].fa->fa_size - + sizeof(struct emds_snapshot_metadata), + "Metadata offset is not equal to the end of the partition"); + zassert_equal(allocated_snapshot.metadata.data_instance_off, 0, + "Data instance offset is not equal to 0 for the empty partition"); + zassert_equal(allocated_snapshot.metadata.data_instance_len, 100, + "Data instance length is not equal to requested for the empty partition"); + zassert_equal(allocated_snapshot.metadata.fresh_cnt, 1, + "Fresh count is not equal to 1 for the empty partition"); + zassert_equal(allocated_snapshot.metadata.metadata_crc, + crc32_k_4_2_update(0, (const unsigned char *)&allocated_snapshot.metadata, + offsetof(struct emds_snapshot_metadata, metadata_crc)), + "Metadata CRC is not equal to calculated for the empty partition"); } -ZTEST(emds_flash_tests, test_permission) +/* Test checks allocation snapshot on the same partition after the freshest one. */ +ZTEST(emds_flash, test_allocation_after_freshest_same_partition) { - char data_in[8] = "Deadbeef"; - char data_out[8] = {0}; - - flash_clear(); - device_reset(); - - zassert_true(emds_flash_prepare(&ctx, sizeof(data_in) + ctx.ate_size) == -EACCES, - "Prepare did not fail"); - zassert_true(emds_flash_read(&ctx, 1, data_out, sizeof(data_in)) == -EACCES, - "Should not be able to read"); - zassert_true(emds_flash_write(&ctx, 1, data_in, sizeof(data_in)) == -EACCES, - "Should not be able to read"); - - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - - zassert_true(emds_flash_write(&ctx, 1, data_in, sizeof(data_in)) == -EACCES, - "Should not be able to read"); - - zassert_false(emds_flash_prepare(&ctx, sizeof(data_in) + ctx.ate_size), "Prepare failed"); - - zassert_true(emds_flash_write(&ctx, 1, data_in, sizeof(data_in)) == sizeof(data_in), - "Should be able to write"); - zassert_true(emds_flash_read(&ctx, 1, data_out, sizeof(data_in)) == sizeof(data_in), - "Should be able to read"); - zassert_false(memcmp(data_out, data_in, sizeof(data_out)), "Retrived wrong value"); - + struct emds_snapshot_candidate freshest_snapshot; + int partition_index = sys_rand32_get() % PARTITIONS_NUM_MAX; + struct emds_snapshot_candidate allocated_snapshot = { + .partition_index = partition_index, + .metadata.fresh_cnt = 2 + }; + struct emds_snapshot_metadata *metadata = NULL; + off_t metadata_off = + partition[partition_index].fa->fa_size - sizeof(struct emds_snapshot_metadata); + uint32_t fresh_cnt = 1; + + metadata = snapshot_make(&partition[partition_index], metadata, metadata_off, true, true, + fresh_cnt); + zassert_not_null(metadata, "Failed to create snapshot on partition %d", partition_index); + freshest_snapshot.metadata_off = metadata_off; + freshest_snapshot.metadata = *metadata; + + zassert_ok(emds_flash_allocate_snapshot(&partition[partition_index], &freshest_snapshot, + &allocated_snapshot, 100), + "Failed to allocate snapshot on partition %d", partition_index); + + zassert_equal(allocated_snapshot.partition_index, partition_index, + "Partition index is not equal to the requested one"); + zassert_equal(allocated_snapshot.metadata_off, + partition[partition_index].fa->fa_size - + 2 * sizeof(struct emds_snapshot_metadata), + "Metadata offset is not equal to expected"); + zassert_equal(allocated_snapshot.metadata.data_instance_off, + ROUND_UP(metadata->data_instance_len, + partition[partition_index].fp->write_block_size), + "Data instance offset is not equal to expected"); + zassert_equal(allocated_snapshot.metadata.data_instance_len, 100, + "Data instance length is not equal to requested"); + zassert_equal(allocated_snapshot.metadata.fresh_cnt, 2, "Fresh count is not equal to 2"); + zassert_equal(allocated_snapshot.metadata.metadata_crc, + crc32_k_4_2_update(0, (const unsigned char *)&allocated_snapshot.metadata, + offsetof(struct emds_snapshot_metadata, metadata_crc)), + "Metadata CRC is not equal to calculated for the empty partition"); } -ZTEST(emds_flash_tests, test_overflow) +/* Test checks allocation snapshot with data size larger than partition size. + */ +ZTEST(emds_flash, test_allocation_data_larger_than_partition) { - char data_in[8] = "Deadbee"; - char data_out[8] = {0}; - - flash_clear(); - device_reset(); - - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - zassert_false(emds_flash_prepare(&ctx, sizeof(data_in) + ctx.ate_size), "Prepare failed"); - - uint16_t test_cnt = 0; - - while (emds_flash_free_space_get(&ctx)) { - emds_flash_write(&ctx, test_cnt, data_in, sizeof(data_in)); - data_in[0]++; - test_cnt++; - } - - /* Try to write to full flash */ - zassert_true(emds_flash_write(&ctx, 1, data_in, sizeof(data_in)) < 0, - "Should not be able to write"); - - device_reset(); - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - - data_in[0] = 'D'; - for (size_t i = 0; i < test_cnt; i++) { - (void)emds_flash_read(&ctx, i, data_out, sizeof(data_out)); - zassert_false(memcmp(data_out, data_in, sizeof(data_out)), "Retrived wrong value"); - memset(data_out, 0, sizeof(data_out)); - data_in[0]++; - } - - - zassert_false(emds_flash_prepare(&ctx, sizeof(data_in) + ctx.ate_size), "Prepare failed"); - - data_in[0] = 'D'; - emds_flash_write(&ctx, 0, data_in, sizeof(data_in)); - - device_reset(); - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - - emds_flash_read(&ctx, 0, data_out, sizeof(data_out)); - zassert_false(memcmp(data_out, data_in, sizeof(data_out)), "Retrived wrong value"); - - zassert_equal(emds_flash_free_space_get(&ctx), - m_test_fd.size - (align_size(sizeof(data_out)) + - align_size(sizeof(struct test_ate)) * 2), - ""); + int partition_index = sys_rand32_get() % PARTITIONS_NUM_MAX; + size_t data_size = partition[partition_index].fa->fa_size + 100; + struct emds_snapshot_candidate allocated_snapshot; + + zassert_equal(emds_flash_allocate_snapshot(&partition[partition_index], NULL, + &allocated_snapshot, data_size), + -EINVAL, + "Expected -EINVAL when trying to allocate snapshot with data size larger " + "than partition size"); } - -ZTEST(emds_flash_tests, test_prepare_overflow) +/* Test checks allocation snapshot on partition if partition with the freshest snapshot is full. + */ +ZTEST(emds_flash, test_allocation_next_partition_if_freshest_full) { - flash_clear(); - device_reset(); + struct emds_snapshot_candidate freshest_snapshot; + int partition_index = sys_rand32_get() % PARTITIONS_NUM_MAX; + struct emds_snapshot_candidate allocated_snapshot; + struct emds_snapshot_metadata *metadata = NULL; + struct emds_snapshot_metadata metadata_prev; + off_t metadata_off = partition[partition_index].fa->fa_size; + off_t metadata_off_prev; + uint32_t fresh_cnt = 0; + int rc; - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - zassert_true(emds_flash_prepare(&ctx, m_test_fd.size), "Prepare should return error"); - zassert_false(emds_flash_prepare(&ctx, m_test_fd.size - 16), "Prepare failed"); - zassert_false(emds_flash_prepare(&ctx, m_test_fd.size - 24), "Prepare failed"); + do { + metadata_off_prev = metadata_off; + metadata_off -= sizeof(struct emds_snapshot_metadata); + fresh_cnt++; + metadata_prev = metadata ? *metadata : (struct emds_snapshot_metadata){0}; + metadata = snapshot_make(&partition[partition_index], metadata, metadata_off, true, + true, fresh_cnt); + } while (metadata != NULL); + freshest_snapshot.metadata_off = metadata_off_prev; + freshest_snapshot.metadata = metadata_prev; + + rc = emds_flash_allocate_snapshot(&partition[partition_index], &freshest_snapshot, + &allocated_snapshot, 200); + zassert_equal(rc, -ENOMEM, + "Expected -ENOMEM when trying to allocate snapshot on full partition %d rc: %d", + partition_index, rc); } -ZTEST(emds_flash_tests, test_full_corrupt_recovery) +/* Test checks allocation snapshot if partition has garbage in metadata place. */ +ZTEST(emds_flash, test_allocation_if_metadata_garbaged) { - flash_clear(); - device_reset(); + int partition_index = sys_rand32_get() % PARTITIONS_NUM_MAX; + struct emds_snapshot_candidate allocated_snapshot = { + .partition_index = partition_index, + .metadata.fresh_cnt = 1 + }; + const struct flash_area *fa = partition[partition_index].fa; + const struct flash_parameters *fp = partition[partition_index].fp; + uint8_t garbage[sizeof(struct emds_snapshot_metadata)]; + int rc; - /* Fill flash with garbage. Expect 0 space left and erase on prepare */ - corrupt_write_all(); - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - zassert_true(ctx.force_erase, "Force erase should be true"); - zassert_equal(0, emds_flash_free_space_get(&ctx), "Expected no free space"); + for (int i = 0; i < sizeof(garbage); i++) { + garbage[i] = sys_rand32_get() % 256; + } - zassert_false(emds_flash_prepare(&ctx, 0), "Prepare failed"); - zassert_equal(m_test_fd.size - align_size(sizeof(struct test_ate)), - emds_flash_free_space_get(&ctx), "Expected no free space"); + zassert_ok(flash_area_write(fa, fa->fa_size - sizeof(garbage), &garbage, sizeof(garbage)), + "Failed to write garbage into metadata flash area"); - zassert_false(flash_cmp_const(m_test_fd.offset, 0xff, m_test_fd.size), "Flash not cleared"); - device_reset(); + rc = emds_flash_allocate_snapshot(&partition[partition_index], NULL, &allocated_snapshot, + 100); + if (flash_params_get_erase_cap(fp) & FLASH_ERASE_C_EXPLICIT) { + zassert_equal(rc, -EADDRINUSE, + "Expected -EADDRINUSE when trying to allocate snapshot on partition " + "with garbage in metadata if memory has explicit erase"); + return; + } + + zassert_ok(rc, "Expected 0 when trying to allocate snapshot on partition with " + "garbage in metadata if memory does not have explicit erase"); + + zassert_equal(allocated_snapshot.partition_index, partition_index, + "Partition index is not equal to the requested one"); + zassert_equal(allocated_snapshot.metadata_off, + fa->fa_size - sizeof(struct emds_snapshot_metadata), + "Metadata offset is not equal to the end of the partition"); + zassert_equal(allocated_snapshot.metadata.data_instance_off, 0, + "Data instance offset is not equal to 0"); + zassert_equal(allocated_snapshot.metadata.data_instance_len, 100, + "Data instance length is not equal to requested one"); + zassert_equal(allocated_snapshot.metadata.fresh_cnt, 1, "Fresh count is not equal to 1"); + zassert_equal(allocated_snapshot.metadata.metadata_crc, + crc32_k_4_2_update(0, (const unsigned char *)&allocated_snapshot.metadata, + offsetof(struct emds_snapshot_metadata, metadata_crc)), + "Metadata CRC is not equal to calculated one"); } -ZTEST(emds_flash_tests, test_corrupted_data) +/* Test checks allocation snapshot if partition has garbage in data place. */ +ZTEST(emds_flash, test_allocation_if_data_garbaged) { - char corrupt[4] = {0}; - char data_in[8] = "Deadbee"; - char data_out[8] = {0}; - - flash_clear(); - device_reset(); - - zassert_false(emds_flash_init(&ctx), "Error when initializing"); - zassert_false(emds_flash_prepare(&ctx, sizeof(data_in) + ctx.ate_size), "Prepare failed"); - - zassert_true(emds_flash_write(&ctx, 1, data_in, sizeof(data_in)) == sizeof(data_in), - "Should be able to write"); + int partition_index = sys_rand32_get() % PARTITIONS_NUM_MAX; + struct emds_snapshot_candidate allocated_snapshot = { + .partition_index = partition_index, + .metadata.fresh_cnt = 1 + }; + const struct flash_area *fa = partition[partition_index].fa; + const struct flash_parameters *fp = partition[partition_index].fp; + uint8_t garbage[sizeof(struct emds_data_entry)]; + int rc; - zassert_true(emds_flash_read(&ctx, 1, data_out, sizeof(data_in)) == sizeof(data_in), - "Should be able to read"); + for (int i = 0; i < sizeof(garbage); i++) { + garbage[i] = sys_rand32_get() % 256; + } - /* Corrupt data entry */ - flash_write(m_test_fd.fd, m_test_fd.data_wra_offset + m_test_fd.offset, corrupt, - sizeof(corrupt)); + zassert_ok(flash_area_write(fa, 0, &garbage, sizeof(garbage)), + "Failed to write garbage into metadata flash area"); - /* Reset */ - device_reset(); - zassert_false(emds_flash_init(&ctx), "Error when initializing"); + rc = emds_flash_allocate_snapshot(&partition[partition_index], NULL, &allocated_snapshot, + 100); + if (flash_params_get_erase_cap(fp) & FLASH_ERASE_C_EXPLICIT) { + zassert_equal(rc, -EADDRINUSE, + "Expected -EADDRINUSE when trying to allocate snapshot on partition " + "with garbage in data area if memory has explicit erase"); + return; + } - zassert_true(emds_flash_read(&ctx, 1, data_out, sizeof(data_in)) < 0, - "Should not be able to read"); + zassert_ok(rc, "Expected 0 when trying to allocate snapshot on partition with " + "garbage in data area if memory does not have explicit erase"); + + zassert_equal(allocated_snapshot.partition_index, partition_index, + "Partition index is not equal to the requested one"); + zassert_equal(allocated_snapshot.metadata_off, + fa->fa_size - sizeof(struct emds_snapshot_metadata), + "Metadata offset is not equal to the end of the partition"); + zassert_equal(allocated_snapshot.metadata.data_instance_off, 0, + "Data instance offset is not equal to 0"); + zassert_equal(allocated_snapshot.metadata.data_instance_len, 100, + "Data instance length is not equal to requested one"); + zassert_equal(allocated_snapshot.metadata.fresh_cnt, 1, "Fresh count is not equal to 1"); + zassert_equal(allocated_snapshot.metadata.metadata_crc, + crc32_k_4_2_update(0, (const unsigned char *)&allocated_snapshot.metadata, + offsetof(struct emds_snapshot_metadata, metadata_crc)), + "Metadata CRC is not equal to calculated one"); } -ZTEST(emds_flash_tests, test_write_speed) +/* Test measures write timings. */ +ZTEST(emds_flash, test_write_speed) { - char data_in[EMDS_FLASH_BLOCK_SIZE]; - uint8_t data_in_big[1024]; + int partition_index = sys_rand32_get() % PARTITIONS_NUM_MAX; + const struct flash_area *fa = partition[partition_index].fa; + static uint8_t data_in[EMDS_FLASH_BLOCK_SIZE]; + static uint8_t data_in_big[1024]; + static uint8_t read_back[1024] = {0}; int64_t tic; int64_t toc; uint64_t store_time_us; + off_t data_off = 0; memset(data_in, 69, sizeof(data_in)); memset(data_in_big, 69, sizeof(data_in_big)); (void)dk_leds_init(); (void)dk_set_led(0, false); - (void)flash_clear(); - device_reset(); #if defined(CONFIG_BT) /* This is done to turn off mpsl scheduler to speed up storage time. */ (void)sdc_disable(); mpsl_uninit(); #endif - (void)emds_flash_init(&ctx); - (void)emds_flash_prepare(&ctx, sizeof(data_in) + ctx.ate_size); - tic = k_uptime_ticks(); dk_set_led(0, true); - emds_flash_write(&ctx, 1, data_in, sizeof(data_in)); - dk_set_led(0, false); + tic = k_uptime_ticks(); + emds_flash_write_data(&partition[partition_index], data_off, data_in, sizeof(data_in)); toc = k_uptime_ticks(); + dk_set_led(0, false); store_time_us = k_ticks_to_us_ceil64(toc - tic); printk("Storing %d bytes took: %lldus\n", sizeof(data_in), store_time_us); zassert_true(store_time_us < EXPECTED_STORE_TIME_BLOCK_SIZE, "Storing %d bytes took to long time", sizeof(data_in)); + zassert_ok(flash_area_read(fa, data_off, read_back, sizeof(data_in))); + zassert_mem_equal(read_back, data_in, sizeof(data_in)); + (void)memset(read_back, 0, sizeof(data_in)); + dk_set_led(0, true); tic = k_uptime_ticks(); - emds_flash_write(&ctx, 2, data_in_big, sizeof(data_in_big)); - dk_set_led(0, false); + emds_flash_write_data(&partition[partition_index], data_off + sizeof(data_in), data_in_big, + sizeof(data_in_big)); toc = k_uptime_ticks(); + dk_set_led(0, false); store_time_us = k_ticks_to_us_ceil64(toc - tic); printk("Storing %d bytes took: %lldus\n", sizeof(data_in_big), store_time_us); zassert_true(store_time_us < EXPECTED_STORE_TIME_1024, - "Storing %d bytes took to long time", sizeof(data_in_big)); + "Storing %d bytes took too long time", sizeof(data_in_big)); + + zassert_ok(flash_area_read(fa, data_off + sizeof(data_in), read_back, sizeof(data_in_big))); + zassert_mem_equal(read_back, data_in_big, sizeof(data_in_big)); } -ZTEST_SUITE(emds_flash_tests, NULL, fs_init, NULL, NULL, NULL); +ZTEST_SUITE(emds_flash, NULL, emds_flash_setup, NULL, emds_flash_partitions_erase, NULL);