ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_fs_fat_lfn_write.c
Go to the documentation of this file.
1
39
40#include <stddef.h>
41#include <stdint.h>
42
43#include "ra8_attributes.h"
44#include "ra8_fs.h"
45#include "ra8_fs_fat_internal.h"
46
63typedef enum : uint32_t {
66
67/* =============================================================================
68 * Alias generation
69 * =============================================================================
70 */
71
104 const dir_loc_t* loc,
105 const uint16_t* leaf,
106 uint32_t n,
107 uint8_t* out11)
108{
109 uint64_t lba = 0U;
110 uint32_t off = 0U;
111 uint8_t entry[k_ra8_fs_dir_entry_bytes] = {};
112 for (uint32_t tail = 1U; tail <= (uint32_t)k_lfn_alias_tail_max; tail++) {
113 priv_lfn_alias_basis(leaf, n, tail, out11);
114 const ra8_err_t err = priv_dir_find(m, loc, out11, &lba, &off, entry);
115 if (err == k_ra8_err_not_found) {
116 return k_ra8_ok;
117 }
118 if (err != k_ra8_ok) {
119 return err;
120 }
121 }
122 /* Unreachable in practice: exhausting this needs 999999 entries sharing one
123 * basis name, and the largest directory this driver can mount holds far
124 * fewer. It is the Rule 2 bound's honest failure answer, not dead code. */
125 return k_ra8_err_no_mem; /* GCOVR_EXCL_LINE -- million-alias exhaustion, bounded-loop fallback */
126}
127
128/* =============================================================================
129 * Free-slot runs and directory growth
130 * =============================================================================
131 */
132
158static uint8_t internal_slot_is_free(const uint8_t* ent)
159{
160 if ((ent[k_dir_off_name] == (uint8_t)k_dir_marker_free_perm) ||
161 (ent[k_dir_off_name] == (uint8_t)k_dir_marker_free_used)) {
162 return 1U;
163 }
164 return 0U;
165}
166
167/* `priv_dir_find_free_run()`: see header for the documented contract. */
169 const dir_loc_t* loc,
170 uint32_t need,
171 dir_slot_t* out)
172{
173 dir_walk_t w = {};
174 priv_dir_walk_init_loc(m, loc, &w);
175 dir_slot_t start = {};
176 uint32_t run = 0U;
177 uint8_t eod = 0U;
178 uint8_t* const buf = priv_sec_walk();
179 while (eod == 0U) {
180 ra8_err_t err = priv_read_sector(m, w.cur_lba, buf);
181 if (err != k_ra8_ok) {
182 return err;
183 }
184 for (uint32_t e = 0U; e < priv_dir_eps(m); e++) {
185 if (internal_slot_is_free(&buf[(size_t)e * (size_t)k_ra8_fs_dir_entry_bytes]) == 0U) {
186 run = 0U;
187 continue;
188 }
189 if (run == 0U) {
190 start.w = w;
191 start.ent = e;
192 }
193 run++;
194 if (run >= need) {
195 *out = start;
196 return k_ra8_ok;
197 }
198 }
199 err = priv_dir_walk_next_sector(m, &w, &eod);
200 if (err != k_ra8_ok) {
201 return err;
202 }
203 }
204 return k_ra8_err_no_mem;
205}
206
236{
237 if ((loc->is_root != 0U) && (m->type != k_ra8_fs_type_fat32)) {
238 return k_ra8_err_no_mem; /* the FAT12/16 root is a fixed sector region */
239 }
240 /* Walk to the chain's last cluster with the shared directory iterator rather
241 * than a second FAT walk of this file's own: the iterator already carries
242 * the cycle guard, and two walks that could disagree about where a directory
243 * ends is exactly the kind of duplication that goes wrong quietly. */
244 dir_walk_t w = {};
245 priv_dir_walk_init_loc(m, loc, &w);
246 uint32_t last = w.cluster;
247 uint8_t eod = 0U;
248 while (eod == 0U) {
249 last = w.cluster;
250 const ra8_err_t werr = priv_dir_walk_next_sector(m, &w, &eod);
251 if (werr != k_ra8_ok) {
252 return werr;
253 }
254 }
255 uint32_t fresh = 0U;
256 ra8_err_t err = priv_alloc_eoc_cluster(m, &fresh);
257 if (err != k_ra8_ok) {
258 return err;
259 }
260 const uint64_t base = priv_cluster_to_lba(m, fresh);
261 for (uint32_t s = 0U; s < m->sectors_per_cluster; s++) {
262 err = priv_write_sector(m, base + s, k_zero_sector);
263 if (err != k_ra8_ok) {
264 (void)priv_free_chain(m, fresh);
265 return err;
266 }
267 }
268 err = priv_fat_set(m, last, fresh);
269 if (err != k_ra8_ok) {
270 (void)priv_free_chain(m, fresh);
271 return err;
272 }
273 return k_ra8_ok;
274}
275
276/* =============================================================================
277 * Reserve / commit
278 * =============================================================================
279 */
280
281/* `priv_dir_reserve()`: see header for the documented contract. */
283priv_dir_reserve(const ra8_fs_mount_t* m, const dir_loc_t* loc, const char* leaf, dir_insert_t* out)
284{
285 out->lfn_entries = 0U;
286 out->nunits = 0U;
287 const ra8_fs_name_kind_t kind =
288 priv_name_classify(leaf, out->units, &out->nunits, out->name83, &out->ntres);
289 if (kind == k_name_kind_invalid) {
291 }
292 uint32_t need = 1U;
293 if (kind == k_name_kind_long) {
294 const ra8_err_t aerr = internal_alias_unique(m, loc, out->units, out->nunits, out->name83);
295 if (aerr != k_ra8_ok) {
296 return aerr;
297 }
298 out->ntres = 0U; /* the chain carries the case; the alias is upper-case */
299 /* Groups of THIRTEEN CODE UNITS -- the slot's capacity. Deriving this from
300 * a byte count put a 2-byte character's worth of slots on a 1-unit
301 * character, and the chain then disagreed with its own contents (#606). */
302 out->lfn_entries = (uint8_t)(((out->nunits + (uint32_t)k_lfn_chars_per_ent) - 1U) /
303 (uint32_t)k_lfn_chars_per_ent);
304 need = (uint32_t)out->lfn_entries + 1U;
305 }
306 for (uint32_t attempt = 0U; attempt <= (uint32_t)k_lfnw_grow_max; attempt++) {
307 const ra8_err_t ferr = priv_dir_find_free_run(m, loc, need, &out->start);
308 if (ferr != k_ra8_err_no_mem) {
309 return ferr;
310 }
311 if (attempt == (uint32_t)k_lfnw_grow_max) {
312 break; /* growing again would only link a cluster nothing then uses */
313 }
314 const ra8_err_t gerr = internal_dir_grow(m, loc);
315 if (gerr != k_ra8_ok) {
316 return gerr;
317 }
318 }
319 return k_ra8_err_no_mem;
320}
321
349static ra8_err_t
350internal_slot_advance(const ra8_fs_mount_t* m, dir_slot_t* cur, uint8_t* buf, uint64_t* lba_io)
351{
352 cur->ent++;
353 if (cur->ent < priv_dir_eps(m)) {
354 return k_ra8_ok;
355 }
356 ra8_err_t err = priv_write_sector(m, *lba_io, buf);
357 if (err != k_ra8_ok) {
358 return err;
359 }
360 uint8_t eod = 0U;
361 err = priv_dir_walk_next_sector(m, &cur->w, &eod);
362 if (err != k_ra8_ok) {
363 return err;
364 }
365 if (eod != 0U) {
366 return k_ra8_err_no_mem; /* the reserved run ran off the directory's end */
367 }
368 cur->ent = 0U;
369 *lba_io = cur->w.cur_lba;
370 return priv_read_sector(m, *lba_io, buf);
371}
372
373/* `priv_dir_commit()`: see header for the documented contract. */
375 const dir_insert_t* plan,
376 const uint8_t* tmpl,
377 uint64_t* out_lba,
378 uint32_t* out_off)
379{
380 const uint32_t nlen = (plan->lfn_entries != 0U) ? plan->nunits : 0U;
381 const uint8_t csum = priv_sfn_checksum(plan->name83);
382 const uint32_t total = (uint32_t)plan->lfn_entries + 1U;
383 dir_slot_t cur = plan->start;
384 uint64_t lba = cur.w.cur_lba;
385 uint8_t* const buf = priv_sec_walk();
386 ra8_err_t err = priv_read_sector(m, lba, buf);
387 if (err != k_ra8_ok) {
388 return err;
389 }
390 for (uint32_t i = 0U; i < total; i++) {
391 uint8_t* slot = &buf[(size_t)cur.ent * (size_t)k_ra8_fs_dir_entry_bytes];
392 if (i < (uint32_t)plan->lfn_entries) {
394 plan->units,
395 nlen,
396 (uint32_t)plan->lfn_entries - i,
397 (uint8_t)((i == 0U) ? 1U : 0U),
398 csum);
399 } else {
400 priv_byte_copy(slot, tmpl, (uint32_t)k_ra8_fs_dir_entry_bytes);
402 slot[k_dir_off_ntres] = plan->ntres;
403 *out_lba = lba;
404 *out_off = cur.ent * (uint32_t)k_ra8_fs_dir_entry_bytes;
405 }
406 if ((i + 1U) < total) {
407 err = internal_slot_advance(m, &cur, buf, &lba);
408 if (err != k_ra8_ok) {
409 return err;
410 }
411 }
412 }
413 return priv_write_sector(m, lba, buf);
414}
415
416/* =============================================================================
417 * Deletion -- the 8.3 entry AND the chain in front of it
418 * =============================================================================
419 */
420
446static void internal_run_push(dir_pos_t* run, uint32_t* len_io, uint64_t lba, uint32_t off)
447{
448 uint32_t len = *len_io;
449 if (len == (uint32_t)k_lfn_erase_max) {
450 for (uint32_t i = 1U; i < (uint32_t)k_lfn_erase_max; i++) {
451 run[i - 1U] = run[i];
452 }
453 len--;
454 }
455 run[len].lba = lba;
456 run[len].off = off;
457 *len_io = len + 1U;
458}
459
493 const dir_loc_t* loc,
494 uint64_t tlba,
495 uint32_t toff,
496 uint8_t csum,
497 dir_pos_t* run,
498 uint32_t* out_len)
499{
500 dir_walk_t w = {};
501 priv_dir_walk_init_loc(m, loc, &w);
502 uint32_t len = 0U;
503 uint8_t eod = 0U;
504 uint8_t* const buf = priv_sec_walk();
505 while (eod == 0U) {
506 ra8_err_t err = priv_read_sector(m, w.cur_lba, buf);
507 if (err != k_ra8_ok) {
508 return err;
509 }
510 for (uint32_t e = 0U; e < priv_dir_eps(m); e++) {
511 const uint32_t off = e * (uint32_t)k_ra8_fs_dir_entry_bytes;
512 if ((w.cur_lba == tlba) && (off == toff)) {
513 *out_len = len;
514 return k_ra8_ok;
515 }
516 const uint8_t* ent = &buf[off];
517 if ((ent[k_dir_off_attr] == (uint8_t)k_ra8_fs_attr_lfn) &&
518 (ent[k_dir_off_name] != (uint8_t)k_dir_marker_free_used) &&
519 (ent[k_lfn_off_checksum] == csum)) {
520 internal_run_push(run, &len, w.cur_lba, off);
521 } else {
522 len = 0U;
523 }
524 }
525 err = priv_dir_walk_next_sector(m, &w, &eod);
526 if (err != k_ra8_ok) {
527 return err;
528 }
529 }
530 return k_ra8_err_not_found;
531}
532
559static ra8_err_t
560internal_dir_erase_positions(const ra8_fs_mount_t* m, const dir_pos_t* pos, uint32_t count)
561{
562 uint8_t* const buf = priv_sec_walk();
563 uint64_t cur = pos[0].lba;
564 ra8_err_t err = priv_read_sector(m, cur, buf);
565 if (err != k_ra8_ok) {
566 return err;
567 }
568 for (uint32_t i = 0U; i < count; i++) {
569 if (pos[i].lba != cur) {
570 err = priv_write_sector(m, cur, buf);
571 if (err != k_ra8_ok) {
572 return err;
573 }
574 cur = pos[i].lba;
575 err = priv_read_sector(m, cur, buf);
576 if (err != k_ra8_ok) {
577 return err;
578 }
579 }
580 buf[pos[i].off + (uint32_t)k_dir_off_name] = (uint8_t)k_dir_marker_free_used;
581 }
582 return priv_write_sector(m, cur, buf);
583}
584
585/* `priv_dir_erase_chain()`: see header for the documented contract. */
587 const dir_loc_t* loc,
588 uint64_t lba,
589 uint32_t off,
590 const uint8_t* name83)
591{
592 dir_pos_t all[(uint32_t)k_lfn_erase_max + 1U] = {};
593 uint32_t len = 0U;
594 const ra8_err_t cerr =
595 internal_dir_collect_chain(m, loc, lba, off, priv_sfn_checksum(name83), all, &len);
596 if (cerr != k_ra8_ok) {
597 return cerr;
598 }
599 all[len].lba = lba;
600 all[len].off = off;
601 return internal_dir_erase_positions(m, all, len + 1U);
602}
603
604/* `priv_dir_lookup_any()`: see header for the documented contract. */
606 const dir_loc_t* loc,
607 const char* leaf,
608 uint64_t* out_lba,
609 uint32_t* out_off,
610 uint8_t out_entry[k_ra8_fs_dir_entry_bytes])
611{
612 uint8_t name83[k_max_8_3_name] = {};
614 if (priv_path_to_83(leaf, name83) != 0U) {
615 err = priv_dir_find(m, loc, name83, out_lba, out_off, out_entry);
616 }
617 if (err == k_ra8_err_not_found) {
618 /* A name that is not 8.3-representable, or one whose 8.3 lookup missed
619 * because the file is filed under a generated `~N` alias, can still match
620 * the reassembled long name. */
621 err = priv_dir_find_long(m, loc, leaf, out_lba, out_off, out_entry);
622 }
623 return err;
624}
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_err_invalid_arg
Invalid function argument.
Definition ra8_err.h:152
@ 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).
ra8_err_t priv_fat_set(const ra8_fs_mount_t *m, uint32_t cluster, uint32_t value)
Write value into the FAT entry for cluster across every FAT copy.
Definition ra8_fs_fat.c:486
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
uint32_t priv_dir_eps(const ra8_fs_mount_t *m)
Directory entries per sector on one mounted volume.
Definition ra8_fs_fat.c:96
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
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
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
uint8_t * priv_sec_walk(void)
The WALK-role sector buffer (directory scans and entry RMW).
const uint8_t k_zero_sector[k_ra8_fs_sector_max]
One whole sector of zero bytes, in read-only storage.
ra8_err_t priv_alloc_eoc_cluster(const ra8_fs_mount_t *m, uint32_t *out_c)
Allocate a fresh cluster, mark it EOC, and return its number.
Cross-TU shared declarations for the FAT/exFAT ra8_fs adapter.
uint8_t priv_sfn_checksum(const uint8_t *name83)
Compute the 8.3 short-name checksum carried in each LFN directory entry.
void priv_lfn_fill_slot(uint8_t *ent, const uint16_t *name, uint32_t nlen, uint32_t order, uint8_t is_last, uint8_t csum)
Fill one 32-byte slot with the order -th group of a long name.
ra8_err_t priv_free_chain(const ra8_fs_mount_t *m, uint32_t start)
Free an entire cluster chain starting at start.
ra8_err_t priv_dir_find_long(const ra8_fs_mount_t *m, const dir_loc_t *loc, const char *want, uint64_t *out_lba, uint32_t *out_entry_off, uint8_t out_entry[k_ra8_fs_dir_entry_bytes])
Find a directory entry by its VFAT long name (case-insensitive).
ra8_err_t priv_dir_reserve(const ra8_fs_mount_t *m, const dir_loc_t *loc, const char *leaf, dir_insert_t *out)
Decide how a name will be stored and set aside the slots for it.
static ra8_err_t internal_dir_erase_positions(const ra8_fs_mount_t *m, const dir_pos_t *pos, uint32_t count)
Mark every listed slot deleted, one sector read-modify-write at a time.
ra8_err_t priv_dir_erase_chain(const ra8_fs_mount_t *m, const dir_loc_t *loc, uint64_t lba, uint32_t off, const uint8_t *name83)
Delete a directory entry together with its long-name chain.
static ra8_err_t internal_alias_unique(const ra8_fs_mount_t *m, const dir_loc_t *loc, const uint16_t *leaf, uint32_t n, uint8_t *out11)
Pick the lowest ~N alias the directory does not already hold.
ra8_err_t priv_dir_find_free_run(const ra8_fs_mount_t *m, const dir_loc_t *loc, uint32_t need, dir_slot_t *out)
Locate need consecutive free entry slots in a given directory.
ra8_err_t priv_dir_lookup_any(const ra8_fs_mount_t *m, const dir_loc_t *loc, const char *leaf, uint64_t *out_lba, uint32_t *out_off, uint8_t out_entry[k_ra8_fs_dir_entry_bytes])
Resolve one leaf name by 8.3 first and by long name second.
ra8_fs_lfnw_local_t
Bounds used only by this translation unit's directory manipulation.
@ k_lfnw_grow_max
Cluster-growth retries before reporting no_mem.
static uint8_t internal_slot_is_free(const uint8_t *ent)
Is this 32-byte slot available for a new entry?
static ra8_err_t internal_slot_advance(const ra8_fs_mount_t *m, dir_slot_t *cur, uint8_t *buf, uint64_t *lba_io)
Step a slot cursor forward one entry, flushing across sector edges.
static ra8_err_t internal_dir_grow(const ra8_fs_mount_t *m, const dir_loc_t *loc)
Append one zeroed cluster to a directory's chain.
ra8_err_t priv_dir_commit(const ra8_fs_mount_t *m, const dir_insert_t *plan, const uint8_t *tmpl, uint64_t *out_lba, uint32_t *out_off)
Write a reserved run: the long-name chain, then the 8.3 entry.
static ra8_err_t internal_dir_collect_chain(const ra8_fs_mount_t *m, const dir_loc_t *loc, uint64_t tlba, uint32_t toff, uint8_t csum, dir_pos_t *run, uint32_t *out_len)
Collect the long-name slots that belong to one 8.3 entry.
static void internal_run_push(dir_pos_t *run, uint32_t *len_io, uint64_t lba, uint32_t off)
Record one long-name slot address, keeping the newest on overflow.
void priv_lfn_alias_basis(const uint16_t *leaf, uint32_t n, uint32_t tail, uint8_t *out11)
Derive the LONGNA~N.TXT 8.3 alias a long name is filed under.
ra8_fs_name_kind_t priv_name_classify(const char *leaf, uint16_t *out_units, uint32_t *out_nunits, uint8_t *out83, uint8_t *out_ntres)
Decide whether a leaf needs one entry, one entry plus case flags, or a chain.
void priv_dir_walk_init_loc(const ra8_fs_mount_t *m, const dir_loc_t *loc, dir_walk_t *w)
Initialise a directory walker for an arbitrary directory location.
uint8_t priv_path_to_83(const char *path, uint8_t *out11)
Convert a "/FILE.TXT"-style path to packed 11-byte 8.3 form.
ra8_err_t priv_dir_find(const ra8_fs_mount_t *m, const dir_loc_t *loc, const uint8_t *name83, uint64_t *out_lba, uint32_t *out_entry_off, uint8_t out_entry[k_ra8_fs_dir_entry_bytes])
Find a directory entry by 8.3 name within a given directory.
ra8_err_t priv_dir_walk_next_sector(const ra8_fs_mount_t *m, dir_walk_t *w, uint8_t *out_eod)
Advance the walker to the next sector.
ra8_fs_name_kind_t
How a leaf component has to be stored in a FAT directory.
@ k_name_kind_invalid
Empty, over-long, or holds an illegal character.
@ k_name_kind_long
Needs a VFAT chain and a generated ~N alias.
@ k_dir_off_attr
MS FAT spec sec 6 "DIR_Attr".
@ k_dir_off_ntres
MS FAT spec sec 6 "DIR_NTRes".
@ k_dir_name_field_len
8 + 3 raw chars (no dot).
@ k_dir_off_name
MS FAT spec sec 6 "DIR_Name" (11 bytes).
@ k_dir_marker_free_used
Slot was used, deleted.
@ k_dir_marker_free_perm
End-of-directory.
@ k_max_8_3_name
8.3 packed length without dot.
@ k_lfn_alias_tail_max
Highest ~N probed by the alias generator.
@ k_lfn_off_checksum
Checksum of the matching 8.3 name.
@ k_lfn_erase_max
Longest chain we ERASE (spec LDIR_Ord max).
@ k_lfn_chars_per_ent
UTF-16 chars carried per LFN entry.
@ k_ra8_fs_type_fat32
count_of_clusters >= 65525.
@ k_ra8_fs_attr_lfn
Long-file-name marker (we skip).
@ k_ra8_fs_dir_entry_bytes
MS FAT spec sec 6 "Directory Entry".
Everything decided about a new directory entry before anything is written.
uint32_t nunits
How many of units the name occupies.
uint8_t name83[k_max_8_3_name]
Packed 8.3 name, or the generated alias.
uint8_t lfn_entries
Chain slots ahead of the 8.3 entry.
uint8_t ntres
DIR_NTRes case flags for the entry.
dir_slot_t start
First slot of the reserved run.
uint16_t units[k_lfn_write_max]
The name, as UTF-16LE code units.
Identifies the directory a lookup/scan should operate in.
uint8_t is_root
1 => the volume root; 0 => the subdirectory at cluster.
The on-disk address of one 32-byte directory slot.
uint32_t off
Byte offset of the slot in that sector.
uint64_t lba
Sector holding the slot.
A directory cursor that addresses one entry, not just one sector.
dir_walk_t w
Sector-level cursor for the containing directory.
uint32_t ent
Entry index within w.cur_lba.
Internal cursor used by the directory iterator.
uint32_t cluster
Current cluster (FAT32 root case).
uint64_t cur_lba
Currently loaded LBA.
Cached parse of one mounted FAT volume.
ra8_fs_type_t type
FAT12 / FAT16 / FAT32.
uint32_t sectors_per_cluster
BPB BPB_SecPerClus.