ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_fs_fat_exfat_write.c
Go to the documentation of this file.
1
16
17#include <stddef.h>
18#include <stdint.h>
19
20#include "ra8_attributes.h"
21#include "ra8_fs.h"
22#include "ra8_fs_fat_internal.h"
23
24/* ---- exFAT one-shot file writer (provisioning) -------------------------- */
25
42static uint16_t internal_exfat_csum_add(uint16_t cs, uint8_t b)
43{
44 uint16_t hi = ((cs & 1U) != 0U) ? (uint16_t)k_exfat_csum_hi_bit : (uint16_t)0U;
45 return (uint16_t)(hi + (uint16_t)(cs >> 1) + (uint16_t)b);
46}
47
48/* `priv_exfat_name_hash()`: see header for the documented contract. */
49uint16_t priv_exfat_name_hash(const uint16_t* name, uint32_t nlen)
50{
51 uint16_t h = 0U;
52 for (uint32_t i = 0U; i < nlen; i++) {
53 /* The spec's definition exactly: fold the UP-CASED name, little-endian, one
54 * byte at a time. Up-casing with an ASCII-only rule -- which is what this
55 * did -- stored a hash no compliant reader recomputes for any name outside
56 * ASCII, so a host could list the file and then not find it (#606). */
57 const uint16_t u = priv_exfat_upcase_unit(name[i]);
58 h = internal_exfat_csum_add(h, (uint8_t)((uint32_t)u & (uint32_t)k_utf_byte_mask));
59 h = internal_exfat_csum_add(h, (uint8_t)((uint32_t)u >> (uint32_t)k_utf_byte_shift));
60 }
61 return h;
62}
63
64/* `priv_exfat_set_checksum()`: see header for the documented contract. */
65uint16_t priv_exfat_set_checksum(const uint8_t* set, uint32_t bytes)
66{
67 uint16_t cs = 0U;
68 for (uint32_t i = 0U; i < bytes; i++) {
69 if (i == (uint32_t)k_exfat_off_file_csum) {
70 continue;
71 }
72 if (i == ((uint32_t)k_exfat_off_file_csum + 1U)) {
73 continue;
74 }
75 cs = internal_exfat_csum_add(cs, set[i]);
76 }
77 return cs;
78}
79
80/* `priv_exfat_find_bitmap()`: see header for the documented contract. */
81ra8_err_t priv_exfat_find_bitmap(const ra8_fs_mount_t* m, uint32_t* out_clus, uint32_t* out_len)
82{
83 /* The bitmap entry lives in the ROOT directory by definition, so this is the
84 * one exFAT scan that is not parameterised by a directory (#605). */
85 exfat_dir_t root = {};
86 priv_exfat_dir_root(m, &root);
87 exfat_cursor_t cur = {};
88 priv_exfat_cursor_init(&root, &cur);
89 while (cur.scanned < (uint32_t)k_exfat_scan_limit) {
90 uint8_t e[k_exfat_entry_bytes] = {};
91 ra8_err_t r = priv_exfat_next_entry(m, &cur, e);
92 if (r != k_ra8_ok) {
93 return r;
94 }
95 if (e[0] == (uint8_t)k_exfat_entry_eod) {
97 }
98 if (e[0] == (uint8_t)k_exfat_entry_bitmap) {
99 *out_clus = priv_rd32(&e[k_exfat_strm_off_clus]);
100 *out_len = priv_rd32(&e[k_exfat_strm_off_dlen]);
101 return k_ra8_ok;
102 }
103 }
104 return k_ra8_err_not_found; /* GCOVR_EXCL_LINE -- k_exfat_scan_limit (65536) entries required */
105}
106
132 uint64_t bmp_lba,
133 uint32_t from,
134 uint32_t need,
135 uint32_t* out_clus)
136{
137 uint32_t run = 0U;
138 uint32_t start = 0U;
139 uint64_t loaded = UINT64_MAX;
140 uint8_t* const sec = priv_sec_io();
141 for (uint32_t idx = from; idx < m->count_of_clusters; idx++) {
142 const uint64_t lba = bmp_lba + ((idx >> k_exfat_bit_shift) / priv_bps(m));
143 if (lba != loaded) {
144 ra8_err_t e = priv_read_sector(m, lba, sec);
145 if (e != k_ra8_ok) {
146 return e;
147 }
148 loaded = lba;
149 }
150 const uint32_t byte = (idx >> k_exfat_bit_shift) % priv_bps(m);
151 const uint32_t bit = idx & k_exfat_bit_mask;
152 if ((((uint32_t)sec[byte] >> bit) & 1U) != 0U) {
153 run = 0U;
154 continue;
155 }
156 if (run == 0U) {
157 start = idx;
158 }
159 run++;
160 if (run >= need) {
161 *out_clus = start + k_cluster_first_data;
162 return k_ra8_ok;
163 }
164 }
165 return k_ra8_err_no_mem;
166}
167
170priv_exfat_bitmap_scan(const ra8_fs_mount_t* m, uint64_t bmp_lba, uint32_t need, uint32_t* out_clus)
171{
172 const uint32_t hint = priv_alloc_hint_get(m);
173 uint32_t from = 0U;
174 if (hint > (uint32_t)k_cluster_first_data) {
175 from = hint - (uint32_t)k_cluster_first_data;
176 }
177 if (from >= m->count_of_clusters) {
178 from = 0U;
179 }
180 const ra8_err_t e = internal_exfat_bitmap_window(m, bmp_lba, from, need, out_clus);
181 if (e != k_ra8_err_no_mem) {
182 return e;
183 }
184 if (from == 0U) {
185 return e;
186 }
187 return internal_exfat_bitmap_window(m, bmp_lba, 0U, need, out_clus);
188}
189
190/* `priv_exfat_bmp_switch()`: see header for the documented contract. */
192priv_exfat_bmp_switch(const ra8_fs_mount_t* m, uint64_t lba, uint64_t* loaded, uint8_t* sec)
193{
194 if (lba == *loaded) {
195 return k_ra8_ok;
196 }
197 if (*loaded != UINT64_MAX) {
198 ra8_err_t we = priv_write_sector(m, *loaded, sec);
199 if (we != k_ra8_ok) {
200 return we;
201 }
202 }
203 ra8_err_t e = priv_read_sector(m, lba, sec);
204 if (e != k_ra8_ok) {
205 return e;
206 }
207 *loaded = lba;
208 return k_ra8_ok;
209}
210
213priv_exfat_bitmap_mark(const ra8_fs_mount_t* m, uint64_t bmp_lba, uint32_t clus, uint32_t count)
214{
215 uint64_t loaded = UINT64_MAX;
216 uint8_t* const sec = priv_sec_io();
217 for (uint32_t k = 0U; k < count; k++) {
218 const uint32_t idx = (clus - k_cluster_first_data) + k;
219 const uint64_t lba = bmp_lba + ((idx >> k_exfat_bit_shift) / priv_bps(m));
220 const uint32_t byte = (idx >> k_exfat_bit_shift) % priv_bps(m);
221 const uint32_t bit = idx & k_exfat_bit_mask;
222 ra8_err_t e = priv_exfat_bmp_switch(m, lba, &loaded, sec);
223 if (e != k_ra8_ok) {
224 return e;
225 }
226 sec[byte] = (uint8_t)(sec[byte] | (uint8_t)(1U << bit));
227 }
228 return priv_write_sector(m, loaded, sec);
229}
230
233priv_exfat_bitmap_test(const ra8_fs_mount_t* m, uint64_t bmp_lba, uint32_t clus, uint8_t* out_free)
234{
235 const uint32_t idx = clus - (uint32_t)k_cluster_first_data;
236 const uint64_t lba = bmp_lba + ((idx >> k_exfat_bit_shift) / priv_bps(m));
237 const uint32_t byte = (idx >> k_exfat_bit_shift) % priv_bps(m);
238 const uint32_t bit = idx & k_exfat_bit_mask;
239
240 uint8_t* const sec = priv_sec_io();
241 const ra8_err_t e = priv_read_sector(m, lba, sec);
242 if (e != k_ra8_ok) {
243 return e;
244 }
245 *out_free = (uint8_t)(((((uint32_t)sec[byte] >> bit) & 1U) != 0U) ? 0U : 1U);
246 return k_ra8_ok;
247}
248
269static ra8_err_t
270internal_exfat_read_entry(const ra8_fs_mount_t* m, uint32_t cluster, uint32_t idx, uint8_t* out)
271{
272 const uint32_t byte_off = idx * (uint32_t)k_exfat_entry_bytes;
273 const uint64_t lba = priv_cluster_to_lba(m, cluster) + (byte_off / priv_bps(m));
274 uint8_t* const sec = priv_sec_io();
275 ra8_err_t e = priv_read_sector(m, lba, sec);
276 if (e != k_ra8_ok) {
277 return e;
278 }
279 priv_byte_copy(out, &sec[byte_off % priv_bps(m)], (uint32_t)k_exfat_entry_bytes);
280 return k_ra8_ok;
281}
282
300static uint8_t internal_exfat_slot_free(uint8_t type_byte)
301{
302 if (type_byte == (uint8_t)k_exfat_entry_eod) {
303 return 1U;
304 }
305 if ((type_byte & (uint8_t)k_exfat_inuse_bit) == 0U) {
306 return 1U;
307 }
308 return 0U;
309}
310
334 uint32_t cluster,
335 uint32_t need,
336 uint32_t* out_idx)
337{
338 const uint32_t per_cluster = priv_cluster_bytes(m) / (uint32_t)k_exfat_entry_bytes;
339 uint32_t run = 0U;
340 for (uint32_t i = 0U; i < per_cluster; i++) {
341 uint8_t e[k_exfat_entry_bytes] = {};
342 const ra8_err_t r = internal_exfat_read_entry(m, cluster, i, e);
343 if (r != k_ra8_ok) {
344 return r;
345 }
346 if (internal_exfat_slot_free(e[0]) == 0U) {
347 run = 0U;
348 continue;
349 }
350 if (run == 0U) {
351 *out_idx = i;
352 }
353 run++;
354 if (run >= need) {
355 return k_ra8_ok;
356 }
357 }
358 return k_ra8_err_no_mem;
359}
360
391 const exfat_dir_t* dir,
392 uint32_t need,
393 uint32_t* out_clus,
394 uint32_t* out_idx)
395{
396 uint32_t cluster = dir->cluster;
397 for (uint32_t guard = 0U; guard < (uint32_t)k_exfat_scan_limit; guard++) {
398 const ra8_err_t r = internal_exfat_space_in_cluster(m, cluster, need, out_idx);
399 if (r == k_ra8_ok) {
400 *out_clus = cluster;
401 return k_ra8_ok;
402 }
403 if (r != k_ra8_err_no_mem) {
404 return r;
405 }
406 uint32_t next = 0U;
407 const ra8_err_t fe = priv_exfat_step_cluster(m, cluster, dir->contig_end, &next);
408 if (fe == k_ra8_err_not_found) {
409 return k_ra8_err_no_mem; /* the current run is exhausted -- the caller grows */
410 }
411 if (fe != k_ra8_ok) {
412 return fe;
413 }
414 cluster = next;
415 }
416 return k_ra8_err_no_mem; /* GCOVR_EXCL_LINE -- 65536 directory clusters required */
417}
418
419/* `priv_exfat_find_dir_space()`: see header for the documented contract. */
421 const exfat_dir_t* dir,
422 uint32_t need,
423 uint32_t* out_clus,
424 uint32_t* out_idx)
425{
426 /* A mutable copy: ::priv_exfat_grow_dir advances `work.contig_end` (and drops
427 * it to 0 when the run converts to a FAT chain) so the rescan below reaches
428 * the cluster the grow just appended. */
429 exfat_dir_t work = *dir;
430 for (uint32_t grow = 0U; grow <= (uint32_t)k_exfat_dir_grow_max; grow++) {
431 const ra8_err_t e = internal_exfat_scan_dir_space(m, &work, need, out_clus, out_idx);
432 if (e != k_ra8_err_no_mem) {
433 return e; /* found a run, or a hard read error */
434 }
435 if (grow == (uint32_t)k_exfat_dir_grow_max) {
436 break; /* one grow already yields more than any set needs; stop */
437 }
438 const ra8_err_t ge = priv_exfat_grow_dir(m, &work);
439 if (ge != k_ra8_ok) {
440 return ge; /* the volume, not the directory, is out of room */
441 }
442 }
443 return k_ra8_err_no_mem;
444}
445
476static uint32_t internal_exfat_build_set(uint8_t* set, const uint16_t* name, uint32_t nlen)
477{
478 const uint32_t name_entries =
479 (nlen + (uint32_t)k_exfat_name_per_entry - 1U) / (uint32_t)k_exfat_name_per_entry;
480 const uint32_t sec_count = 1U + name_entries;
481 const uint32_t total = (1U + sec_count) * (uint32_t)k_exfat_entry_bytes;
482 for (uint32_t i = 0U; i < total; i++) {
483 set[i] = 0U;
484 }
485 set[0] = (uint8_t)k_exfat_entry_file;
486 set[k_exfat_off_file_secnt] = (uint8_t)sec_count;
488 /* Before the SetChecksum below, which covers these bytes. exFAT's stamps are
489 * zero-is-illegal for the same reason FAT's are -- month and day are 1-based
490 * -- and it has three fields FAT does not: the 10 ms increments and the
491 * UtcOffset bytes (#601). */
493 uint8_t* strm = &set[k_exfat_entry_bytes];
494 strm[0] = (uint8_t)k_exfat_entry_stream;
496 strm[k_exfat_strm_off_nlen] = (uint8_t)nlen;
498 for (uint32_t n = 0U; n < name_entries; n++) {
499 uint8_t* ne = &set[(size_t)(2U + n) * (size_t)k_exfat_entry_bytes];
500 ne[0] = (uint8_t)k_exfat_entry_name;
501 for (uint32_t c = 0U; c < (uint32_t)k_exfat_name_per_entry; c++) {
502 const uint32_t pos = (n * (uint32_t)k_exfat_name_per_entry) + c;
503 if (pos < nlen) {
504 /* A whole UTF-16 unit, not the low byte of one: writing one unit per
505 * INPUT BYTE turned every multi-byte character into that many garbage
506 * characters and made NameLength count bytes where the format counts
507 * units (#606). */
508 priv_wr16(&ne[k_exfat_name_off + (c * 2U)], name[pos]);
509 }
510 }
511 }
513 return total;
514}
515
516/* `priv_exfat_write_dir_set()`: see header for the documented contract. */
518 uint32_t cluster,
519 uint32_t idx,
520 const uint8_t* set,
521 uint32_t bytes)
522{
523 const uint32_t count = bytes / (uint32_t)k_exfat_entry_bytes;
524 for (uint32_t k = 0U; k < count; k++) {
525 const uint32_t byte_off = (idx + k) * (uint32_t)k_exfat_entry_bytes;
526 const uint64_t lba = priv_cluster_to_lba(m, cluster) + (byte_off / priv_bps(m));
527 uint8_t* const sec = priv_sec_io();
528 ra8_err_t e = priv_read_sector(m, lba, sec);
529 if (e != k_ra8_ok) {
530 return e;
531 }
532 priv_byte_copy(&sec[byte_off % priv_bps(m)],
533 &set[(size_t)k * (size_t)k_exfat_entry_bytes],
534 (uint32_t)k_exfat_entry_bytes);
535 e = priv_write_sector(m, lba, sec);
536 if (e != k_ra8_ok) {
537 return e;
538 }
539 }
540 return k_ra8_ok;
541}
542
545 const exfat_dir_t* dir,
546 const uint16_t* name,
547 uint32_t nlen,
548 exfat_setpos_t* out_head,
549 uint32_t* out_count)
550{
551 const uint32_t name_entries =
552 (nlen + (uint32_t)k_exfat_name_per_entry - 1U) / (uint32_t)k_exfat_name_per_entry;
553 const uint32_t need = 2U + name_entries;
554 uint32_t dclus = 0U;
555 uint32_t didx = 0U;
556 ra8_err_t e = priv_exfat_find_dir_space(m, dir, need, &dclus, &didx);
557 if (e != k_ra8_ok) {
558 return e;
559 }
560 uint8_t set[k_exfat_max_set_bytes] = {};
561 const uint32_t bytes = internal_exfat_build_set(set, name, nlen);
562 e = priv_exfat_write_dir_set(m, dclus, didx, set, bytes);
563 if (e != k_ra8_ok) {
564 return e;
565 }
566 out_head->cluster = dclus;
567 out_head->index = didx;
568 *out_count = need;
569 return k_ra8_ok;
570}
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
@ k_ra8_err_no_mem
Static buffer exhausted (no dynamic memory on this project).
Definition ra8_err.h:142
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_not_found
Requested item not found (lookup / search missed).
Definition ra8_err.h:173
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
Minimal FAT12/FAT16/FAT32 filesystem adapter (read + write).
uint32_t priv_rd32(const uint8_t *p)
Decode a little-endian uint32_t from a byte buffer.
Definition ra8_fs_fat.c:48
uint64_t priv_cluster_to_lba(const ra8_fs_mount_t *m, uint32_t cluster)
Convert a cluster number into its first data-region LBA.
Definition ra8_fs_fat.c:541
ra8_err_t priv_read_sector(const ra8_fs_mount_t *m, uint64_t lba, uint8_t *buf)
Read a single sector into the module scratch buffer.
Definition ra8_fs_fat.c:134
uint32_t priv_bps(const ra8_fs_mount_t *m)
One mounted volume's sector size in bytes.
Definition ra8_fs_fat.c:84
void priv_byte_copy(uint8_t *dst, const uint8_t *src, uint32_t n)
Length-checked byte copy used in place of memcpy().
Definition ra8_fs_fat.c:110
uint32_t priv_cluster_bytes(const ra8_fs_mount_t *m)
One mounted volume's cluster size in bytes.
Definition ra8_fs_fat.c:90
ra8_err_t priv_write_sector(const ra8_fs_mount_t *m, uint64_t lba, const uint8_t *buf)
Write a single sector from a caller-provided buffer.
Definition ra8_fs_fat.c:140
void priv_wr16(uint8_t *p, uint16_t v)
Encode a little-endian uint16_t into a byte buffer.
Definition ra8_fs_fat.c:61
uint32_t priv_alloc_hint_get(const ra8_fs_mount_t *m)
Report the cluster a free-space scan should start from.
uint8_t * priv_sec_io(void)
The IO-role sector buffer (leaf data / bitmap sector transfers).
void priv_exfat_dir_root(const ra8_fs_mount_t *m, exfat_dir_t *out)
Fill out with the volume root's directory location.
ra8_err_t priv_exfat_step_cluster(const ra8_fs_mount_t *m, uint32_t cluster, uint32_t contig_end, uint32_t *out_next)
Compute the cluster that follows cluster in a directory.
void priv_exfat_cursor_init(const exfat_dir_t *dir, exfat_cursor_t *out)
Aim a fresh cursor at the start of dir.
ra8_err_t priv_exfat_next_entry(const ra8_fs_mount_t *m, exfat_cursor_t *cur, uint8_t *out)
Fetch the next 32-byte directory entry, following the cluster chain.
ra8_err_t priv_exfat_grow_dir(const ra8_fs_mount_t *m, exfat_dir_t *dir)
Append one zeroed cluster to a directory, keeping its entry set true.
uint16_t priv_exfat_upcase_unit(uint16_t unit)
Fold a UTF-16 code unit through the canonical exFAT up-case table.
static uint32_t internal_exfat_build_set(uint8_t *set, const uint16_t *name, uint32_t nlen)
Build a zero-length File + Stream + Name entry set into set.
ra8_err_t priv_exfat_bitmap_mark(const ra8_fs_mount_t *m, uint64_t bmp_lba, uint32_t clus, uint32_t count)
Implementation of priv_exfat_bitmap_mark() – one read-modify-write per sector.
ra8_err_t priv_exfat_bitmap_test(const ra8_fs_mount_t *m, uint64_t bmp_lba, uint32_t clus, uint8_t *out_free)
Implementation of priv_exfat_bitmap_test() – one sector read, one bit.
static uint16_t internal_exfat_csum_add(uint16_t cs, uint8_t b)
One step of exFAT's rotate-right-then-add 16-bit checksum.
uint16_t priv_exfat_set_checksum(const uint8_t *set, uint32_t bytes)
Compute the SetChecksum over a built directory entry set.
static ra8_err_t internal_exfat_bitmap_window(const ra8_fs_mount_t *m, uint64_t bmp_lba, uint32_t from, uint32_t need, uint32_t *out_clus)
Scan a window of the allocation bitmap for a contiguous free run.
uint16_t priv_exfat_name_hash(const uint16_t *name, uint32_t nlen)
Compute the exFAT NameHash for a name in UTF-16 code units.
ra8_err_t priv_exfat_bitmap_scan(const ra8_fs_mount_t *m, uint64_t bmp_lba, uint32_t need, uint32_t *out_clus)
Implementation of priv_exfat_bitmap_scan() – hinted pass, then a full rescan.
ra8_err_t priv_exfat_write_dir_set(const ra8_fs_mount_t *m, uint32_t cluster, uint32_t idx, const uint8_t *set, uint32_t bytes)
Write a pre-built entry set into consecutive directory entries.
ra8_err_t priv_exfat_bmp_switch(const ra8_fs_mount_t *m, uint64_t lba, uint64_t *loaded, uint8_t *sec)
Flush the cached bitmap sector and load lba if it changed.
static ra8_err_t internal_exfat_read_entry(const ra8_fs_mount_t *m, uint32_t cluster, uint32_t idx, uint8_t *out)
Read one 32-byte directory entry by index within a cluster.
static ra8_err_t internal_exfat_space_in_cluster(const ra8_fs_mount_t *m, uint32_t cluster, uint32_t need, uint32_t *out_idx)
Scan one directory cluster for need consecutive free entries.
ra8_err_t priv_exfat_find_dir_space(const ra8_fs_mount_t *m, const exfat_dir_t *dir, uint32_t need, uint32_t *out_clus, uint32_t *out_idx)
Find need consecutive free entry slots inside one directory cluster.
static uint8_t internal_exfat_slot_free(uint8_t type_byte)
True if a directory entry slot is free (end-of-dir or deleted).
static ra8_err_t internal_exfat_scan_dir_space(const ra8_fs_mount_t *m, const exfat_dir_t *dir, uint32_t need, uint32_t *out_clus, uint32_t *out_idx)
Scan a directory's existing clusters for need consecutive free slots.
ra8_err_t priv_exfat_find_bitmap(const ra8_fs_mount_t *m, uint32_t *out_clus, uint32_t *out_len)
Locate the allocation-bitmap entry in the exFAT root directory.
ra8_err_t priv_exfat_link(const ra8_fs_mount_t *m, const exfat_dir_t *dir, const uint16_t *name, uint32_t nlen, exfat_setpos_t *out_head, uint32_t *out_count)
Implementation of priv_exfat_link() – one directory-slot scan, one set write.
Cross-TU shared declarations for the FAT/exFAT ra8_fs adapter.
void priv_exfat_file_stamp_create(uint8_t *file_entry)
Stamp a fresh exFAT File entry's create, modify, and access fields.
@ k_exfat_name_off
File-name entry character offset.
@ k_exfat_strm_off_dlen
Stream-ext DataLength (low 32 used).
@ k_exfat_strm_off_nlen
Stream-ext NameLength (UTF-16 units).
@ k_exfat_strm_off_clus
Stream-ext FirstCluster.
@ k_exfat_strm_off_flags
Stream-ext GeneralSecondaryFlags.
@ k_exfat_entry_bitmap
Allocation-bitmap directory entry.
@ k_exfat_off_file_csum
File entry: SetChecksum (2 bytes).
@ k_exfat_name_per_entry
UTF-16 units per file-name entry.
@ k_exfat_entry_bytes
Directory entry size.
@ k_exfat_csum_hi_bit
Wrap bit for the rotate-add checksum.
@ k_exfat_off_strm_hash
Stream entry: NameHash (2 bytes).
@ k_exfat_secflag_poss
GeneralSecondaryFlags: AllocationPossible.
@ k_exfat_entry_eod
End-of-directory marker.
@ k_exfat_bit_shift
log2(8): cluster index -> byte.
@ k_exfat_entry_stream
Stream-extension entry.
@ k_exfat_off_file_attr
File entry: FileAttributes (2 bytes).
@ k_exfat_off_file_secnt
File entry: SecondaryCount.
@ k_exfat_max_set_bytes
(2 + ceil(64/15)) * 32 = 7 entries.
@ k_exfat_attr_archive
FileAttributes: archive.
@ k_exfat_scan_limit
Max dir entries scanned (P10 bound).
@ k_exfat_dir_grow_max
Max clusters one find-space call appends.
@ k_exfat_bit_mask
Bit index within a bitmap byte.
@ k_exfat_entry_name
File-name entry.
@ k_exfat_inuse_bit
Directory entry type bit 7 = in use.
@ k_exfat_entry_file
File directory entry.
@ k_cluster_first_data
Cluster numbers start at 2.
@ k_utf_byte_mask
Mask isolating a unit's low byte.
@ k_utf_byte_shift
Shift bringing a unit's high byte down.
Linear cursor over a directory's 32-byte entries.
uint32_t scanned
Total entries read (P10 bound).
Identifies the exFAT directory a lookup, scan or link operates in.
uint32_t cluster
First cluster of the directory.
uint32_t contig_end
One past the run's last cluster; 0 => FAT-chained.
Directory position (cluster + entry index) of one 32-byte entry.
uint32_t index
Entry index within that cluster (0-based).
uint32_t cluster
Directory cluster holding the entry.
Cached parse of one mounted FAT volume.
uint32_t count_of_clusters
Per MS spec: data_sectors / SPC.