From c8eb77e8ffea4987cf79120ba4b65375a4f4ac51 Mon Sep 17 00:00:00 2001
From: Vincent Dupont <vincent@otakeys.com>
Date: Tue, 19 Dec 2017 12:22:02 +0100
Subject: [PATCH] pkg: add littlefs pkg and vfs integration

---
 Makefile.dep                                  |   6 +
 pkg/littlefs/Makefile                         |  15 +
 pkg/littlefs/Makefile.include                 |   5 +
 pkg/littlefs/Makefile.littlefs                |   8 +
 pkg/littlefs/doc.txt                          |   7 +
 pkg/littlefs/fs/Makefile                      |   3 +
 pkg/littlefs/fs/littlefs_fs.c                 | 497 ++++++++++++++++++
 sys/include/fs/littlefs_fs.h                  | 107 ++++
 tests/unittests/tests-littlefs/Makefile       |   1 +
 .../unittests/tests-littlefs/Makefile.include |   6 +
 .../unittests/tests-littlefs/tests-littlefs.c | 404 ++++++++++++++
 .../unittests/tests-littlefs/tests-littlefs.h |  37 ++
 12 files changed, 1096 insertions(+)
 create mode 100644 pkg/littlefs/Makefile
 create mode 100644 pkg/littlefs/Makefile.include
 create mode 100644 pkg/littlefs/Makefile.littlefs
 create mode 100644 pkg/littlefs/doc.txt
 create mode 100644 pkg/littlefs/fs/Makefile
 create mode 100644 pkg/littlefs/fs/littlefs_fs.c
 create mode 100644 sys/include/fs/littlefs_fs.h
 create mode 100644 tests/unittests/tests-littlefs/Makefile
 create mode 100644 tests/unittests/tests-littlefs/Makefile.include
 create mode 100644 tests/unittests/tests-littlefs/tests-littlefs.c
 create mode 100644 tests/unittests/tests-littlefs/tests-littlefs.h

diff --git a/Makefile.dep b/Makefile.dep
index bdc0cac9b9..0b9c59c29c 100644
--- a/Makefile.dep
+++ b/Makefile.dep
@@ -646,6 +646,12 @@ ifneq (,$(filter spiffs,$(USEMODULE)))
   USEMODULE += spiffs_fs
   USEMODULE += mtd
 endif
+ifneq (,$(filter littlefs,$(USEMODULE)))
+  USEPKG += littlefs
+  USEMODULE += vfs
+  USEMODULE += littlefs_fs
+  USEMODULE += mtd
+endif
 
 ifneq (,$(filter l2filter_%,$(USEMODULE)))
   USEMODULE += l2filter
diff --git a/pkg/littlefs/Makefile b/pkg/littlefs/Makefile
new file mode 100644
index 0000000000..3881f76fea
--- /dev/null
+++ b/pkg/littlefs/Makefile
@@ -0,0 +1,15 @@
+PKG_NAME=littlefs
+PKG_URL=https://github.com/geky/littlefs.git
+PKG_VERSION=be22d3449f23a24e7462114349ce04d751e42437
+PKG_BUILDDIR ?= $(PKGDIRBASE)/$(PKG_NAME)
+
+.PHONY: all
+
+all: git-download
+	@mkdir -p "$(PKG_BUILDDIR)/riotbuild"
+	@cp $(PKG_BUILDDIR)/*.c $(PKG_BUILDDIR)/*.h $(PKG_BUILDDIR)/riotbuild
+	@cp $(CURDIR)/Makefile.littlefs $(PKG_BUILDDIR)/riotbuild/Makefile
+
+	"$(MAKE)" -C $(PKG_BUILDDIR)/riotbuild
+
+include $(RIOTBASE)/pkg/pkg.mk
diff --git a/pkg/littlefs/Makefile.include b/pkg/littlefs/Makefile.include
new file mode 100644
index 0000000000..1505758926
--- /dev/null
+++ b/pkg/littlefs/Makefile.include
@@ -0,0 +1,5 @@
+INCLUDES += -I$(PKGDIRBASE)/littlefs/riotbuild/
+
+ifneq (,$(filter littlefs_fs,$(USEMODULE)))
+  DIRS += $(RIOTBASE)/pkg/littlefs/fs
+endif
diff --git a/pkg/littlefs/Makefile.littlefs b/pkg/littlefs/Makefile.littlefs
new file mode 100644
index 0000000000..6cd393189f
--- /dev/null
+++ b/pkg/littlefs/Makefile.littlefs
@@ -0,0 +1,8 @@
+MODULE := littlefs
+
+CFLAGS += -Wno-unused-parameter -Wno-format
+# GCC 4.9 bug (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64480)
+# used by MIPS
+CFLAGS += -Wno-missing-field-initializers
+
+include $(RIOTBASE)/Makefile.base
diff --git a/pkg/littlefs/doc.txt b/pkg/littlefs/doc.txt
new file mode 100644
index 0000000000..261fce9e4a
--- /dev/null
+++ b/pkg/littlefs/doc.txt
@@ -0,0 +1,7 @@
+/**
+ * @defgroup pkg_littlefs   littlefs file system
+ * @ingroup  pkg
+ * @ingroup  sys_fs
+ * @brief    A little fail-safe filesystem designed for embedded systems
+ * @see      https://github.com/geky/littlefs
+ */
diff --git a/pkg/littlefs/fs/Makefile b/pkg/littlefs/fs/Makefile
new file mode 100644
index 0000000000..d62ca553d3
--- /dev/null
+++ b/pkg/littlefs/fs/Makefile
@@ -0,0 +1,3 @@
+MODULE := littlefs_fs
+
+include $(RIOTBASE)/Makefile.base
diff --git a/pkg/littlefs/fs/littlefs_fs.c b/pkg/littlefs/fs/littlefs_fs.c
new file mode 100644
index 0000000000..b8b1736c7f
--- /dev/null
+++ b/pkg/littlefs/fs/littlefs_fs.c
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2017 OTA keys S.A.
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     sys_littlefs
+ * @{
+ *
+ * @file
+ * @brief       littlefs integration with vfs
+ *
+ * @author      Vincent Dupont <vincent@otakeys.com>
+ *
+ * @}
+ */
+
+
+#include <fcntl.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include "fs/littlefs_fs.h"
+
+#include "kernel_defines.h"
+
+#define ENABLE_DEBUG (0)
+#include <debug.h>
+
+static int littlefs_err_to_errno(ssize_t err)
+{
+    switch (err) {
+    case LFS_ERR_OK:
+        return 0;
+    case LFS_ERR_IO:
+        return -EIO;
+    case LFS_ERR_CORRUPT:
+        return -ENODEV;
+    case LFS_ERR_NOENT:
+        return -ENOENT;
+    case LFS_ERR_EXIST:
+        return -EEXIST;
+    case LFS_ERR_NOTDIR:
+        return -ENOTDIR;
+    case LFS_ERR_ISDIR:
+        return -EISDIR;
+    case LFS_ERR_INVAL:
+        return -EINVAL;
+    case LFS_ERR_NOSPC:
+        return -ENOSPC;
+    case LFS_ERR_NOMEM:
+        return -ENOMEM;
+    default:
+        return err;
+    }
+}
+
+static int _dev_read(const struct lfs_config *c, lfs_block_t block,
+                 lfs_off_t off, void *buffer, lfs_size_t size)
+{
+    littlefs_desc_t *fs = c->context;
+    mtd_dev_t *mtd = fs->dev;
+
+    DEBUG("lfs_read: c=%p, block=%" PRIu32 ", off=%" PRIu32 ", buf=%p, size=%" PRIu32 "\n",
+          (void *)c, block, off, buffer, size);
+
+    int ret = mtd_read(mtd, buffer, ((fs->base_addr + block) * c->block_size) + off, size);
+    if (ret >= 0) {
+        return 0;
+    }
+
+    return ret;
+}
+
+static int _dev_write(const struct lfs_config *c, lfs_block_t block,
+                  lfs_off_t off, const void *buffer, lfs_size_t size)
+{
+    littlefs_desc_t *fs = c->context;
+    mtd_dev_t *mtd = fs->dev;
+
+    DEBUG("lfs_write: c=%p, block=%" PRIu32 ", off=%" PRIu32 ", buf=%p, size=%" PRIu32 "\n",
+          (void *)c, block, off, buffer, size);
+
+    int ret = mtd_write(mtd, buffer, ((fs->base_addr + block) * c->block_size) + off, size);
+    if (ret >= 0) {
+        return 0;
+    }
+
+    return ret;
+}
+
+static int _dev_erase(const struct lfs_config *c, lfs_block_t block)
+{
+    littlefs_desc_t *fs = c->context;
+    mtd_dev_t *mtd = fs->dev;
+
+    DEBUG("lfs_erase: c=%p, block=%" PRIu32 "\n", (void *)c, block);
+
+    int ret = mtd_erase(mtd, ((fs->base_addr + block) * c->block_size), c->block_size);
+    if (ret >= 0) {
+        return 0;
+    }
+
+    return ret;
+}
+
+static int _dev_sync(const struct lfs_config *c)
+{
+    (void)c;
+
+    return 0;
+}
+
+static int _mount(vfs_mount_t *mountp)
+{
+    littlefs_desc_t *fs = mountp->private_data;
+
+    mutex_init(&fs->lock);
+    mutex_lock(&fs->lock);
+
+    DEBUG("littlefs: mount: mountp=%p\n", (void *)mountp);
+
+    if (!fs->config.block_count) {
+        fs->config.block_count = fs->dev->sector_count - fs->base_addr;
+    }
+    if (!fs->config.block_size) {
+        fs->config.block_size = fs->dev->page_size * fs->dev->pages_per_sector;
+    }
+    if (!fs->config.prog_size) {
+        fs->config.prog_size = fs->dev->page_size;
+    }
+    if (!fs->config.read_size) {
+        fs->config.read_size = fs->dev->page_size;
+    }
+    fs->config.lookahead = LITTLEFS_LOOKAHEAD_SIZE;
+    fs->config.lookahead_buffer = fs->lookahead_buf;
+    fs->config.context = fs;
+    fs->config.read = _dev_read;
+    fs->config.prog = _dev_write;
+    fs->config.erase = _dev_erase;
+    fs->config.sync = _dev_sync;
+#if LITTLEFS_FILE_BUFFER_SIZE
+    fs->config.file_buffer = fs->file_buf;
+#endif
+#if LITTLEFS_READ_BUFFER_SIZE
+    fs->config.read_buffer = fs->read_buf;
+#endif
+#if LITTLEFS_PROG_BUFFER_SIZE
+    fs->config.prog_buffer = fs->prog_buf;
+#endif
+
+    mtd_init(fs->dev);
+
+    int ret = lfs_mount(&fs->fs, &fs->config);
+    if (ret < 0) {
+        DEBUG("littlefs: formatting\n");
+        ret = lfs_format(&fs->fs, &fs->config);
+        if (ret >= 0) {
+            DEBUG("littlefs: mounting\n");
+            ret = lfs_mount(&fs->fs, &fs->config);
+        }
+    }
+
+    mutex_unlock(&fs->lock);
+
+    return littlefs_err_to_errno(ret);
+}
+
+static int _umount(vfs_mount_t *mountp)
+{
+    littlefs_desc_t *fs = mountp->private_data;
+
+    mutex_lock(&fs->lock);
+
+    DEBUG("littlefs: umount: mountp=%p\n", (void *)mountp);
+
+    int ret = lfs_unmount(&fs->fs);
+    mutex_unlock(&fs->lock);
+
+    return littlefs_err_to_errno(ret);
+}
+
+static int _unlink(vfs_mount_t *mountp, const char *name)
+{
+    littlefs_desc_t *fs = mountp->private_data;
+
+    mutex_lock(&fs->lock);
+
+    DEBUG("littlefs: unlink: mountp=%p, name=%s\n",
+          (void *)mountp, name);
+
+    int ret = lfs_remove(&fs->fs, name);
+    mutex_unlock(&fs->lock);
+
+    return littlefs_err_to_errno(ret);
+}
+
+static int _rename(vfs_mount_t *mountp, const char *from_path, const char *to_path)
+{
+    littlefs_desc_t *fs = mountp->private_data;
+
+    mutex_lock(&fs->lock);
+
+    DEBUG("littlefs: rename: mountp=%p, from=%s, to=%s\n",
+          (void *)mountp, from_path, to_path);
+
+    int ret = lfs_rename(&fs->fs, from_path, to_path);
+    mutex_unlock(&fs->lock);
+
+    return littlefs_err_to_errno(ret);
+}
+
+static int _mkdir(vfs_mount_t *mountp, const char *name, mode_t mode)
+{
+    (void)mode;
+    littlefs_desc_t *fs = mountp->private_data;
+
+    mutex_lock(&fs->lock);
+
+    DEBUG("littlefs: mkdir: mountp=%p, name=%s, mode=%" PRIu32 "\n",
+          (void *)mountp, name, mode);
+
+    int ret = lfs_mkdir(&fs->fs, name);
+    mutex_unlock(&fs->lock);
+
+    return littlefs_err_to_errno(ret);
+}
+
+static int _rmdir(vfs_mount_t *mountp, const char *name)
+{
+    littlefs_desc_t *fs = mountp->private_data;
+
+    mutex_lock(&fs->lock);
+
+    DEBUG("littlefs: rmdir: mountp=%p, name=%s\n",
+          (void *)mountp, name);
+
+    int ret = lfs_remove(&fs->fs, name);
+    mutex_unlock(&fs->lock);
+
+    return littlefs_err_to_errno(ret);
+}
+
+static int _open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path)
+{
+    littlefs_desc_t *fs = filp->mp->private_data;
+    lfs_file_t *fp = (lfs_file_t *)&filp->private_data.buffer;
+    (void) abs_path;
+    (void) mode;
+
+    mutex_lock(&fs->lock);
+
+    DEBUG("littlefs: open: filp=%p, fp=%p\n", (void *)filp, (void *)fp);
+
+    int l_flags = 0;
+    if ((flags & O_ACCMODE) == O_RDONLY) {
+        l_flags |= LFS_O_RDONLY;
+    }
+    if ((flags & O_APPEND) == O_APPEND) {
+        l_flags |= LFS_O_APPEND;
+    }
+    if ((flags & O_TRUNC) == O_TRUNC) {
+        l_flags |= LFS_O_TRUNC;
+    }
+    if ((flags & O_CREAT) == O_CREAT) {
+        l_flags |= LFS_O_CREAT;
+    }
+    if ((flags & O_ACCMODE) == O_WRONLY) {
+        l_flags |= LFS_O_WRONLY;
+    }
+    if ((flags & O_ACCMODE) == O_RDWR) {
+        l_flags |= LFS_O_RDWR;
+    }
+    if ((flags & O_EXCL) == O_EXCL) {
+        l_flags |= LFS_O_EXCL;
+    }
+
+    DEBUG("littlefs: open: %s (abs_path: %s), flags: 0x%x\n", name, abs_path, (int) l_flags);
+
+    int ret = lfs_file_open(&fs->fs, fp, name, l_flags);
+    mutex_unlock(&fs->lock);
+
+    return littlefs_err_to_errno(ret);
+}
+
+static int _close(vfs_file_t *filp)
+{
+    littlefs_desc_t *fs = filp->mp->private_data;
+    lfs_file_t *fp = (lfs_file_t *)&filp->private_data.buffer;
+
+    mutex_lock(&fs->lock);
+
+    DEBUG("littlefs: close: filp=%p, fp=%p\n", (void *)filp, (void *)fp);
+
+    int ret = lfs_file_close(&fs->fs, fp);
+    mutex_unlock(&fs->lock);
+
+    return littlefs_err_to_errno(ret);
+}
+
+static ssize_t _write(vfs_file_t *filp, const void *src, size_t nbytes)
+{
+    littlefs_desc_t *fs = filp->mp->private_data;
+    lfs_file_t *fp = (lfs_file_t *)&filp->private_data.buffer;
+
+    mutex_lock(&fs->lock);
+
+    DEBUG("littlefs: write: filp=%p, fp=%p, src=%p, nbytes=%u\n",
+          (void *)filp, (void *)fp, (void *)src, (unsigned)nbytes);
+
+    ssize_t ret = lfs_file_write(&fs->fs, fp, src, nbytes);
+    mutex_unlock(&fs->lock);
+
+    return littlefs_err_to_errno(ret);
+}
+
+static ssize_t _read(vfs_file_t *filp, void *dest, size_t nbytes)
+{
+    littlefs_desc_t *fs = filp->mp->private_data;
+    lfs_file_t *fp = (lfs_file_t *)&filp->private_data.buffer;
+
+    mutex_lock(&fs->lock);
+
+    DEBUG("littlefs: read: filp=%p, fp=%p, dest=%p, nbytes=%u\n",
+          (void *)filp, (void *)fp, (void *)dest, (unsigned)nbytes);
+
+    ssize_t ret = lfs_file_read(&fs->fs, fp, dest, nbytes);
+    mutex_unlock(&fs->lock);
+
+    return littlefs_err_to_errno(ret);
+}
+
+static off_t _lseek(vfs_file_t *filp, off_t off, int whence)
+{
+    littlefs_desc_t *fs = filp->mp->private_data;
+    lfs_file_t *fp = (lfs_file_t *)&filp->private_data.buffer;
+
+    mutex_lock(&fs->lock);
+
+    DEBUG("littlefs: seek: filp=%p, fp=%p, off=%ld, whence=%d\n",
+          (void *)filp, (void *)fp, (long)off, whence);
+
+    int ret = lfs_file_seek(&fs->fs, fp, off, whence);
+    mutex_unlock(&fs->lock);
+
+    return littlefs_err_to_errno(ret);
+}
+
+static int _stat(vfs_mount_t *mountp, const char *restrict path, struct stat *restrict buf)
+{
+    littlefs_desc_t *fs = mountp->private_data;
+
+    mutex_lock(&fs->lock);
+
+    DEBUG("littlefs: stat: mountp=%p, path=%s, buf=%p\n",
+          (void *)mountp, path, (void *)buf);
+
+    struct lfs_info info;
+    int ret = lfs_stat(&fs->fs, path, &info);
+    mutex_unlock(&fs->lock);
+    /* info.name */
+    buf->st_size = info.size;
+    switch (info.type) {
+    case LFS_TYPE_REG:
+        buf->st_mode = S_IFREG;
+        break;
+    case LFS_TYPE_DIR:
+        buf->st_mode = S_IFDIR;
+        break;
+    }
+
+    return littlefs_err_to_errno(ret);
+}
+
+static int _traverse_cb(void *param, lfs_block_t block)
+{
+    (void)block;
+    unsigned long *nb_blocks = param;
+    (*nb_blocks)++;
+
+    return 0;
+}
+
+static int _statvfs(vfs_mount_t *mountp, const char *restrict path, struct statvfs *restrict buf)
+{
+    (void)path;
+    littlefs_desc_t *fs = mountp->private_data;
+
+    mutex_lock(&fs->lock);
+
+    DEBUG("littlefs: statvfs: mountp=%p, path=%s, buf=%p\n",
+          (void *)mountp, path, (void *)buf);
+
+    unsigned long nb_blocks = 0;
+    int ret = lfs_traverse(&fs->fs, _traverse_cb, &nb_blocks);
+    mutex_unlock(&fs->lock);
+
+    buf->f_bsize = fs->fs.cfg->block_size;      /* block size */
+    buf->f_frsize = fs->fs.cfg->block_size;     /* fundamental block size */
+    buf->f_blocks = fs->fs.cfg->block_count;    /* Blocks total */
+    buf->f_bfree = buf->f_blocks - nb_blocks;   /* Blocks free */
+    buf->f_bavail = buf->f_blocks - nb_blocks;  /* Blocks available to non-privileged processes */
+    buf->f_flag = ST_NOSUID;
+    buf->f_namemax = LFS_NAME_MAX;
+
+    return littlefs_err_to_errno(ret);
+}
+
+static int _opendir(vfs_DIR *dirp, const char *dirname, const char *abs_path)
+{
+    (void)abs_path;
+    littlefs_desc_t *fs = dirp->mp->private_data;
+    lfs_dir_t *dir = (lfs_dir_t *)&dirp->private_data.buffer;
+
+    mutex_lock(&fs->lock);
+
+    DEBUG("littlefs: opendir: dirp=%p, dirname=%s (abs_path=%s)\n",
+          (void *)dirp, dirname, abs_path);
+
+    int ret = lfs_dir_open(&fs->fs, dir, dirname);
+    mutex_unlock(&fs->lock);
+
+    return littlefs_err_to_errno(ret);
+}
+
+static int _readdir(vfs_DIR *dirp, vfs_dirent_t *entry)
+{
+    littlefs_desc_t *fs = dirp->mp->private_data;
+    lfs_dir_t *dir = (lfs_dir_t *)&dirp->private_data.buffer;
+
+    mutex_lock(&fs->lock);
+
+    DEBUG("littlefs: readdir: dirp=%p, entry=%p\n",
+          (void *)dirp, (void *)entry);
+
+    struct lfs_info info;
+    int ret = lfs_dir_read(&fs->fs, dir, &info);
+    if (ret >= 0) {
+        entry->d_ino = info.type;
+        entry->d_name[0] = '/';
+        strncpy(entry->d_name + 1, info.name, VFS_NAME_MAX - 1);
+    }
+
+    mutex_unlock(&fs->lock);
+
+    return littlefs_err_to_errno(ret);
+}
+
+static int _closedir(vfs_DIR *dirp)
+{
+    littlefs_desc_t *fs = dirp->mp->private_data;
+    lfs_dir_t *dir = (lfs_dir_t *)&dirp->private_data.buffer;
+
+    mutex_lock(&fs->lock);
+
+    DEBUG("littlefs: closedir: dirp=%p\n", (void *)dirp);
+
+    int ret = lfs_dir_close(&fs->fs, dir);
+    mutex_unlock(&fs->lock);
+
+    return littlefs_err_to_errno(ret);
+}
+
+static const vfs_file_system_ops_t littlefs_fs_ops = {
+    .mount = _mount,
+    .umount = _umount,
+    .unlink = _unlink,
+    .mkdir = _mkdir,
+    .rmdir = _rmdir,
+    .rename = _rename,
+    .stat = _stat,
+    .statvfs = _statvfs,
+};
+
+static const vfs_file_ops_t littlefs_file_ops = {
+    .open = _open,
+    .close = _close,
+    .read = _read,
+    .write = _write,
+    .lseek = _lseek,
+};
+
+static const vfs_dir_ops_t littlefs_dir_ops = {
+    .opendir = _opendir,
+    .readdir = _readdir,
+    .closedir = _closedir,
+};
+
+const vfs_file_system_t littlefs_file_system = {
+    .fs_op = &littlefs_fs_ops,
+    .f_op = &littlefs_file_ops,
+    .d_op = &littlefs_dir_ops,
+};
diff --git a/sys/include/fs/littlefs_fs.h b/sys/include/fs/littlefs_fs.h
new file mode 100644
index 0000000000..ec90d67088
--- /dev/null
+++ b/sys/include/fs/littlefs_fs.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 OTA keys S.A.
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @defgroup    sys_littlefs  littlefs integration
+ * @ingroup     pkg_littlefs
+ * @brief       RIOT integration of littlefs
+ *
+ * @{
+ *
+ * @file
+ * @brief       littlefs integration with vfs
+ *
+ * @author      Vincent Dupont <vincent@otakeys.com>
+ */
+
+#ifndef FS_LITTLEFS_FS_H
+#define FS_LITTLEFS_FS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "vfs.h"
+#include "lfs.h"
+#include "mtd.h"
+#include "mutex.h"
+
+#if VFS_FILE_BUFFER_SIZE < 52
+#error "VFS_FILE_BUFFER_SIZE is too small, at least 52 bytes is required"
+#endif
+
+#if VFS_DIR_BUFFER_SIZE < 44
+#error "VFS_DIR_BUFFER_SIZE is too small, at least 44 bytes is required"
+#endif
+
+/**
+ * @name    littlefs configuration
+ * @{
+ */
+#ifndef LITTLEFS_LOOKAHEAD_SIZE
+/** Default lookahead size */
+#define LITTLEFS_LOOKAHEAD_SIZE     (128)
+#endif
+
+#ifndef LITTLEFS_FILE_BUFFER_SIZE
+/** File buffer size, if 0, dynamic allocation is used.
+ * If set, only one file can be used at a time, must be program size (mtd page size
+ * is used internally as program size) */
+#define LITTLEFS_FILE_BUFFER_SIZE   (0)
+#endif
+
+#ifndef LITTLEFS_READ_BUFFER_SIZE
+/** Read buffer size, if 0, dynamic allocation is used.
+ * If set, it must be read size (mtd page size is used internally as read size) */
+#define LITTLEFS_READ_BUFFER_SIZE   (0)
+#endif
+
+#ifndef LITTLEFS_PROG_BUFFER_SIZE
+/** Prog buffer size, if 0, dynamic allocation is used.
+ * If set, it must be program size */
+#define LITTLEFS_PROG_BUFFER_SIZE   (0)
+#endif
+/** @} */
+
+/**
+ * @brief   littlefs descriptor for vfs integration
+ */
+typedef struct {
+    lfs_t fs;                   /**< littlefs descriptor */
+    struct lfs_config config;   /**< littlefs config */
+    mtd_dev_t *dev;             /**< mtd device to use */
+    mutex_t lock;               /**< mutex */
+    /** first block number to use,
+     * total number of block is defined in @p config.
+     * if set to 0, the total number of sectors from the mtd is used */
+    uint32_t base_addr;
+#if LITTLEFS_FILE_BUFFER_SIZE || DOXYGEN
+    /** file buffer to use internally if LITTLEFS_FILE_BUFFER_SIZE is set */
+    uint8_t file_buf[LITTLEFS_FILE_BUFFER_SIZE];
+#endif
+#if LITTLEFS_READ_BUFFER_SIZE || DOXYGEN
+    /** read buffer to use internally if LITTLEFS_READ_BUFFER_SIZE is set */
+    uint8_t read_buf[LITTLEFS_READ_BUFFER_SIZE];
+#endif
+#if LITTLEFS_PROG_BUFFER_SIZE || DOXYGEN
+    /** prog buffer to use internally if LITTLEFS_PROG_BUFFER_SIZE is set */
+    uint8_t prog_buf[LITTLEFS_PROG_BUFFER_SIZE];
+#endif
+    /** lookahead buffer to use internally */
+    uint8_t lookahead_buf[LITTLEFS_LOOKAHEAD_SIZE / 8];
+} littlefs_desc_t;
+
+/** The littlefs vfs driver */
+extern const vfs_file_system_t littlefs_file_system;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FS_LITTLEFS_FS_H */
+/** @} */
diff --git a/tests/unittests/tests-littlefs/Makefile b/tests/unittests/tests-littlefs/Makefile
new file mode 100644
index 0000000000..48422e909a
--- /dev/null
+++ b/tests/unittests/tests-littlefs/Makefile
@@ -0,0 +1 @@
+include $(RIOTBASE)/Makefile.base
diff --git a/tests/unittests/tests-littlefs/Makefile.include b/tests/unittests/tests-littlefs/Makefile.include
new file mode 100644
index 0000000000..aa1928f832
--- /dev/null
+++ b/tests/unittests/tests-littlefs/Makefile.include
@@ -0,0 +1,6 @@
+USEMODULE += littlefs
+
+# Set vfs file and dir buffer sizes
+CFLAGS += -DVFS_FILE_BUFFER_SIZE=52 -DVFS_DIR_BUFFER_SIZE=44
+# Reduce LFS_NAME_MAX to 31 (as VFS_NAME_MAX default)
+CFLAGS += -DLFS_NAME_MAX=31
diff --git a/tests/unittests/tests-littlefs/tests-littlefs.c b/tests/unittests/tests-littlefs/tests-littlefs.c
new file mode 100644
index 0000000000..e8030ef2d1
--- /dev/null
+++ b/tests/unittests/tests-littlefs/tests-littlefs.c
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2017 OTA keys S.A.
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @{
+ *
+ * @file
+ */
+#include "fs/littlefs_fs.h"
+#include "vfs.h"
+#include "mtd.h"
+#include "board.h"
+
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "embUnit/embUnit.h"
+
+#include "tests-littlefs.h"
+
+/* Define MTD_0 in board.h to use the board mtd if any */
+#ifdef MTD_0
+#define _dev (MTD_0)
+#else
+/* Test mock object implementing a simple RAM-based mtd */
+#ifndef SECTOR_COUNT
+#define SECTOR_COUNT 4
+#endif
+#ifndef PAGE_PER_SECTOR
+#define PAGE_PER_SECTOR 8
+#endif
+#ifndef PAGE_SIZE
+#define PAGE_SIZE 128
+#endif
+
+static uint8_t dummy_memory[PAGE_PER_SECTOR * PAGE_SIZE * SECTOR_COUNT];
+
+static int _init(mtd_dev_t *dev)
+{
+    (void)dev;
+
+    memset(dummy_memory, 0xff, sizeof(dummy_memory));
+    return 0;
+}
+
+static int _read(mtd_dev_t *dev, void *buff, uint32_t addr, uint32_t size)
+{
+    (void)dev;
+
+    if (addr + size > sizeof(dummy_memory)) {
+        return -EOVERFLOW;
+    }
+    memcpy(buff, dummy_memory + addr, size);
+
+    return size;
+}
+
+static int _write(mtd_dev_t *dev, const void *buff, uint32_t addr, uint32_t size)
+{
+    (void)dev;
+
+    if (addr + size > sizeof(dummy_memory)) {
+        return -EOVERFLOW;
+    }
+    if (size > PAGE_SIZE) {
+        return -EOVERFLOW;
+    }
+    memcpy(dummy_memory + addr, buff, size);
+
+    return size;
+}
+
+static int _erase(mtd_dev_t *dev, uint32_t addr, uint32_t size)
+{
+    (void)dev;
+
+    if (size % (PAGE_PER_SECTOR * PAGE_SIZE) != 0) {
+        return -EOVERFLOW;
+    }
+    if (addr % (PAGE_PER_SECTOR * PAGE_SIZE) != 0) {
+        return -EOVERFLOW;
+    }
+    if (addr + size > sizeof(dummy_memory)) {
+        return -EOVERFLOW;
+    }
+    memset(dummy_memory + addr, 0xff, size);
+
+    return 0;
+}
+
+static int _power(mtd_dev_t *dev, enum mtd_power_state power)
+{
+    (void)dev;
+    (void)power;
+    return 0;
+}
+
+static const mtd_desc_t driver = {
+    .init = _init,
+    .read = _read,
+    .write = _write,
+    .erase = _erase,
+    .power = _power,
+};
+
+static mtd_dev_t dev = {
+    .driver = &driver,
+    .sector_count = SECTOR_COUNT,
+    .pages_per_sector = PAGE_PER_SECTOR,
+    .page_size = PAGE_SIZE,
+};
+
+static mtd_dev_t *_dev = (mtd_dev_t*) &dev;
+#endif /* MTD_0 */
+
+static littlefs_desc_t littlefs_desc;
+
+static vfs_mount_t _test_littlefs_mount = {
+    .fs = &littlefs_file_system,
+    .mount_point = "/test-littlefs",
+    .private_data = &littlefs_desc,
+};
+
+static void test_littlefs_setup(void)
+{
+    littlefs_desc.dev = _dev;
+    vfs_mount(&_test_littlefs_mount);
+}
+
+static void test_littlefs_teardown(void)
+{
+    vfs_unlink("/test-littlefs/test.txt");
+    vfs_unlink("/test-littlefs/test0.txt");
+    vfs_unlink("/test-littlefs/test1.txt");
+    vfs_unlink("/test-littlefs/a/test2.txt");
+    vfs_rmdir("/test-littlefs/a");
+    vfs_umount(&_test_littlefs_mount);
+}
+
+static void tests_littlefs_mount_umount(void)
+{
+    int res;
+    res = vfs_umount(&_test_littlefs_mount);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_mount(&_test_littlefs_mount);
+    TEST_ASSERT_EQUAL_INT(0, res);
+}
+
+static void tests_littlefs_open_close(void)
+{
+    int res;
+    res = vfs_open("/test-littlefs/test.txt", O_CREAT | O_RDWR, 0);
+    TEST_ASSERT(res >= 0);
+
+    res = vfs_close(res);
+    TEST_ASSERT_EQUAL_INT(0, res);
+}
+
+static void tests_littlefs_write(void)
+{
+    const char buf[] = "TESTSTRING";
+    char r_buf[2 * sizeof(buf)];
+
+    int res;
+    int fd = vfs_open("/test-littlefs/test.txt", O_CREAT | O_RDWR, 0);
+    TEST_ASSERT(fd >= 0);
+
+    res = vfs_write(fd, buf, sizeof(buf));
+    TEST_ASSERT_EQUAL_INT(sizeof(buf), res);
+
+    res = vfs_lseek(fd, 0, SEEK_SET);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_read(fd, r_buf, sizeof(r_buf));
+    TEST_ASSERT_EQUAL_INT(sizeof(buf), res);
+    TEST_ASSERT_EQUAL_STRING(&buf[0], &r_buf[0]);
+
+    res = vfs_close(fd);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    fd = vfs_open("/test-littlefs/test.txt", O_RDONLY, 0);
+    TEST_ASSERT(fd >= 0);
+
+    res = vfs_read(fd, r_buf, sizeof(r_buf));
+    TEST_ASSERT_EQUAL_INT(sizeof(buf), res);
+    TEST_ASSERT_EQUAL_STRING(&buf[0], &r_buf[0]);
+
+    res = vfs_close(fd);
+    TEST_ASSERT_EQUAL_INT(0, res);
+}
+
+static void tests_littlefs_unlink(void)
+{
+    const char buf[] = "TESTSTRING";
+
+    int res;
+    int fd = vfs_open("/test-littlefs/test.txt", O_CREAT | O_RDWR, 0);
+    TEST_ASSERT(fd >= 0);
+
+    res = vfs_write(fd, buf, sizeof(buf));
+    TEST_ASSERT_EQUAL_INT(sizeof(buf), res);
+
+    res = vfs_close(fd);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_unlink("/test-littlefs/test.txt");
+    TEST_ASSERT_EQUAL_INT(0, res);
+}
+
+static void tests_littlefs_readdir(void)
+{
+    const char buf0[] = "TESTSTRING";
+    const char buf1[] = "TESTTESTSTRING";
+    const char buf2[] = "TESTSTRINGSTRING";
+
+    int res;
+    int fd0 = vfs_open("/test-littlefs/test0.txt", O_CREAT | O_RDWR, 0);
+    TEST_ASSERT(fd0 >= 0);
+
+    int fd1 = vfs_open("/test-littlefs/test1.txt", O_CREAT | O_RDWR, 0);
+    TEST_ASSERT(fd1 >= 0);
+
+    int fd2 = vfs_open("/test-littlefs/a/test2.txt", O_CREAT | O_RDWR, 0);
+    TEST_ASSERT(fd2 < 0);
+
+    res = vfs_mkdir("/test-littlefs/a", 0);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    fd2 = vfs_open("/test-littlefs/a/test2.txt", O_CREAT | O_RDWR, 0);
+    TEST_ASSERT(fd2 >= 0);
+
+    res = vfs_write(fd0, buf0, sizeof(buf0));
+    TEST_ASSERT_EQUAL_INT(sizeof(buf0), res);
+
+    res = vfs_write(fd1, buf1, sizeof(buf1));
+    TEST_ASSERT_EQUAL_INT(sizeof(buf1), res);
+
+    res = vfs_write(fd2, buf2, sizeof(buf2));
+    TEST_ASSERT_EQUAL_INT(sizeof(buf2), res);
+
+    res = vfs_close(fd0);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_close(fd1);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_close(fd2);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    vfs_DIR dirp;
+    res = vfs_opendir(&dirp, "/test-littlefs");
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    vfs_dirent_t entry;
+    int nb_files = 0;
+    do {
+        res = vfs_readdir(&dirp, &entry);
+        if (res == 1 && (strcmp("/test0.txt", &(entry.d_name[0])) == 0 ||
+                         strcmp("/test1.txt", &(entry.d_name[0])) == 0)) {
+            nb_files++;
+        }
+    } while (res == 1);
+
+    TEST_ASSERT_EQUAL_INT(2, nb_files);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_closedir(&dirp);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_opendir(&dirp, "/test-littlefs/a");
+    TEST_ASSERT_EQUAL_INT(0, res);
+    nb_files = 0;
+    do {
+        res = vfs_readdir(&dirp, &entry);
+        if (res == 1 && strcmp("/test2.txt", &(entry.d_name[0])) == 0) {
+            nb_files++;
+        }
+    } while (res == 1);
+
+    TEST_ASSERT_EQUAL_INT(1, nb_files);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_unlink("/test-littlefs/test0.txt");
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_unlink("/test-littlefs/test1.txt");
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_unlink("/test-littlefs/a/test2.txt");
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_closedir(&dirp);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_rmdir("/test-littlefs/a");
+    TEST_ASSERT_EQUAL_INT(0, res);
+}
+
+static void tests_littlefs_rename(void)
+{
+    const char buf[] = "TESTSTRING";
+    char r_buf[2 * sizeof(buf)];
+
+    int res;
+    int fd = vfs_open("/test-littlefs/test.txt", O_CREAT | O_RDWR, 0);
+    TEST_ASSERT(fd >= 0);
+
+    res = vfs_write(fd, buf, sizeof(buf));
+    TEST_ASSERT(res == sizeof(buf));
+
+    res = vfs_lseek(fd, 0, SEEK_SET);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_read(fd, r_buf, sizeof(r_buf));
+    TEST_ASSERT(res == sizeof(buf));
+    TEST_ASSERT_EQUAL_STRING(&buf[0], &r_buf[0]);
+
+    res = vfs_close(fd);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_rename("/test-littlefs/test.txt", "/test-littlefs/test1.txt");
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    fd = vfs_open("/test-littlefs/test.txt", O_RDONLY, 0);
+    TEST_ASSERT(fd < 0);
+
+    fd = vfs_open("/test-littlefs/test1.txt", O_RDONLY, 0);
+    TEST_ASSERT(fd >= 0);
+
+    res = vfs_lseek(fd, 0, SEEK_SET);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_read(fd, r_buf, sizeof(r_buf));
+    TEST_ASSERT(res == sizeof(buf));
+    TEST_ASSERT_EQUAL_STRING(&buf[0], &r_buf[0]);
+
+    res = vfs_close(fd);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_unlink("/test-littlefs/test1.txt");
+    TEST_ASSERT_EQUAL_INT(0, res);
+}
+
+static void tests_littlefs_statvfs(void)
+{
+    const char buf[] = "TESTSTRING";
+    struct statvfs stat1;
+    struct statvfs stat2;
+
+    int res = vfs_statvfs("/test-littlefs/", &stat1);
+    TEST_ASSERT_EQUAL_INT(0, res);
+    TEST_ASSERT_EQUAL_INT(_dev->page_size * _dev->pages_per_sector, stat1.f_bsize);
+    TEST_ASSERT_EQUAL_INT(_dev->page_size * _dev->pages_per_sector, stat1.f_frsize);
+    TEST_ASSERT((_dev->pages_per_sector * _dev->page_size * _dev->sector_count) >=
+                          stat1.f_blocks);
+
+    int fd = vfs_open("/test-littlefs/test.txt", O_CREAT | O_RDWR, 0);
+    TEST_ASSERT(fd >= 0);
+
+    res = vfs_write(fd, buf, sizeof(buf));
+    TEST_ASSERT(res == sizeof(buf));
+
+    res = vfs_close(fd);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    res = vfs_statvfs("/test-littlefs/", &stat2);
+    TEST_ASSERT_EQUAL_INT(0, res);
+
+    TEST_ASSERT_EQUAL_INT(_dev->page_size * _dev->pages_per_sector, stat2.f_bsize);
+    TEST_ASSERT_EQUAL_INT(_dev->page_size * _dev->pages_per_sector, stat2.f_frsize);
+    TEST_ASSERT(stat1.f_bfree > stat2.f_bfree);
+    TEST_ASSERT(stat1.f_bavail > stat2.f_bavail);
+}
+
+Test *tests_littlefs_tests(void)
+{
+    EMB_UNIT_TESTFIXTURES(fixtures) {
+        new_TestFixture(tests_littlefs_mount_umount),
+        new_TestFixture(tests_littlefs_open_close),
+        new_TestFixture(tests_littlefs_write),
+        new_TestFixture(tests_littlefs_unlink),
+        new_TestFixture(tests_littlefs_readdir),
+        new_TestFixture(tests_littlefs_rename),
+        new_TestFixture(tests_littlefs_statvfs),
+    };
+
+    EMB_UNIT_TESTCALLER(littlefs_tests, test_littlefs_setup, test_littlefs_teardown, fixtures);
+
+    return (Test *)&littlefs_tests;
+}
+
+void tests_littlefs(void)
+{
+    TESTS_RUN(tests_littlefs_tests());
+}
+/** @} */
diff --git a/tests/unittests/tests-littlefs/tests-littlefs.h b/tests/unittests/tests-littlefs/tests-littlefs.h
new file mode 100644
index 0000000000..5e983bedb1
--- /dev/null
+++ b/tests/unittests/tests-littlefs/tests-littlefs.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 OTA keys S.A.
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @addtogroup  unittests
+ * @{
+ *
+ * @file
+ * @brief       Unittests for littlefs
+ *
+ * @author      Vincent Dupont <vincent@otakeys.com>
+ */
+#ifndef TESTS_LITTLEFS_H
+#define TESTS_LITTLEFS_H
+
+#include "embUnit.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief   The entry point of this test suite.
+ */
+void tests_littlefs(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TESTS_LITTLEFS_H */
+/** @} */
-- 
GitLab