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.
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.
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.
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.
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().
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().
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
1lftl_write(&nvma,nvm.a.data0,new_data0,sizeof(new_data0));
Updating several variables atomically
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);