You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
638 lines
15 KiB
638 lines
15 KiB
/* |
|
* Copyright (c) 2016 Intel Corporation. |
|
* Copyright 2024 NXP |
|
* |
|
* 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> |
|
#include <diskio.h> |
|
#include <zfs_diskio.h> /* Zephyr specific FatFS API */ |
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_DECLARE(fs, CONFIG_FS_LOG_LEVEL); |
|
|
|
#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 int translate_disk_error(int error) |
|
{ |
|
switch (error) { |
|
case RES_OK: |
|
return 0; |
|
case RES_WRPRT: |
|
return -EPERM; |
|
case RES_PARERR: |
|
return -EINVAL; |
|
case RES_NOTRDY: |
|
case RES_ERROR: |
|
return -EIO; |
|
} |
|
|
|
return -EIO; |
|
} |
|
|
|
/* Converts a zephyr path like /SD:/foo into a path digestible by FATFS by stripping the |
|
* leading slash, i.e. SD:/foo. |
|
*/ |
|
static const char *translate_path(const char *path) |
|
{ |
|
/* this is guaranteed by the fs subsystem */ |
|
__ASSERT_NO_MSG(path[0] == '/'); |
|
|
|
return &path[1]; |
|
} |
|
|
|
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, translate_path(file_name), 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(translate_path(path)); |
|
|
|
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(translate_path(to), &fno); |
|
if (res == FR_OK) { |
|
res = f_unlink(translate_path(to)); |
|
if (res != FR_OK) { |
|
return translate_error(res); |
|
} |
|
} |
|
|
|
res = f_rename(translate_path(from), translate_path(to)); |
|
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(translate_path(path)); |
|
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, translate_path(path)); |
|
|
|
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(translate_path(path), &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(translate_path(mountp->mnt_point), &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, translate_path(mountp->mnt_point), 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(translate_path(mountp->mnt_point), &mkfs_opt, work, sizeof(work)); |
|
if (res == FR_OK) { |
|
res = f_mount((FATFS *)mountp->fs_data, |
|
translate_path(mountp->mnt_point), 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; |
|
DRESULT disk_res; |
|
uint8_t param = DISK_IOCTL_POWER_OFF; |
|
|
|
res = f_mount(NULL, translate_path(mountp->mnt_point), 0); |
|
if (res != FR_OK) { |
|
LOG_ERR("Unmount failed (%d)", res); |
|
return translate_error(res); |
|
} |
|
|
|
/* Make direct disk IOCTL call to deinit disk */ |
|
disk_res = disk_ioctl(((FATFS *)mountp->fs_data)->pdrv, CTRL_POWER, ¶m); |
|
if (disk_res != RES_OK) { |
|
LOG_ERR("Could not power off disk (%d)", disk_res); |
|
return translate_disk_error(disk_res); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
#if defined(CONFIG_FILE_SYSTEM_MKFS) && defined(CONFIG_FS_FATFS_MKFS) |
|
|
|
static MKFS_PARM def_cfg = { |
|
.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 */ |
|
}; |
|
|
|
static int fatfs_mkfs(uintptr_t dev_id, void *cfg, int flags) |
|
{ |
|
FRESULT res; |
|
uint8_t work[FF_MAX_SS]; |
|
MKFS_PARM *mkfs_opt = &def_cfg; |
|
|
|
if (cfg != NULL) { |
|
mkfs_opt = (MKFS_PARM *)cfg; |
|
} |
|
|
|
res = f_mkfs((char *)dev_id, mkfs_opt, work, sizeof(work)); |
|
|
|
return translate_error(res); |
|
} |
|
|
|
#endif /* CONFIG_FILE_SYSTEM_MKFS && FS_FATFS_MKFS */ |
|
|
|
/* 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, |
|
#if defined(CONFIG_FILE_SYSTEM_MKFS) && defined(CONFIG_FS_FATFS_MKFS) |
|
.mkfs = fatfs_mkfs, |
|
#endif |
|
}; |
|
|
|
#define DT_DRV_COMPAT zephyr_fstab_fatfs |
|
|
|
#define DEFINE_FS(inst) \ |
|
BUILD_ASSERT(DT_INST_PROP(inst, disk_access), "FATFS needs disk-access"); \ |
|
BUILD_ASSERT(!DT_INST_PROP(inst, read_only), \ |
|
"READ_ONLY not supported for individual instances see FS_FATFS_READ_ONLY"); \ |
|
BUILD_ASSERT(!DT_INST_PROP(inst, no_format), \ |
|
"NO_FORMAT not supported for individual instanzes FS_FATFS_MKFS"); \ |
|
static FATFS fs_data_##inst; \ |
|
struct fs_mount_t FS_FSTAB_ENTRY(DT_DRV_INST(inst)) = { \ |
|
.type = FS_FATFS, \ |
|
.mnt_point = DT_INST_PROP(inst, mount_point), \ |
|
.fs_data = &fs_data_##inst, \ |
|
.storage_dev = NULL, \ |
|
.flags = FSTAB_ENTRY_DT_MOUNT_FLAGS(DT_DRV_INST(inst)), \ |
|
}; |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(DEFINE_FS); |
|
|
|
#ifdef CONFIG_FS_FATFS_FSTAB_AUTOMOUNT |
|
#define REFERENCE_MOUNT(inst) (&FS_FSTAB_ENTRY(DT_DRV_INST(inst))), |
|
|
|
static void automount_if_enabled(struct fs_mount_t *mountp) |
|
{ |
|
int ret = 0; |
|
|
|
if ((mountp->flags & FS_MOUNT_FLAG_AUTOMOUNT) != 0) { |
|
ret = fs_mount(mountp); |
|
if (ret < 0) { |
|
LOG_ERR("Error mounting filesystem: at %s: %d", mountp->mnt_point, ret); |
|
} else { |
|
LOG_DBG("FATFS Filesystem \"%s\" initialized", mountp->mnt_point); |
|
} |
|
} |
|
} |
|
#endif /* CONFIG_FS_FATFS_FSTAB_AUTOMOUNT */ |
|
|
|
#if CONFIG_FS_FATFS_CUSTOM_MOUNT_POINT_COUNT |
|
const char *VolumeStr[CONFIG_FS_FATFS_CUSTOM_MOUNT_POINT_COUNT]; |
|
#endif /* CONFIG_FS_FATFS_CUSTOM_MOUNT_POINT_COUNT */ |
|
|
|
static int fatfs_init(void) |
|
{ |
|
#if CONFIG_FS_FATFS_CUSTOM_MOUNT_POINT_COUNT |
|
static char mount_points[] = CONFIG_FS_FATFS_CUSTOM_MOUNT_POINTS; |
|
int mount_point_count = 0; |
|
|
|
VolumeStr[0] = mount_points; |
|
for (int i = 0; i < ARRAY_SIZE(mount_points) - 1; i++) { |
|
if (mount_points[i] == ',') { |
|
mount_points[i] = 0; |
|
mount_point_count++; |
|
if (mount_point_count >= ARRAY_SIZE(VolumeStr)) { |
|
LOG_ERR("Mount point count not sufficient for defined mount " |
|
"points."); |
|
return -1; |
|
} |
|
VolumeStr[mount_point_count] = &mount_points[i + 1]; |
|
} |
|
} |
|
#endif /* CONFIG_FS_FATFS_CUSTOM_MOUNT_POINT_COUNT */ |
|
int rc = fs_register(FS_FATFS, &fatfs_fs); |
|
|
|
#ifdef CONFIG_FS_FATFS_FSTAB_AUTOMOUNT |
|
if (rc == 0) { |
|
struct fs_mount_t *partitions[] = {DT_INST_FOREACH_STATUS_OKAY(REFERENCE_MOUNT)}; |
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(partitions); i++) { |
|
struct fs_mount_t *mpi = partitions[i]; |
|
|
|
automount_if_enabled(mpi); |
|
} |
|
} |
|
#endif /* CONFIG_FS_FATFS_FSTAB_AUTOMOUNT */ |
|
|
|
return rc; |
|
} |
|
|
|
SYS_INIT(fatfs_init, POST_KERNEL, CONFIG_FILE_SYSTEM_INIT_PRIORITY);
|
|
|