1 /*
2 * Copyright (c) 2016 Intel Corporation.
3 * Copyright 2024 NXP
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include <stdio.h>
9 #include <string.h>
10 #include <zephyr/kernel.h>
11 #include <zephyr/types.h>
12 #include <errno.h>
13 #include <zephyr/init.h>
14 #include <zephyr/fs/fs.h>
15 #include <zephyr/fs/fs_sys.h>
16 #include <zephyr/sys/__assert.h>
17 #include <ff.h>
18 #include <diskio.h>
19 #include <zfs_diskio.h> /* Zephyr specific FatFS API */
20 #include <zephyr/logging/log.h>
21 LOG_MODULE_DECLARE(fs, CONFIG_FS_LOG_LEVEL);
22
23 #define FATFS_MAX_FILE_NAME 12 /* Uses 8.3 SFN */
24
25 /* Memory pool for FatFs directory objects */
26 K_MEM_SLAB_DEFINE(fatfs_dirp_pool, sizeof(DIR),
27 CONFIG_FS_FATFS_NUM_DIRS, 4);
28
29 /* Memory pool for FatFs file objects */
30 K_MEM_SLAB_DEFINE(fatfs_filep_pool, sizeof(FIL),
31 CONFIG_FS_FATFS_NUM_FILES, 4);
32
translate_error(int error)33 static int translate_error(int error)
34 {
35 switch (error) {
36 case FR_OK:
37 return 0;
38 case FR_NO_FILE:
39 case FR_NO_PATH:
40 case FR_INVALID_NAME:
41 return -ENOENT;
42 case FR_DENIED:
43 return -EACCES;
44 case FR_EXIST:
45 return -EEXIST;
46 case FR_INVALID_OBJECT:
47 return -EBADF;
48 case FR_WRITE_PROTECTED:
49 return -EROFS;
50 case FR_INVALID_DRIVE:
51 case FR_NOT_ENABLED:
52 case FR_NO_FILESYSTEM:
53 return -ENODEV;
54 case FR_NOT_ENOUGH_CORE:
55 return -ENOMEM;
56 case FR_TOO_MANY_OPEN_FILES:
57 return -EMFILE;
58 case FR_INVALID_PARAMETER:
59 return -EINVAL;
60 case FR_LOCKED:
61 case FR_TIMEOUT:
62 case FR_MKFS_ABORTED:
63 case FR_DISK_ERR:
64 case FR_INT_ERR:
65 case FR_NOT_READY:
66 return -EIO;
67 }
68
69 return -EIO;
70 }
71
translate_disk_error(int error)72 static int translate_disk_error(int error)
73 {
74 switch (error) {
75 case RES_OK:
76 return 0;
77 case RES_WRPRT:
78 return -EPERM;
79 case RES_PARERR:
80 return -EINVAL;
81 case RES_NOTRDY:
82 case RES_ERROR:
83 return -EIO;
84 }
85
86 return -EIO;
87 }
88
89 /* Converts a zephyr path like /SD:/foo into a path digestible by FATFS by stripping the
90 * leading slash, i.e. SD:/foo.
91 */
translate_path(const char * path)92 static const char *translate_path(const char *path)
93 {
94 /* this is guaranteed by the fs subsystem */
95 __ASSERT_NO_MSG(path[0] == '/');
96
97 return &path[1];
98 }
99
translate_flags(fs_mode_t flags)100 static uint8_t translate_flags(fs_mode_t flags)
101 {
102 uint8_t fat_mode = 0;
103
104 fat_mode |= (flags & FS_O_READ) ? FA_READ : 0;
105 fat_mode |= (flags & FS_O_WRITE) ? FA_WRITE : 0;
106 fat_mode |= (flags & FS_O_CREATE) ? FA_OPEN_ALWAYS : 0;
107 /* NOTE: FA_APPEND is not translated because FAT FS does not
108 * support append semantics of the Zephyr, where file position
109 * is forwarded to the end before each write, the fatfs_write
110 * will be tasked with setting a file position to the end,
111 * if FA_APPEND flag is present.
112 */
113
114 return fat_mode;
115 }
116
fatfs_open(struct fs_file_t * zfp,const char * file_name,fs_mode_t mode)117 static int fatfs_open(struct fs_file_t *zfp, const char *file_name,
118 fs_mode_t mode)
119 {
120 FRESULT res;
121 uint8_t fs_mode;
122 void *ptr;
123
124 if (k_mem_slab_alloc(&fatfs_filep_pool, &ptr, K_NO_WAIT) == 0) {
125 (void)memset(ptr, 0, sizeof(FIL));
126 zfp->filep = ptr;
127 } else {
128 return -ENOMEM;
129 }
130
131 fs_mode = translate_flags(mode);
132
133 res = f_open(zfp->filep, translate_path(file_name), fs_mode);
134
135 if (res != FR_OK) {
136 k_mem_slab_free(&fatfs_filep_pool, ptr);
137 zfp->filep = NULL;
138 }
139
140 return translate_error(res);
141 }
142
fatfs_close(struct fs_file_t * zfp)143 static int fatfs_close(struct fs_file_t *zfp)
144 {
145 FRESULT res;
146
147 res = f_close(zfp->filep);
148
149 /* Free file ptr memory */
150 k_mem_slab_free(&fatfs_filep_pool, zfp->filep);
151 zfp->filep = NULL;
152
153 return translate_error(res);
154 }
155
fatfs_unlink(struct fs_mount_t * mountp,const char * path)156 static int fatfs_unlink(struct fs_mount_t *mountp, const char *path)
157 {
158 int res = -ENOTSUP;
159
160 #if !defined(CONFIG_FS_FATFS_READ_ONLY)
161 res = f_unlink(translate_path(path));
162
163 res = translate_error(res);
164 #endif
165
166 return res;
167 }
168
fatfs_rename(struct fs_mount_t * mountp,const char * from,const char * to)169 static int fatfs_rename(struct fs_mount_t *mountp, const char *from,
170 const char *to)
171 {
172 int res = -ENOTSUP;
173
174 #if !defined(CONFIG_FS_FATFS_READ_ONLY)
175 FILINFO fno;
176
177 /* Check if 'to' path exists; remove it if it does */
178 res = f_stat(translate_path(to), &fno);
179 if (res == FR_OK) {
180 res = f_unlink(translate_path(to));
181 if (res != FR_OK) {
182 return translate_error(res);
183 }
184 }
185
186 res = f_rename(translate_path(from), translate_path(to));
187 res = translate_error(res);
188 #endif
189
190 return res;
191 }
192
fatfs_read(struct fs_file_t * zfp,void * ptr,size_t size)193 static ssize_t fatfs_read(struct fs_file_t *zfp, void *ptr, size_t size)
194 {
195 FRESULT res;
196 unsigned int br;
197
198 res = f_read(zfp->filep, ptr, size, &br);
199 if (res != FR_OK) {
200 return translate_error(res);
201 }
202
203 return br;
204 }
205
fatfs_write(struct fs_file_t * zfp,const void * ptr,size_t size)206 static ssize_t fatfs_write(struct fs_file_t *zfp, const void *ptr, size_t size)
207 {
208 int res = -ENOTSUP;
209
210 #if !defined(CONFIG_FS_FATFS_READ_ONLY)
211 unsigned int bw;
212 off_t pos = f_size((FIL *)zfp->filep);
213 res = FR_OK;
214
215 /* FA_APPEND flag means that file has been opened for append.
216 * The FAT FS write does not support the POSIX append semantics,
217 * to always write at the end of file, so set file position
218 * at the end before each write if FA_APPEND is set.
219 */
220 if (zfp->flags & FS_O_APPEND) {
221 res = f_lseek(zfp->filep, pos);
222 }
223
224 if (res == FR_OK) {
225 res = f_write(zfp->filep, ptr, size, &bw);
226 }
227
228 if (res != FR_OK) {
229 res = translate_error(res);
230 } else {
231 res = bw;
232 }
233 #endif
234
235 return res;
236 }
237
fatfs_seek(struct fs_file_t * zfp,off_t offset,int whence)238 static int fatfs_seek(struct fs_file_t *zfp, off_t offset, int whence)
239 {
240 FRESULT res = FR_OK;
241 off_t pos;
242
243 switch (whence) {
244 case FS_SEEK_SET:
245 pos = offset;
246 break;
247 case FS_SEEK_CUR:
248 pos = f_tell((FIL *)zfp->filep) + offset;
249 break;
250 case FS_SEEK_END:
251 pos = f_size((FIL *)zfp->filep) + offset;
252 break;
253 default:
254 return -EINVAL;
255 }
256
257 if ((pos < 0) || (pos > f_size((FIL *)zfp->filep))) {
258 return -EINVAL;
259 }
260
261 res = f_lseek(zfp->filep, pos);
262
263 return translate_error(res);
264 }
265
fatfs_tell(struct fs_file_t * zfp)266 static off_t fatfs_tell(struct fs_file_t *zfp)
267 {
268 return f_tell((FIL *)zfp->filep);
269 }
270
fatfs_truncate(struct fs_file_t * zfp,off_t length)271 static int fatfs_truncate(struct fs_file_t *zfp, off_t length)
272 {
273 int res = -ENOTSUP;
274
275 #if !defined(CONFIG_FS_FATFS_READ_ONLY)
276 off_t cur_length = f_size((FIL *)zfp->filep);
277
278 /* f_lseek expands file if new position is larger than file size */
279 res = f_lseek(zfp->filep, length);
280 if (res != FR_OK) {
281 return translate_error(res);
282 }
283
284 if (length < cur_length) {
285 res = f_truncate(zfp->filep);
286 } else {
287 /*
288 * Get actual length after expansion. This could be
289 * less if there was not enough space in the volume
290 * to expand to the requested length
291 */
292 length = f_tell((FIL *)zfp->filep);
293
294 res = f_lseek(zfp->filep, cur_length);
295 if (res != FR_OK) {
296 return translate_error(res);
297 }
298
299 /*
300 * The FS module does caching and optimization of
301 * writes. Here we write 1 byte at a time to avoid
302 * using additional code and memory for doing any
303 * optimization.
304 */
305 unsigned int bw;
306 uint8_t c = 0U;
307
308 for (int i = cur_length; i < length; i++) {
309 res = f_write(zfp->filep, &c, 1, &bw);
310 if (res != FR_OK) {
311 break;
312 }
313 }
314 }
315
316 res = translate_error(res);
317 #endif
318
319 return res;
320 }
321
fatfs_sync(struct fs_file_t * zfp)322 static int fatfs_sync(struct fs_file_t *zfp)
323 {
324 int res = -ENOTSUP;
325
326 #if !defined(CONFIG_FS_FATFS_READ_ONLY)
327 res = f_sync(zfp->filep);
328 res = translate_error(res);
329 #endif
330 return res;
331 }
332
fatfs_mkdir(struct fs_mount_t * mountp,const char * path)333 static int fatfs_mkdir(struct fs_mount_t *mountp, const char *path)
334 {
335 int res = -ENOTSUP;
336
337 #if !defined(CONFIG_FS_FATFS_READ_ONLY)
338 res = f_mkdir(translate_path(path));
339 res = translate_error(res);
340 #endif
341
342 return res;
343 }
344
fatfs_opendir(struct fs_dir_t * zdp,const char * path)345 static int fatfs_opendir(struct fs_dir_t *zdp, const char *path)
346 {
347 FRESULT res;
348 void *ptr;
349
350 if (k_mem_slab_alloc(&fatfs_dirp_pool, &ptr, K_NO_WAIT) == 0) {
351 (void)memset(ptr, 0, sizeof(DIR));
352 zdp->dirp = ptr;
353 } else {
354 return -ENOMEM;
355 }
356
357 res = f_opendir(zdp->dirp, translate_path(path));
358
359 if (res != FR_OK) {
360 k_mem_slab_free(&fatfs_dirp_pool, ptr);
361 zdp->dirp = NULL;
362 }
363
364 return translate_error(res);
365 }
366
fatfs_readdir(struct fs_dir_t * zdp,struct fs_dirent * entry)367 static int fatfs_readdir(struct fs_dir_t *zdp, struct fs_dirent *entry)
368 {
369 FRESULT res;
370 FILINFO fno;
371
372 res = f_readdir(zdp->dirp, &fno);
373 if (res == FR_OK) {
374 strcpy(entry->name, fno.fname);
375 if (entry->name[0] != 0) {
376 entry->type = ((fno.fattrib & AM_DIR) ?
377 FS_DIR_ENTRY_DIR : FS_DIR_ENTRY_FILE);
378 entry->size = fno.fsize;
379 }
380 }
381
382 return translate_error(res);
383 }
384
fatfs_closedir(struct fs_dir_t * zdp)385 static int fatfs_closedir(struct fs_dir_t *zdp)
386 {
387 FRESULT res;
388
389 res = f_closedir(zdp->dirp);
390
391 /* Free file ptr memory */
392 k_mem_slab_free(&fatfs_dirp_pool, zdp->dirp);
393
394 return translate_error(res);
395 }
396
fatfs_stat(struct fs_mount_t * mountp,const char * path,struct fs_dirent * entry)397 static int fatfs_stat(struct fs_mount_t *mountp,
398 const char *path, struct fs_dirent *entry)
399 {
400 FRESULT res;
401 FILINFO fno;
402
403 res = f_stat(translate_path(path), &fno);
404 if (res == FR_OK) {
405 entry->type = ((fno.fattrib & AM_DIR) ?
406 FS_DIR_ENTRY_DIR : FS_DIR_ENTRY_FILE);
407 strcpy(entry->name, fno.fname);
408 entry->size = fno.fsize;
409 }
410
411 return translate_error(res);
412 }
413
fatfs_statvfs(struct fs_mount_t * mountp,const char * path,struct fs_statvfs * stat)414 static int fatfs_statvfs(struct fs_mount_t *mountp,
415 const char *path, struct fs_statvfs *stat)
416 {
417 int res = -ENOTSUP;
418 #if !defined(CONFIG_FS_FATFS_READ_ONLY)
419 FATFS *fs;
420 DWORD f_bfree = 0;
421
422 res = f_getfree(translate_path(mountp->mnt_point), &f_bfree, &fs);
423 if (res != FR_OK) {
424 return -EIO;
425 }
426
427 stat->f_bfree = f_bfree;
428
429 /*
430 * If FF_MIN_SS and FF_MAX_SS differ, variable sector size support is
431 * enabled and the file system object structure contains the actual sector
432 * size, otherwise it is configured to a fixed value give by FF_MIN_SS.
433 */
434 #if FF_MAX_SS != FF_MIN_SS
435 stat->f_bsize = fs->ssize;
436 #else
437 stat->f_bsize = FF_MIN_SS;
438 #endif
439 stat->f_frsize = fs->csize * stat->f_bsize;
440 stat->f_blocks = (fs->n_fatent - 2);
441
442 res = translate_error(res);
443 #endif
444 return res;
445 }
446
fatfs_mount(struct fs_mount_t * mountp)447 static int fatfs_mount(struct fs_mount_t *mountp)
448 {
449 FRESULT res;
450
451 res = f_mount((FATFS *)mountp->fs_data, translate_path(mountp->mnt_point), 1);
452
453 #if defined(CONFIG_FS_FATFS_MOUNT_MKFS)
454 if (res == FR_NO_FILESYSTEM &&
455 (mountp->flags & FS_MOUNT_FLAG_READ_ONLY) != 0) {
456 return -EROFS;
457 }
458 /* If no file system found then create one */
459 if (res == FR_NO_FILESYSTEM &&
460 (mountp->flags & FS_MOUNT_FLAG_NO_FORMAT) == 0) {
461 uint8_t work[FF_MAX_SS];
462 MKFS_PARM mkfs_opt = {
463 .fmt = FM_ANY | FM_SFD, /* Any suitable FAT */
464 .n_fat = 1, /* One FAT fs table */
465 .align = 0, /* Get sector size via diskio query */
466 .n_root = CONFIG_FS_FATFS_MAX_ROOT_ENTRIES,
467 .au_size = 0 /* Auto calculate cluster size */
468 };
469
470 res = f_mkfs(translate_path(mountp->mnt_point), &mkfs_opt, work, sizeof(work));
471 if (res == FR_OK) {
472 res = f_mount((FATFS *)mountp->fs_data,
473 translate_path(mountp->mnt_point), 1);
474 }
475 }
476 #endif /* CONFIG_FS_FATFS_MOUNT_MKFS */
477
478 if (res == FR_OK) {
479 mountp->flags |= FS_MOUNT_FLAG_USE_DISK_ACCESS;
480 }
481
482 return translate_error(res);
483
484 }
485
fatfs_unmount(struct fs_mount_t * mountp)486 static int fatfs_unmount(struct fs_mount_t *mountp)
487 {
488 FRESULT res;
489 DRESULT disk_res;
490 uint8_t param = DISK_IOCTL_POWER_OFF;
491
492 res = f_mount(NULL, translate_path(mountp->mnt_point), 0);
493 if (res != FR_OK) {
494 LOG_ERR("Unmount failed (%d)", res);
495 return translate_error(res);
496 }
497
498 /* Make direct disk IOCTL call to deinit disk */
499 disk_res = disk_ioctl(((FATFS *)mountp->fs_data)->pdrv, CTRL_POWER, ¶m);
500 if (disk_res != RES_OK) {
501 LOG_ERR("Could not power off disk (%d)", disk_res);
502 return translate_disk_error(disk_res);
503 }
504
505 return 0;
506 }
507
508 #if defined(CONFIG_FILE_SYSTEM_MKFS) && defined(CONFIG_FS_FATFS_MKFS)
509
510 static MKFS_PARM def_cfg = {
511 .fmt = FM_ANY | FM_SFD, /* Any suitable FAT */
512 .n_fat = 1, /* One FAT fs table */
513 .align = 0, /* Get sector size via diskio query */
514 .n_root = CONFIG_FS_FATFS_MAX_ROOT_ENTRIES,
515 .au_size = 0 /* Auto calculate cluster size */
516 };
517
fatfs_mkfs(uintptr_t dev_id,void * cfg,int flags)518 static int fatfs_mkfs(uintptr_t dev_id, void *cfg, int flags)
519 {
520 FRESULT res;
521 uint8_t work[FF_MAX_SS];
522 MKFS_PARM *mkfs_opt = &def_cfg;
523
524 if (cfg != NULL) {
525 mkfs_opt = (MKFS_PARM *)cfg;
526 }
527
528 res = f_mkfs((char *)dev_id, mkfs_opt, work, sizeof(work));
529
530 return translate_error(res);
531 }
532
533 #endif /* CONFIG_FILE_SYSTEM_MKFS && FS_FATFS_MKFS */
534
535 /* File system interface */
536 static const struct fs_file_system_t fatfs_fs = {
537 .open = fatfs_open,
538 .close = fatfs_close,
539 .read = fatfs_read,
540 .write = fatfs_write,
541 .lseek = fatfs_seek,
542 .tell = fatfs_tell,
543 .truncate = fatfs_truncate,
544 .sync = fatfs_sync,
545 .opendir = fatfs_opendir,
546 .readdir = fatfs_readdir,
547 .closedir = fatfs_closedir,
548 .mount = fatfs_mount,
549 .unmount = fatfs_unmount,
550 .unlink = fatfs_unlink,
551 .rename = fatfs_rename,
552 .mkdir = fatfs_mkdir,
553 .stat = fatfs_stat,
554 .statvfs = fatfs_statvfs,
555 #if defined(CONFIG_FILE_SYSTEM_MKFS) && defined(CONFIG_FS_FATFS_MKFS)
556 .mkfs = fatfs_mkfs,
557 #endif
558 };
559
fatfs_init(void)560 static int fatfs_init(void)
561 {
562
563 return fs_register(FS_FATFS, &fatfs_fs);
564 }
565
566 SYS_INIT(fatfs_init, POST_KERNEL, CONFIG_FILE_SYSTEM_INIT_PRIORITY);
567