ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_fs_fat_file.c
Go to the documentation of this file.
1
13
14#include <stddef.h>
15#include <stdint.h>
16
17#include "ra8_attributes.h"
18#include "ra8_fs.h"
19#include "ra8_fs_fat_internal.h"
20
21/* =============================================================================
22 * Public API: open / close
23 * =============================================================================
24 */
25
26/* `priv_entry_first_cluster()`: see header for the documented contract. */
27uint32_t priv_entry_first_cluster(const uint8_t* entry)
28{
29 const uint32_t hi = priv_rd16(&entry[k_dir_off_fst_clus_hi]);
30 const uint32_t lo = priv_rd16(&entry[k_dir_off_fst_clus_lo]);
31 return (hi << k_shift_two_bytes) | lo;
32}
33
34/* `priv_entry_set_cluster_size()`: see header for the documented contract. */
35void priv_entry_set_cluster_size(uint8_t* entry, uint32_t cluster, uint32_t size)
36{
38 (uint16_t)((cluster >> k_shift_two_bytes) & k_word_mask));
39 priv_wr16(&entry[k_dir_off_fst_clus_lo], (uint16_t)(cluster & k_word_mask));
40 priv_wr32(&entry[k_dir_off_file_size], size);
41}
42
43/* `priv_fat_entry_apply_attr()`: see header for the documented contract. */
44void priv_fat_entry_apply_attr(uint8_t* entry, uint8_t set_mask, uint8_t clear_mask)
45{
46 const uint8_t attr = entry[k_dir_off_attr];
47 entry[k_dir_off_attr] = (uint8_t)((uint8_t)(attr & (uint8_t)~clear_mask) | (uint8_t)set_mask);
48}
49
77 uint64_t lba,
78 uint32_t off)
79{
81 ra8_err_t err = priv_free_chain(handle, f->first_cluster);
82 if (err != k_ra8_ok) {
83 return err;
84 }
85 }
86 f->first_cluster = 0;
87 f->cur_cluster = 0;
88 f->walk_cache_idx = 0;
89 f->walk_cache_cluster = 0; /* < 2: no read cache for a fresh file */
90 f->size_bytes = 0;
91 f->offset = 0;
92 uint8_t* const buf = priv_sec_walk();
93 ra8_err_t err = priv_read_sector(handle, lba, buf);
94 if (err != k_ra8_ok) {
95 return err;
96 }
97 priv_entry_set_cluster_size(&buf[off], 0, 0);
98 /* Truncation IS a content change -- the file went from N bytes to zero -- so
99 * the modification time has to move even if the caller never writes a byte
100 * afterwards. Without this, `open(write)` + `close()` left a PC-authored
101 * mtime describing contents that no longer exist (#601). */
103 /* A content change also sets the archive attribute, the FAT/exFAT convention a
104 * backup tool clears and the OS re-sets on every modification (#681). */
105 priv_fat_entry_apply_attr(&buf[off], (uint8_t)k_ra8_fs_attr_archive, 0U);
106 return priv_write_sector(handle, lba, buf);
107}
108
141 const uint8_t* entry,
142 uint64_t lba,
143 uint32_t off,
144 ra8_fs_mode_t mode,
145 ra8_fs_file_t** out_file)
146{
147 /* A directory is not a file (#604). Without this the write path ran
148 * priv_truncate_existing() on it -- freeing the chain that held every child
149 * and stamping cluster 0 / size 0 into an entry still flagged ATTR_DIRECTORY
150 * -- and the read path handed back a zero-byte handle, because a directory's
151 * DIR_FileSize is 0 by specification. Rejected for every mode, before the
152 * file slot is allocated, so a refused open consumes nothing. */
153 if ((entry[k_dir_off_attr] & (uint8_t)k_ra8_fs_attr_directory) != 0U) {
155 }
156 /* Honor the read-only attribute (#681). A writing open truncates or appends,
157 * so a file a host marked read-only must be refused BEFORE the slot is taken
158 * and BEFORE priv_truncate_existing() frees its chain -- the DOS/Windows
159 * semantics say `open(read-only, write)` is denied, not silently obeyed. A
160 * read open is untouched: reading a read-only file is exactly what the bit
161 * permits. */
162 if ((mode != k_ra8_fs_mode_read) &&
163 ((entry[k_dir_off_attr] & (uint8_t)k_ra8_fs_attr_read_only) != 0U)) {
165 }
167 if (f == nullptr) {
168 return k_ra8_err_no_mem;
169 }
170 f->mount = handle;
173 f->walk_cache_idx = 0U; /* read accelerator seeded at the chain head */
176 f->dir_entry_lba = lba;
177 f->dir_entry_idx = off;
178 f->mode = mode;
179 f->no_fat_chain = 0U;
180 f->dirty = 0U;
181 f->in_use = 1;
182 if (mode == k_ra8_fs_mode_write) {
183 ra8_err_t err = internal_truncate_existing(handle, f, lba, off);
184 if (err != k_ra8_ok) {
185 f->in_use = 0;
186 return err;
187 }
188 } else if (mode == k_ra8_fs_mode_append) {
189 f->offset = f->size_bytes;
190 } else {
191 f->offset = 0;
192 }
193 *out_file = f;
194 return k_ra8_ok;
195}
196
223 ra8_fs_mount_t* handle,
224 ra8_fs_mode_t mode,
225 uint64_t free_lba,
226 uint32_t free_off)
227{
228 f->mount = handle;
229 f->first_cluster = 0;
230 f->cur_cluster = 0;
231 f->walk_cache_idx = 0;
232 f->walk_cache_cluster = 0; /* < 2: no read cache for a fresh file */
233 f->size_bytes = 0;
234 f->offset = 0;
235 f->dir_entry_lba = free_lba;
236 f->dir_entry_idx = free_off;
237 f->mode = mode;
238 f->no_fat_chain = 0U;
239 f->dirty = 0U;
240 f->in_use = 1;
241}
242
244typedef enum : uint32_t {
247
279 const dir_loc_t* cur,
280 const char* comp,
281 uint32_t len,
282 dir_loc_t* out)
283{
284 /* A BYTE bound, because the component is UTF-8 here: the unit-count limit is
285 * ::k_lfn_write_max and lands in priv_name_classify(). Bounding the bytes by
286 * the unit cap would refuse a perfectly storable name three characters into
287 * a Cyrillic directory (#606). */
288 if (len >= (uint32_t)k_lfn_utf8_cap) {
290 }
291 /* Sized for a long name, not an 8.3 one: once `mkdir` can create
292 * `/Reading List`, every path THROUGH it has to resolve as well, and a
293 * 13-byte buffer would have refused the component before the lookup. */
294 char namebuf[k_lfn_utf8_cap] = {};
295 for (uint32_t i = 0; i < len; i++) {
296 namebuf[i] = comp[i];
297 }
298 namebuf[len] = '\0';
299 uint64_t lba = 0;
300 uint32_t off = 0;
301 uint8_t entry[k_ra8_fs_dir_entry_bytes] = {};
302 ra8_err_t err = priv_dir_lookup_any(m, cur, namebuf, &lba, &off, entry);
303 if (err != k_ra8_ok) {
304 return err;
305 }
306 if ((entry[k_dir_off_attr] & k_ra8_fs_attr_directory) == 0U) {
308 }
309 const uint32_t cl = priv_entry_first_cluster(entry);
310 if (cl < (uint32_t)k_cluster_first_data) {
312 }
313 out->is_root = 0U;
314 out->cluster = cl;
315 return k_ra8_ok;
316}
317
318/* `priv_resolve_parent()`: see header for the documented contract. */
320 const char* path,
321 dir_loc_t* out_parent,
322 const char** out_leaf)
323{
324 const char* p = path;
325 while (*p == '/') {
326 p++;
327 }
328 dir_loc_t cur = {.is_root = 1U, .cluster = 0U};
329 for (uint32_t depth = 0; depth < (uint32_t)k_path_max_depth; depth++) {
330 const char* end = p;
331 while (*end != '\0') {
332 if (*end == '/') {
333 break;
334 }
335 end++;
336 }
337 if (*end == '\0') {
338 *out_parent = cur;
339 *out_leaf = p;
340 return k_ra8_ok;
341 }
342 dir_loc_t next = {};
343 const ra8_err_t err = internal_enter_subdir(m, &cur, p, (uint32_t)(end - p), &next);
344 if (err != k_ra8_ok) {
345 return err;
346 }
347 cur = next;
348 p = end;
349 while (*p == '/') {
350 p++;
351 }
352 }
353 return k_ra8_err_invalid_arg; /* deeper than k_path_max_depth */
354}
355
356/* `priv_resolve_dir()`: see header for the documented contract. */
357ra8_err_t priv_resolve_dir(const ra8_fs_mount_t* m, const char* path, dir_loc_t* out)
358{
359 const char* p = path;
360 while (*p == '/') {
361 p++;
362 }
363 if (*p == '\0') {
364 out->is_root = 1U;
365 out->cluster = 0U;
366 return k_ra8_ok;
367 }
368 dir_loc_t parent = {};
369 const char* leaf = nullptr;
370 const ra8_err_t err = priv_resolve_parent(m, path, &parent, &leaf);
371 if (err != k_ra8_ok) {
372 return err;
373 }
374 uint32_t len = 0;
375 while (leaf[len] != '\0') {
376 len++;
377 }
378 if (len == 0U) {
379 *out = parent; /* trailing slash: the parent is the directory */
380 return k_ra8_ok;
381 }
382 return internal_enter_subdir(m, &parent, leaf, len, out);
383}
384
420 const dir_loc_t* parent,
421 const char* leaf,
422 ra8_fs_mode_t mode,
423 ra8_fs_file_t** out_file)
424{
425 dir_insert_t plan = {};
426 ra8_err_t err = priv_dir_reserve(handle, parent, leaf, &plan);
427 if (err != k_ra8_ok) {
428 return err;
429 }
431 if (f == nullptr) {
432 return k_ra8_err_no_mem;
433 }
434 uint8_t tmpl[k_ra8_fs_dir_entry_bytes] = {};
435 tmpl[k_dir_off_attr] = (uint8_t)k_ra8_fs_attr_archive;
436 priv_entry_set_cluster_size(tmpl, 0U, 0U);
437 /* The template is zero-filled above, and a zero FAT date is not a date: month
438 * and day are both 1-based, so 0x0000 claims month 0 of day 0 and every host
439 * decodes it differently (#601). Stamped here rather than inside
440 * `priv_dir_commit()` because that same commit re-files an EXISTING entry for
441 * `rename`, which must keep the creation date it already has. */
443 uint64_t lba = 0;
444 uint32_t off = 0;
445 err = priv_dir_commit(handle, &plan, tmpl, &lba, &off);
446 if (err != k_ra8_ok) {
447 return err;
448 }
449 internal_init_new_file(f, handle, mode, lba, off);
450 *out_file = f;
451 return k_ra8_ok;
452}
453
454/* `priv_open_locked()`: see header for the documented contract. */
456 const char* path,
457 ra8_fs_mode_t mode,
458 ra8_fs_file_t** out_file)
459{
460 if (handle == nullptr || path == nullptr || out_file == nullptr) {
461 return k_ra8_err_null_ptr;
462 }
463 if (handle->in_use == 0U) {
465 }
466 if (handle->type == k_ra8_fs_type_exfat) {
467 return priv_exfat_open(handle, path, mode, out_file);
468 }
469 dir_loc_t parent = {};
470 const char* leaf = nullptr;
471 const ra8_err_t rerr = priv_resolve_parent(handle, path, &parent, &leaf);
472 if (rerr != k_ra8_ok) {
473 return rerr;
474 }
475 uint64_t lba = 0;
476 uint32_t off = 0;
477 uint8_t entry[k_ra8_fs_dir_entry_bytes] = {};
478 const ra8_err_t err = priv_dir_lookup_any(handle, &parent, leaf, &lba, &off, entry);
479 if (err == k_ra8_ok) {
480 return internal_open_existing(handle, entry, lba, off, mode, out_file);
481 }
482 if (err != k_ra8_err_not_found) {
483 return err;
484 }
485 if (mode == k_ra8_fs_mode_read) {
486 return k_ra8_err_not_found;
487 }
488 /* Creation no longer needs an 8.3-representable name: `priv_dir_reserve()`
489 * generates the alias and reserves the chain's slots (#600). */
490 return internal_create_new(handle, &parent, leaf, mode, out_file);
491}
492
521{
522 const ra8_fs_mount_t* m = file->mount;
523 /* exFAT keeps its metadata in a checksummed entry set, not one 32-byte
524 * directory entry, so the close flush is the same commit every write already
525 * performs -- stamp, patch, checksum, write (#602). */
526 if (m->type == k_ra8_fs_type_exfat) {
527 return priv_exfat_flush_set(file);
528 }
529 uint8_t* const sec = priv_sec_walk();
530 ra8_err_t err = priv_read_sector(m, file->dir_entry_lba, sec);
531 if (err != k_ra8_ok) {
532 return err;
533 }
534 priv_fat_entry_stamp_write(&sec[file->dir_entry_idx]);
535 err = priv_write_sector(m, file->dir_entry_lba, sec);
536 if (err != k_ra8_ok) {
537 return err;
538 }
539 return priv_fsinfo_flush(m);
540}
541
542/* `priv_close_locked()`: see header for the documented contract. */
544{
545 if (file == nullptr) {
546 return k_ra8_err_null_ptr;
547 }
548 ra8_err_t err = k_ra8_ok;
549 /* Never touch the volume through a handle that is already closed or whose
550 * mount has gone: the dir-entry LBA would be read against a mount slot that
551 * now describes some other card. */
552 if ((file->dirty != 0U) && (file->in_use != 0U) && (file->mount != nullptr) &&
553 (file->mount->in_use != 0U)) {
554 err = internal_close_stamp(file);
555 }
556 file->dirty = 0;
557 file->in_use = 0;
558 file->mount = nullptr;
559 return err;
560}
561
562/* =============================================================================
563 * Public entry points -- the lock brackets
564 * =============================================================================
565 */
566
567RA8_OWNS_RESOURCE("ra8_fs_lock")
569ra8_fs_open(ra8_fs_mount_t* handle, const char* path, ra8_fs_mode_t mode, ra8_fs_file_t** out_file)
570{
572 const ra8_err_t err = priv_open_locked(handle, path, mode, out_file);
574 return err;
575}
576
577RA8_OWNS_RESOURCE("ra8_fs_lock")
579{
581 const ra8_err_t err = priv_close_locked(file);
583 return err;
584}
Annotation-attribute framework macros for ra8-firmware.
#define RA8_OWNS_RESOURCE(kind)
RAII-style resource ownership contract.
#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_err_invalid_state
Module in wrong state for requested operation.
Definition ra8_err.h:161
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_null_ptr
Pointer was NULL where a valid pointer was required.
Definition ra8_err.h:478
@ k_ra8_err_access_denied
Operation refused because the target is protected against it.
Definition ra8_err.h:276
@ k_ra8_err_protocol_error
Protocol-level error (e.g.
Definition ra8_err.h:429
@ 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 ra8_fs_open(ra8_fs_mount_t *handle, const char *path, ra8_fs_mode_t mode, ra8_fs_file_t **out_file)
Open or create a file by path, 8.3 or long.
ra8_err_t ra8_fs_close(ra8_fs_file_t *file)
Close an open file, stamping its final modification time.
uint32_t priv_rd32(const uint8_t *p)
Decode a little-endian uint32_t from a byte buffer.
Definition ra8_fs_fat.c:48
uint16_t priv_rd16(const uint8_t *p)
Decode a little-endian uint16_t from a byte buffer.
Definition ra8_fs_fat.c:42
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_wr32(uint8_t *p, uint32_t v)
Encode a little-endian uint32_t into a byte buffer.
Definition ra8_fs_fat.c:68
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
ra8_err_t priv_fsinfo_flush(const ra8_fs_mount_t *m)
Write the tracked free count and next-free hint back into FSInfo.
uint8_t * priv_sec_walk(void)
The WALK-role sector buffer (directory scans and entry RMW).
ra8_err_t priv_exfat_open(ra8_fs_mount_t *handle, const char *path, ra8_fs_mode_t mode, ra8_fs_file_t **out_file)
Open a file (read-only) on a mounted exFAT volume.
ra8_err_t priv_exfat_flush_set(ra8_fs_file_t *file)
Rewrite a file's entry set from the handle, checksum last.
ra8_err_t priv_close_locked(ra8_fs_file_t *file)
Close an open file – the guarded body of ra8_fs_close().
static void internal_init_new_file(ra8_fs_file_t *f, ra8_fs_mount_t *handle, ra8_fs_mode_t mode, uint64_t free_lba, uint32_t free_off)
Populate a freshly allocated file slot for an empty new file.
ra8_err_t priv_resolve_dir(const ra8_fs_mount_t *m, const char *path, dir_loc_t *out)
Resolve a whole path to the directory it names.
uint32_t priv_entry_first_cluster(const uint8_t *entry)
Read the first cluster from a 32-byte directory entry.
void priv_entry_set_cluster_size(uint8_t *entry, uint32_t cluster, uint32_t size)
Patch first-cluster + size back into a 32-byte directory entry.
static ra8_err_t internal_close_stamp(ra8_fs_file_t *file)
Stamp the final modification time of a file that was written.
static ra8_err_t internal_truncate_existing(const ra8_fs_mount_t *handle, ra8_fs_file_t *f, uint64_t lba, uint32_t off)
Truncate an existing file's chain and zero its dir-entry size.
static ra8_err_t internal_open_existing(ra8_fs_mount_t *handle, const uint8_t *entry, uint64_t lba, uint32_t off, ra8_fs_mode_t mode, ra8_fs_file_t **out_file)
Populate a fresh file handle from an existing on-disk dir entry.
static ra8_err_t internal_enter_subdir(const ra8_fs_mount_t *m, const dir_loc_t *cur, const char *comp, uint32_t len, dir_loc_t *out)
Descend into the named directory component within cur.
ra8_fs_path_cap_t
Path-resolution caps (statically bound the component walk).
@ k_path_max_depth
Max nested directory components per path.
static ra8_err_t internal_create_new(ra8_fs_mount_t *handle, const dir_loc_t *parent, const char *leaf, ra8_fs_mode_t mode, ra8_fs_file_t **out_file)
Carve a fresh directory entry for leaf and populate a file handle.
void priv_fat_entry_apply_attr(uint8_t *entry, uint8_t set_mask, uint8_t clear_mask)
Clear then set attribute bits in a 32-byte FAT directory entry.
ra8_err_t priv_open_locked(ra8_fs_mount_t *handle, const char *path, ra8_fs_mode_t mode, ra8_fs_file_t **out_file)
Open a file by path – the guarded body of ra8_fs_open().
ra8_err_t priv_resolve_parent(const ra8_fs_mount_t *m, const char *path, dir_loc_t *out_parent, const char **out_leaf)
Resolve all-but-the-last path component to a parent directory.
Cross-TU shared declarations for the FAT/exFAT ra8_fs adapter.
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_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.
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_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.
void priv_lock_release(void)
Drop the library lock taken by priv_lock_acquire.
void priv_lock_acquire(void)
Take the library lock, if the caller installed one.
ra8_fs_file_t * priv_alloc_file_slot(void)
Allocate a free entry from the file table; returns NULL if full.
void priv_fat_entry_stamp_write(uint8_t *entry)
Advance a FAT directory entry's modification time and access date.
void priv_fat_entry_stamp_create(uint8_t *entry)
Stamp a fresh FAT directory entry's create, write, and access fields.
@ k_dir_off_fst_clus_lo
MS FAT spec sec 6 "DIR_FstClusLO".
@ k_dir_off_attr
MS FAT spec sec 6 "DIR_Attr".
@ k_dir_off_file_size
MS FAT spec sec 6 "DIR_FileSize".
@ k_dir_off_fst_clus_hi
MS FAT spec sec 6 "DIR_FstClusHI".
@ k_cluster_first_data
Cluster numbers start at 2.
@ k_shift_two_bytes
Shift two bytes.
@ k_word_mask
Word mask.
@ k_lfn_utf8_cap
A 247-unit name in UTF-8: 3 * 247, + NUL.
@ k_ra8_fs_type_exfat
exFAT (read + streaming write + format).
@ k_ra8_fs_attr_directory
MS FAT spec sec 6 "ATTR_DIRECTORY".
@ k_ra8_fs_attr_archive
MS FAT spec sec 6 "ATTR_ARCHIVE".
@ k_ra8_fs_attr_read_only
MS FAT spec sec 6 "ATTR_READ_ONLY".
ra8_fs_mode_t
File-open modes accepted by ra8_fs_open().
@ k_ra8_fs_mode_append
Open at EOF for writing.
@ k_ra8_fs_mode_read
Read-only, must exist.
@ k_ra8_fs_mode_write
Truncate (or create) for writing.
@ k_ra8_fs_dir_entry_bytes
MS FAT spec sec 6 "Directory Entry".
Everything decided about a new directory entry before anything is written.
Identifies the directory a lookup/scan should operate in.
uint8_t is_root
1 => the volume root; 0 => the subdirectory at cluster.
uint32_t cluster
First cluster of the subdirectory (ignored when root).
Open-file state.
uint64_t size_bytes
File size (DIR_FileSize / DataLength).
uint64_t offset
Current read/write offset.
uint32_t first_cluster
Head of the file's cluster chain.
uint8_t in_use
0 = slot free, 1 = open.
uint32_t dir_entry_idx
FAT: byte offset of the entry in it.
uint32_t walk_cache_cluster
Cluster at walk_cache_idx; < 2 = no cache.
ra8_fs_mode_t mode
Open mode.
uint8_t no_fat_chain
exFAT contiguous file (no FAT walk).
uint32_t cur_cluster
Cluster the offset currently points into.
uint8_t dirty
1 once written; drives the close mtime.
uint64_t dir_entry_lba
FAT: sector containing the dir entry.
ra8_fs_mount_t * mount
Owning mount point.
uint32_t walk_cache_idx
Chain index whose cluster is cached below.
Cached parse of one mounted FAT volume.
ra8_fs_type_t type
FAT12 / FAT16 / FAT32.
uint8_t in_use
0 = slot free, 1 = mounted.