|
ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
|
Flash Translation Layer implementation – copy-on-write + wear-levelling. More...
#include "ra8_ftl.h"#include <stddef.h>#include <stdint.h>#include <string.h>#include "ra8_attributes.h"#include "ra8_check.h"#include "ra8_err.h"#include "ra8_io_blockdev.h"#include "ra8_io_blockdev_backend.h"Go to the source code of this file.
Enumerations | |
| enum | ra8_ftl_impl_const_t : uint32_t { k_ra8_ftl_one_block = 1 , k_ra8_ftl_req_erase_unit = 1 , k_ra8_ftl_count_max = 0xFFFFFFFFU } |
| Implementation-private layout constants. More... | |
Functions | |
| static ra8_err_t | internal_reclaim_stale (ra8_ftl_t *ftl) |
| Reclaim every STALE physical block by erasing it back to FREE. | |
| static ra8_err_t | internal_pick_free (const ra8_ftl_t *ftl, uint32_t *out) |
| Select the least-erased FREE physical block (wear-levelling). | |
| static ra8_err_t | internal_alloc_blank (ra8_ftl_t *ftl, uint32_t *out) |
| Allocate a blank physical block, reclaiming stale blocks if needed. | |
| static ra8_err_t | internal_read_one (const ra8_ftl_t *ftl, uint32_t lbn, uint8_t *dst) |
| Read one logical block, resolving the map (or erase value if unmapped). | |
| static ra8_err_t | internal_write_one (ra8_ftl_t *ftl, uint32_t lbn, const uint8_t *src) |
| Write one logical block via copy-on-write relocation. | |
| static ra8_err_t | internal_bounds (const ra8_ftl_t *ftl, uint32_t lba, uint32_t count) |
| Reject an out-of-range presented [lba, lba+count) range. | |
| static ra8_err_t | internal_dev_read (void *ctx, uint32_t lba, uint32_t count, uint8_t *buf) |
| Presented vtable: read count logical blocks at lba. | |
| static ra8_err_t | internal_dev_write (void *ctx, uint32_t lba, uint32_t count, const uint8_t *buf) |
| Presented vtable: write count logical blocks at lba. | |
| static ra8_err_t | internal_dev_erase (void *ctx, uint32_t lba, uint32_t count) |
| Presented vtable: erase count logical blocks at lba. | |
| static ra8_err_t | internal_dev_get_caps (const void *ctx, ra8_io_blockdev_caps_t *out) |
| Presented vtable: report the FTL's free-overwrite capabilities. | |
| static ra8_err_t | internal_dev_sync (void *ctx) |
| Presented vtable: flush the underlying device. | |
| static ra8_err_t | internal_check_caps (const ra8_io_blockdev_caps_t *caps, uint32_t logical_blocks, uint32_t physical_blocks, uint8_t *erase_out) |
| Validate the underlying device's capabilities for FTL use. | |
| static ra8_err_t | internal_reset_tables (ra8_ftl_t *ftl) |
| Cold-start the FTL bookkeeping tables. | |
| static ra8_err_t | internal_validate_init_args (const ra8_ftl_t *bd, const ra8_io_blockdev_t *raw, const uint16_t *map, const ra8_ftl_pblock_t *pblocks, const uint8_t *scratch, uint32_t logical_blocks, uint32_t physical_blocks) |
| Validate the pointer + sizing arguments to 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) |
| 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. | |
Variables | |
| static const char *const | s_tag = "ra8_ftl" |
| Module log tag. | |
| static const ra8_io_blockdev_iface_t | s_ftl_iface |
| Presented FTL vtable – a clean free-overwrite block device. | |
Flash Translation Layer implementation – copy-on-write + wear-levelling.
Implements ra8_ftl_init, ra8_ftl_as_blockdev, and the ra8_io_blockdev_iface vtable the FTL presents upward. Reads resolve the logical->physical map and forward to the underlying device (or synthesise the erase value for unmapped blocks). Writes allocate the least-erased free physical block, erase it, program the data, and re-point the map – never touching a non-blank block.
Definition in file ra8_ftl.c.
| enum ra8_ftl_impl_const_t : uint32_t |
Implementation-private layout constants.
Single-block transfer count and the required underlying erase granularity.
| Enumerator | |
|---|---|
| k_ra8_ftl_one_block | Count for a single-block raw transfer. |
| k_ra8_ftl_req_erase_unit | Required raw erase_unit_blocks value. |
| k_ra8_ftl_count_max | Sentinel: highest possible erase count. |
Allocate a blank physical block, reclaiming stale blocks if needed.
Tries internal_pick_free; if no free block exists it runs internal_reclaim_stale and retries exactly once. Two attempts suffice because the FTL guarantees at least one spare block, so after reclamation a free block always exists. The chosen block is erased (advancing its erase count) so it is blank before the caller programs it.
| [in,out] | ftl | Initialised FTL handle. |
| [out] | out | Receives the allocated, erased physical block index. |
| k_ra8_ok | A blank block was allocated into *out. |
| k_ra8_err_null_ptr | ftl or out was NULL. |
| k_ra8_err_no_mem | No block available even after reclamation. |
| other | Propagated underlying erase error. |
Definition at line 184 of file ra8_ftl.c.
References ra8_ftl_pblock_t::erase_count, internal_pick_free(), internal_reclaim_stale(), k_ra8_err_no_data, k_ra8_err_no_mem, k_ra8_ftl_one_block, k_ra8_ok, ra8_ftl_t::pblocks, RA8_CHECK_NULL_PTR, ra8_io_blockdev_erase(), ra8_ftl_t::raw, and s_tag.
Referenced by internal_write_one().
Reject an out-of-range presented [lba, lba+count) range.
Two single-condition checks (no compound decision) guarding against count overflow and a range that runs past logical_blocks.
| [in] | ftl | Initialised FTL handle. |
| [in] | lba | First logical block address. |
| [in] | count | Number of logical blocks in the range. |
| k_ra8_ok | Range is within the presented capacity. |
| k_ra8_err_out_of_range | Range exceeds logical_blocks. |
Definition at line 341 of file ra8_ftl.c.
References k_ra8_err_out_of_range, k_ra8_ok, and ra8_ftl_t::logical_blocks.
Referenced by internal_dev_erase(), internal_dev_read(), and internal_dev_write().
|
static |
Validate the underlying device's capabilities for FTL use.
Single-condition checks (no compound decisions): the device must not be read-only, must have erase_unit_blocks == 1, must hold at least physical_blocks blocks, and must leave at least one spare over logical_blocks. Also snapshots the erase value into *erase_out.
| [in] | caps | Underlying device capabilities. |
| [in] | logical_blocks | Blocks the FTL will present. |
| [in] | physical_blocks | Blocks the caller claims the device has. |
| [out] | erase_out | Receives the medium erase value. |
| k_ra8_ok | Underlying device is suitable. |
| k_ra8_err_null_ptr | erase_out was NULL. |
| k_ra8_err_invalid_arg | Device unsuitable (read-only, wrong erase unit, too small, or no spare block). |
Definition at line 609 of file ra8_ftl.c.
References ra8_io_blockdev_caps_t::block_count, ra8_io_blockdev_caps_t::erase_unit_blocks, ra8_io_blockdev_caps_t::erase_value, k_ra8_err_invalid_arg, k_ra8_ftl_min_spare, k_ra8_ftl_req_erase_unit, k_ra8_ok, RA8_CHECK_NULL_PTR, ra8_io_blockdev_caps_t::read_only, and s_tag.
Referenced by ra8_ftl_init().
|
static |
Presented vtable: erase count logical blocks at lba.
For the free-overwrite device the FTL presents, an erase simply unmaps each logical block: its current physical block becomes STALE (reclaimed later) and a subsequent read returns the erase value. The per-block loop is statically bounded by count.
| [in] | ctx | FTL handle (as a void cookie). |
| [in] | lba | First logical block address. |
| [in] | count | Number of logical blocks to erase. |
| k_ra8_ok | Logical blocks unmapped. |
| k_ra8_err_null_ptr | ctx was NULL. |
| k_ra8_err_out_of_range | Range past presented capacity. |
Definition at line 475 of file ra8_ftl.c.
References internal_bounds(), k_ra8_ftl_pstate_stale, k_ra8_ftl_unmapped, k_ra8_ok, ra8_ftl_t::map, ra8_ftl_t::pblocks, RA8_CHECK_NULL_PTR, s_tag, and ra8_ftl_pblock_t::state.
|
static |
Presented vtable: report the FTL's free-overwrite capabilities.
Reports logical_blocks blocks of a medium that needs no erase-before-write (the FTL hides that), with the underlying erase value preserved so the FAT bridge handles formatting consistently.
| [in] | ctx | FTL handle (as a const void cookie). |
| [out] | out | Capabilities snapshot. |
| k_ra8_ok | *out populated. |
| k_ra8_err_null_ptr | ctx or out was NULL. |
Definition at line 518 of file ra8_ftl.c.
References ra8_io_blockdev_caps_t::block_count, ra8_io_blockdev_caps_t::erase_unit_blocks, ra8_ftl_t::erase_value, ra8_io_blockdev_caps_t::erase_value, k_ra8_ftl_one_block, k_ra8_io_block_size_bytes, k_ra8_ok, ra8_io_blockdev_caps_t::logical_block_bytes, ra8_ftl_t::logical_blocks, ra8_io_blockdev_caps_t::must_erase_before_write, ra8_io_blockdev_caps_t::program_size_bytes, RA8_CHECK_NULL_PTR, ra8_io_blockdev_caps_t::read_only, and s_tag.
|
static |
Presented vtable: read count logical blocks at lba.
Bounds-checks then resolves each logical block one at a time through internal_read_one. The per-block loop is statically bounded by count.
| [in] | ctx | FTL handle (as a void cookie). |
| [in] | lba | First logical block address. |
| [in] | count | Number of logical blocks to read. |
| [out] | buf | Destination buffer (>= count * 512 bytes). |
| k_ra8_ok | Blocks resolved into buf. |
| k_ra8_err_null_ptr | ctx or buf was NULL. |
| k_ra8_err_out_of_range | Range past presented capacity. |
| other | Propagated underlying read error. |
Definition at line 380 of file ra8_ftl.c.
References internal_bounds(), internal_read_one(), k_ra8_io_block_size_bytes, k_ra8_ok, RA8_CHECK_NULL_PTR, and s_tag.
|
static |
Presented vtable: flush the underlying device.
The FTL holds no write buffering of its own (each write commits immediately), so it simply forwards the sync to the underlying device.
| [in] | ctx | FTL handle (as a void cookie). |
| k_ra8_ok | Underlying device flushed (or nothing to flush). |
| k_ra8_err_null_ptr | ctx was NULL. |
| other | Propagated underlying sync error. |
Definition at line 557 of file ra8_ftl.c.
References RA8_CHECK_NULL_PTR, ra8_io_blockdev_sync(), ra8_ftl_t::raw, and s_tag.
|
static |
Presented vtable: write count logical blocks at lba.
Bounds-checks then relocates each logical block one at a time through internal_write_one (copy-on-write). The per-block loop is statically bounded by count.
| [in] | ctx | FTL handle (as a void cookie). |
| [in] | lba | First logical block address. |
| [in] | count | Number of logical blocks to write. |
| [in] | buf | Source buffer (>= count * 512 bytes). |
| k_ra8_ok | Blocks written. |
| k_ra8_err_null_ptr | ctx or buf was NULL. |
| k_ra8_err_out_of_range | Range past presented capacity. |
| other | Propagated underlying erase/program error. |
Definition at line 428 of file ra8_ftl.c.
References internal_bounds(), internal_write_one(), k_ra8_io_block_size_bytes, k_ra8_ok, RA8_CHECK_NULL_PTR, and s_tag.
Select the least-erased FREE physical block (wear-levelling).
Linear scan bounded by physical_blocks tracking the FREE block with the lowest erase count. Spreading allocation toward the least-worn free block is the wear-levelling policy. Returns k_ra8_err_no_data if no FREE block exists so the caller can trigger reclamation and retry.
| [in] | ftl | Initialised FTL handle. |
| [out] | out | Receives the chosen physical block index. |
| k_ra8_ok | A free block was chosen into *out. |
| k_ra8_err_null_ptr | ftl or out was NULL. |
| k_ra8_err_no_data | No FREE physical block is available. |
Definition at line 128 of file ra8_ftl.c.
References ra8_ftl_pblock_t::erase_count, k_ra8_err_no_data, k_ra8_ftl_pstate_free, k_ra8_ok, ra8_ftl_t::pblocks, ra8_ftl_t::physical_blocks, RA8_CHECK_NULL_PTR, s_tag, and ra8_ftl_pblock_t::state.
Referenced by internal_alloc_blank().
Read one logical block, resolving the map (or erase value if unmapped).
If the logical block is unmapped the destination is filled with the medium erase value (the block has never been written). Otherwise the underlying physical block is read through.
| [in] | ftl | Initialised FTL handle. |
| [in] | lbn | Logical block number (< logical_blocks). |
| [out] | dst | 512-byte destination buffer. |
| k_ra8_ok | Block resolved into dst. |
| k_ra8_err_null_ptr | ftl or dst was NULL. |
| other | Propagated underlying read error. |
Definition at line 247 of file ra8_ftl.c.
References ra8_ftl_t::erase_value, k_ra8_ftl_one_block, k_ra8_ftl_unmapped, k_ra8_io_block_size_bytes, k_ra8_ok, ra8_ftl_t::map, memset(), RA8_CHECK_NULL_PTR, ra8_io_blockdev_read(), ra8_ftl_t::raw, and s_tag.
Referenced by internal_dev_read().
Reclaim every STALE physical block by erasing it back to FREE.
Scans the physical metadata and, for each STALE block, issues a single-block erase on the underlying device, advances its erase count, and marks it FREE. The loop is statically bounded by physical_blocks. Called only when the allocator finds no FREE block, so reclamation cost is amortised.
| [in,out] | ftl | Initialised FTL handle. |
| k_ra8_ok | Reclamation pass completed (some blocks may stay live). |
| k_ra8_err_null_ptr | ftl was NULL. |
| other | Propagated underlying-device erase error. |
Definition at line 83 of file ra8_ftl.c.
References ra8_ftl_pblock_t::erase_count, k_ra8_ftl_one_block, k_ra8_ftl_pstate_free, k_ra8_ftl_pstate_stale, k_ra8_ok, ra8_ftl_t::pblocks, ra8_ftl_t::physical_blocks, RA8_CHECK_NULL_PTR, ra8_io_blockdev_erase(), ra8_ftl_t::raw, s_tag, and ra8_ftl_pblock_t::state.
Referenced by internal_alloc_blank().
Cold-start the FTL bookkeeping tables.
Sets every logical map entry to k_ra8_ftl_unmapped and every physical block to FREE with a zero erase count. Both loops are statically bounded by their respective table sizes.
| [in,out] | ftl | Handle whose map/pblocks/sizes are already populated. |
| k_ra8_ok | Tables initialised to the cold-start state. |
| k_ra8_err_null_ptr | ftl was NULL. |
Definition at line 656 of file ra8_ftl.c.
References ra8_ftl_pblock_t::erase_count, k_ra8_ftl_pstate_free, k_ra8_ftl_unmapped, k_ra8_ok, ra8_ftl_t::logical_blocks, ra8_ftl_t::map, ra8_ftl_t::pblocks, ra8_ftl_t::physical_blocks, RA8_CHECK_NULL_PTR, s_tag, and ra8_ftl_pblock_t::state.
Referenced by ra8_ftl_init().
|
static |
Validate the pointer + sizing arguments to ra8_ftl_init.
Split out of ra8_ftl_init so that function stays within the clang-tidy statement/complexity budget. Rejects any null storage pointer, a zero logical-block count, and a physical-block count past the static k_ra8_ftl_max_pblocks ceiling.
| [in] | bd | FTL state to populate (non-NULL). |
| [in] | raw | Backing erase-before-write block device (non-NULL). |
| [in] | map | Logical->physical map storage (non-NULL). |
| [in] | pblocks | Per-physical-block metadata storage (non-NULL). |
| [in] | scratch | Copy-on-write scratch buffer (non-NULL). |
| [in] | logical_blocks | Logical block count (> 0). |
| [in] | physical_blocks | Physical block count (<= k_ra8_ftl_max_pblocks). |
| k_ra8_ok | All arguments valid. |
| k_ra8_err_null_ptr | A required pointer argument was NULL. |
| k_ra8_err_invalid_size | logical_blocks is 0 or physical_blocks exceeds the ceiling. |
Definition at line 702 of file ra8_ftl.c.
References k_ra8_err_invalid_size, k_ra8_ftl_max_pblocks, k_ra8_ok, RA8_CHECK_NULL_PTR, and s_tag.
Referenced by ra8_ftl_init().
Write one logical block via copy-on-write relocation.
Allocates a blank physical block, programs src into it, then re-points the map and marks the previous physical block (if any) STALE for later reclamation. The old block is never overwritten, so the erase-before-write contract holds. On a program failure the freshly erased block is left FREE and the map is unchanged, so the prior data remains intact.
| [in,out] | ftl | Initialised FTL handle. |
| [in] | lbn | Logical block number (< logical_blocks). |
| [in] | src | 512-byte source buffer. |
| k_ra8_ok | Logical block now reflects src. |
| k_ra8_err_null_ptr | ftl or src was NULL. |
| k_ra8_err_no_mem | No physical block available to relocate into. |
| other | Propagated underlying erase/program error. |
Definition at line 289 of file ra8_ftl.c.
References internal_alloc_blank(), k_ra8_ftl_one_block, k_ra8_ftl_pstate_live, k_ra8_ftl_pstate_stale, k_ra8_ftl_unmapped, k_ra8_ok, ra8_ftl_t::map, ra8_ftl_t::pblocks, RA8_CHECK_NULL_PTR, ra8_io_blockdev_write(), ra8_ftl_t::raw, s_tag, and ra8_ftl_pblock_t::state.
Referenced by internal_dev_write().
|
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 |
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().
|
static |
Presented FTL vtable – a clean free-overwrite block device.
Definition at line 566 of file ra8_ftl.c.
Referenced by ra8_ftl_as_blockdev().