User guide

Header to include

The lean-ftl.h header includes all the symbols. This file is the same no matter the build type that you use. It is therefore safe to use dist/debug/lean-ftl.h even if you link against the minSizeRel library.

Enabling helper macros

lean-ftl.h contains few helper macros that ease the declaration of your LFTL areas. To use those helper macros, you must define 3 macros before including lean-ftl.h:

  • LFTL_DEFINE_HELPERS: Indicates that you want to use the helper macros. Value does not matter.

  • LFTL_WU_SIZE: Defines the size for “write unit”. Value shall be the minimum write size in the NVM or a multiple of it.

  • LFTL_PAGE_SIZE: Defines the size for “LFTL page”. Value shall be the minimum erase size in the NVM or a multiple of it and LFTL_WU_SIZE.

Example: Enabling helper macros for STM32U5
1#define LFTL_DEFINE_HELPERS
2#define LFTL_PAGE_SIZE (8*1024)
3#define LFTL_WU_SIZE 16
4#include "lean-ftl.h"

Note

This user guide assumes that you use the helper macros

Declaring variables in the NVM

The helper macros are assuming that you declare all your LFTL areas within a struct called nvm.

Within the nvm struct, you can declare one or more LFTL areas using the macro LFTL_AREA.

Example: Declaring variables in two LFTL areas
 1typedef struct data_flash_struct {
 2
 3  LFTL_AREA(a,
 4    uint64_t data0[4];
 5    uint64_t data1[4];
 6    ,2)
 7
 8  LFTL_AREA(b,
 9    uint64_t data2[4];
10    uint64_t data3[4];
11    ,2)
12
13} __attribute__ ((aligned (LFTL_PAGE_SIZE))) data_flash_t;
14data_flash_t nvm;

Note

In the example above, data0 is accessible via nvm.a.data0.

Note

Members declared inside an LFTL area can be of any type however they should be padded to occupy a multiple of the LFTL_WU_SIZE.

Choosing the wear leveling factor

Typical NVMs have a limited “endurance”, i.e., a given page supports a limited number of erase/write cycles. This typically ranges from 1k to 100k, Please refer to the NVM datasheet.

lean-ftl implements “wear-leveling” to remove this barrier however this feature does not allow unlimited erase/write, it merely multiplies the endurance of the NVM. The multiplier is what we call the “wear-leveling-factor”. In other words, if you use 2 (which is the minimum), the native endurance is multiplied by 2.

Why not setting it to 1 billion once and for all ? Well, it also multiplies the size of the area, so a factor of 3 use one third more than a factor of 2. It is therefore desirable to set the wear leveling factor with the minimum value that provides the endurance required by the use case.

Note

Some other implementation are more efficient than lean-ftl in term of area consumption for a given wear-leveling-factor however they are typically prone to fragmentation and they are more dependant on the usage pattern.

Single LFTL area vs many

Using a single LFTL area is fine for simple applications. This section highlight cases where it make sens to declare more than one LFTL area.

For applications which have a large amount of mostly static data and few data with high endurance requirements, it make sense to declare 2 LFTL areas:

  • one for the mostly static data, with wear-level factor = 2

  • one for the high endurance data, with wear-level factor > 2

For applications which have several independant processes, it make sense to declare one LFTL area for each process:

  • lean-ftl is not thread safe.

  • A single transaction is supported at any time for a given LFTL area.

Note

Even when using one LFTL area for each process, the user need to take care about synchronization either at the call back level or at the application level.

One downside of having multiple LFTL areas is that transactions are limited to one area, so it is not possible to cover all NVM changes with a single transaction anymore. Another downside is the potential overhead incurred for each LFTL area, especially if the target NVM as large pages: declaring an LFTL area consumes at least 2 NVM pages, even if the data is much smaller.

Declaring NVM properties

lean-ftl needs to know few basic properties of the target(s) NVM(s). The integrator shall declare one lftl_nvm_props_t for each targeted NVM.

Example: Declaring NVM properties
1lftl_nvm_props_t nvm_props = {
2  .base = &nvm,
3  .size = sizeof(nvm),
4  .write_size = nvm_write_size,
5  .erase_size = nvm_erase_size,
6};

Note

base and size can be a subset of the physical NVM.

Implementing the callbacks

In order to use LFTL, the following callbacks needs to be implemented on your target platform:

You can find an implementation of those callbacks for STM32U5 and STM32L5 in https://github.com/sebastien-riou/lean-ftl/tree/main/target/stm32

Declaring LFTL areas

Each LFTL area has its volatile context maintained in a lftl_ctx_t struct.

Example: Declaring two LFTL areas
 1lftl_ctx_t nvma = {
 2  .nvm_props = &nvm_props,
 3  .area = &nvm.a_pages,
 4  .area_size = sizeof(nvm.a_pages),
 5  .data = LFTL_INVALID_POINTER,
 6  .data_size = sizeof(nvm.a_data),
 7  .erase = nvm_erase,
 8  .write = nvm_write,
 9  .read = nvm_read,
10  .error_handler = throw_exception,
11  .transaction_tracker = LFTL_INVALID_POINTER,
12  .next = LFTL_INVALID_POINTER
13};
14lftl_ctx_t nvmb = {
15  .nvm_props = &nvm_props,
16  .area = &nvm.b_pages,
17  .area_size = sizeof(nvm.b_pages),
18  .data = LFTL_INVALID_POINTER,
19  .data_size = sizeof(nvm.b_data),
20  .erase = nvm_erase,
21  .write = nvm_write,
22  .read = nvm_read,
23  .error_handler = throw_exception,
24  .transaction_tracker = LFTL_INVALID_POINTER,
25  .next = LFTL_INVALID_POINTER
26};

Library initialization

After a power up, the library must be initialized using lftl_lib_init(). Each area must be registered using lftl_register_area().

Example: Librairy initialization
1lftl_init_lib();
2lftl_register_area(&nvma);
3lftl_register_area(&nvmb);

Initial formatting

Each LFTL area must be formatted before being used. This is done using lftl_format().

Example: Initial formatting
1lftl_format(&nvma);
2lftl_format(&nvmb);

Note

LFTL does not provide a way to know if an area has been already formated or not. The application shall track that by maintaining a flag in NVM.

Updating a single variable atomically

Example: single variable atomic update
1lftl_write(&nvma,nvm.a.data0,new_data0,sizeof(new_data0));

Updating several variables atomically

Example: multiple variables atomic update
1uint8_t transaction_tracker[LFTL_TRANSACTION_TRACKER_SIZE(&nvma)];
2lftl_transaction_start(&nvma, transaction_tracker);
3lftl_write(&nvma,nvm.a.data0,new_data0,sizeof(new_data0));
4lftl_write(&nvma,nvm.a.data1,new_data1,sizeof(new_data1));
5lftl_transaction_commit(&nvma);