|
ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
|
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"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_t * | internal_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_t * | s_fat_cache_owner = nullptr |
| Mount whose volume s_fat_cache holds a sector of. | |
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):
NASA Power-of-Ten compliance:
Definition in file ra8_fs_fat_alloc.c.
|
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.
| [in] | m | Mount to interrogate. |
| [out] | out_lba | Receives the sector number, or k_fs_fsinfo_absent. |
| k_ra8_ok | out_lba is populated (possibly with "absent"). |
| k_ra8_err_* | The boot sector could not be re-read. |
m and out_lba are non-NULL; the mount's geometry is populated. out_lba is either 0 or a sector inside the reserved region. 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().
|
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.
| [in] | sec | The candidate sector's contents. |
| true | Lead, struct and trailing signatures all match. |
| false | Any one of them does not. |
sec is non-NULL and addresses a whole sector. sec was read from the sector BPB_FSInfo names. sec.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().
|
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.
| [in] | m | Mount providing the FAT geometry. |
| [in] | lba | Volume-relative sector. |
| true | lba is in [first_fat_lba, first_fat_lba + fat_size_sectors). |
| false | Anywhere else, including the mirror FAT copies. |
m is non-NULL with its geometry populated. lba is volume-relative, not backend-absolute. 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().
|
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.
| [in] | m | Mount to look up. |
| non-NULL | The slot describing m. |
| NULL | m has no bound slot; callers fall back to old behaviour. |
m is non-NULL. m is unmounted.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().
| 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.
| [in] | m | Mount to query. |
| 2..UINT32_MAX | The hint, or 2 when m has no bound slot. |
m is non-NULL. 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().
| 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.
| [in] | m | Mount to update. |
| [in] | cluster | Cluster that has just become free. |
m is non-NULL. cluster's FAT entry (or bitmap bit) already reads as free. cluster. 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().
| 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.
| [in] | m | Mount to update. |
| [in] | cluster | Cluster the next scan should start at. |
m is non-NULL. cluster is the cluster after the one just allocated. cluster. 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().
| 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.
| [in] | m | Mount that has just been parsed. |
m is non-NULL and its geometry fields are populated. m owns a slot whose hint is cluster 2 and whose cache is empty. 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().
| 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.
| [in] | m | Mount being unmounted. |
m is non-NULL. m owns no slot; a later bind starts from a clean state. m.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().
| 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.
| [in] | m | Mounted exFAT volume. |
| [out] | out_lba | Receives the volume-relative first LBA of the bitmap. |
| k_ra8_ok | The bitmap was located (cached or freshly found). |
| k_ra8_err_not_found | The root directory carries no bitmap entry. |
| k_ra8_err_* | Backend read failure during the directory walk. |
m and out_lba are non-NULL and m->type is exFAT. 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().
| 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.
| [in] | m | Mount providing the backend and the FAT geometry. |
| [in] | lba | Volume-relative sector to read. |
| [out] | buf | Destination of at least m->bytes_per_sector bytes. |
| k_ra8_ok | buf holds the sector (from the cache or the backend). |
| k_ra8_err_* | Backend read failure; buf is unspecified. |
m and buf are non-NULL; the mount's backend is bound. buf addresses a whole writable sector. buf equals the on-disk sector at lba. 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().
| 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.
| [in] | m | Mount whose FAT geometry decides whether lba is cacheable. |
| [in] | buf | The bytes that were written. |
| [in] | lba | Volume-relative sector that was written. |
m and buf are non-NULL. buf to lba already succeeded. lba is cacheable, the cache holds buf for m. 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().
| 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.
| [in] | m | Mount to update. |
| [in] | n | Free-cluster count just measured. |
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. 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().
| 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.
| [in] | m | Mount to update. |
| [in] | n | Clusters freed. |
m is non-NULL. n clusters have actually been marked free on disk. n but never past count_of_clusters. 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().
| 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".
| [in] | m | Mount to query. |
| k_fs_free_unknown | No trusted count, or m has no bound slot. |
| 0..count_of_clusters | The tracked count. |
m is non-NULL. 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().
| 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.
| [in] | m | Mount to update. |
| [in] | n | Clusters allocated. |
m is non-NULL. n clusters have actually been marked used on disk. n, or stays unknown. 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().
| 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.
| [in] | m | Mount to flush. |
| k_ra8_ok | Written, or there was nothing to write. |
| k_ra8_err_* | Backend read/write failure on the FSInfo sector. |
m is non-NULL. 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().
| 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.
| [in] | m | Freshly parsed mount with a bound allocator slot. |
| k_ra8_ok | Seeded, or the volume legitimately has no FSInfo. |
| k_ra8_err_* | The backend could not read a sector inside the volume's own reserved region. |
m is non-NULL and priv_alloc_state_bind has run for it. m's geometry fields are populated. 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().
|
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.
Definition at line 89 of file ra8_fs_fat_alloc.c.
Referenced by internal_state_for().
|
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.
Definition at line 103 of file ra8_fs_fat_alloc.c.
Referenced by priv_fat_sector_read(), and priv_fat_sector_wrote().
|
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.
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().
|
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.
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().