ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_ftl.h File Reference

Flash Translation Layer – free overwrite over erase-before-write media. More...

#include <stdint.h>
#include "ra8_err.h"
#include "ra8_io_blockdev.h"
Include dependency graph for ra8_ftl.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  ra8_ftl_pblock_t
 Caller-owned metadata for one physical erase block. More...
struct  ra8_ftl_t
 Caller-allocated FTL handle binding the wrapper to the raw device. More...

Enumerations

enum  ra8_ftl_const_t : uint16_t {
  k_ra8_ftl_unmapped = 0xFFFF ,
  k_ra8_ftl_max_pblocks = 0xFFFE ,
  k_ra8_ftl_min_spare = 1
}
 FTL sizing and sentinel constants. More...
enum  ra8_ftl_pstate_t : uint8_t {
  k_ra8_ftl_pstate_free = 0 ,
  k_ra8_ftl_pstate_live = 1 ,
  k_ra8_ftl_pstate_stale = 2
}
 Lifecycle state of one physical erase block. More...

Functions

ra8_err_t ra8_ftl_init (ra8_ftl_t *bd, const ra8_io_blockdev_t *raw, uint16_t *map, uint32_t logical_blocks, ra8_ftl_pblock_t *pblocks, uint32_t physical_blocks, uint8_t *scratch)
 Initialise an FTL over an erase-before-write block device.
ra8_err_t ra8_ftl_as_blockdev (ra8_ftl_t *ftl, ra8_io_blockdev_t *out)
 Expose an initialised FTL as a free-overwrite ra8_io_blockdev_t.
ra8_err_t ra8_ftl_wear_stats (const ra8_ftl_t *ftl, uint32_t *max_out, uint32_t *min_out)
 Report the highest per-physical-block erase count seen so far.
ra8_err_t ra8_ftl_phys_of (const ra8_ftl_t *ftl, uint32_t lbn, uint16_t *phys_out)
 Report the physical block currently backing a logical block.
ra8_err_t ra8_ftl_checkpoint_size (const ra8_ftl_t *ftl, uint32_t *size_out)
 Report the buffer size a checkpoint of this FTL requires, in bytes.
ra8_err_t ra8_ftl_checkpoint_save (const ra8_ftl_t *ftl, uint8_t *buf, uint32_t buf_len)
 Serialise the FTL's mapping state into a caller buffer.
ra8_err_t ra8_ftl_checkpoint_load (ra8_ftl_t *ftl, const uint8_t *buf, uint32_t buf_len)
 Restore FTL mapping state from a checkpoint produced by save.

Detailed Description

Flash Translation Layer – free overwrite over erase-before-write media.

Tag
[Ring 4 / PAL] {World: NS}

The RA8D2 on-chip extra MRAM (data flash) is erase-before-write: a 512-byte logical block can only be programmed after its backing erase block has been cleared to the erase value (0xFF). FAT, by contrast, freely overwrites individual sectors in place (directory entries, FAT links) with no erase step, so layering FAT directly over the raw MRAM block device corrupts data. See issue #165.

This module is a Flash Translation Layer (FTL). It wraps an erase-before-write ra8_io_blockdev_t (the underlying device) and presents a clean free-overwrite ra8_io_blockdev_t to the FAT/VFS layer above. The FAT layer sees a device it can overwrite at will; the FTL handles erase ordering, copy-on-write relocation, stale-block reclamation, and wear-levelling underneath.

Model

The FTL works one logical block to one physical erase block. The underlying device must report erase_unit_blocks == 1 (each 512-byte logical block is exactly one erase unit) – this is true of the MRAM backend, whose 512-byte logical block already maps onto a whole set of native MRAM erase units. The underlying device has P physical blocks; the FTL presents L logical blocks where L < P. The surplus P - L blocks are spare capacity used as copy-on-write relocation targets and as wear-levelling headroom.

  • Mapping table (caller storage, one uint16_t per logical block): map[lbn] is the physical block currently holding that logical block's data, or k_ra8_ftl_unmapped if the logical block has never been written (a read of an unmapped block returns the erase value).
  • Per-physical-block metadata (caller storage, one ra8_ftl_pblock_t per physical block): the block's state (free / live / stale) and its cumulative erase count for wear-levelling.

Write path (copy-on-write + wear-levelling)

On a logical-block write the FTL:

  1. Picks the least-erased FREE physical block (wear-levelling); if no free block exists it first reclaims STALE blocks by erasing them.
  2. Erases that physical block (advancing its erase count), then programs the new data into it.
  3. Re-points map[lbn] at the new physical block and marks the previous physical block (if any) STALE for later reclamation.

This never overwrites a non-blank physical block, so the underlying erase-before-write contract is always honoured.

Invariants

  • map[lbn] is either k_ra8_ftl_unmapped or a valid physical index whose metadata state is LIVE.
  • Every LIVE physical block is referenced by exactly one map[] entry.
  • A FREE physical block reads back entirely as the erase value.
  • L < P always (at least one spare block) so a write can always relocate.

Storage (zero dynamic allocation, NASA P10 Rule 3)

All state is caller-provided: the ra8_ftl_t handle, the map array (logical_blocks entries of uint16_t), the pblocks array (physical_blocks entries of ra8_ftl_pblock_t), and one 512-byte scratch buffer for copy operations. Nothing is allocated.

extern ra8_io_blockdev_t raw;
uint16_t map[24] = {};
ra8_ftl_pblock_t pblocks[24] = {};
uint8_t scratch[512];
ra8_ftl_t ftl = {};
ra8_err_t err = ra8_ftl_init(&ftl, &raw, map, 16U, pblocks, 24U, scratch);
if (err == k_ra8_ok) {
err = ra8_ftl_as_blockdev(&ftl, &bd);
}
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
ra8_err_t ra8_ftl_init(ra8_ftl_t *bd, const ra8_io_blockdev_t *raw, uint16_t *map, uint32_t logical_blocks, ra8_ftl_pblock_t *pblocks, uint32_t physical_blocks, uint8_t *scratch)
Initialise an FTL over an erase-before-write block device.
Definition ra8_ftl.c:724
ra8_err_t ra8_ftl_as_blockdev(ra8_ftl_t *ftl, ra8_io_blockdev_t *out)
Expose an initialised FTL as a free-overwrite ra8_io_blockdev_t.
Definition ra8_ftl.c:757
Caller-owned metadata for one physical erase block.
Definition ra8_ftl.h:162
Caller-allocated FTL handle binding the wrapper to the raw device.
Definition ra8_ftl.h:190
Caller-allocated block-device handle binding a backend to its context.
Since
0.1.0

Definition in file ra8_ftl.h.

Enumeration Type Documentation

◆ ra8_ftl_const_t

enum ra8_ftl_const_t : uint16_t

FTL sizing and sentinel constants.

The mapping table stores physical block indices as uint16_t, so the FTL addresses at most k_ra8_ftl_max_pblocks physical blocks. k_ra8_ftl_unmapped is the reserved sentinel meaning "this logical block has never been written".

Since
0.1.0
Enumerator
k_ra8_ftl_unmapped 

map[] sentinel: logical block unwritten.

k_ra8_ftl_max_pblocks 

Max physical blocks an FTL may wrap.

k_ra8_ftl_min_spare 

Minimum spare blocks (P - L >= 1).

Definition at line 118 of file ra8_ftl.h.

◆ ra8_ftl_pstate_t

enum ra8_ftl_pstate_t : uint8_t

Lifecycle state of one physical erase block.

A physical block cycles FREE -> LIVE (programmed, referenced by a map[] entry) -> STALE (superseded by a copy-on-write relocation) -> FREE (after the FTL reclaims it with an erase).

Since
0.1.0
Enumerator
k_ra8_ftl_pstate_free 

Blank (reads as erase value); allocatable.

k_ra8_ftl_pstate_live 

Holds current data for one logical block.

k_ra8_ftl_pstate_stale 

Superseded; reclaimable by erase to FREE.

Definition at line 135 of file ra8_ftl.h.

Function Documentation

◆ ra8_ftl_as_blockdev()

ra8_err_t ra8_ftl_as_blockdev ( ra8_ftl_t * ftl,
ra8_io_blockdev_t * out )
nodiscard

Expose an initialised FTL as a free-overwrite ra8_io_blockdev_t.

Binds out to the FTL vtable with the ra8_ftl_t as its context, so the layers above (FAT, VFS, the write-back cache) see a normal block device that supports in-place overwrite, never needs an explicit erase, and reads unwritten blocks back as the underlying erase value. The handle passed as ftl must out-live the resulting block device.

Parameters
[in]ftlFTL handle previously initialised by ra8_ftl_init.
[out]outBlock-device handle to bind (zero-initialised by the caller).
Returns
ra8_err_t Error code.
Return values
k_ra8_okout bound to the FTL.
k_ra8_err_null_ptrftl or out was NULL.
k_ra8_err_not_initializedftl was not initialised (no raw device).
Precondition
ftl was initialised by ra8_ftl_init.
out is writable and out-lives every block-device call.
Postcondition
On success out dispatches every operation through the FTL.
On any non-ok return out is left unbound.
Note
Not thread-safe with respect to the same FTL.
See also
ra8_ftl_init
Since
0.1.0

Definition at line 757 of file ra8_ftl.c.

References ra8_io_blockdev_t::ctx, ra8_io_blockdev_t::iface, k_ra8_err_not_initialized, k_ra8_ok, RA8_CHECK_NULL_PTR, ra8_ftl_t::raw, s_ftl_iface, and s_tag.

Referenced by demo_reopen_naive(), and demo_wear_phase().

◆ ra8_ftl_checkpoint_load()

ra8_err_t ra8_ftl_checkpoint_load ( ra8_ftl_t * ftl,
const uint8_t * buf,
uint32_t buf_len )
nodiscard

Restore FTL mapping state from a checkpoint produced by save.

Validates buf as an exact-length checkpoint whose version and geometry match ftl, verifies its CRC, and rejects out-of-range or duplicate map entries, invalid states, unreferenced LIVE blocks, and references to non-LIVE blocks. Validation uses fixed windows in the FTL's caller-owned scratch block before the saved map and pblocks values are committed, so a freshly ra8_ftl_init handle resumes the exact mapping it had when the checkpoint was taken. Call ra8_ftl_init first (to re-bind the underlying device and re-establish geometry), then this to overwrite the cold-start tables with the persisted state; the underlying data blocks are untouched, so a subsequent read of any logical block returns its pre-reset contents.

Legacy native-layout checkpoints are recognized in either byte order but rejected as unsupported: their producer ABI and padding cannot be recovered safely from the blob.

Parameters
[in,out]ftlHandle freshly initialised by ra8_ftl_init.
[in]bufCheckpoint buffer from ra8_ftl_checkpoint_save.
[in]buf_lenNumber of valid bytes in buf.
Returns
ra8_err_t Error code.
Return values
k_ra8_okMapping state restored.
k_ra8_err_null_ptrftl or buf was NULL.
k_ra8_err_not_initializedftl was not initialised.
k_ra8_err_invalid_sizebuf_len is not the exact encoded length.
k_ra8_err_invalid_stateThe buffer is not an FTL checkpoint (bad magic or mapping invariant).
k_ra8_err_invalid_argThe checkpoint geometry does not match ftl, or input aliases live/scratch state.
k_ra8_err_not_supportedUnknown version or recognized legacy ABI.
k_ra8_err_crc_mismatchCheckpoint bytes fail their CRC-32 trailer.
Precondition
ftl was re-initialised by ra8_ftl_init over the retained device.
buf holds exactly one checkpoint and does not overlap the FTL map, physical-block table, or scratch block.
Postcondition
On success map/pblocks mirror the checkpointed state.
On any non-ok return the cold-start tables are left as ra8_ftl_init set them.
Note
Not thread-safe with respect to the data path.
See also
ra8_ftl_checkpoint_save
Since
0.1.0

Definition at line 733 of file ra8_ftl_checkpoint.c.

References internal_decode_commit(), internal_disjoint(), internal_validate_header(), internal_validate_wire(), k_ra8_ok, RA8_CHECK_NULL_PTR, ra8_ftl_checkpoint_size(), and s_tag.

Referenced by demo_restore().

◆ ra8_ftl_checkpoint_save()

ra8_err_t ra8_ftl_checkpoint_save ( const ra8_ftl_t * ftl,
uint8_t * buf,
uint32_t buf_len )
nodiscard

Serialise the FTL's mapping state into a caller buffer.

Writes a self-describing checkpoint of the volatile mapping tables (map and pblocks) into buf: a versioned little-endian header (magic, exact length, and geometry), individually encoded map and physical-block records, and a CRC-32/ISO-HDLC trailer. Persisting this blob to a non-volatile region of the underlying device and reloading it after a reset (see ra8_ftl_checkpoint_load) is what lets logical data survive a power cycle – the FTL keeps no on-media metadata of its own, so without a checkpoint a cold re-init cannot resolve which physical block holds which logical block.

Version 1 is canonical across architectures: no native object representation or struct padding is copied. The pre-versioned native-layout format is deliberately not migrated because its producer ABI cannot be proven from the bytes; load identifies either legacy byte order and fails closed with k_ra8_err_not_supported.

Parameters
[in]ftlInitialised FTL handle.
[out]bufDestination buffer (>= ra8_ftl_checkpoint_size bytes).
[in]buf_lenCapacity of buf in bytes.
Returns
ra8_err_t Error code.
Return values
k_ra8_okCheckpoint written into buf.
k_ra8_err_null_ptrftl or buf was NULL.
k_ra8_err_not_initializedftl was not initialised.
k_ra8_err_invalid_sizebuf_len is smaller than the checkpoint.
k_ra8_err_invalid_argOutput aliases FTL tables or scratch.
k_ra8_err_invalid_stateLive mapping invariants are corrupt.
Precondition
ftl was initialised by ra8_ftl_init.
buf is writable for at least buf_len bytes and does not overlap the FTL map, physical-block table, or scratch block.
Postcondition
On success buf[0 .. checkpoint_size) holds a loadable checkpoint.
On any non-ok return buf is left unchanged.
Note
Not thread-safe with respect to the data path.
See also
ra8_ftl_checkpoint_load
ra8_ftl_checkpoint_size
Since
0.1.0

Definition at line 709 of file ra8_ftl_checkpoint.c.

References internal_disjoint(), internal_encode(), internal_validate_native(), k_ra8_err_invalid_size, k_ra8_ok, RA8_CHECK_NULL_PTR, ra8_ftl_checkpoint_size(), and s_tag.

Referenced by demo_checkpoint().

◆ ra8_ftl_checkpoint_size()

ra8_err_t ra8_ftl_checkpoint_size ( const ra8_ftl_t * ftl,
uint32_t * size_out )
nodiscard

Report the buffer size a checkpoint of this FTL requires, in bytes.

A checkpoint captures the FTL's volatile mapping state (the map and pblocks tables) so it can be persisted to non-volatile media and reloaded after a reset. This returns the exact byte count ra8_ftl_checkpoint_save needs for the current geometry: a fixed canonical header, two bytes per map entry, five bytes per physical-block entry, and a CRC-32 trailer. The value is independent of compiler padding, native integer layout, and host byte order, and is stable for a given initialised handle.

Parameters
[in]ftlInitialised FTL handle.
[out]size_outReceives the required checkpoint size in bytes.
Returns
ra8_err_t Error code.
Return values
k_ra8_ok*size_out populated.
k_ra8_err_null_ptrftl or size_out was NULL.
k_ra8_err_not_initializedftl was not initialised.
Precondition
ftl was initialised by ra8_ftl_init.
size_out is writable.
Postcondition
On success *size_out > 0.
No FTL or device state is mutated.
Note
Thread-safe (pure computation over immutable geometry).
See also
ra8_ftl_checkpoint_save
ra8_ftl_checkpoint_load
Since
0.1.0

Definition at line 692 of file ra8_ftl_checkpoint.c.

References internal_ready(), internal_size_values(), k_ra8_ok, ra8_ftl_t::logical_blocks, ra8_ftl_t::physical_blocks, RA8_CHECK_NULL_PTR, and s_tag.

Referenced by demo_checkpoint(), ra8_ftl_checkpoint_load(), and ra8_ftl_checkpoint_save().

◆ ra8_ftl_init()

ra8_err_t ra8_ftl_init ( ra8_ftl_t * bd,
const ra8_io_blockdev_t * raw,
uint16_t * map,
uint32_t logical_blocks,
ra8_ftl_pblock_t * pblocks,
uint32_t physical_blocks,
uint8_t * scratch )
nodiscard

Initialise an FTL over an erase-before-write block device.

Validates the underlying device's capabilities (it must report erase_unit_blocks == 1, must not be read-only, and must have at least logical_blocks + k_ra8_ftl_min_spare physical blocks), records the caller-provided storage, snapshots the medium erase value, and sets every logical block to k_ra8_ftl_unmapped and every physical block to FREE. No erase or program is issued here; physical blocks are erased lazily on first write. No allocation occurs.

Parameters
[out]bdFTL handle to initialise; caller zero-initialises it before use.
[in]rawBound underlying erase-before-write device.
[out]mapCaller array of logical_blocks uint16_t.
[in]logical_blocksBlocks to present to FAT (>= 1).
[out]pblocksCaller array of physical_blocks metadata entries; caller zero-initialises it.
[in]physical_blocksPhysical blocks in raw (>= logical_blocks + k_ra8_ftl_min_spare).
[out]scratchCaller 512-byte copy scratch buffer.
Returns
ra8_err_t Error code.
Return values
k_ra8_okFTL initialised and ready.
k_ra8_err_null_ptrAny pointer argument was NULL.
k_ra8_err_invalid_sizelogical_blocks was zero or physical_blocks exceeds k_ra8_ftl_max_pblocks.
k_ra8_err_invalid_argUnderlying caps query failed, the device is read-only, erase_unit_blocks != 1, or there is no spare block.
k_ra8_err_not_initializedNo backend is bound to raw.
Precondition
All pointer arguments out-live every call through the FTL.
pblocks[0 .. physical_blocks) were zero-initialised by the caller.
Postcondition
On success every logical block is unmapped and every physical block FREE.
On any non-ok return bd is left unbound.
Note
Not thread-safe with respect to the same FTL.
See also
ra8_ftl_as_blockdev
Since
0.1.0

Definition at line 724 of file ra8_ftl.c.

References ra8_ftl_t::erase_value, internal_check_caps(), internal_reset_tables(), internal_validate_init_args(), k_ra8_ok, ra8_ftl_t::logical_blocks, ra8_ftl_t::map, ra8_ftl_t::pblocks, ra8_ftl_t::physical_blocks, ra8_io_blockdev_get_caps(), ra8_ftl_t::raw, and ra8_ftl_t::scratch.

Referenced by demo_reopen_naive(), and demo_wear_phase().

◆ ra8_ftl_phys_of()

ra8_err_t ra8_ftl_phys_of ( const ra8_ftl_t * ftl,
uint32_t lbn,
uint16_t * phys_out )
nodiscard

Report the physical block currently backing a logical block.

Read-only mapping telemetry: resolves map[lbn] and returns the physical block index that presently holds the logical block's data, or k_ra8_ftl_unmapped if the logical block has never been written. Because a copy-on-write write re-points map[lbn] at a freshly allocated physical block, calling this after each overwrite exposes the wear-levelling relocation: the reported index migrates while the logical address stays fixed. Intended for demos, tests, and telemetry – not the data path.

Parameters
[in]ftlInitialised FTL handle.
[in]lbnLogical block number (< logical_blocks).
[out]phys_outReceives the physical block index, or k_ra8_ftl_unmapped when the block is unwritten.
Returns
ra8_err_t Error code.
Return values
k_ra8_ok*phys_out populated.
k_ra8_err_null_ptrftl or phys_out was NULL.
k_ra8_err_not_initializedftl was not initialised.
k_ra8_err_out_of_rangelbn >= logical_blocks.
Precondition
ftl was initialised by ra8_ftl_init.
phys_out is writable.
Postcondition
On success *phys_out is either k_ra8_ftl_unmapped or a valid physical index (< physical_blocks).
No FTL or device state is mutated.
Note
Not thread-safe with respect to the data path.
See also
ra8_ftl_wear_stats
Since
0.1.0

Definition at line 793 of file ra8_ftl.c.

References k_ra8_err_not_initialized, k_ra8_err_out_of_range, k_ra8_ok, ra8_ftl_t::logical_blocks, ra8_ftl_t::map, RA8_CHECK_NULL_PTR, and s_tag.

Referenced by demo_restore(), and demo_write_verify().

◆ ra8_ftl_wear_stats()

ra8_err_t ra8_ftl_wear_stats ( const ra8_ftl_t * ftl,
uint32_t * max_out,
uint32_t * min_out )
nodiscard

Report the highest per-physical-block erase count seen so far.

Wear-levelling diagnostic: returns the maximum and minimum erase_count across all physical blocks. A healthy FTL keeps these close together; a large spread indicates a hot block. Intended for tests and telemetry, not the data path.

Parameters
[in]ftlInitialised FTL handle.
[out]max_outReceives the maximum erase count.
[out]min_outReceives the minimum erase count.
Returns
ra8_err_t Error code.
Return values
k_ra8_ok*max_out and *min_out populated.
k_ra8_err_null_ptrAny pointer argument was NULL.
k_ra8_err_not_initializedftl was not initialised.
Precondition
ftl was initialised by ra8_ftl_init.
max_out and min_out are writable.
Postcondition
On success *max_out >= *min_out.
No FTL or device state is mutated.
Note
Thread-safe with respect to the data path is NOT guaranteed.
Since
0.1.0

Definition at line 769 of file ra8_ftl.c.

References ra8_ftl_pblock_t::erase_count, k_ra8_err_not_initialized, k_ra8_ftl_count_max, k_ra8_ok, ra8_ftl_t::pblocks, ra8_ftl_t::physical_blocks, RA8_CHECK_NULL_PTR, and s_tag.

Referenced by demo_report_wear().