/*
* 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 <crc.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;
rc = nffs_inode_data_len(inode, &size);
if (rc) {
return rc;
}
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 = DT_FLASH_AREA_STORAGE_OFFSET;
flash_desc->area_size = DT_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);