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

Per-mount allocator state: next-free hint, free count, FAT cache. More...

#include <stdint.h>
#include "ra8_attributes.h"
#include "ra8_fs.h"
Include dependency graph for ra8_fs_fat_alloc_internal.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Enumerations

enum  ra8_fs_alloc_const_t : uint32_t {
  k_fs_free_unknown = 0xFFFFFFFFU ,
  k_fs_alloc_slots = 2U ,
  k_fs_fsinfo_absent = 0U
}
 Sentinels and bounds for the per-mount allocator state. More...
enum  ra8_fs_alloc_lba_const_t : uint64_t {
  k_fs_cache_empty = 0xFFFFFFFFFFFFFFFFU ,
  k_fs_bitmap_unknown = 0xFFFFFFFFFFFFFFFFU
}
 64-bit LBA sentinels for the per-mount allocator state. More...

Functions

void priv_alloc_state_bind (const ra8_fs_mount_t *m)
 Claim and reset the allocator state slot for a freshly mounted volume.
void priv_alloc_state_release (const ra8_fs_mount_t *m)
 Release the allocator state slot held by 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.
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.
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.

Detailed Description

Per-mount allocator state: next-free hint, free count, FAT cache.

Allocation used to be quadratic. priv_alloc_cluster() restarted its scan at cluster 2 on every call, and priv_fat_get() had no cache, so examining cluster N cost N block reads and writing a K-cluster file cost O(K*N) real block-device round trips. On a 32 GB FAT32 card the FAT is thousands of sectors and a multi-megabyte write re-read most of it per cluster. It never showed up in a unit test because a mock backend's "read" is a memcpy.

Three pieces of state fix it, and they belong together because they are the same fact seen three ways – where the free space is:

  1. a next-free hint, so a scan starts where the last one stopped and wraps exactly once before declaring the volume full;
  2. a one-sector FAT cache, so the 128 FAT32 entries that share a sector cost one read between them instead of 128;
  3. a free-cluster count, seeded from FAT32's FSInfo sector at mount and written back when a file closes or the volume unmounts, so fsck.fat stops reporting "Free cluster summary wrong" on a card this firmware wrote and Explorer stops showing stale free space.

The state is keyed by mount POINTER in a small module-static table rather than living in ra8_fs_mount_t. That is deliberate: priv_fat_get() and every chain walker in this adapter take a const ra8_fs_mount_t*, and making the mount mutable to carry a cache would have cast that const off – or propagated a non-const mount through a dozen call chains – to add what is, semantically, a cache and not part of the volume.

Every accessor here is total: a mount with no bound slot behaves exactly like the old code (hint at cluster 2, no cache, free count unknown), so a missing binding degrades performance and never correctness.

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.
Since
0.1.0

Definition in file ra8_fs_fat_alloc_internal.h.

Enumeration Type Documentation

◆ ra8_fs_alloc_const_t

enum ra8_fs_alloc_const_t : uint32_t

Sentinels and bounds for the per-mount allocator state.

Invariant
k_fs_alloc_slots equals k_ra8_fs_max_mounts, so a bind can never fail while a mount slot is available.
See also
priv_alloc_state_bind()
Since
0.1.0
Enumerator
k_fs_free_unknown 

MS FAT spec sec 5: count not known.

k_fs_alloc_slots 

One state slot per mount slot.

k_fs_fsinfo_absent 

No usable FSInfo sector on this vol.

Definition at line 62 of file ra8_fs_fat_alloc_internal.h.

◆ ra8_fs_alloc_lba_const_t

enum ra8_fs_alloc_lba_const_t : uint64_t

64-bit LBA sentinels for the per-mount allocator state.

These double as "not resolved" and as an LBA no volume can address, which is why they are UINT64_MAX rather than 0 (LBA 0 is the boot sector and a perfectly real address). They are 64-bit because LBAs are: on beyond-2-TiB media the old 32-bit sentinel value 0xFFFFFFFF is an ordinary addressable sector (#683).

Invariant
Both values are above any LBA a supported medium can carry.
See also
priv_exfat_bitmap_lba()
Since
0.1.0
Enumerator
k_fs_cache_empty 

Cache holds no sector.

k_fs_bitmap_unknown 

exFAT bitmap LBA not resolved yet.

Definition at line 82 of file ra8_fs_fat_alloc_internal.h.

Function Documentation

◆ 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().