673 lines
17 KiB
C
673 lines
17 KiB
C
/*
|
||
* File : dfs_uffs.c
|
||
* This file is part of Device File System in RT-Thread RTOS
|
||
* COPYRIGHT (C) 2004-2012, RT-Thread Development Team
|
||
*
|
||
* This program is free software; you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License as published by
|
||
* the Free Software Foundation; either version 2 of the License, or
|
||
* (at your option) any later version.
|
||
*
|
||
* This program is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License along
|
||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||
*
|
||
* Change Logs:
|
||
* Date Author Notes
|
||
* 2011-10-22 prife the first version
|
||
* 2012-03-28 prife use mtd device interface
|
||
* 2012-04-05 prife update uffs with official repo and use uffs_UnMount/Mount
|
||
* 2017-04-12 lizhen9880 fix the f_bsize and f_blocks issue in function dfs_uffs_statfs
|
||
*/
|
||
|
||
#include <rtthread.h>
|
||
|
||
#include <dfs_fs.h>
|
||
#include <dfs_file.h>
|
||
#include <rtdevice.h>
|
||
|
||
#include "dfs_uffs.h"
|
||
|
||
#include "uffs/uffs_fd.h" /* posix file api is here */
|
||
#include "uffs/uffs_mtb.h"
|
||
#include "uffs/uffs_mem.h"
|
||
#include "uffs/uffs_utils.h"
|
||
|
||
/*
|
||
* RT-Thread DFS Interface for uffs
|
||
*/
|
||
#define UFFS_DEVICE_MAX 2 /* the max partions on a nand deivce*/
|
||
#define UFFS_MOUNT_PATH_MAX 128 /* the mount point max length */
|
||
#define FILE_PATH_MAX 256 /* the longest file path */
|
||
|
||
struct _nand_dev
|
||
{
|
||
struct rt_mtd_nand_device * dev;
|
||
struct uffs_StorageAttrSt storage;
|
||
uffs_Device uffs_dev;
|
||
uffs_MountTable mount_table;
|
||
char mount_path[UFFS_MOUNT_PATH_MAX];
|
||
void * data; /* when uffs use static buf, it will save ptr here */
|
||
};
|
||
/* make sure the following struct var had been initilased to 0! */
|
||
static struct _nand_dev nand_part[UFFS_DEVICE_MAX] = {0};
|
||
|
||
static int uffs_result_to_dfs(int result)
|
||
{
|
||
int status = -1;
|
||
|
||
result = result < 0 ? -result : result;
|
||
switch (result)
|
||
{
|
||
case UENOERR:/** no error */
|
||
break;
|
||
case UEACCES:/** Tried to open read-only file for writing, or files sharing mode
|
||
does not allow specified operations, or given path is directory */
|
||
status = -EINVAL;
|
||
break;/* no suitable */
|
||
case UEEXIST: /** _O_CREAT and _O_EXCL flags specified, but filename already exists */
|
||
status = -EEXIST;
|
||
break;
|
||
case UEINVAL: /** Invalid oflag or pmode argument */
|
||
status = -EINVAL;
|
||
break;
|
||
case UEMFILE: /** No more file handles available(too many open files) */
|
||
status = -1;
|
||
break;
|
||
case UENOENT: /** file or path not found */
|
||
status = -ENOENT;
|
||
break;
|
||
case UETIME: /** can't set file time */
|
||
status = -1;
|
||
break;
|
||
case UEBADF: /** invalid file handle */
|
||
status = -EBADF;
|
||
break;
|
||
case UENOMEM:/** no enough memory */
|
||
status = -ENOSPC;
|
||
break;
|
||
case UEIOERR: /** I/O error from lower level flash operation */
|
||
status = -EIO;
|
||
break;
|
||
case UENOTDIR: /** Not a directory */
|
||
status = -ENOTDIR;
|
||
break;
|
||
case UEISDIR: /** Is a directory */
|
||
status = -EISDIR;
|
||
break;
|
||
case UEUNKNOWN_ERR:
|
||
default:
|
||
status = -1;
|
||
break; /* unknown error! */
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
static URET _device_init(uffs_Device *dev)
|
||
{
|
||
dev->attr->_private = NULL; // hook nand_chip data structure to attr->_private
|
||
dev->ops = (struct uffs_FlashOpsSt *)&nand_ops;
|
||
|
||
return U_SUCC;
|
||
}
|
||
|
||
static URET _device_release(uffs_Device *dev)
|
||
{
|
||
return U_SUCC;
|
||
}
|
||
|
||
static int init_uffs_fs(
|
||
struct _nand_dev * nand_part)
|
||
{
|
||
uffs_MountTable * mtb;
|
||
struct rt_mtd_nand_device * nand;
|
||
struct uffs_StorageAttrSt * flash_storage;
|
||
|
||
mtb = &nand_part->mount_table;
|
||
nand = nand_part->dev;
|
||
flash_storage = &nand_part->storage;
|
||
|
||
/* setup nand storage attributes */
|
||
uffs_setup_storage(flash_storage, nand);
|
||
|
||
/* register mount table */
|
||
if(mtb->dev)
|
||
{
|
||
/* set memory allocator for uffs */
|
||
#if CONFIG_USE_SYSTEM_MEMORY_ALLOCATOR > 0
|
||
uffs_MemSetupSystemAllocator(&mtb->dev->mem);
|
||
#endif
|
||
/* setup device init/release entry */
|
||
mtb->dev->Init = _device_init;
|
||
mtb->dev->Release = _device_release;
|
||
mtb->dev->attr = flash_storage;
|
||
|
||
uffs_RegisterMountTable(mtb);
|
||
}
|
||
/* mount uffs partion on nand device */
|
||
return uffs_Mount(nand_part->mount_path) == U_SUCC ? 0 : -1;
|
||
}
|
||
|
||
static int dfs_uffs_mount(
|
||
struct dfs_filesystem* fs,
|
||
unsigned long rwflag,
|
||
const void* data)
|
||
{
|
||
rt_base_t index;
|
||
uffs_MountTable * mount_part;
|
||
struct rt_mtd_nand_device * dev;
|
||
|
||
RT_ASSERT(rt_strlen(fs->path) < (UFFS_MOUNT_PATH_MAX-1));
|
||
dev = RT_MTD_NAND_DEVICE(fs->dev_id);
|
||
|
||
/*1. find a empty entry in partition table */
|
||
for (index = 0; index < UFFS_DEVICE_MAX ; index ++)
|
||
{
|
||
if (nand_part[index].dev == RT_NULL)
|
||
break;
|
||
}
|
||
if (index == UFFS_DEVICE_MAX)
|
||
return -ENOENT;
|
||
|
||
/*2. fill partition structure */
|
||
nand_part[index].dev = dev;
|
||
|
||
/* make a right mount path for uffs, end with '/' */
|
||
rt_snprintf(nand_part[index].mount_path, UFFS_MOUNT_PATH_MAX, "%s/", fs->path);
|
||
if (nand_part[index].mount_path[1] == '/')
|
||
nand_part[index].mount_path[1] = 0;
|
||
|
||
mount_part = &(nand_part[index].mount_table);
|
||
mount_part->mount = nand_part[index].mount_path;
|
||
mount_part->dev = &(nand_part[index].uffs_dev);
|
||
rt_memset(mount_part->dev, 0, sizeof(uffs_Device));//in order to make uffs happy.
|
||
mount_part->dev->_private = dev; /* save dev_id into uffs */
|
||
mount_part->start_block = dev->block_start;
|
||
mount_part->end_block = dev->block_end;
|
||
/*3. mount uffs */
|
||
if (init_uffs_fs(&nand_part[index]) < 0)
|
||
{
|
||
return uffs_result_to_dfs(uffs_get_error());
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int dfs_uffs_unmount(struct dfs_filesystem* fs)
|
||
{
|
||
rt_base_t index;
|
||
int result;
|
||
|
||
/* find the device index and then unmount it */
|
||
for (index = 0; index < UFFS_DEVICE_MAX; index++)
|
||
{
|
||
if (nand_part[index].dev == RT_MTD_NAND_DEVICE(fs->dev_id))
|
||
{
|
||
nand_part[index].dev = RT_NULL;
|
||
result = uffs_UnMount(nand_part[index].mount_path);
|
||
if (result != U_SUCC)
|
||
break;
|
||
|
||
result = uffs_UnRegisterMountTable(& nand_part[index].mount_table);
|
||
return (result == U_SUCC) ? RT_EOK : -1;
|
||
}
|
||
}
|
||
return -ENOENT;
|
||
}
|
||
|
||
static int dfs_uffs_mkfs(rt_device_t dev_id)
|
||
{
|
||
rt_base_t index;
|
||
rt_uint32_t block;
|
||
struct rt_mtd_nand_device * mtd;
|
||
|
||
/*1. find the device index */
|
||
for (index = 0; index < UFFS_DEVICE_MAX; index++)
|
||
{
|
||
if (nand_part[index].dev == (struct rt_mtd_nand_device *)dev_id)
|
||
break;
|
||
}
|
||
|
||
if (index == UFFS_DEVICE_MAX)
|
||
{
|
||
/* can't find device driver */
|
||
return -ENOENT;
|
||
}
|
||
|
||
/*2. then unmount the partition */
|
||
uffs_Mount(nand_part[index].mount_path);
|
||
mtd = nand_part[index].dev;
|
||
|
||
/*3. erase all blocks on the partition */
|
||
block = mtd->block_start;
|
||
for (; block <= mtd->block_end; block++)
|
||
{
|
||
rt_mtd_nand_erase_block(mtd, block);
|
||
if (rt_mtd_nand_check_block(mtd, block) != RT_EOK)
|
||
{
|
||
rt_kprintf("found bad block %d\n", block);
|
||
rt_mtd_nand_mark_badblock(mtd, block);
|
||
}
|
||
}
|
||
|
||
/*4. remount it */
|
||
if (init_uffs_fs(&nand_part[index]) < 0)
|
||
{
|
||
return uffs_result_to_dfs(uffs_get_error());
|
||
}
|
||
return RT_EOK;
|
||
}
|
||
|
||
static int dfs_uffs_statfs(struct dfs_filesystem* fs,
|
||
struct statfs *buf)
|
||
{
|
||
rt_base_t index;
|
||
struct rt_mtd_nand_device * mtd = RT_MTD_NAND_DEVICE(fs->dev_id);
|
||
|
||
RT_ASSERT(mtd != RT_NULL);
|
||
|
||
/* find the device index */
|
||
for (index = 0; index < UFFS_DEVICE_MAX; index++)
|
||
{
|
||
if (nand_part[index].dev == (void *)mtd)
|
||
break;
|
||
}
|
||
if (index == UFFS_DEVICE_MAX)
|
||
return -ENOENT;
|
||
|
||
buf->f_bsize = mtd->page_size*mtd->pages_per_block;
|
||
buf->f_blocks = (mtd->block_end - mtd->block_start + 1);
|
||
buf->f_bfree = uffs_GetDeviceFree(&nand_part[index].uffs_dev)/buf->f_bsize ;
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int dfs_uffs_open(struct dfs_fd* file)
|
||
{
|
||
int fd;
|
||
int oflag, mode;
|
||
char * file_path;
|
||
|
||
oflag = file->flags;
|
||
if (oflag & O_DIRECTORY) /* operations about dir */
|
||
{
|
||
uffs_DIR * dir;
|
||
|
||
if (oflag & O_CREAT) /* create a dir*/
|
||
{
|
||
if (uffs_mkdir(file->path) < 0)
|
||
return uffs_result_to_dfs(uffs_get_error());
|
||
}
|
||
/* open dir */
|
||
file_path = rt_malloc(FILE_PATH_MAX);
|
||
if(file_path == RT_NULL)
|
||
return -ENOMEM;
|
||
|
||
if (file->path[0] == '/' && !(file->path[1] == 0))
|
||
rt_snprintf(file_path, FILE_PATH_MAX, "%s/", file->path);
|
||
else
|
||
{
|
||
file_path[0] = '/';
|
||
file_path[1] = 0;
|
||
}
|
||
|
||
dir = uffs_opendir(file_path);
|
||
|
||
if (dir == RT_NULL)
|
||
{
|
||
rt_free(file_path);
|
||
return uffs_result_to_dfs(uffs_get_error());
|
||
}
|
||
/* save this pointer,will used by dfs_uffs_getdents*/
|
||
file->data = dir;
|
||
rt_free(file_path);
|
||
return RT_EOK;
|
||
}
|
||
/* regular file operations */
|
||
/* int uffs_open(const char *name, int oflag, ...); what is this?
|
||
* uffs_open can open dir!! **/
|
||
mode = 0;
|
||
if (oflag & O_RDONLY) mode |= UO_RDONLY;
|
||
if (oflag & O_WRONLY) mode |= UO_WRONLY;
|
||
if (oflag & O_RDWR) mode |= UO_RDWR;
|
||
/* Opens the file, if it is existing. If not, a new file is created. */
|
||
if (oflag & O_CREAT) mode |= UO_CREATE;
|
||
/* Creates a new file. If the file is existing, it is truncated and overwritten. */
|
||
if (oflag & O_TRUNC) mode |= UO_TRUNC;
|
||
/* Creates a new file. The function fails if the file is already existing. */
|
||
if (oflag & O_EXCL) mode |= UO_EXCL;
|
||
|
||
fd = uffs_open(file->path, mode);
|
||
if (fd < 0)
|
||
{
|
||
return uffs_result_to_dfs(uffs_get_error());
|
||
}
|
||
|
||
/* save this pointer, it will be used when calling read()<29><>write(),
|
||
* flush(), seek(), and will be free when calling close()*/
|
||
|
||
file->data = (void *)fd;
|
||
file->pos = uffs_seek(fd, 0, USEEK_CUR);
|
||
file->size = uffs_seek(fd, 0, USEEK_END);
|
||
uffs_seek(fd, file->pos, USEEK_SET);
|
||
|
||
if (oflag & O_APPEND)
|
||
{
|
||
file->pos = uffs_seek(fd, 0, USEEK_END);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int dfs_uffs_close(struct dfs_fd* file)
|
||
{
|
||
int oflag;
|
||
int fd;
|
||
|
||
oflag = file->flags;
|
||
if (oflag & O_DIRECTORY)
|
||
{
|
||
/* operations about dir */
|
||
if (uffs_closedir((uffs_DIR *)(file->data)) < 0)
|
||
return uffs_result_to_dfs(uffs_get_error());
|
||
|
||
return 0;
|
||
}
|
||
/* regular file operations */
|
||
fd = (int)(file->data);
|
||
|
||
if (uffs_close(fd) == 0)
|
||
return 0;
|
||
|
||
return uffs_result_to_dfs(uffs_get_error());
|
||
}
|
||
|
||
static int dfs_uffs_ioctl(struct dfs_fd * file, int cmd, void* args)
|
||
{
|
||
return -ENOSYS;
|
||
}
|
||
|
||
static int dfs_uffs_read(struct dfs_fd * file, void* buf, size_t len)
|
||
{
|
||
int fd;
|
||
int char_read;
|
||
|
||
fd = (int)(file->data);
|
||
char_read = uffs_read(fd, buf, len);
|
||
if (char_read < 0)
|
||
return uffs_result_to_dfs(uffs_get_error());
|
||
|
||
/* update position */
|
||
file->pos = uffs_seek(fd, 0, USEEK_CUR);
|
||
return char_read;
|
||
}
|
||
|
||
static int dfs_uffs_write(struct dfs_fd* file,
|
||
const void* buf,
|
||
size_t len)
|
||
{
|
||
int fd;
|
||
int char_write;
|
||
|
||
fd = (int)(file->data);
|
||
|
||
char_write = uffs_write(fd, buf, len);
|
||
if (char_write < 0)
|
||
return uffs_result_to_dfs(uffs_get_error());
|
||
|
||
/* update position */
|
||
file->pos = uffs_seek(fd, 0, USEEK_CUR);
|
||
return char_write;
|
||
}
|
||
|
||
static int dfs_uffs_flush(struct dfs_fd* file)
|
||
{
|
||
int fd;
|
||
int result;
|
||
|
||
fd = (int)(file->data);
|
||
|
||
result = uffs_flush(fd);
|
||
if (result < 0 )
|
||
return uffs_result_to_dfs(uffs_get_error());
|
||
return 0;
|
||
}
|
||
|
||
int uffs_seekdir(uffs_DIR *dir, long offset)
|
||
{
|
||
int i = 0;
|
||
|
||
while(i < offset)
|
||
{
|
||
if (uffs_readdir(dir) == RT_NULL)
|
||
return -1;
|
||
i++;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
|
||
static int dfs_uffs_seek(struct dfs_fd* file,
|
||
rt_off_t offset)
|
||
{
|
||
int result;
|
||
|
||
/* set offset as current offset */
|
||
if (file->type == FT_DIRECTORY)
|
||
{
|
||
uffs_rewinddir((uffs_DIR *)(file->data));
|
||
result = uffs_seekdir((uffs_DIR *)(file->data), offset/sizeof(struct dirent));
|
||
if (result >= 0)
|
||
{
|
||
file->pos = offset;
|
||
return offset;
|
||
}
|
||
}
|
||
else if (file->type == FT_REGULAR)
|
||
{
|
||
result = uffs_seek((int)(file->data), offset, USEEK_SET);
|
||
if (result >= 0)
|
||
return offset;
|
||
}
|
||
|
||
return uffs_result_to_dfs(uffs_get_error());
|
||
}
|
||
|
||
/* return the size of struct dirent*/
|
||
static int dfs_uffs_getdents(
|
||
struct dfs_fd* file,
|
||
struct dirent* dirp,
|
||
uint32_t count)
|
||
{
|
||
rt_uint32_t index;
|
||
char * file_path;
|
||
struct dirent* d;
|
||
uffs_DIR* dir;
|
||
struct uffs_dirent * uffs_d;
|
||
|
||
dir = (uffs_DIR*)(file->data);
|
||
RT_ASSERT(dir != RT_NULL);
|
||
|
||
/* round count, count is always 1 */
|
||
count = (count / sizeof(struct dirent)) * sizeof(struct dirent);
|
||
if (count == 0) return -EINVAL;
|
||
|
||
/* allocate file name */
|
||
file_path = rt_malloc(FILE_PATH_MAX);
|
||
if (file_path == RT_NULL)
|
||
return -ENOMEM;
|
||
|
||
index = 0;
|
||
/* usually, the while loop should only be looped only once! */
|
||
while (1)
|
||
{
|
||
struct uffs_stat s;
|
||
|
||
d = dirp + index;
|
||
|
||
uffs_d = uffs_readdir(dir);
|
||
if (uffs_d == RT_NULL)
|
||
{
|
||
rt_free(file_path);
|
||
return (uffs_result_to_dfs(uffs_get_error()));
|
||
}
|
||
|
||
if (file->path[0] == '/' && !(file->path[1] == 0))
|
||
rt_snprintf(file_path, FILE_PATH_MAX, "%s/%s", file->path, uffs_d->d_name);
|
||
else
|
||
rt_strncpy(file_path, uffs_d->d_name, FILE_PATH_MAX);
|
||
|
||
uffs_stat(file_path, &s);
|
||
switch(s.st_mode & US_IFMT) /* file type mark */
|
||
{
|
||
case US_IFREG: /* directory */
|
||
d->d_type = DT_REG;
|
||
break;
|
||
case US_IFDIR: /* regular file */
|
||
d->d_type = DT_DIR;
|
||
break;
|
||
case US_IFLNK: /* symbolic link */
|
||
case US_IREAD: /* read permission */
|
||
case US_IWRITE:/* write permission */
|
||
default:
|
||
d->d_type = DT_UNKNOWN;
|
||
break;
|
||
}
|
||
|
||
/* write the rest args of struct dirent* dirp */
|
||
d->d_namlen = rt_strlen(uffs_d->d_name);
|
||
d->d_reclen = (rt_uint16_t)sizeof(struct dirent);
|
||
rt_strncpy(d->d_name, uffs_d->d_name, rt_strlen(uffs_d->d_name) + 1);
|
||
|
||
index ++;
|
||
if (index * sizeof(struct dirent) >= count)
|
||
break;
|
||
}
|
||
|
||
/* free file name buf */
|
||
rt_free(file_path);
|
||
|
||
if (index == 0)
|
||
return uffs_result_to_dfs(uffs_get_error());
|
||
|
||
file->pos += index * sizeof(struct dirent);
|
||
|
||
return index * sizeof(struct dirent);
|
||
}
|
||
|
||
static int dfs_uffs_unlink(struct dfs_filesystem* fs, const char* path)
|
||
{
|
||
int result;
|
||
struct uffs_stat s;
|
||
|
||
/* judge file type, dir is to be delete by uffs_rmdir, others by uffs_remove */
|
||
if (uffs_lstat(path, &s) < 0)
|
||
{
|
||
return uffs_result_to_dfs(uffs_get_error());
|
||
}
|
||
|
||
switch(s.st_mode & US_IFMT)
|
||
{
|
||
case US_IFREG:
|
||
result = uffs_remove(path);
|
||
break;
|
||
case US_IFDIR:
|
||
result = uffs_rmdir(path);
|
||
break;
|
||
default:
|
||
/* unknown file type */
|
||
return -1;
|
||
}
|
||
if (result < 0)
|
||
return uffs_result_to_dfs(uffs_get_error());
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int dfs_uffs_rename(
|
||
struct dfs_filesystem* fs,
|
||
const char* oldpath,
|
||
const char* newpath)
|
||
{
|
||
int result;
|
||
|
||
result = uffs_rename(oldpath, newpath);
|
||
if (result < 0)
|
||
return uffs_result_to_dfs(uffs_get_error());
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int dfs_uffs_stat(struct dfs_filesystem* fs, const char *path, struct stat *st)
|
||
{
|
||
int result;
|
||
struct uffs_stat s;
|
||
|
||
result = uffs_stat(path, &s);
|
||
if (result < 0)
|
||
return uffs_result_to_dfs(uffs_get_error());
|
||
|
||
/* convert uffs stat to dfs stat structure */
|
||
/* FIXME, these field may not be the same */
|
||
st->st_dev = 0;
|
||
st->st_mode = s.st_mode;
|
||
st->st_size = s.st_size;
|
||
st->st_mtime = s.st_mtime;
|
||
|
||
return 0;
|
||
}
|
||
|
||
static const struct dfs_file_ops dfs_uffs_fops =
|
||
{
|
||
dfs_uffs_open,
|
||
dfs_uffs_close,
|
||
dfs_uffs_ioctl,
|
||
dfs_uffs_read,
|
||
dfs_uffs_write,
|
||
dfs_uffs_flush,
|
||
dfs_uffs_seek,
|
||
dfs_uffs_getdents,
|
||
};
|
||
|
||
static const struct dfs_filesystem_ops dfs_uffs_ops =
|
||
{
|
||
"uffs", /* file system type: uffs */
|
||
#if RTTHREAD_VERSION >= 10100
|
||
DFS_FS_FLAG_FULLPATH,
|
||
#else
|
||
#error "uffs can only work with rtthread whose version should >= 1.01\n"
|
||
#endif
|
||
&dfs_uffs_fops,
|
||
|
||
dfs_uffs_mount,
|
||
dfs_uffs_unmount,
|
||
dfs_uffs_mkfs,
|
||
dfs_uffs_statfs,
|
||
|
||
dfs_uffs_unlink,
|
||
dfs_uffs_stat,
|
||
dfs_uffs_rename,
|
||
};
|
||
|
||
int dfs_uffs_init(void)
|
||
{
|
||
/* register uffs file system */
|
||
dfs_register(&dfs_uffs_ops);
|
||
|
||
if (uffs_InitObjectBuf() == U_SUCC)
|
||
{
|
||
if (uffs_DirEntryBufInit() == U_SUCC)
|
||
{
|
||
uffs_InitGlobalFsLock();
|
||
return RT_EOK;
|
||
}
|
||
}
|
||
return -RT_ERROR;
|
||
}
|
||
INIT_COMPONENT_EXPORT(dfs_uffs_init);
|
||
|