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

Where the free space is: next-free hint, FAT sector cache, FSInfo. More...

#include <stddef.h>
#include <stdint.h>
#include "ra8_attributes.h"
#include "ra8_fs.h"
#include "ra8_fs_fat_internal.h"
Include dependency graph for ra8_fs_fat_alloc.c:

Go to the source code of this file.

Data Structures

struct  fat_alloc_state_t
 One mounted volume's answer to "where is the free space". More...

Functions

static fat_alloc_state_tinternal_state_for (const ra8_fs_mount_t *m)
 Find the allocator state slot bound to m.
void priv_alloc_state_bind (const ra8_fs_mount_t *m)
 Claim and reset the allocator state slot for a freshly mounted volume.
ra8_err_t priv_exfat_bitmap_lba (const ra8_fs_mount_t *m, uint64_t *out_lba)
 Resolve the exFAT allocation bitmap's first LBA, once per mount.
void priv_alloc_state_release (const ra8_fs_mount_t *m)
 Release the allocator state slot held by m.
static bool internal_lba_is_cacheable (const ra8_fs_mount_t *m, uint64_t lba)
 True when lba lies inside the first FAT copy of m.
ra8_err_t priv_fat_sector_read (const ra8_fs_mount_t *m, uint64_t lba, uint8_t *buf)
 Read one FAT sector, through the shared one-sector cache.
void priv_fat_sector_wrote (const ra8_fs_mount_t *m, const uint8_t *buf, uint64_t lba)
 Tell the cache that lba has just been written with buf.
uint32_t priv_alloc_hint_get (const ra8_fs_mount_t *m)
 Report the cluster a free-space scan should start from.
void priv_alloc_hint_set (const ra8_fs_mount_t *m, uint32_t cluster)
 Move the next-free hint forward to cluster.
void priv_alloc_hint_lower (const ra8_fs_mount_t *m, uint32_t cluster)
 Pull the next-free hint back to cluster if it is further on.
void priv_free_count_took (const ra8_fs_mount_t *m, uint32_t n)
 Account n clusters as taken out of the volume's free space.
void priv_free_count_gave (const ra8_fs_mount_t *m, uint32_t n)
 Account n clusters as returned to the volume's free space.
uint32_t priv_free_count_peek (const ra8_fs_mount_t *m)
 Report the tracked free-cluster count, or k_fs_free_unknown.
void priv_free_count_cache (const ra8_fs_mount_t *m, uint32_t n)
 Cache a freshly counted free-cluster total in the allocator slot.
static ra8_err_t internal_fsinfo_locate (const ra8_fs_mount_t *m, uint32_t *out_lba)
 Report the FSInfo sector number this volume declares, or absent.
static bool internal_fsinfo_signatures_ok (const uint8_t *sec)
 Check the three FSInfo signatures in a candidate sector.
ra8_err_t priv_fsinfo_seed (const ra8_fs_mount_t *m)
 Validate FAT32's FSInfo sector and seed the hint and free count.
ra8_err_t priv_fsinfo_flush (const ra8_fs_mount_t *m)
 Write the tracked free count and next-free hint back into FSInfo.

Variables

static fat_alloc_state_t s_alloc [k_fs_alloc_slots] = {}
 One allocator state slot per mount slot.
static uint8_t s_fat_cache [k_ra8_fs_sector_max] = {}
 The one cached FAT sector, shared by every mount.
static uint64_t s_fat_cache_lba = k_fs_cache_empty
 Volume-relative LBA held in s_fat_cache, or k_fs_cache_empty.
static const ra8_fs_mount_ts_fat_cache_owner = nullptr
 Mount whose volume s_fat_cache holds a sector of.

Detailed Description

Where the free space is: next-free hint, FAT sector cache, FSInfo.

The state that turns cluster allocation from quadratic into amortised constant, plus the FAT32 FSInfo maintenance that stops a card this firmware wrote from reporting the wrong free space on a desktop.

The three pieces are one mechanism seen from three angles – see ra8_fs_fat_alloc_internal.h for the argument. What lives here is the storage and the accessors; the scan itself stays in ra8_fs_fat.c next to the FAT entry decoding it drives.

Two deliberate shapes are worth stating, because both are load-bearing:

The state is keyed by mount pointer, not held in the mount. Every chain walker in this adapter takes a const ra8_fs_mount_t*. Putting a mutable cache in the mount would have meant either casting that const away or propagating a non-const mount through a dozen call chains, to add something that is not part of the volume at all.

One FAT sector cache is shared by every mount, not one per mount. The adapter is single-threaded by contract, so no operation interleaves two volumes' FAT walks; a second buffer would cost another 512 bytes of .bss to serve a case that cannot arise. The cache therefore records which mount it belongs to, and a mount that finds someone else's sector simply misses.

References (every shorthand citation in this file):

  • "MS FAT spec" = Microsoft Corp., "FAT: General Overview of On-Disk Format", v1.03, December 6 2000. FSInfo is sec 5.

NASA Power-of-Ten compliance:

  • Rule 2: the only loops are over k_fs_alloc_slots (2).
  • Rule 3: zero malloc; all state lives in static arrays.
  • Rule 7: every backend call is checked.
Since
0.1.0

Definition in file ra8_fs_fat_alloc.c.

Function Documentation

◆ internal_fsinfo_locate()

ra8_err_t internal_fsinfo_locate ( const ra8_fs_mount_t * m,
uint32_t * out_lba )
static

Report the FSInfo sector number this volume declares, or absent.

Re-reads the boot sector rather than caching BPB_FSInfo in the mount: it costs one sector read once per mount and keeps the whole FSInfo mechanism inside this file. The declared sector must lie inside the volume's own reserved region – a BPB pointing anywhere else is describing something that is not an FSInfo sector.

Parameters
[in]mMount to interrogate.
[out]out_lbaReceives the sector number, or k_fs_fsinfo_absent.
Returns
Error code.
Return values
k_ra8_okout_lba is populated (possibly with "absent").
k_ra8_err_*The boot sector could not be re-read.
Precondition
m and out_lba are non-NULL; the mount's geometry is populated.
The mount is FAT32 (callers check; other types have no FSInfo).
Postcondition
On success out_lba is either 0 or a sector inside the reserved region.
No volume state is modified.
Note
Not thread-safe; callers serialise mount operations.
Since
0.1.0

Definition at line 418 of file ra8_fs_fat_alloc.c.

References k_fmt_off_f32_fsinfo, k_fs_fsinfo_absent, k_ra8_ok, priv_rd16(), priv_read_sector(), priv_sec_walk(), and ra8_fs_mount_t::reserved_sectors.

Referenced by priv_fsinfo_seed().

◆ internal_fsinfo_signatures_ok()

bool internal_fsinfo_signatures_ok ( const uint8_t * sec)
static

Check the three FSInfo signatures in a candidate sector.

MS FAT spec sec 5. All three must match before any field is believed; two of three is a sector that happens to start with "RRaA" and is not an FSInfo sector.

Parameters
[in]secThe candidate sector's contents.
Returns
Whether the sector is a valid FSInfo sector.
Return values
trueLead, struct and trailing signatures all match.
falseAny one of them does not.
Precondition
sec is non-NULL and addresses a whole sector.
sec was read from the sector BPB_FSInfo names.
Postcondition
No state modified.
Result depends only on sec.
Note
Pure function; trivially thread-safe.
Since
0.1.0

Definition at line 460 of file ra8_fs_fat_alloc.c.

References k_fmt_fsi_lead_sig, k_fmt_fsi_off_lead, k_fmt_fsi_off_struct, k_fmt_fsi_off_trail, k_fmt_fsi_struct_sig, k_fmt_fsi_trail_sig, and priv_rd32().

Referenced by priv_fsinfo_seed().

◆ internal_lba_is_cacheable()

bool internal_lba_is_cacheable ( const ra8_fs_mount_t * m,
uint64_t lba )
static

True when lba lies inside the first FAT copy of m.

Only the first copy is cached. Reads never look anywhere else, and caching a mirror-copy write would evict the sector an allocation scan is walking – disabling the cache during the one storm it exists for.

Parameters
[in]mMount providing the FAT geometry.
[in]lbaVolume-relative sector.
Returns
Whether the sector is cacheable.
Return values
truelba is in [first_fat_lba, first_fat_lba + fat_size_sectors).
falseAnywhere else, including the mirror FAT copies.
Precondition
m is non-NULL with its geometry populated.
lba is volume-relative, not backend-absolute.
Postcondition
No state modified.
Result depends only on the inputs.
Note
Pure function; trivially thread-safe.
MC/DC:
Decision: (lba - first_fat_lba) < fat_size_sectors (1 condition).
  • a sector of FAT copy 0 -> true -> cacheable.
  • a sector of a mirror -> false -> passed straight to the backend. The subtraction is unsigned, so an LBA BELOW the first FAT copy wraps to a value far above any FAT length and the same test rejects it. Spelling that out as a second range term would add a condition nothing can make false – every caller derives its LBA from first_fat_lba – and an untestable condition is a permanent MC/DC hole.
Since
0.1.0

Definition at line 265 of file ra8_fs_fat_alloc.c.

References ra8_fs_mount_t::fat_size_sectors, and ra8_fs_mount_t::first_fat_lba.

Referenced by priv_fat_sector_read(), and priv_fat_sector_wrote().

◆ internal_state_for()

fat_alloc_state_t * internal_state_for ( const ra8_fs_mount_t * m)
static

Find the allocator state slot bound to m.

Linear search over two entries. Returns NULL rather than binding lazily: a slot must be RESET when a volume is mounted, and a lazy bind cannot tell a first use from a reuse of a recycled mount address.

Parameters
[in]mMount to look up.
Returns
The bound slot, or NULL.
Return values
non-NULLThe slot describing m.
NULLm has no bound slot; callers fall back to old behaviour.
Precondition
m is non-NULL.
Mount and unmount are not running concurrently.
Postcondition
No state modified.
The returned pointer stays valid until m is unmounted.
Note
Not thread-safe; callers serialise filesystem operations.
Since
0.1.0

Definition at line 155 of file ra8_fs_fat_alloc.c.

References k_fs_alloc_slots, owner, and s_alloc.

Referenced by priv_alloc_hint_get(), priv_alloc_hint_lower(), priv_alloc_hint_set(), priv_alloc_state_bind(), priv_alloc_state_release(), priv_exfat_bitmap_lba(), priv_free_count_cache(), priv_free_count_gave(), priv_free_count_peek(), priv_free_count_took(), priv_fsinfo_flush(), and priv_fsinfo_seed().

◆ priv_alloc_hint_get()

uint32_t priv_alloc_hint_get ( const ra8_fs_mount_t * m)

Report the cluster a free-space scan should start from.

Seeded from FAT32's FSI_Nxt_Free when the FSInfo sector validates, otherwise cluster 2. Always returns a value the caller must still range-check: a hint is an optimisation, not a promise, and an on-disk FSInfo written by some other implementation is not trusted to be in range.

Parameters
[in]mMount to query.
Returns
The cluster number to begin scanning at.
Return values
2..UINT32_MAXThe hint, or 2 when m has no bound slot.
Precondition
m is non-NULL.
The mount's geometry is populated.
Postcondition
No state modified.
The result is at least k_cluster_first_data.
Note
Not thread-safe; callers serialise filesystem operations.
Since
0.1.0

Definition at line 307 of file ra8_fs_fat_alloc.c.

References internal_state_for(), k_cluster_first_data, and fat_alloc_state_t::next_free.

Referenced by priv_alloc_cluster(), and priv_exfat_bitmap_scan().

◆ priv_alloc_hint_lower()

void priv_alloc_hint_lower ( const ra8_fs_mount_t * m,
uint32_t cluster )

Pull the next-free hint back to cluster if it is further on.

Called for every cluster returned to the volume. Without it a delete followed by a create would step over the space it just released and only find it again after a full wrap – correct, but it would make freed clusters look unreusable to any test (and any user) watching where the next file lands.

Parameters
[in]mMount to update.
[in]clusterCluster that has just become free.
Returns
Nothing.
Precondition
m is non-NULL.
cluster's FAT entry (or bitmap bit) already reads as free.
Postcondition
The hint is at most cluster.
No backend I/O is issued.
Note
Not thread-safe; callers serialise filesystem operations.
Since
0.1.0

Definition at line 327 of file ra8_fs_fat_alloc.c.

References internal_state_for(), and fat_alloc_state_t::next_free.

Referenced by priv_exfat_bitmap_clear(), and priv_free_chain().

◆ priv_alloc_hint_set()

void priv_alloc_hint_set ( const ra8_fs_mount_t * m,
uint32_t cluster )

Move the next-free hint forward to cluster.

Called after a successful allocation with the cluster AFTER the one taken, so a run of allocations walks forward instead of re-reading the entries it has just filled in.

Parameters
[in]mMount to update.
[in]clusterCluster the next scan should start at.
Returns
Nothing.
Precondition
m is non-NULL.
cluster is the cluster after the one just allocated.
Postcondition
A later priv_alloc_hint_get returns cluster.
No backend I/O is issued.
Note
Not thread-safe; callers serialise filesystem operations.
Since
0.1.0

Definition at line 317 of file ra8_fs_fat_alloc.c.

References internal_state_for(), and fat_alloc_state_t::next_free.

Referenced by internal_exfat_dir_alloc(), internal_exfat_dir_append(), internal_exfat_grow_one(), and priv_alloc_cluster().

◆ priv_alloc_state_bind()

void priv_alloc_state_bind ( const ra8_fs_mount_t * m)

Claim and reset the allocator state slot for a freshly mounted volume.

Called once from ra8_fs_mount() after the geometry is known. The reset is the load-bearing half: the mount table is a fixed array, so the same ra8_fs_mount_t address is handed out again after an unmount, and a slot carrying the PREVIOUS volume's next-free hint would hand the new volume another disk's answers. The shared FAT sector cache is NOT dropped here – priv_alloc_state_release owns that, and a mount slot cannot be handed out again without passing through it, so a second drop would be an unreachable branch pretending to be a safety net.

Parameters
[in]mMount that has just been parsed.
Returns
Nothing.
Precondition
m is non-NULL and its geometry fields are populated.
No allocator accessor is running concurrently.
Postcondition
m owns a slot whose hint is cluster 2 and whose cache is empty.
The free count is k_fs_free_unknown until priv_fsinfo_seed runs.
Note
Not thread-safe; callers serialise mount operations.
Since
0.1.0

Definition at line 166 of file ra8_fs_fat_alloc.c.

References fat_alloc_state_t::bitmap_lba, fat_alloc_state_t::dirty, fat_alloc_state_t::free_count, fat_alloc_state_t::fsinfo_lba, internal_state_for(), k_cluster_first_data, k_fs_bitmap_unknown, k_fs_free_unknown, k_fs_fsinfo_absent, fat_alloc_state_t::next_free, and fat_alloc_state_t::owner.

Referenced by internal_mount_locked().

◆ priv_alloc_state_release()

void priv_alloc_state_release ( const ra8_fs_mount_t * m)

Release the allocator state slot held by m.

Called from ra8_fs_unmount() after any FSInfo writeback. Also drops the shared FAT sector cache when it belongs to m, so a later mount of a different volume cannot be served stale bytes.

Parameters
[in]mMount being unmounted.
Returns
Nothing.
Precondition
m is non-NULL.
Any pending FSInfo writeback has already been flushed.
Postcondition
m owns no slot; a later bind starts from a clean state.
The shared FAT sector cache holds nothing belonging to m.
Note
Not thread-safe; callers serialise mount operations.
Since
0.1.0

Definition at line 213 of file ra8_fs_fat_alloc.c.

References internal_state_for(), k_fs_cache_empty, s_fat_cache_lba, and s_fat_cache_owner.

Referenced by internal_mount_locked(), and internal_unmount_locked().

◆ priv_exfat_bitmap_lba()

ra8_err_t priv_exfat_bitmap_lba ( const ra8_fs_mount_t * m,
uint64_t * out_lba )

Resolve the exFAT allocation bitmap's first LBA, once per mount.

The bitmap's location lives in a system directory entry in the root, so finding it means walking the root directory – fine once, ruinous per cluster of a streaming write, which is exactly how often the grow path needs it. The answer cannot change while a volume is mounted (the bitmap is not relocatable), so the first walk is cached in the mount's allocator state and every later call is a load.

A mount with no bound allocator slot still gets a correct answer, just an uncached one: the accessor is total, like the rest of this header's.

Parameters
[in]mMounted exFAT volume.
[out]out_lbaReceives the volume-relative first LBA of the bitmap.
Returns
Error code.
Return values
k_ra8_okThe bitmap was located (cached or freshly found).
k_ra8_err_not_foundThe root directory carries no bitmap entry.
k_ra8_err_*Backend read failure during the directory walk.
Precondition
m and out_lba are non-NULL and m->type is exFAT.
The volume is mounted; the root directory is readable.
Postcondition
On success *out_lba addresses the bitmap's first sector.
On failure *out_lba is unmodified.
Note
Not thread-safe; callers serialise filesystem operations.
Since
0.1.0

Definition at line 184 of file ra8_fs_fat_alloc.c.

References fat_alloc_state_t::bitmap_lba, internal_state_for(), k_fs_bitmap_unknown, k_ra8_ok, priv_cluster_to_lba(), and priv_exfat_find_bitmap().

Referenced by internal_exfat_dir_append(), internal_exfat_grow_one(), and priv_exfat_free_clusters().

◆ priv_fat_sector_read()

ra8_err_t priv_fat_sector_read ( const ra8_fs_mount_t * m,
uint64_t lba,
uint8_t * buf )

Read one FAT sector, through the shared one-sector cache.

The whole point of the exercise: a FAT32 sector holds 128 entries and a FAT16 sector 256, so a free-cluster scan that reads through here issues one backend read per 128 (or 256) clusters examined instead of one per cluster.

Only sectors inside the FIRST FAT copy are cached. Reads never touch the other copies, and a priv_fat_set() mirroring an entry into copy 1 would otherwise evict the very sector the scan is walking – turning the cache off exactly during the allocation storm it exists for. An LBA outside that window is passed straight to the backend.

Parameters
[in]mMount providing the backend and the FAT geometry.
[in]lbaVolume-relative sector to read.
[out]bufDestination of at least m->bytes_per_sector bytes.
Returns
Error code.
Return values
k_ra8_okbuf holds the sector (from the cache or the backend).
k_ra8_err_*Backend read failure; buf is unspecified.
Precondition
m and buf are non-NULL; the mount's backend is bound.
buf addresses a whole writable sector.
Postcondition
On success buf equals the on-disk sector at lba.
A cacheable sector that missed is now the cached one.
Note
Not thread-safe; the cache is module-static like g_fs_scratch.
Since
0.1.0

Definition at line 271 of file ra8_fs_fat_alloc.c.

References internal_lba_is_cacheable(), k_ra8_ok, priv_bps(), priv_byte_copy(), priv_read_sector(), s_fat_cache, s_fat_cache_lba, and s_fat_cache_owner.

Referenced by internal_exfat_fat_set_one(), internal_fat12_set_one(), internal_fat16_set_one(), internal_fat32_set_one(), and priv_fat_get().

◆ priv_fat_sector_wrote()

void priv_fat_sector_wrote ( const ra8_fs_mount_t * m,
const uint8_t * buf,
uint64_t lba )

Tell the cache that lba has just been written with buf.

Write-through: the caller has already committed the sector, so the cache is refreshed rather than invalidated and the next read of the same sector still costs nothing. Called after every FAT sector write, including the ones that land in a mirror copy – those are outside the cached window and are simply ignored.

Parameters
[in]mMount whose FAT geometry decides whether lba is cacheable.
[in]bufThe bytes that were written.
[in]lbaVolume-relative sector that was written.
Returns
Nothing.
Precondition
m and buf are non-NULL.
The write of buf to lba already succeeded.
Postcondition
If lba is cacheable, the cache holds buf for m.
No backend I/O is issued.
Note
Not thread-safe; the cache is module-static like g_fs_scratch.
Since
0.1.0

Definition at line 291 of file ra8_fs_fat_alloc.c.

References internal_lba_is_cacheable(), priv_bps(), priv_byte_copy(), s_fat_cache, s_fat_cache_lba, and s_fat_cache_owner.

Referenced by internal_exfat_fat_set_one(), internal_fat12_store(), internal_fat16_set_one(), and internal_fat32_set_one().

◆ priv_free_count_cache()

void priv_free_count_cache ( const ra8_fs_mount_t * m,
uint32_t n )

Cache a freshly counted free-cluster total in the allocator slot.

After ra8_fs_free_space walks the FAT or the exFAT bitmap on a volume that had no trusted count, it records the result here so a second query is O(1). The value is clamped to the volume's cluster count – a bitmap or FAT that counts more free clusters than the volume has is corrupt, and a free count above the disk size is worse than a walk. It does NOT flag the volume dirty: a computed count is a cache, not a change to write back, and a FAT32 whose FSInfo genuinely needed this would have seeded a trusted count at mount instead.

Parameters
[in]mMount to update.
[in]nFree-cluster count just measured.
Returns
Nothing.
Precondition
m is non-NULL with a bound slot (else the call is a no-op).
n was counted from the on-disk FAT or allocation bitmap.
Postcondition
A later priv_free_count_peek returns min(n, count_of_clusters).
The FSInfo dirty flag is unchanged.
Note
Not thread-safe; callers serialise filesystem operations.
Since
0.1.0

Definition at line 378 of file ra8_fs_fat_alloc.c.

References ra8_fs_mount_t::count_of_clusters, fat_alloc_state_t::free_count, and internal_state_for().

Referenced by internal_space_free_clusters().

◆ priv_free_count_gave()

void priv_free_count_gave ( const ra8_fs_mount_t * m,
uint32_t n )

Account n clusters as returned to the volume's free space.

The mirror of priv_free_count_took, and equally a no-op while the count is unknown. Clamped at the volume's cluster count so a double-free on a corrupt chain cannot inflate the reported free space past the size of the disk.

Parameters
[in]mMount to update.
[in]nClusters freed.
Returns
Nothing.
Precondition
m is non-NULL.
n clusters have actually been marked free on disk.
Postcondition
The tracked free count rises by n but never past count_of_clusters.
The volume is flagged for FSInfo writeback.
Note
Not thread-safe; callers serialise filesystem operations.
Since
0.1.0

Definition at line 353 of file ra8_fs_fat_alloc.c.

References ra8_fs_mount_t::count_of_clusters, fat_alloc_state_t::dirty, fat_alloc_state_t::free_count, internal_state_for(), and k_fs_free_unknown.

Referenced by priv_free_chain().

◆ priv_free_count_peek()

uint32_t priv_free_count_peek ( const ra8_fs_mount_t * m)

Report the tracked free-cluster count, or k_fs_free_unknown.

The read half of the allocator's free count – what ra8_fs_free_space consults before deciding whether it must walk the FAT (or the exFAT bitmap) itself. Returns k_fs_free_unknown both when the count was never established (FAT12/16, or a FAT32 whose FSInfo did not validate) and when m has no bound slot, so a caller treats "unknown" as "I must count it".

Parameters
[in]mMount to query.
Returns
The tracked free-cluster count.
Return values
k_fs_free_unknownNo trusted count, or m has no bound slot.
0..count_of_clustersThe tracked count.
Precondition
m is non-NULL.
The mount's geometry is populated.
Postcondition
No state modified.
The result is <= m->count_of_clusters unless it is k_fs_free_unknown.
Note
Not thread-safe; callers serialise filesystem operations.
Since
0.1.0

Definition at line 368 of file ra8_fs_fat_alloc.c.

References fat_alloc_state_t::free_count, internal_state_for(), and k_fs_free_unknown.

Referenced by internal_space_free_clusters().

◆ priv_free_count_took()

void priv_free_count_took ( const ra8_fs_mount_t * m,
uint32_t n )

Account n clusters as taken out of the volume's free space.

A no-op while the count is k_fs_free_unknown: a number we are not sure of is worse than no number, because FSInfo's "unknown" value is honest and a wrong count is what makes fsck.fat complain in the first place.

Parameters
[in]mMount to update.
[in]nClusters allocated.
Returns
Nothing.
Precondition
m is non-NULL.
n clusters have actually been marked used on disk.
Postcondition
The tracked free count drops by n, or stays unknown.
The volume is flagged for FSInfo writeback.
Note
Not thread-safe; callers serialise filesystem operations.
Since
0.1.0

Definition at line 339 of file ra8_fs_fat_alloc.c.

References fat_alloc_state_t::dirty, fat_alloc_state_t::free_count, internal_state_for(), and k_fs_free_unknown.

Referenced by priv_alloc_cluster().

◆ priv_fsinfo_flush()

ra8_err_t priv_fsinfo_flush ( const ra8_fs_mount_t * m)

Write the tracked free count and next-free hint back into FSInfo.

Read-modify-write of the FSInfo sector, so the signatures and every reserved byte survive untouched. Does nothing when the volume has no FSInfo or when nothing has been allocated or freed since the last flush, which keeps a read-only workload from writing to the card at all.

An unknown free count is written as k_fs_free_unknown rather than a guess: the format defines that value for exactly this case, and fsck.fat accepts it silently instead of reporting a wrong summary.

Parameters
[in]mMount to flush.
Returns
Error code.
Return values
k_ra8_okWritten, or there was nothing to write.
k_ra8_err_*Backend read/write failure on the FSInfo sector.
Precondition
m is non-NULL.
Every cluster allocation and release has already been committed.
Postcondition
On success the on-disk FSInfo matches the tracked state.
The dirty flag is clear, so a second flush is a no-op.
Note
Not thread-safe; callers serialise filesystem operations.
Since
0.1.0

Definition at line 512 of file ra8_fs_fat_alloc.c.

References fat_alloc_state_t::dirty, fat_alloc_state_t::free_count, fat_alloc_state_t::fsinfo_lba, internal_state_for(), k_fmt_fsi_off_free, k_fmt_fsi_off_nxtfree, k_fs_fsinfo_absent, k_ra8_ok, fat_alloc_state_t::next_free, priv_read_sector(), priv_sec_walk(), priv_wr32(), and priv_write_sector().

Referenced by internal_close_stamp(), and internal_unmount_locked().

◆ priv_fsinfo_seed()

ra8_err_t priv_fsinfo_seed ( const ra8_fs_mount_t * m)

Validate FAT32's FSInfo sector and seed the hint and free count.

MS FAT spec sec 5. All three signatures must match – lead 0x41615252, struct 0x61417272, trailing 0xAA550000 – before a single field is believed, and each field is then range-checked independently: FSI_Nxt_Free must name a real cluster and FSI_Free_Count must not exceed the volume's cluster count. Either one failing leaves that half at its default without discarding the other.

A volume with no FSInfo (FAT12, FAT16, exFAT, or a FAT32 BPB whose BPB_FSInfo points outside the reserved region) is not an error: the mount keeps the cluster-2 hint and an unknown free count, and nothing is ever written back.

Parameters
[in]mFreshly parsed mount with a bound allocator slot.
Returns
Error code.
Return values
k_ra8_okSeeded, or the volume legitimately has no FSInfo.
k_ra8_err_*The backend could not read a sector inside the volume's own reserved region.
Precondition
m is non-NULL and priv_alloc_state_bind has run for it.
m's geometry fields are populated.
Postcondition
On success the hint and free count reflect a validated FSInfo, or their defaults.
No FSInfo bytes are modified.
Note
Not thread-safe; callers serialise mount operations.
Since
0.1.0

Definition at line 472 of file ra8_fs_fat_alloc.c.

References ra8_fs_mount_t::count_of_clusters, fat_alloc_state_t::free_count, fat_alloc_state_t::fsinfo_lba, internal_fsinfo_locate(), internal_fsinfo_signatures_ok(), internal_state_for(), k_cluster_first_data, k_fmt_fsi_off_free, k_fmt_fsi_off_nxtfree, k_fs_fsinfo_absent, k_ra8_fs_type_fat32, k_ra8_ok, fat_alloc_state_t::next_free, priv_rd32(), priv_read_sector(), priv_sec_walk(), and ra8_fs_mount_t::type.

Referenced by internal_mount_locked().

Variable Documentation

◆ s_alloc

fat_alloc_state_t s_alloc[k_fs_alloc_slots] = {}
static

One allocator state slot per mount slot.

Indexed by search on owner, not by mount index, because this file has no visibility of the mount table in ra8_fs_fat_mount.c.

Note
Not reentrant; the adapter is single-threaded by contract.
Warning
Never index directly; go through ::priv_state_for.
Since
0.1.0

Definition at line 89 of file ra8_fs_fat_alloc.c.

Referenced by internal_state_for().

◆ s_fat_cache

uint8_t s_fat_cache[k_ra8_fs_sector_max] = {}
static

The one cached FAT sector, shared by every mount.

Valid only when s_fat_cache_lba != k_fs_cache_empty, and then only for s_fat_cache_owner.

Note
Not reentrant; the adapter is single-threaded by contract.
Warning
Never read directly; go through priv_fat_sector_read.
Since
0.1.0

Definition at line 103 of file ra8_fs_fat_alloc.c.

Referenced by priv_fat_sector_read(), and priv_fat_sector_wrote().

◆ s_fat_cache_lba

uint64_t s_fat_cache_lba = k_fs_cache_empty
static

Volume-relative LBA held in s_fat_cache, or k_fs_cache_empty.

UINT64_MAX is the empty sentinel because LBA 0 is a real address.

Note
Not reentrant; the adapter is single-threaded by contract.
Warning
Never modify directly.
Since
0.1.0

Definition at line 113 of file ra8_fs_fat_alloc.c.

Referenced by priv_alloc_state_release(), priv_fat_sector_read(), and priv_fat_sector_wrote().

◆ s_fat_cache_owner

const ra8_fs_mount_t* s_fat_cache_owner = nullptr
static

Mount whose volume s_fat_cache holds a sector of.

Two volumes can be mounted at once and their LBAs mean different things, so the owner is part of the cache key.

Note
Not reentrant; the adapter is single-threaded by contract.
Warning
Never modify directly.
Since
0.1.0

Definition at line 124 of file ra8_fs_fat_alloc.c.

Referenced by priv_alloc_state_release(), priv_fat_sector_read(), and priv_fat_sector_wrote().