/*
* 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/nffs.h>
#include <nffs/os.h>
/*
* 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;
static struct device *flash_dev;
static struct nffs_flash_desc flash_desc;
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(u8_t id, u32_t address, void *dst, u32_t num_bytes)
{
int rc;
rc = flash_read(flash_dev, address, dst, num_bytes);
return rc;
}
int nffs_os_flash_write(u8_t id, u32_t address, const void *src,
u32_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(u8_t id, u32_t address, u32_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(u8_t id, u32_t sector, u32_t *address, u32_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;
}
u16_t nffs_os_crc16_ccitt(u16_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;
u32_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;
}
int fs_open(fs_file_t *zfp, const char *file_name)
{
int rc;
k_mutex_lock(&nffs_lock, K_FOREVER);
zfp->fp = NULL;
if (!nffs_misc_ready()) {
k_mutex_unlock(&nffs_lock);
return -ENODEV;
}
rc = nffs_file_open(&zfp->fp, file_name,
FS_ACCESS_READ | FS_ACCESS_WRITE);
k_mutex_unlock(&nffs_lock);
return translate_error(rc);
}
int fs_close(fs_file_t *zfp)
{
int rc;
k_mutex_lock(&nffs_lock, K_FOREVER);
rc = nffs_file_close(zfp->fp);
if (!rc) {
zfp->fp = NULL;
}
k_mutex_unlock(&nffs_lock);
return translate_error(rc);
}
int fs_unlink(const char *path)
{
int rc;
k_mutex_lock(&nffs_lock, K_FOREVER);
rc = nffs_path_unlink(path);
k_mutex_unlock(&nffs_lock);
return translate_error(rc);
}
ssize_t fs_read(fs_file_t *zfp, void *ptr, size_t size)
{
u32_t br;
int rc;
k_mutex_lock(&nffs_lock, K_FOREVER);
rc = nffs_file_read(zfp->fp, size, ptr, &br);
k_mutex_unlock(&nffs_lock);
if (rc) {
return translate_error(rc);
}
return br;
}
ssize_t fs_write(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->fp, ptr, size);
k_mutex_unlock(&nffs_lock);
if (rc) {
return translate_error(rc);
}
/* We need to assume all bytes were written */
return size;
}
int fs_seek(fs_file_t *zfp, off_t offset, int whence)
{
u32_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 = zfp->fp->nf_offset + offset;
break;
case FS_SEEK_END:
rc = nffs_inode_data_len(zfp->fp->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->fp, pos);
k_mutex_unlock(&nffs_lock);
return translate_error(rc);
}
off_t fs_tell(fs_file_t *zfp)
{
u32_t offset;
k_mutex_lock(&nffs_lock, K_FOREVER);
if (!zfp->fp) {
return -EIO;
k_mutex_unlock(&nffs_lock);
}
offset = zfp->fp->nf_offset;
k_mutex_unlock(&nffs_lock);
return offset;
}
int fs_truncate(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;
}
int fs_sync(fs_file_t *zfp)
{
/*
* Files are written to flash immediately so we do not need to support
* sync call - just return success.
*/
return 0;
}
int fs_mkdir(const char *path)
{
int rc;
k_mutex_lock(&nffs_lock, K_FOREVER);
if (!nffs_misc_ready()) {
k_mutex_unlock(&nffs_lock);
return -ENODEV;
}
rc = nffs_path_new_dir(path, NULL);
k_mutex_unlock(&nffs_lock);
return translate_error(rc);
}
int fs_opendir(fs_dir_t *zdp, const char *path)
{
int rc;
k_mutex_lock(&nffs_lock, K_FOREVER);
zdp->dp = NULL;
if (!nffs_misc_ready()) {
k_mutex_unlock(&nffs_lock);
return -ENODEV;
}
rc = nffs_dir_open(path, &zdp->dp);
k_mutex_unlock(&nffs_lock);
return translate_error(rc);
}
int fs_readdir(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->dp, &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);
}
int fs_closedir(fs_dir_t *zdp)
{
int rc;
k_mutex_lock(&nffs_lock, K_FOREVER);
rc = nffs_dir_close(zdp->dp);
if (!rc) {
zdp->dp = NULL;
}
k_mutex_unlock(&nffs_lock);
return translate_error(rc);
}
int fs_stat(const char *path, struct fs_dirent *entry)
{
struct nffs_path_parser parser;
struct nffs_inode_entry *parent;
struct nffs_inode_entry *inode;
int rc;
k_mutex_lock(&nffs_lock, K_FOREVER);
nffs_path_parser_new(&parser, path);
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);
}
int fs_statvfs(struct fs_statvfs *stat)
{
/*
* FIXME:
* There is not API to retrieve such data in NFFS.
*/
return -ENOTSUP;
}
static int fs_init(struct device *dev)
{
struct nffs_area_desc descs[CONFIG_NFFS_FILESYSTEM_MAX_AREAS + 1];
int cnt;
int rc;
ARG_UNUSED(dev);
k_mutex_init(&nffs_lock);
flash_dev = device_get_binding(CONFIG_FS_NFFS_FLASH_DEV_NAME);
if (!flash_dev) {
return -ENODEV;
}
flash_desc.id = 0;
flash_desc.sector_count = flash_get_page_count(flash_dev);
flash_desc.area_offset = FLASH_AREA_NFFS_OFFSET;
flash_desc.area_size = FLASH_AREA_NFFS_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;
}
SYS_INIT(fs_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);