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, &param);
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