|
ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
|
Per-mount allocator state: next-free hint, free count, FAT cache. More...
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. | |
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:
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):
Definition in file ra8_fs_fat_alloc_internal.h.
| enum ra8_fs_alloc_const_t : uint32_t |
Sentinels and bounds for the per-mount allocator state.
| 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.
| 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).
| 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.
| 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().