602 lines
12 KiB
C
602 lines
12 KiB
C
/*
|
|
* Copyright (c) 2017 Codecoup
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <zephyr/types.h>
|
|
#include <errno.h>
|
|
#include <init.h>
|
|
#include <flash.h>
|
|
#include <fs.h>
|
|
#include <crc16.h>
|
|
#include <misc/__assert.h>
|
|
#include <misc/printk.h>
|
|
#include <nffs/os.h>
|
|
#include <nffs/nffs.h>
|
|
|
|
#define NFFS_MAX_FILE_NAME 256
|
|
|
|
/*
|
|
* NFFS code keeps fs state in RAM but access to these structures is not
|
|
* thread-safe - we need global lock for each fs operation to guarantee two
|
|
* threads won't modify NFFS at the same time.
|
|
*/
|
|
static struct k_mutex nffs_lock;
|
|
|
|
/*
|
|
* TODO: Get rid of global flash_dev which limits
|
|
* system to have multiple instances of NFFS.
|
|
*/
|
|
static struct device *flash_dev;
|
|
|
|
/* nffs flash area descriptors */
|
|
static struct nffs_area_desc descs[CONFIG_NFFS_FILESYSTEM_MAX_AREAS + 1];
|
|
|
|
K_MEM_SLAB_DEFINE(nffs_file_pool, sizeof(struct nffs_file),
|
|
CONFIG_FS_NFFS_NUM_FILES, 4);
|
|
K_MEM_SLAB_DEFINE(nffs_dir_pool, sizeof(struct nffs_dir),
|
|
CONFIG_FS_NFFS_NUM_DIRS, 4);
|
|
K_MEM_SLAB_DEFINE(nffs_inode_entry_pool, sizeof(struct nffs_inode_entry),
|
|
CONFIG_FS_NFFS_NUM_INODES, 4);
|
|
K_MEM_SLAB_DEFINE(nffs_block_entry_pool, sizeof(struct nffs_hash_entry),
|
|
CONFIG_FS_NFFS_NUM_BLOCKS, 4);
|
|
K_MEM_SLAB_DEFINE(nffs_cache_inode_pool, sizeof(struct nffs_cache_inode),
|
|
CONFIG_FS_NFFS_NUM_CACHE_INODES, 4);
|
|
K_MEM_SLAB_DEFINE(nffs_cache_block_pool, sizeof(struct nffs_cache_block),
|
|
CONFIG_FS_NFFS_NUM_CACHE_BLOCKS, 4);
|
|
|
|
static int translate_error(int error)
|
|
{
|
|
switch (error) {
|
|
case FS_EOK:
|
|
return 0;
|
|
case FS_ECORRUPT:
|
|
case FS_EHW:
|
|
return -EIO;
|
|
case FS_EOFFSET:
|
|
case FS_EINVAL:
|
|
return -EINVAL;
|
|
case FS_ENOMEM:
|
|
return -ENOMEM;
|
|
case FS_ENOENT:
|
|
return -ENOENT;
|
|
case FS_EEMPTY:
|
|
return -ENODEV;
|
|
case FS_EFULL:
|
|
return -ENOSPC;
|
|
case FS_EUNEXP:
|
|
case FS_EOS:
|
|
return -EIO;
|
|
case FS_EEXIST:
|
|
return -EEXIST;
|
|
case FS_EACCESS:
|
|
return -EACCES;
|
|
case FS_EUNINIT:
|
|
return -EIO;
|
|
}
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
int nffs_os_mempool_init(void)
|
|
{
|
|
/*
|
|
* Just reinitialize slabs here - this is what original implementation
|
|
* does. We assume all references to previously allocated blocks, if
|
|
* any, are invalidated in NFFS code already.
|
|
*/
|
|
|
|
k_mem_slab_init(&nffs_file_pool, _k_mem_slab_buf_nffs_file_pool,
|
|
sizeof(struct nffs_file),
|
|
CONFIG_FS_NFFS_NUM_FILES);
|
|
k_mem_slab_init(&nffs_dir_pool, _k_mem_slab_buf_nffs_dir_pool,
|
|
sizeof(struct nffs_dir),
|
|
CONFIG_FS_NFFS_NUM_DIRS);
|
|
k_mem_slab_init(&nffs_inode_entry_pool,
|
|
_k_mem_slab_buf_nffs_inode_entry_pool,
|
|
sizeof(struct nffs_inode_entry),
|
|
CONFIG_FS_NFFS_NUM_INODES);
|
|
k_mem_slab_init(&nffs_block_entry_pool,
|
|
_k_mem_slab_buf_nffs_block_entry_pool,
|
|
sizeof(struct nffs_hash_entry),
|
|
CONFIG_FS_NFFS_NUM_BLOCKS);
|
|
k_mem_slab_init(&nffs_cache_inode_pool,
|
|
_k_mem_slab_buf_nffs_cache_inode_pool,
|
|
sizeof(struct nffs_cache_inode),
|
|
CONFIG_FS_NFFS_NUM_CACHE_INODES);
|
|
k_mem_slab_init(&nffs_cache_block_pool,
|
|
_k_mem_slab_buf_nffs_cache_block_pool,
|
|
sizeof(struct nffs_cache_block),
|
|
CONFIG_FS_NFFS_NUM_CACHE_BLOCKS);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void *nffs_os_mempool_get(nffs_os_mempool_t *pool)
|
|
{
|
|
int rc;
|
|
void *ptr;
|
|
|
|
rc = k_mem_slab_alloc(pool, &ptr, K_NO_WAIT);
|
|
if (rc) {
|
|
ptr = NULL;
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
int nffs_os_mempool_free(nffs_os_mempool_t *pool, void *block)
|
|
{
|
|
k_mem_slab_free(pool, &block);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int nffs_os_flash_read(uint8_t id, uint32_t address, void *dst,
|
|
uint32_t num_bytes)
|
|
{
|
|
int rc;
|
|
|
|
rc = flash_read(flash_dev, address, dst, num_bytes);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int nffs_os_flash_write(uint8_t id, uint32_t address, const void *src,
|
|
uint32_t num_bytes)
|
|
{
|
|
int rc;
|
|
|
|
rc = flash_write_protection_set(flash_dev, false);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
rc = flash_write(flash_dev, address, src, num_bytes);
|
|
|
|
/* Ignore errors here - this does not affect write operation */
|
|
(void) flash_write_protection_set(flash_dev, true);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int nffs_os_flash_erase(uint8_t id, uint32_t address, uint32_t num_bytes)
|
|
{
|
|
int rc;
|
|
|
|
rc = flash_write_protection_set(flash_dev, false);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
rc = flash_erase(flash_dev, address, num_bytes);
|
|
|
|
/* Ignore errors here - this does not affect erase operation */
|
|
(void) flash_write_protection_set(flash_dev, true);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int nffs_os_flash_info(uint8_t id, uint32_t sector, uint32_t *address,
|
|
uint32_t *size)
|
|
{
|
|
struct flash_pages_info pi;
|
|
int rc;
|
|
|
|
rc = flash_get_page_info_by_idx(flash_dev, sector, &pi);
|
|
__ASSERT(rc == 0, "Failed to obtain flash page data");
|
|
|
|
*address = pi.start_offset;
|
|
*size = pi.size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint16_t nffs_os_crc16_ccitt(uint16_t initial_crc, const void *buf, int len,
|
|
int final)
|
|
{
|
|
return crc16(buf, len, 0x1021, initial_crc, final);
|
|
}
|
|
|
|
static int inode_to_dirent(struct nffs_inode_entry *inode,
|
|
struct fs_dirent *entry)
|
|
{
|
|
u8_t name_len;
|
|
uint32_t size;
|
|
int rc;
|
|
|
|
rc = nffs_inode_read_filename(inode, sizeof(entry->name), entry->name,
|
|
&name_len);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
if (nffs_hash_id_is_dir(inode->nie_hash_entry.nhe_id)) {
|
|
entry->type = FS_DIR_ENTRY_DIR;
|
|
entry->size = 0;
|
|
} else {
|
|
entry->type = FS_DIR_ENTRY_FILE;
|
|
nffs_inode_data_len(inode, &size);
|
|
entry->size = size;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int nffs_open(struct fs_file_t *zfp, const char *file_name)
|
|
{
|
|
int rc, match_len;
|
|
|
|
k_mutex_lock(&nffs_lock, K_FOREVER);
|
|
|
|
zfp->filep = NULL;
|
|
|
|
if (!nffs_misc_ready()) {
|
|
k_mutex_unlock(&nffs_lock);
|
|
return -ENODEV;
|
|
}
|
|
|
|
match_len = strlen(zfp->mp->mnt_point);
|
|
rc = nffs_file_open((struct nffs_file **)&zfp->filep, &file_name[match_len],
|
|
FS_ACCESS_READ | FS_ACCESS_WRITE);
|
|
|
|
k_mutex_unlock(&nffs_lock);
|
|
|
|
return translate_error(rc);
|
|
}
|
|
|
|
static int nffs_close(struct fs_file_t *zfp)
|
|
{
|
|
int rc;
|
|
|
|
k_mutex_lock(&nffs_lock, K_FOREVER);
|
|
|
|
rc = nffs_file_close(zfp->filep);
|
|
if (!rc) {
|
|
zfp->filep = NULL;
|
|
}
|
|
|
|
k_mutex_unlock(&nffs_lock);
|
|
|
|
return translate_error(rc);
|
|
}
|
|
|
|
static int nffs_unlink(struct fs_mount_t *mountp, const char *path)
|
|
{
|
|
int rc, match_len;
|
|
|
|
k_mutex_lock(&nffs_lock, K_FOREVER);
|
|
|
|
match_len = strlen(mountp->mnt_point);
|
|
rc = nffs_path_unlink(&path[match_len]);
|
|
|
|
k_mutex_unlock(&nffs_lock);
|
|
|
|
return translate_error(rc);
|
|
}
|
|
|
|
static ssize_t nffs_read(struct fs_file_t *zfp, void *ptr, size_t size)
|
|
{
|
|
uint32_t br;
|
|
int rc;
|
|
|
|
k_mutex_lock(&nffs_lock, K_FOREVER);
|
|
|
|
rc = nffs_file_read(zfp->filep, size, ptr, &br);
|
|
|
|
k_mutex_unlock(&nffs_lock);
|
|
|
|
if (rc) {
|
|
return translate_error(rc);
|
|
}
|
|
|
|
return br;
|
|
}
|
|
|
|
static ssize_t nffs_write(struct fs_file_t *zfp, const void *ptr, size_t size)
|
|
{
|
|
int rc;
|
|
|
|
k_mutex_lock(&nffs_lock, K_FOREVER);
|
|
|
|
rc = nffs_write_to_file(zfp->filep, ptr, size);
|
|
|
|
k_mutex_unlock(&nffs_lock);
|
|
|
|
if (rc) {
|
|
return translate_error(rc);
|
|
}
|
|
|
|
/* We need to assume all bytes were written */
|
|
return size;
|
|
}
|
|
|
|
static int nffs_seek(struct fs_file_t *zfp, off_t offset, int whence)
|
|
{
|
|
uint32_t len;
|
|
u32_t pos;
|
|
int rc;
|
|
|
|
k_mutex_lock(&nffs_lock, K_FOREVER);
|
|
|
|
switch (whence) {
|
|
case FS_SEEK_SET:
|
|
pos = offset;
|
|
break;
|
|
case FS_SEEK_CUR:
|
|
pos = ((struct nffs_file *)zfp->filep)->nf_offset + offset;
|
|
break;
|
|
case FS_SEEK_END:
|
|
rc = nffs_inode_data_len(((struct nffs_file *)zfp->filep)->nf_inode_entry, &len);
|
|
if (rc) {
|
|
k_mutex_unlock(&nffs_lock);
|
|
return -EINVAL;
|
|
}
|
|
pos = len + offset;
|
|
break;
|
|
default:
|
|
k_mutex_unlock(&nffs_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = nffs_file_seek(zfp->filep, pos);
|
|
|
|
k_mutex_unlock(&nffs_lock);
|
|
|
|
return translate_error(rc);
|
|
}
|
|
|
|
static off_t nffs_tell(struct fs_file_t *zfp)
|
|
{
|
|
u32_t offset;
|
|
|
|
k_mutex_lock(&nffs_lock, K_FOREVER);
|
|
|
|
if (!zfp->filep) {
|
|
k_mutex_unlock(&nffs_lock);
|
|
return -EIO;
|
|
}
|
|
|
|
offset = ((struct nffs_file *)zfp->filep)->nf_offset;
|
|
|
|
k_mutex_unlock(&nffs_lock);
|
|
|
|
return offset;
|
|
}
|
|
|
|
static int nffs_truncate(struct fs_file_t *zfp, off_t length)
|
|
{
|
|
/*
|
|
* FIXME:
|
|
* There is no API in NFFS to truncate opened file. For now we return
|
|
* ENOTSUP, but this should be revisited if truncation is implemented
|
|
* in NFFS at some point.
|
|
*/
|
|
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static int nffs_sync(struct fs_file_t *zfp)
|
|
{
|
|
/*
|
|
* Files are written to flash immediately so we do not need to support
|
|
* sync call - just return success.
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nffs_mkdir(struct fs_mount_t *mountp, const char *path)
|
|
{
|
|
int rc, match_len;
|
|
|
|
k_mutex_lock(&nffs_lock, K_FOREVER);
|
|
|
|
if (!nffs_misc_ready()) {
|
|
k_mutex_unlock(&nffs_lock);
|
|
return -ENODEV;
|
|
}
|
|
|
|
match_len = strlen(mountp->mnt_point);
|
|
rc = nffs_path_new_dir(&path[match_len], NULL);
|
|
|
|
k_mutex_unlock(&nffs_lock);
|
|
|
|
return translate_error(rc);
|
|
}
|
|
|
|
static int nffs_opendir(struct fs_dir_t *zdp, const char *path)
|
|
{
|
|
int rc, match_len;
|
|
|
|
k_mutex_lock(&nffs_lock, K_FOREVER);
|
|
|
|
zdp->dirp = NULL;
|
|
|
|
if (!nffs_misc_ready()) {
|
|
k_mutex_unlock(&nffs_lock);
|
|
return -ENODEV;
|
|
}
|
|
|
|
match_len = strlen(zdp->mp->mnt_point);
|
|
rc = nffs_dir_open(&path[match_len], (struct nffs_dir **)&zdp->dirp);
|
|
|
|
k_mutex_unlock(&nffs_lock);
|
|
|
|
return translate_error(rc);
|
|
}
|
|
|
|
static int nffs_readdir(struct fs_dir_t *zdp, struct fs_dirent *entry)
|
|
{
|
|
struct nffs_dirent *dirent;
|
|
int rc;
|
|
|
|
k_mutex_lock(&nffs_lock, K_FOREVER);
|
|
|
|
rc = nffs_dir_read(zdp->dirp, &dirent);
|
|
switch (rc) {
|
|
case 0:
|
|
rc = inode_to_dirent(dirent->nde_inode_entry, entry);
|
|
break;
|
|
case FS_ENOENT:
|
|
entry->name[0] = 0;
|
|
rc = 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
k_mutex_unlock(&nffs_lock);
|
|
|
|
return translate_error(rc);
|
|
}
|
|
|
|
static int nffs_closedir(struct fs_dir_t *zdp)
|
|
{
|
|
int rc;
|
|
|
|
k_mutex_lock(&nffs_lock, K_FOREVER);
|
|
|
|
rc = nffs_dir_close(zdp->dirp);
|
|
if (!rc) {
|
|
zdp->dirp = NULL;
|
|
}
|
|
|
|
k_mutex_unlock(&nffs_lock);
|
|
|
|
return translate_error(rc);
|
|
}
|
|
|
|
static int nffs_stat(struct fs_mount_t *mountp,
|
|
const char *path, struct fs_dirent *entry)
|
|
{
|
|
struct nffs_path_parser parser;
|
|
struct nffs_inode_entry *parent;
|
|
struct nffs_inode_entry *inode;
|
|
int rc, match_len;
|
|
|
|
k_mutex_lock(&nffs_lock, K_FOREVER);
|
|
|
|
match_len = strlen(mountp->mnt_point);
|
|
nffs_path_parser_new(&parser, &path[match_len]);
|
|
|
|
rc = nffs_path_find(&parser, &inode, &parent);
|
|
if (rc == 0) {
|
|
rc = inode_to_dirent(inode, entry);
|
|
}
|
|
|
|
k_mutex_unlock(&nffs_lock);
|
|
|
|
return translate_error(rc);
|
|
}
|
|
|
|
static int nffs_statvfs(struct fs_mount_t *mountp,
|
|
const char *path, struct fs_statvfs *stat)
|
|
{
|
|
/*
|
|
* FIXME:
|
|
* There is not API to retrieve such data in NFFS.
|
|
*/
|
|
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static int nffs_rename(struct fs_mount_t *mountp, const char *from,
|
|
const char *to)
|
|
{
|
|
int rc, match_len;
|
|
|
|
k_mutex_lock(&nffs_lock, K_FOREVER);
|
|
|
|
if (!nffs_misc_ready()) {
|
|
k_mutex_unlock(&nffs_lock);
|
|
return -ENODEV;
|
|
}
|
|
|
|
match_len = strlen(mountp->mnt_point);
|
|
rc = nffs_path_rename(&from[match_len], &to[match_len]);
|
|
|
|
k_mutex_unlock(&nffs_lock);
|
|
|
|
return translate_error(rc);
|
|
}
|
|
|
|
static int nffs_mount(struct fs_mount_t *mountp)
|
|
{
|
|
struct nffs_flash_desc *flash_desc =
|
|
(struct nffs_flash_desc *)mountp->fs_data;
|
|
int cnt;
|
|
int rc;
|
|
|
|
/* Set flash device */
|
|
flash_dev = (struct device *)mountp->storage_dev;
|
|
|
|
/* Set flash descriptor fields */
|
|
flash_desc->id = 0;
|
|
flash_desc->sector_count = flash_get_page_count(flash_dev);
|
|
flash_desc->area_offset = FLASH_AREA_STORAGE_OFFSET;
|
|
flash_desc->area_size = FLASH_AREA_STORAGE_SIZE;
|
|
|
|
rc = nffs_misc_reset();
|
|
if (rc) {
|
|
return -EIO;
|
|
}
|
|
|
|
cnt = CONFIG_NFFS_FILESYSTEM_MAX_AREAS;
|
|
rc = nffs_misc_desc_from_flash_area(flash_desc, &cnt, descs);
|
|
if (rc) {
|
|
return -EIO;
|
|
}
|
|
|
|
rc = nffs_restore_full(descs);
|
|
switch (rc) {
|
|
case 0:
|
|
break;
|
|
case FS_ECORRUPT:
|
|
rc = nffs_format_full(descs);
|
|
if (rc) {
|
|
return -EIO;
|
|
}
|
|
break;
|
|
default:
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* File system interface */
|
|
static struct fs_file_system_t nffs_fs = {
|
|
.open = nffs_open,
|
|
.close = nffs_close,
|
|
.read = nffs_read,
|
|
.write = nffs_write,
|
|
.lseek = nffs_seek,
|
|
.tell = nffs_tell,
|
|
.truncate = nffs_truncate,
|
|
.sync = nffs_sync,
|
|
.opendir = nffs_opendir,
|
|
.readdir = nffs_readdir,
|
|
.closedir = nffs_closedir,
|
|
.mount = nffs_mount,
|
|
.unlink = nffs_unlink,
|
|
.rename = nffs_rename,
|
|
.mkdir = nffs_mkdir,
|
|
.stat = nffs_stat,
|
|
.statvfs = nffs_statvfs,
|
|
};
|
|
|
|
static int nffs_init(struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
k_mutex_init(&nffs_lock);
|
|
|
|
return fs_register(FS_NFFS, &nffs_fs);
|
|
}
|
|
|
|
SYS_INIT(nffs_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|