|  | /* | 
|  | * Copyright (c) 2016 Intel Corporation. | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <stdio.h> | 
|  | #include <string.h> | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/types.h> | 
|  | #include <errno.h> | 
|  | #include <zephyr/init.h> | 
|  | #include <zephyr/fs/fs.h> | 
|  | #include <zephyr/fs/fs_sys.h> | 
|  | #include <zephyr/sys/__assert.h> | 
|  | #include <ff.h> | 
|  |  | 
|  | #define FATFS_MAX_FILE_NAME 12 /* Uses 8.3 SFN */ | 
|  |  | 
|  | /* Memory pool for FatFs directory objects */ | 
|  | K_MEM_SLAB_DEFINE(fatfs_dirp_pool, sizeof(DIR), | 
|  | CONFIG_FS_FATFS_NUM_DIRS, 4); | 
|  |  | 
|  | /* Memory pool for FatFs file objects */ | 
|  | K_MEM_SLAB_DEFINE(fatfs_filep_pool, sizeof(FIL), | 
|  | CONFIG_FS_FATFS_NUM_FILES, 4); | 
|  |  | 
|  | static int translate_error(int error) | 
|  | { | 
|  | switch (error) { | 
|  | case FR_OK: | 
|  | return 0; | 
|  | case FR_NO_FILE: | 
|  | case FR_NO_PATH: | 
|  | case FR_INVALID_NAME: | 
|  | return -ENOENT; | 
|  | case FR_DENIED: | 
|  | return -EACCES; | 
|  | case FR_EXIST: | 
|  | return -EEXIST; | 
|  | case FR_INVALID_OBJECT: | 
|  | return -EBADF; | 
|  | case FR_WRITE_PROTECTED: | 
|  | return -EROFS; | 
|  | case FR_INVALID_DRIVE: | 
|  | case FR_NOT_ENABLED: | 
|  | case FR_NO_FILESYSTEM: | 
|  | return -ENODEV; | 
|  | case FR_NOT_ENOUGH_CORE: | 
|  | return -ENOMEM; | 
|  | case FR_TOO_MANY_OPEN_FILES: | 
|  | return -EMFILE; | 
|  | case FR_INVALID_PARAMETER: | 
|  | return -EINVAL; | 
|  | case FR_LOCKED: | 
|  | case FR_TIMEOUT: | 
|  | case FR_MKFS_ABORTED: | 
|  | case FR_DISK_ERR: | 
|  | case FR_INT_ERR: | 
|  | case FR_NOT_READY: | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | static uint8_t translate_flags(fs_mode_t flags) | 
|  | { | 
|  | uint8_t fat_mode = 0; | 
|  |  | 
|  | fat_mode |= (flags & FS_O_READ) ? FA_READ : 0; | 
|  | fat_mode |= (flags & FS_O_WRITE) ? FA_WRITE : 0; | 
|  | fat_mode |= (flags & FS_O_CREATE) ? FA_OPEN_ALWAYS : 0; | 
|  | /* NOTE: FA_APPEND is not translated because FAT FS does not | 
|  | * support append semantics of the Zephyr, where file position | 
|  | * is forwarded to the end before each write, the fatfs_write | 
|  | * will be tasked with setting a file position to the end, | 
|  | * if FA_APPEND flag is present. | 
|  | */ | 
|  |  | 
|  | return fat_mode; | 
|  | } | 
|  |  | 
|  | static int fatfs_open(struct fs_file_t *zfp, const char *file_name, | 
|  | fs_mode_t mode) | 
|  | { | 
|  | FRESULT res; | 
|  | uint8_t fs_mode; | 
|  | void *ptr; | 
|  |  | 
|  | if (k_mem_slab_alloc(&fatfs_filep_pool, &ptr, K_NO_WAIT) == 0) { | 
|  | (void)memset(ptr, 0, sizeof(FIL)); | 
|  | zfp->filep = ptr; | 
|  | } else { | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | fs_mode = translate_flags(mode); | 
|  |  | 
|  | res = f_open(zfp->filep, &file_name[1], fs_mode); | 
|  |  | 
|  | if (res != FR_OK) { | 
|  | k_mem_slab_free(&fatfs_filep_pool, &ptr); | 
|  | zfp->filep = NULL; | 
|  | } | 
|  |  | 
|  | return translate_error(res); | 
|  | } | 
|  |  | 
|  | static int fatfs_close(struct fs_file_t *zfp) | 
|  | { | 
|  | FRESULT res; | 
|  |  | 
|  | res = f_close(zfp->filep); | 
|  |  | 
|  | /* Free file ptr memory */ | 
|  | k_mem_slab_free(&fatfs_filep_pool, &zfp->filep); | 
|  | zfp->filep = NULL; | 
|  |  | 
|  | return translate_error(res); | 
|  | } | 
|  |  | 
|  | static int fatfs_unlink(struct fs_mount_t *mountp, const char *path) | 
|  | { | 
|  | int res = -ENOTSUP; | 
|  |  | 
|  | #if !defined(CONFIG_FS_FATFS_READ_ONLY) | 
|  | res = f_unlink(&path[1]); | 
|  |  | 
|  | res = translate_error(res); | 
|  | #endif | 
|  |  | 
|  | return res; | 
|  | } | 
|  |  | 
|  | static int fatfs_rename(struct fs_mount_t *mountp, const char *from, | 
|  | const char *to) | 
|  | { | 
|  | int res = -ENOTSUP; | 
|  |  | 
|  | #if !defined(CONFIG_FS_FATFS_READ_ONLY) | 
|  | FILINFO fno; | 
|  |  | 
|  | /* Check if 'to' path exists; remove it if it does */ | 
|  | res = f_stat(&to[1], &fno); | 
|  | if (FR_OK == res) { | 
|  | res = f_unlink(&to[1]); | 
|  | if (FR_OK != res) | 
|  | return translate_error(res); | 
|  | } | 
|  |  | 
|  | res = f_rename(&from[1], &to[1]); | 
|  | res = translate_error(res); | 
|  | #endif | 
|  |  | 
|  | return res; | 
|  | } | 
|  |  | 
|  | static ssize_t fatfs_read(struct fs_file_t *zfp, void *ptr, size_t size) | 
|  | { | 
|  | FRESULT res; | 
|  | unsigned int br; | 
|  |  | 
|  | res = f_read(zfp->filep, ptr, size, &br); | 
|  | if (res != FR_OK) { | 
|  | return translate_error(res); | 
|  | } | 
|  |  | 
|  | return br; | 
|  | } | 
|  |  | 
|  | static ssize_t fatfs_write(struct fs_file_t *zfp, const void *ptr, size_t size) | 
|  | { | 
|  | int res = -ENOTSUP; | 
|  |  | 
|  | #if !defined(CONFIG_FS_FATFS_READ_ONLY) | 
|  | unsigned int bw; | 
|  | off_t pos = f_size((FIL *)zfp->filep); | 
|  | res = FR_OK; | 
|  |  | 
|  | /* FA_APPEND flag means that file has been opened for append. | 
|  | * The FAT FS write does not support the POSIX append semantics, | 
|  | * to always write at the end of file, so set file position | 
|  | * at the end before each write if FA_APPEND is set. | 
|  | */ | 
|  | if (zfp->flags & FS_O_APPEND) { | 
|  | res = f_lseek(zfp->filep, pos); | 
|  | } | 
|  |  | 
|  | if (res == FR_OK) { | 
|  | res = f_write(zfp->filep, ptr, size, &bw); | 
|  | } | 
|  |  | 
|  | if (res != FR_OK) { | 
|  | res = translate_error(res); | 
|  | } else { | 
|  | res = bw; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | return res; | 
|  | } | 
|  |  | 
|  | static int fatfs_seek(struct fs_file_t *zfp, off_t offset, int whence) | 
|  | { | 
|  | FRESULT res = FR_OK; | 
|  | off_t pos; | 
|  |  | 
|  | switch (whence) { | 
|  | case FS_SEEK_SET: | 
|  | pos = offset; | 
|  | break; | 
|  | case FS_SEEK_CUR: | 
|  | pos = f_tell((FIL *)zfp->filep) + offset; | 
|  | break; | 
|  | case FS_SEEK_END: | 
|  | pos = f_size((FIL *)zfp->filep) + offset; | 
|  | break; | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if ((pos < 0) || (pos > f_size((FIL *)zfp->filep))) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | res = f_lseek(zfp->filep, pos); | 
|  |  | 
|  | return translate_error(res); | 
|  | } | 
|  |  | 
|  | static off_t fatfs_tell(struct fs_file_t *zfp) | 
|  | { | 
|  | return f_tell((FIL *)zfp->filep); | 
|  | } | 
|  |  | 
|  | static int fatfs_truncate(struct fs_file_t *zfp, off_t length) | 
|  | { | 
|  | int res = -ENOTSUP; | 
|  |  | 
|  | #if !defined(CONFIG_FS_FATFS_READ_ONLY) | 
|  | off_t cur_length = f_size((FIL *)zfp->filep); | 
|  |  | 
|  | /* f_lseek expands file if new position is larger than file size */ | 
|  | res = f_lseek(zfp->filep, length); | 
|  | if (res != FR_OK) { | 
|  | return translate_error(res); | 
|  | } | 
|  |  | 
|  | if (length < cur_length) { | 
|  | res = f_truncate(zfp->filep); | 
|  | } else { | 
|  | /* | 
|  | * Get actual length after expansion. This could be | 
|  | * less if there was not enough space in the volume | 
|  | * to expand to the requested length | 
|  | */ | 
|  | length = f_tell((FIL *)zfp->filep); | 
|  |  | 
|  | res = f_lseek(zfp->filep, cur_length); | 
|  | if (res != FR_OK) { | 
|  | return translate_error(res); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * The FS module does caching and optimization of | 
|  | * writes. Here we write 1 byte at a time to avoid | 
|  | * using additional code and memory for doing any | 
|  | * optimization. | 
|  | */ | 
|  | unsigned int bw; | 
|  | uint8_t c = 0U; | 
|  |  | 
|  | for (int i = cur_length; i < length; i++) { | 
|  | res = f_write(zfp->filep, &c, 1, &bw); | 
|  | if (res != FR_OK) { | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | res = translate_error(res); | 
|  | #endif | 
|  |  | 
|  | return res; | 
|  | } | 
|  |  | 
|  | static int fatfs_sync(struct fs_file_t *zfp) | 
|  | { | 
|  | int res = -ENOTSUP; | 
|  |  | 
|  | #if !defined(CONFIG_FS_FATFS_READ_ONLY) | 
|  | res = f_sync(zfp->filep); | 
|  | res = translate_error(res); | 
|  | #endif | 
|  | return res; | 
|  | } | 
|  |  | 
|  | static int fatfs_mkdir(struct fs_mount_t *mountp, const char *path) | 
|  | { | 
|  | int res = -ENOTSUP; | 
|  |  | 
|  | #if !defined(CONFIG_FS_FATFS_READ_ONLY) | 
|  | res = f_mkdir(&path[1]); | 
|  | res = translate_error(res); | 
|  | #endif | 
|  |  | 
|  | return res; | 
|  | } | 
|  |  | 
|  | static int fatfs_opendir(struct fs_dir_t *zdp, const char *path) | 
|  | { | 
|  | FRESULT res; | 
|  | void *ptr; | 
|  |  | 
|  | if (k_mem_slab_alloc(&fatfs_dirp_pool, &ptr, K_NO_WAIT) == 0) { | 
|  | (void)memset(ptr, 0, sizeof(DIR)); | 
|  | zdp->dirp = ptr; | 
|  | } else { | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | res = f_opendir(zdp->dirp, &path[1]); | 
|  |  | 
|  | if (res != FR_OK) { | 
|  | k_mem_slab_free(&fatfs_dirp_pool, &ptr); | 
|  | zdp->dirp = NULL; | 
|  | } | 
|  |  | 
|  | return translate_error(res); | 
|  | } | 
|  |  | 
|  | static int fatfs_readdir(struct fs_dir_t *zdp, struct fs_dirent *entry) | 
|  | { | 
|  | FRESULT res; | 
|  | FILINFO fno; | 
|  |  | 
|  | res = f_readdir(zdp->dirp, &fno); | 
|  | if (res == FR_OK) { | 
|  | strcpy(entry->name, fno.fname); | 
|  | if (entry->name[0] != 0) { | 
|  | entry->type = ((fno.fattrib & AM_DIR) ? | 
|  | FS_DIR_ENTRY_DIR : FS_DIR_ENTRY_FILE); | 
|  | entry->size = fno.fsize; | 
|  | } | 
|  | } | 
|  |  | 
|  | return translate_error(res); | 
|  | } | 
|  |  | 
|  | static int fatfs_closedir(struct fs_dir_t *zdp) | 
|  | { | 
|  | FRESULT res; | 
|  |  | 
|  | res = f_closedir(zdp->dirp); | 
|  |  | 
|  | /* Free file ptr memory */ | 
|  | k_mem_slab_free(&fatfs_dirp_pool, &zdp->dirp); | 
|  |  | 
|  | return translate_error(res); | 
|  | } | 
|  |  | 
|  | static int fatfs_stat(struct fs_mount_t *mountp, | 
|  | const char *path, struct fs_dirent *entry) | 
|  | { | 
|  | FRESULT res; | 
|  | FILINFO fno; | 
|  |  | 
|  | res = f_stat(&path[1], &fno); | 
|  | if (res == FR_OK) { | 
|  | entry->type = ((fno.fattrib & AM_DIR) ? | 
|  | FS_DIR_ENTRY_DIR : FS_DIR_ENTRY_FILE); | 
|  | strcpy(entry->name, fno.fname); | 
|  | entry->size = fno.fsize; | 
|  | } | 
|  |  | 
|  | return translate_error(res); | 
|  | } | 
|  |  | 
|  | static int fatfs_statvfs(struct fs_mount_t *mountp, | 
|  | const char *path, struct fs_statvfs *stat) | 
|  | { | 
|  | int res = -ENOTSUP; | 
|  | #if !defined(CONFIG_FS_FATFS_READ_ONLY) | 
|  | FATFS *fs; | 
|  | DWORD f_bfree = 0; | 
|  |  | 
|  | res = f_getfree(&mountp->mnt_point[1], &f_bfree, &fs); | 
|  | if (res != FR_OK) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | stat->f_bfree = f_bfree; | 
|  |  | 
|  | /* | 
|  | * If FF_MIN_SS and FF_MAX_SS differ, variable sector size support is | 
|  | * enabled and the file system object structure contains the actual sector | 
|  | * size, otherwise it is configured to a fixed value give by FF_MIN_SS. | 
|  | */ | 
|  | #if FF_MAX_SS != FF_MIN_SS | 
|  | stat->f_bsize = fs->ssize; | 
|  | #else | 
|  | stat->f_bsize = FF_MIN_SS; | 
|  | #endif | 
|  | stat->f_frsize = fs->csize * stat->f_bsize; | 
|  | stat->f_blocks = (fs->n_fatent - 2); | 
|  |  | 
|  | res = translate_error(res); | 
|  | #endif | 
|  | return res; | 
|  | } | 
|  |  | 
|  | static int fatfs_mount(struct fs_mount_t *mountp) | 
|  | { | 
|  | FRESULT res; | 
|  |  | 
|  | res = f_mount((FATFS *)mountp->fs_data, &mountp->mnt_point[1], 1); | 
|  |  | 
|  | #if defined(CONFIG_FS_FATFS_MOUNT_MKFS) | 
|  | if (res == FR_NO_FILESYSTEM && | 
|  | (mountp->flags & FS_MOUNT_FLAG_READ_ONLY) != 0) { | 
|  | return -EROFS; | 
|  | } | 
|  | /* If no file system found then create one */ | 
|  | if (res == FR_NO_FILESYSTEM && | 
|  | (mountp->flags & FS_MOUNT_FLAG_NO_FORMAT) == 0) { | 
|  | uint8_t work[FF_MAX_SS]; | 
|  | MKFS_PARM mkfs_opt = { | 
|  | .fmt = FM_ANY | FM_SFD,	/* Any suitable FAT */ | 
|  | .n_fat = 1,		/* One FAT fs table */ | 
|  | .align = 0,		/* Get sector size via diskio query */ | 
|  | .n_root = CONFIG_FS_FATFS_MAX_ROOT_ENTRIES, | 
|  | .au_size = 0		/* Auto calculate cluster size */ | 
|  | }; | 
|  |  | 
|  | res = f_mkfs(&mountp->mnt_point[1], &mkfs_opt, work, sizeof(work)); | 
|  | if (res == FR_OK) { | 
|  | res = f_mount((FATFS *)mountp->fs_data, | 
|  | &mountp->mnt_point[1], 1); | 
|  | } | 
|  | } | 
|  | #endif /* CONFIG_FS_FATFS_MOUNT_MKFS */ | 
|  |  | 
|  | if (res == FR_OK) { | 
|  | mountp->flags |= FS_MOUNT_FLAG_USE_DISK_ACCESS; | 
|  | } | 
|  |  | 
|  | return translate_error(res); | 
|  |  | 
|  | } | 
|  |  | 
|  | static int fatfs_unmount(struct fs_mount_t *mountp) | 
|  | { | 
|  | FRESULT res; | 
|  |  | 
|  | res = f_mount(NULL, &mountp->mnt_point[1], 0); | 
|  |  | 
|  | return translate_error(res); | 
|  | } | 
|  |  | 
|  | /* File system interface */ | 
|  | static const struct fs_file_system_t fatfs_fs = { | 
|  | .open = fatfs_open, | 
|  | .close = fatfs_close, | 
|  | .read = fatfs_read, | 
|  | .write = fatfs_write, | 
|  | .lseek = fatfs_seek, | 
|  | .tell = fatfs_tell, | 
|  | .truncate = fatfs_truncate, | 
|  | .sync = fatfs_sync, | 
|  | .opendir = fatfs_opendir, | 
|  | .readdir = fatfs_readdir, | 
|  | .closedir = fatfs_closedir, | 
|  | .mount = fatfs_mount, | 
|  | .unmount = fatfs_unmount, | 
|  | .unlink = fatfs_unlink, | 
|  | .rename = fatfs_rename, | 
|  | .mkdir = fatfs_mkdir, | 
|  | .stat = fatfs_stat, | 
|  | .statvfs = fatfs_statvfs, | 
|  | }; | 
|  |  | 
|  | static int fatfs_init(const struct device *dev) | 
|  | { | 
|  | ARG_UNUSED(dev); | 
|  |  | 
|  | return fs_register(FS_FATFS, &fatfs_fs); | 
|  | } | 
|  |  | 
|  | SYS_INIT(fatfs_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); |