|
ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
|
Flash Translation Layer – free overwrite over erase-before-write media. More...
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. | |
Flash Translation Layer – free overwrite over erase-before-write media.
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.
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.
On a logical-block write the FTL:
This never overwrites a non-blank physical block, so the underlying erase-before-write contract is always honoured.
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.
Definition in file ra8_ftl.h.
| 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".
| 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). |
| 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).
|
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.
| [in] | ftl | FTL handle previously initialised by ra8_ftl_init. |
| [out] | out | Block-device handle to bind (zero-initialised by the caller). |
| k_ra8_ok | out bound to the FTL. |
| k_ra8_err_null_ptr | ftl or out was NULL. |
| k_ra8_err_not_initialized | ftl was not initialised (no raw device). |
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().
|
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.
| [in,out] | ftl | Handle freshly initialised by ra8_ftl_init. |
| [in] | buf | Checkpoint buffer from ra8_ftl_checkpoint_save. |
| [in] | buf_len | Number of valid bytes in buf. |
| k_ra8_ok | Mapping state restored. |
| k_ra8_err_null_ptr | ftl or buf was NULL. |
| k_ra8_err_not_initialized | ftl was not initialised. |
| k_ra8_err_invalid_size | buf_len is not the exact encoded length. |
| k_ra8_err_invalid_state | The buffer is not an FTL checkpoint (bad magic or mapping invariant). |
| k_ra8_err_invalid_arg | The checkpoint geometry does not match ftl, or input aliases live/scratch state. |
| k_ra8_err_not_supported | Unknown version or recognized legacy ABI. |
| k_ra8_err_crc_mismatch | Checkpoint bytes fail their CRC-32 trailer. |
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().
|
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.
| [in] | ftl | Initialised FTL handle. |
| [out] | buf | Destination buffer (>= ra8_ftl_checkpoint_size bytes). |
| [in] | buf_len | Capacity of buf in bytes. |
| k_ra8_ok | Checkpoint written into buf. |
| k_ra8_err_null_ptr | ftl or buf was NULL. |
| k_ra8_err_not_initialized | ftl was not initialised. |
| k_ra8_err_invalid_size | buf_len is smaller than the checkpoint. |
| k_ra8_err_invalid_arg | Output aliases FTL tables or scratch. |
| k_ra8_err_invalid_state | Live mapping invariants are corrupt. |
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().
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.
| [in] | ftl | Initialised FTL handle. |
| [out] | size_out | Receives the required checkpoint size in bytes. |
| k_ra8_ok | *size_out populated. |
| k_ra8_err_null_ptr | ftl or size_out was NULL. |
| k_ra8_err_not_initialized | ftl was not initialised. |
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().
|
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.
| [out] | bd | FTL handle to initialise; caller zero-initialises it before use. |
| [in] | raw | Bound underlying erase-before-write device. |
| [out] | map | Caller array of logical_blocks uint16_t. |
| [in] | logical_blocks | Blocks to present to FAT (>= 1). |
| [out] | pblocks | Caller array of physical_blocks metadata entries; caller zero-initialises it. |
| [in] | physical_blocks | Physical blocks in raw (>= logical_blocks + k_ra8_ftl_min_spare). |
| [out] | scratch | Caller 512-byte copy scratch buffer. |
| k_ra8_ok | FTL initialised and ready. |
| k_ra8_err_null_ptr | Any pointer argument was NULL. |
| k_ra8_err_invalid_size | logical_blocks was zero or physical_blocks exceeds k_ra8_ftl_max_pblocks. |
| k_ra8_err_invalid_arg | Underlying caps query failed, the device is read-only, erase_unit_blocks != 1, or there is no spare block. |
| k_ra8_err_not_initialized | No backend is bound to raw. |
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().
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.
| [in] | ftl | Initialised FTL handle. |
| [in] | lbn | Logical block number (< logical_blocks). |
| [out] | phys_out | Receives the physical block index, or k_ra8_ftl_unmapped when the block is unwritten. |
| k_ra8_ok | *phys_out populated. |
| k_ra8_err_null_ptr | ftl or phys_out was NULL. |
| k_ra8_err_not_initialized | ftl was not initialised. |
| k_ra8_err_out_of_range | lbn >= logical_blocks. |
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().
|
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.
| [in] | ftl | Initialised FTL handle. |
| [out] | max_out | Receives the maximum erase count. |
| [out] | min_out | Receives the minimum erase count. |
| k_ra8_ok | *max_out and *min_out populated. |
| k_ra8_err_null_ptr | Any pointer argument was NULL. |
| k_ra8_err_not_initialized | ftl was not initialised. |
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().