ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_fs_fat_lfn.c
Go to the documentation of this file.
1
19
20#include <stddef.h>
21#include <stdint.h>
22
23#include "ra8_attributes.h"
24#include "ra8_fs.h"
25#include "ra8_fs_fat_internal.h"
26
27/* ===========================================================================
28 * VFAT long-filename (LFN) read support
29 *
30 * A long name is stored as a chain of attr-0x0F entries IMMEDIATELY before the
31 * 8.3 short entry, in reverse order: the entry tagged 0x40 (last logical group)
32 * is physically first, then group N-1 ... group 1, then the 8.3 entry. Each LFN
33 * entry carries 13 UTF-16LE chars (offsets 1/3/5/7/9, 14/16/18/20/22/24, 28/30)
34 * and a checksum of the 8.3 name (offset 13) that ties the chain to its entry.
35 * Reading a chain came first (#101); writing one came later (#600) and shares
36 * this file's layout table rather than restating it.
37 * ===========================================================================
38 */
39
62
72static const uint8_t s_lfn_char_off[k_lfn_chars_per_ent] = {(uint8_t)k_lfn_char_off_0,
73 (uint8_t)k_lfn_char_off_1,
74 (uint8_t)k_lfn_char_off_2,
75 (uint8_t)k_lfn_char_off_3,
76 (uint8_t)k_lfn_char_off_4,
77 (uint8_t)k_lfn_char_off_5,
78 (uint8_t)k_lfn_char_off_6,
79 (uint8_t)k_lfn_char_off_7,
80 (uint8_t)k_lfn_char_off_8,
81 (uint8_t)k_lfn_char_off_9,
82 (uint8_t)k_lfn_char_off_10,
83 (uint8_t)k_lfn_char_off_11,
84 (uint8_t)k_lfn_char_off_12};
85
86/* `priv_sfn_checksum()`: see header for the documented contract. */
87uint8_t priv_sfn_checksum(const uint8_t* name83)
88{
89 uint8_t sum = 0U;
90 for (uint32_t i = 0U; i < (uint32_t)k_dir_name_field_len; i++) {
91 sum = (uint8_t)((((sum & 1U) != 0U) ? (uint32_t)k_sfn_csum_high_bit : 0U) +
92 (uint32_t)(sum >> 1U) + (uint32_t)name83[i]);
93 }
94 return sum;
95}
96
97/* `priv_lfn_fill_slot()`: see header for the documented contract. */
98void priv_lfn_fill_slot(uint8_t* ent,
99 const uint16_t* name,
100 uint32_t nlen,
101 uint32_t order,
102 uint8_t is_last,
103 uint8_t csum)
104{
105 for (uint32_t i = 0U; i < (uint32_t)k_ra8_fs_dir_entry_bytes; i++) {
106 ent[i] = 0U;
107 }
108 ent[k_lfn_off_seq] = (uint8_t)(order | ((is_last != 0U) ? (uint32_t)k_lfn_seq_last : 0U));
109 ent[k_dir_off_attr] = (uint8_t)k_ra8_fs_attr_lfn;
110 ent[k_lfn_off_type] = 0U;
111 ent[k_lfn_off_checksum] = csum;
112 priv_wr16(&ent[k_lfn_off_clus_lo], 0U);
113 const uint32_t base = (order - 1U) * (uint32_t)k_lfn_chars_per_ent;
114 for (uint32_t i = 0U; i < (uint32_t)k_lfn_chars_per_ent; i++) {
115 const uint32_t pos = base + i;
116 uint16_t val = (uint16_t)k_lfn_unicode_pad;
117 if (pos < nlen) {
118 /* The unit itself. Taking one byte of the caller's name per slot was
119 * correct only for ASCII, and silently wrong for everything else (#606). */
120 val = name[pos];
121 } else if (pos == nlen) {
122 val = 0U; /* the NUL that terminates the last populated group */
123 } else {
124 /* past the terminator: 0xFFFF padding, already in `val` */
125 }
126 priv_wr16(&ent[s_lfn_char_off[i]], val);
127 }
128}
129
130/* `priv_lfn_reset()`: see header for the documented contract. */
132{
133 for (uint32_t i = 0U; i < (uint32_t)k_lfn_write_max; i++) {
134 s->units[i] = 0U;
135 }
136 s->checksum = 0U;
137 s->have = 0U;
138}
139
140/* `priv_lfn_add()`: see header for the documented contract. */
141void priv_lfn_add(lfn_state_t* s, const uint8_t* ent)
142{
143 const uint32_t order = (uint32_t)(ent[k_lfn_off_seq] & (uint8_t)k_lfn_seq_order_mask);
144 if ((order < 1U) || (order > (uint32_t)k_lfn_max_entries)) {
145 return; /* out-of-range sequence -> corrupt chain, ignore this entry */
146 }
148 s->have = 1U;
149 const uint32_t base = (order - 1U) * (uint32_t)k_lfn_chars_per_ent;
150 for (uint32_t i = 0U; i < (uint32_t)k_lfn_chars_per_ent; i++) {
151 const uint32_t off = (uint32_t)s_lfn_char_off[i];
152 const uint32_t val = (uint32_t)ent[off] | ((uint32_t)ent[off + 1U] << 8U);
153 const uint32_t pos = base + i;
154 if (pos >= (uint32_t)k_lfn_write_max) {
155 /* Unreachable: max pos = (k_lfn_max_entries-1)*k_lfn_chars_per_ent +
156 * (k_lfn_chars_per_ent-1) = 18*13+12 = 246 < k_lfn_write_max = 247. */
157 break; /* GCOVR_EXCL_LINE -- loop index proof keeps pos below write bound */
158 }
159 if ((val == 0U) || (val == (uint32_t)k_lfn_unicode_pad)) {
160 s->units[pos] = 0U; /* terminator / padding ends this group's name */
161 break;
162 }
163 /* The unit as stored. Substituting '?' for everything above 0x7F -- which
164 * is what this did -- made the reported name one the caller could not hand
165 * back to `ra8_fs_open()`, so the file was listed and unopenable (#606). */
166 s->units[pos] = (uint16_t)val;
167 }
168}
169
170/* `priv_lfn_units_for()`: see header for the documented contract. */
171const uint16_t* priv_lfn_units_for(const lfn_state_t* s, const uint8_t* name83, uint32_t* out_units)
172{
173 *out_units = 0U;
174 if ((s->have == 0U) || (s->units[0] == 0U)) {
175 return nullptr;
176 }
177 if (s->checksum != priv_sfn_checksum(name83)) {
178 return nullptr;
179 }
180 uint32_t n = 0U;
181 while ((n < (uint32_t)k_lfn_write_max) && (s->units[n] != 0U)) {
182 n++;
183 }
184 *out_units = n;
185 return s->units;
186}
187
200
235 const uint16_t* needle,
236 uint32_t nneedle,
237 const uint8_t* buf,
238 uint64_t cur_lba,
239 lfn_state_t* lfn,
240 uint64_t* out_lba,
241 uint32_t* out_entry_off,
242 uint8_t out_entry[k_ra8_fs_dir_entry_bytes])
243{
244 for (uint32_t e = 0; e < priv_dir_eps(m); e++) {
245 const uint8_t* ent = &buf[(size_t)e * (size_t)k_ra8_fs_dir_entry_bytes];
247 return k_lfn_scan_eod;
248 }
250 priv_lfn_reset(lfn); /* a deleted slot breaks the chain */
251 continue;
252 }
253 if (ent[k_dir_off_attr] == k_ra8_fs_attr_lfn) {
254 priv_lfn_add(lfn, ent);
255 continue;
256 }
257 /* Compared as UTF-16, which is the domain the name is stored in and the
258 * domain the up-case table folds. Comparing the reassembled text meant
259 * comparing against a name the reader had already mangled, so a file whose
260 * name held an accent could not be opened by its real name (#606). */
261 uint32_t lnunits = 0U;
262 const uint16_t* lunits = priv_lfn_units_for(lfn, ent, &lnunits);
263 if ((lunits != nullptr) && (priv_utf16_ieq(needle, nneedle, lunits, lnunits) != 0U)) {
264 *out_lba = cur_lba;
265 *out_entry_off = e * (uint32_t)k_ra8_fs_dir_entry_bytes;
267 return k_lfn_scan_found;
268 }
269 priv_lfn_reset(lfn); /* 8.3 entry consumed -> next chain starts fresh */
270 }
271 return k_lfn_scan_continue;
272}
273
274/* `priv_dir_find_long()`: see header for the documented contract. */
276 const dir_loc_t* loc,
277 const char* want,
278 uint64_t* out_lba,
279 uint32_t* out_entry_off,
280 uint8_t out_entry[k_ra8_fs_dir_entry_bytes])
281{
282 const char* want_leaf = want;
283 if (want_leaf[0] == '/') {
284 want_leaf++; /* flat root: ignore a leading slash */
285 }
286 uint16_t needle[k_lfn_write_max] = {};
287 uint32_t nneedle = 0U;
288 ra8_err_t nerr = priv_utf8_to_utf16(want_leaf, needle, (uint32_t)k_lfn_write_max, &nneedle);
289 if (nerr == k_ra8_err_no_mem) {
290 /* Longer than any long name this format stores, so nothing here is it. */
291 return k_ra8_err_not_found;
292 }
293 if (nerr != k_ra8_ok) {
294 return nerr;
295 }
296 dir_walk_t w = {};
297 priv_dir_walk_init_loc(m, loc, &w);
298 lfn_state_t lfn = {};
299 priv_lfn_reset(&lfn);
300 uint8_t eod = 0;
301 uint8_t* const buf = priv_sec_walk();
302 while (eod == 0U) {
303 ra8_err_t err = priv_read_sector(m, w.cur_lba, buf);
304 if (err != k_ra8_ok) {
305 return err;
306 }
308 needle,
309 nneedle,
310 buf,
311 w.cur_lba,
312 &lfn,
313 out_lba,
314 out_entry_off,
315 out_entry);
316 if (scan == k_lfn_scan_found) {
317 return k_ra8_ok;
318 }
319 if (scan == k_lfn_scan_eod) {
320 return k_ra8_err_not_found;
321 }
322 err = priv_dir_walk_next_sector(m, &w, &eod);
323 if (err != k_ra8_ok) {
324 return err;
325 }
326 }
327 return k_ra8_err_not_found;
328}
329
330/* `priv_free_chain()`: see header for the documented contract. */
331ra8_err_t priv_free_chain(const ra8_fs_mount_t* m, uint32_t start)
332{
333 uint32_t cur = start;
334 uint32_t guard = 0;
335 while (cur >= k_cluster_first_data && (cur - k_cluster_first_data) < m->count_of_clusters) {
336 uint32_t next = 0;
337 ra8_err_t err = priv_fat_get(m, cur, &next);
338 if (err != k_ra8_ok) {
339 return err;
340 }
341 err = priv_fat_set(m, cur, k_cluster_free);
342 if (err != k_ra8_ok) {
343 return err;
344 }
345 /* The volume just got a cluster back, so say so (#607): the free count
346 * feeds FSInfo, and pulling the hint back is what makes the freed space
347 * the next thing allocated rather than something only found after a full
348 * wrap of the scan. */
350 priv_alloc_hint_lower(m, cur);
351 if (priv_is_eoc(m, next) != 0U) {
352 break;
353 }
354 cur = next;
355 /* Bounded loop -- can't visit more clusters than exist. */
356 guard++;
357 if (guard > m->count_of_clusters) {
359 }
360 }
361 return k_ra8_ok;
362}
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_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 priv_fat_get(const ra8_fs_mount_t *m, uint32_t cluster, uint32_t *out_value)
Fetch the FAT entry for cluster, returning the next-cluster value.
Definition ra8_fs_fat.c:187
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
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
uint8_t priv_is_eoc(const ra8_fs_mount_t *m, uint32_t value)
Test whether value is an end-of-chain marker for this FAT type.
Definition ra8_fs_fat.c:511
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
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
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.
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.
uint8_t * priv_sec_walk(void)
The WALK-role sector buffer (directory scans and entry RMW).
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.
static const uint8_t s_lfn_char_off[k_lfn_chars_per_ent]
Byte offset of each of an LFN entry's thirteen name characters.
static ra8_fs_lfn_scan_t internal_dir_find_long_sector(const ra8_fs_mount_t *m, const uint16_t *needle, uint32_t nneedle, const uint8_t *buf, uint64_t cur_lba, lfn_state_t *lfn, uint64_t *out_lba, uint32_t *out_entry_off, uint8_t out_entry[k_ra8_fs_dir_entry_bytes])
Scan one directory sector for a long-name match, updating the chain.
ra8_fs_lfn_scan_t
Outcome of scanning one directory sector for a long-name match.
@ k_lfn_scan_eod
End-of-directory marker hit; stop the walk.
@ k_lfn_scan_continue
No match in this sector; advance to the next.
@ k_lfn_scan_found
Long name matched; out parameters populated.
ra8_fs_lfn_char_off_t
Byte offsets of the 13 UTF-16 name characters in a 32-byte LFN entry.
@ k_lfn_char_off_12
LDIR_Name3 char 1.
@ k_lfn_char_off_8
LDIR_Name2 char 3.
@ k_lfn_char_off_0
LDIR_Name1 char 0.
@ k_lfn_char_off_9
LDIR_Name2 char 4.
@ k_lfn_char_off_6
LDIR_Name2 char 1.
@ k_lfn_char_off_3
LDIR_Name1 char 3.
@ k_lfn_char_off_4
LDIR_Name1 char 4.
@ k_lfn_char_off_7
LDIR_Name2 char 2.
@ k_lfn_char_off_5
LDIR_Name2 char 0.
@ k_lfn_char_off_11
LDIR_Name3 char 0.
@ k_lfn_char_off_2
LDIR_Name1 char 2.
@ k_lfn_char_off_1
LDIR_Name1 char 1.
@ k_lfn_char_off_10
LDIR_Name2 char 5.
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.
void priv_lfn_add(lfn_state_t *s, const uint8_t *ent)
Fold one LFN directory entry's 13 UTF-16LE code units into the state.
const uint16_t * priv_lfn_units_for(const lfn_state_t *s, const uint8_t *name83, uint32_t *out_units)
Code units of the chain that precedes name83, or NULL if none.
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).
void priv_lfn_reset(lfn_state_t *s)
Reset the LFN reassembly state so a fresh chain can start.
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.
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.
@ k_dir_off_attr
MS FAT spec sec 6 "DIR_Attr".
@ 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_cluster_free
Cluster free.
@ k_cluster_first_data
Cluster numbers start at 2.
@ k_lfn_unicode_pad
Slot padding past the name terminator.
@ k_sfn_csum_high_bit
Rotate-in bit when the running sum is odd.
@ k_lfn_off_type
LDIR_Type – zero for a name component.
@ k_lfn_seq_order_mask
Order = seq & 0x1F (1..20).
@ k_lfn_off_checksum
Checksum of the matching 8.3 name.
@ k_lfn_write_max
Longest name we WRITE (k_lfn_max_entries).
@ k_lfn_off_seq
Sequence/order byte (LDIR_Ord).
@ k_lfn_seq_last
Set on the last logical group.
@ k_lfn_max_entries
Cap so 19*13 = 247 chars fits the buffer.
@ k_lfn_chars_per_ent
UTF-16 chars carried per LFN entry.
@ k_lfn_off_clus_lo
LDIR_FstClusLO – must be written as 0.
@ k_ra8_fs_attr_lfn
Long-file-name marker (we skip).
@ k_ra8_fs_dir_entry_bytes
MS FAT spec sec 6 "Directory Entry".
uint8_t priv_utf16_ieq(const uint16_t *a, uint32_t an, const uint16_t *b, uint32_t bn)
Compare two UTF-16 names for case-insensitive equality.
Definition ra8_fs_utf.c:529
ra8_err_t priv_utf8_to_utf16(const char *in, uint16_t *out, uint32_t cap, uint32_t *out_units)
Convert a NUL-terminated UTF-8 name into UTF-16LE code units.
Definition ra8_fs_utf.c:283
Identifies the directory a lookup/scan should operate in.
Internal cursor used by the directory iterator.
uint64_t cur_lba
Currently loaded LBA.
In-progress reassembly of one LFN chain across the directory scan.
uint8_t checksum
8.3 checksum the chain claims.
uint8_t have
1 once any group has been accumulated.
uint16_t units[k_lfn_write_max]
Reassembled UTF-16LE units; 0 ends it.
Cached parse of one mounted FAT volume.
uint32_t count_of_clusters
Per MS spec: data_sectors / SPC.