diff --git a/.gitmodules b/.gitmodules
index 7a3370e..5dddd18 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -10,4 +10,9 @@
 [submodule "bootloader/rboot"]
 	path = bootloader/rboot
 	url = https://github.com/raburton/rboot.git
-
+[submodule "extras/spiffs/spiffs"]
+	path = extras/spiffs/spiffs
+	url = https://github.com/pellepl/spiffs.git
+[submodule "examples/posix_fs/fs-test"]
+	path = examples/posix_fs/fs-test
+	url = https://github.com/sheinz/fs-test
diff --git a/common.mk b/common.mk
index 172fbe2..c3923c8 100644
--- a/common.mk
+++ b/common.mk
@@ -68,7 +68,7 @@ Q := @
 vecho := @echo
 endif
 
-.PHONY: all clean flash erase_flash
+.PHONY: all clean flash erase_flash test size rebuild
 
 all: $(PROGRAM_OUT) $(FW_FILE_1) $(FW_FILE_2) $(FW_FILE)
 
@@ -209,8 +209,9 @@ $(FW_FILE): $(PROGRAM_OUT) $(FIRMWARE_DIR)
 	$(vecho) "FW $@"
 	$(Q) $(ESPTOOL) elf2image --version=2 $(ESPTOOL_ARGS) $< -o $(FW_FILE)
 
-flash: $(FW_FILE)
-	$(ESPTOOL) -p $(ESPPORT) --baud $(ESPBAUD) write_flash $(ESPTOOL_ARGS) 0x0 $(RBOOT_BIN) 0x1000 $(RBOOT_CONF) 0x2000 $(FW_FILE)
+flash: all
+	$(ESPTOOL) -p $(ESPPORT) --baud $(ESPBAUD) write_flash $(ESPTOOL_ARGS) \
+		0x0 $(RBOOT_BIN) 0x1000 $(RBOOT_CONF) 0x2000 $(FW_FILE) $(SPIFFS_ESPTOOL_ARGS)
 
 erase_flash:
 	$(ESPTOOL) -p $(ESPPORT) --baud $(ESPBAUD) erase_flash
diff --git a/core/newlib_syscalls.c b/core/newlib_syscalls.c
index 023872c..577ecda 100644
--- a/core/newlib_syscalls.c
+++ b/core/newlib_syscalls.c
@@ -41,7 +41,7 @@ IRAM caddr_t _sbrk_r (struct _reent *r, int incr)
 }
 
 /* syscall implementation for stdio write to UART */
-long _write_r(struct _reent *r, int fd, const char *ptr, int len )
+__attribute__((weak)) long _write_r(struct _reent *r, int fd, const char *ptr, int len )
 {
     if(fd != r->_stdout->_file) {
         r->_errno = EBADF;
@@ -79,10 +79,23 @@ __attribute__((weak)) long _read_r( struct _reent *r, int fd, char *ptr, int len
 /* Stub syscall implementations follow, to allow compiling newlib functions that
    pull these in via various codepaths
 */
-__attribute__((alias("syscall_returns_enosys"))) int _open_r(struct _reent *r, const char *pathname, int flags, int mode);
-__attribute__((alias("syscall_returns_enosys"))) int _fstat_r(struct _reent *r, int fd, void *buf);
-__attribute__((alias("syscall_returns_enosys"))) int _close_r(struct _reent *r, int fd);
-__attribute__((alias("syscall_returns_enosys"))) off_t _lseek_r(struct _reent *r, int fd, off_t offset, int whence);
+__attribute__((weak, alias("syscall_returns_enosys"))) 
+int _open_r(struct _reent *r, const char *pathname, int flags, int mode);
+
+__attribute__((weak, alias("syscall_returns_enosys"))) 
+int _close_r(struct _reent *r, int fd);
+
+__attribute__((weak, alias("syscall_returns_enosys"))) 
+int _unlink_r(struct _reent *r, const char *path);
+
+__attribute__((weak, alias("syscall_returns_enosys"))) 
+int _fstat_r(struct _reent *r, int fd, void *buf);
+
+__attribute__((weak, alias("syscall_returns_enosys"))) 
+int _stat_r(struct _reent *r, const char *pathname, void *buf);
+
+__attribute__((weak, alias("syscall_returns_enosys"))) 
+off_t _lseek_r(struct _reent *r, int fd, off_t offset, int whence);
 
 /* Generic stub for any newlib syscall that fails with errno ENOSYS
    ("Function not implemented") and a return value equivalent to
diff --git a/examples/posix_fs/Makefile b/examples/posix_fs/Makefile
new file mode 100644
index 0000000..bf45ed7
--- /dev/null
+++ b/examples/posix_fs/Makefile
@@ -0,0 +1,11 @@
+PROGRAM=posix_fs_example
+PROGRAM_EXTRA_SRC_FILES=./fs-test/fs_test.c
+
+EXTRA_COMPONENTS = extras/spiffs
+FLASH_SIZE = 32
+
+# spiffs configuration
+SPIFFS_BASE_ADDR = 0x200000
+SPIFFS_SIZE = 0x100000
+
+include ../../common.mk
diff --git a/examples/posix_fs/README.md b/examples/posix_fs/README.md
new file mode 100644
index 0000000..7f05bd4
--- /dev/null
+++ b/examples/posix_fs/README.md
@@ -0,0 +1,10 @@
+# POSIX file access example
+
+This example runs several file system tests on ESP8266.
+It uses fs-test library to perform file operations test. fs-test library uses
+only POSIX file functions so can be run on host system as well.
+
+Currently included tests:
+ * File system load test. Perform multiple file operations in random order.
+ * File system speed test. Measures files read/write speed.
+
diff --git a/examples/posix_fs/fs-test b/examples/posix_fs/fs-test
new file mode 160000
index 0000000..2ad547a
--- /dev/null
+++ b/examples/posix_fs/fs-test
@@ -0,0 +1 @@
+Subproject commit 2ad547adc5f725594b3c6752f036ff4401b221fc
diff --git a/examples/posix_fs/posix_fs_example.c b/examples/posix_fs/posix_fs_example.c
new file mode 100644
index 0000000..2f5ad14
--- /dev/null
+++ b/examples/posix_fs/posix_fs_example.c
@@ -0,0 +1,55 @@
+#include "espressif/esp_common.h"
+#include "esp/uart.h"
+#include "esp/timer.h"
+#include "FreeRTOS.h"
+#include "task.h"
+#include "esp8266.h"
+#include <stdio.h>
+
+#include "esp_spiffs.h"
+#include "spiffs.h"
+
+#include "fs-test/fs_test.h"
+
+static fs_time_t get_current_time()
+{
+     return timer_get_count(FRC2) / 5000;  // to get roughly 1ms resolution
+}
+
+void test_task(void *pvParameters)
+{
+    esp_spiffs_init();
+    esp_spiffs_mount();
+    SPIFFS_unmount(&fs);  // FS must be unmounted before formating
+    if (SPIFFS_format(&fs) == SPIFFS_OK) {
+        printf("Format complete\n");
+    } else {
+        printf("Format failed\n");
+    }
+    esp_spiffs_mount();
+
+    while (1) {
+        vTaskDelay(5000 / portTICK_RATE_MS);
+        if (fs_load_test_run(100)) {
+            printf("PASS\n");
+        } else {
+            printf("FAIL\n");
+        }
+
+        vTaskDelay(5000 / portTICK_RATE_MS);
+        float write_rate, read_rate;
+        if (fs_speed_test_run(get_current_time, &write_rate, &read_rate)) {
+            printf("Read speed: %.0f bytes/s\n", read_rate * 1000); 
+            printf("Write speed: %.0f bytes/s\n", write_rate * 1000); 
+        } else {
+            printf("FAIL\n");
+        }
+    }
+}
+
+void user_init(void)
+{
+    uart_set_baud(0, 115200);
+
+    xTaskCreate(test_task, (signed char *)"test_task", 1024, NULL, 2, NULL);
+}
diff --git a/examples/spiffs/Makefile b/examples/spiffs/Makefile
new file mode 100644
index 0000000..7327707
--- /dev/null
+++ b/examples/spiffs/Makefile
@@ -0,0 +1,11 @@
+PROGRAM=spiffs_example
+EXTRA_COMPONENTS = extras/spiffs
+FLASH_SIZE = 32
+
+# spiffs configuration
+SPIFFS_BASE_ADDR = 0x200000
+SPIFFS_SIZE = 0x010000
+
+include ../../common.mk
+
+$(eval $(call make_spiffs_image,files))
diff --git a/examples/spiffs/files/test.txt b/examples/spiffs/files/test.txt
new file mode 100644
index 0000000..c86fb0c
--- /dev/null
+++ b/examples/spiffs/files/test.txt
@@ -0,0 +1 @@
+This file will go to SPIFFS image.
diff --git a/examples/spiffs/spiffs_example.c b/examples/spiffs/spiffs_example.c
new file mode 100644
index 0000000..98decaf
--- /dev/null
+++ b/examples/spiffs/spiffs_example.c
@@ -0,0 +1,104 @@
+#include "espressif/esp_common.h"
+#include "esp/uart.h"
+#include "FreeRTOS.h"
+#include "task.h"
+#include "esp8266.h"
+
+#include "fcntl.h"
+#include "unistd.h"
+
+#include "spiffs.h"
+#include "esp_spiffs.h"
+
+
+static void example_read_file_posix()
+{
+    const int buf_size = 0xFF;
+    uint8_t buf[buf_size];
+
+    int fd = open("test.txt", O_RDONLY);
+    if (fd < 0) {
+        printf("Error opening file\n");
+        return;
+    }
+
+    int read_bytes = read(fd, buf, buf_size);
+    printf("Read %d bytes\n", read_bytes);
+
+    buf[read_bytes] = '\0';    // zero terminate string
+    printf("Data: %s\n", buf);
+
+    close(fd);
+}
+
+static void example_read_file_spiffs()
+{
+    const int buf_size = 0xFF;
+    uint8_t buf[buf_size];
+
+    spiffs_file fd = SPIFFS_open(&fs, "other.txt", SPIFFS_RDONLY, 0);
+    if (fd < 0) {
+        printf("Error opening file\n");
+        return;
+    }
+
+    int read_bytes = SPIFFS_read(&fs, fd, buf, buf_size);
+    printf("Read %d bytes\n", read_bytes);
+
+    buf[read_bytes] = '\0';    // zero terminate string
+    printf("Data: %s\n", buf);
+
+    SPIFFS_close(&fs, fd);
+}
+
+static void example_write_file()
+{
+    uint8_t buf[] = "Example data, written by ESP8266";
+
+    int fd = open("other.txt", O_WRONLY|O_CREAT, 0);
+    if (fd < 0) {
+        printf("Error opening file\n");
+        return;
+    }
+
+    int written = write(fd, buf, sizeof(buf));
+    printf("Written %d bytes\n", written);
+
+    close(fd);
+}
+
+static void example_fs_info()
+{
+    uint32_t total, used;
+    SPIFFS_info(&fs, &total, &used);
+    printf("Total: %d bytes, used: %d bytes", total, used);
+}
+
+void test_task(void *pvParameters)
+{
+    esp_spiffs_init();
+    if (esp_spiffs_mount() != SPIFFS_OK) {
+        printf("Error mount SPIFFS\n");
+    }
+
+    while (1) {
+        vTaskDelay(2000 / portTICK_RATE_MS);
+
+        example_write_file();
+
+        example_read_file_posix();
+
+        example_read_file_spiffs();
+
+        example_fs_info();
+
+        printf("\n\n");
+    }
+}
+
+void user_init(void)
+{
+    uart_set_baud(0, 115200);
+
+    xTaskCreate(test_task, (signed char *)"test_task", 1024, NULL, 2, NULL);
+}
diff --git a/extras/spiffs/README.md b/extras/spiffs/README.md
new file mode 100644
index 0000000..76d2081
--- /dev/null
+++ b/extras/spiffs/README.md
@@ -0,0 +1,153 @@
+# SPIFFS ESP8266 File system
+
+This component adds file system support for ESP8266. File system of choice
+for ESP8266 is [SPIFFS](https://github.com/pellepl/spiffs). 
+It was specifically designed to use with SPI NOR flash on embedded systems.
+The main advantage of SPIFFS is wear leveling, which prolongs life time 
+of a flash memory.
+
+## Features
+
+ * SPIFFS - embedded file system for NOR flash memory.
+ * POSIX file operations.
+ * Static files upload to ESP8266 file system within build process.
+ * SPIFFS singleton configuration. Only one instance of FS on a device.
+
+## Usage
+
+In order to use file system in a project the following steps should be made:
+ * Add SPIFFS component in a project Makefile `EXTRA_COMPONENTS = extras/spiffs`
+ * Specify your flash size in the Makefile `FLASH_SIZE = 32`
+ * Specify the start address of file system region on the flash memory
+`SPIFFS_BASE_ADDR = 0x200000`
+ * If you want to upload files to a file system during flash process specify
+the directory with files `$(eval $(call make_spiffs_image,files))`
+
+In the end the Makefile should look like:
+
+```
+PROGRAM=spiffs_example
+EXTRA_COMPONENTS = extras/spiffs
+FLASH_SIZE = 32
+
+SPIFFS_BASE_ADDR = 0x200000
+SPIFFS_SIZE = 0x100000
+
+include ../../common.mk
+
+$(eval $(call make_spiffs_image,files))
+```
+
+Note: Macro call to prepare SPIFFS image for flashing should go after
+`include common.mk`
+
+### Files upload
+
+To upload files to a file system during flash process the following macro is
+used:
+
+```
+$(eval $(call make_spiffs_image,files))
+```
+
+It enables the build of a helper utility **mkspiffs**. This utility creates
+an SPIFFS image with files in the specified directory.
+
+The SPIFFS image is created during build stage, after `make` is run.
+The image is flashed into the device along with firmware during flash stage,
+after `make flash` is run.
+
+**mkspiffs** utility uses the same SPIFFS source code and the same
+configuration as ESP8266. So the created image should always be compatible
+with SPIFFS on a device.
+
+The build process will catch any changes in files directory and rebuild the
+image each time `make` is run.
+The build process will handle SPIFFS_SIZE change and rebuild **mkspiffs**
+utility and the image.
+
+## Example
+
+### Mount
+
+```
+esp_spiffs_init();   // allocate memory buffers
+if (esp_spiffs_mount() != SPIFFS_OK) {
+    printf("Error mounting SPIFFS\n");
+}
+```
+
+### Format
+
+Formatting SPIFFS is a little bit awkward. Before formatting SPIFFS must be
+mounted and unmounted.
+```
+esp_spiffs_init();
+if (esp_spiffs_mount() != SPIFFS_OK) {
+    printf("Error mount SPIFFS\n");
+}
+SPIFFS_unmount(&fs);  // FS must be unmounted before formating
+if (SPIFFS_format(&fs) == SPIFFS_OK) {
+    printf("Format complete\n");
+} else {
+    printf("Format failed\n");
+}
+esp_spiffs_mount();
+```
+
+### POSIX read
+
+Nothing special here.
+
+```
+const int buf_size = 0xFF;
+uint8_t buf[buf_size];
+
+int fd = open("test.txt", O_RDONLY);
+if (fd < 0) {
+    printf("Error opening file\n");
+}
+
+read(fd, buf, buf_size);
+printf("Data: %s\n", buf);
+
+close(fd);
+```
+
+### SPIFFS read
+
+SPIFFS interface is intended to be as close to POSIX as possible.
+
+```
+const int buf_size = 0xFF;
+uint8_t buf[buf_size];
+
+spiffs_file fd = SPIFFS_open(&fs, "other.txt", SPIFFS_RDONLY, 0);
+if (fd < 0) {
+    printf("Error opening file\n");
+}
+
+SPIFFS_read(&fs, fd, buf, buf_size);
+printf("Data: %s\n", buf);
+
+SPIFFS_close(&fs, fd);
+```
+
+### POSIX write
+
+```
+uint8_t buf[] = "Example data, written by ESP8266";
+
+int fd = open("other.txt", O_WRONLY|O_CREAT, 0);
+if (fd < 0) {
+    printf("Error opening file\n");
+}
+
+write(fd, buf, sizeof(buf));
+
+close(fd);
+```
+
+## Resources
+
+[SPIFFS](https://github.com/pellepl/spiffs)
diff --git a/extras/spiffs/component.mk b/extras/spiffs/component.mk
new file mode 100644
index 0000000..f7f5a49
--- /dev/null
+++ b/extras/spiffs/component.mk
@@ -0,0 +1,65 @@
+# Component makefile for extras/spiffs
+
+SPIFFS_BASE_ADDR ?= 0x300000
+SPIFFS_SIZE ?= 0x100000
+
+INC_DIRS += $(spiffs_ROOT)
+INC_DIRS += $(spiffs_ROOT)spiffs/src
+
+# args for passing into compile rule generation
+spiffs_SRC_DIR = $(spiffs_ROOT)spiffs/src
+spiffs_SRC_DIR += $(spiffs_ROOT)
+
+spiffs_CFLAGS = $(CFLAGS)
+spiffs_CFLAGS += -DSPIFFS_BASE_ADDR=$(SPIFFS_BASE_ADDR)
+spiffs_CFLAGS += -DSPIFFS_SIZE=$(SPIFFS_SIZE)
+
+
+# Create an SPIFFS image of specified directory and flash it with
+# the rest of the firmware.
+#
+# Argumens:
+#   $(1) - directory with files which go into spiffs image
+#
+# Example:
+#  $(eval $(call make_spiffs_image,files))
+define make_spiffs_image
+SPIFFS_IMAGE = $(addprefix $(FIRMWARE_DIR),spiffs.bin)
+MKSPIFFS_DIR = $(ROOT)/extras/spiffs/mkspiffs
+MKSPIFFS = $$(MKSPIFFS_DIR)/mkspiffs
+SPIFFS_FILE_LIST = $(shell find $(1))
+
+all: $$(SPIFFS_IMAGE)
+
+clean: clean_spiffs_img clean_mkspiffs
+
+$$(SPIFFS_IMAGE): $$(MKSPIFFS) $$(SPIFFS_FILE_LIST)
+	$$< $(1) $$@
+
+# Rebuild SPIFFS if Makefile is changed, where SPIFF_SIZE is defined
+$$(spiffs_ROOT)spiffs_config.h: Makefile
+	$$(Q) touch $$@
+
+$$(MKSPIFFS)_MAKE:
+	$$(MAKE) -C $$(MKSPIFFS_DIR) SPIFFS_SIZE=$(SPIFFS_SIZE)
+
+# if SPIFFS_SIZE in Makefile is changed rebuild mkspiffs
+$$(MKSPIFFS): Makefile
+	$$(MAKE) -C $$(MKSPIFFS_DIR) clean
+	$$(MAKE) -C $$(MKSPIFFS_DIR) SPIFFS_SIZE=$(SPIFFS_SIZE)
+
+clean_spiffs_img:
+	$$(Q) rm -f $$(SPIFFS_IMAGE)
+
+clean_mkspiffs:
+	$$(Q) $$(MAKE) -C $$(MKSPIFFS_DIR) clean
+
+# run make for mkspiffs always
+all: $$(MKSPIFFS)_MAKE
+
+.PHONY: $$(MKSPIFFS)_MAKE
+
+SPIFFS_ESPTOOL_ARGS = $(SPIFFS_BASE_ADDR) $$(SPIFFS_IMAGE)
+endef
+
+$(eval $(call component_compile_rules,spiffs))
diff --git a/extras/spiffs/esp_spiffs.c b/extras/spiffs/esp_spiffs.c
new file mode 100644
index 0000000..9ae4dea
--- /dev/null
+++ b/extras/spiffs/esp_spiffs.c
@@ -0,0 +1,198 @@
+/**
+ * ESP8266 SPIFFS HAL configuration.
+ *
+ * Part of esp-open-rtos
+ * Copyright (c) 2016 sheinz https://github.com/sheinz
+ * MIT License
+ */
+#include "esp_spiffs.h"
+#include "spiffs.h"
+#include <espressif/spi_flash.h>
+#include <stdbool.h>
+#include <esp/uart.h>
+#include <fcntl.h>
+#include "esp_spiffs_flash.h"
+
+spiffs fs;
+
+typedef struct {
+    void *buf;
+    uint32_t size;
+} fs_buf_t;
+
+static fs_buf_t work_buf = {0};
+static fs_buf_t fds_buf = {0};
+static fs_buf_t cache_buf = {0};
+
+/**
+ * Number of file descriptors opened at the same time
+ */
+#define ESP_SPIFFS_FD_NUMBER       5
+
+#define ESP_SPIFFS_CACHE_PAGES     5
+
+static s32_t esp_spiffs_read(u32_t addr, u32_t size, u8_t *dst)
+{
+    if (esp_spiffs_flash_read(addr, dst, size) == ESP_SPIFFS_FLASH_ERROR) {
+        return SPIFFS_ERR_INTERNAL;
+    }
+
+    return SPIFFS_OK;
+}
+
+static s32_t esp_spiffs_write(u32_t addr, u32_t size, u8_t *src)
+{
+    if (esp_spiffs_flash_write(addr, src, size) == ESP_SPIFFS_FLASH_ERROR) {
+        return SPIFFS_ERR_INTERNAL;
+    }
+
+    return SPIFFS_OK;
+}
+
+static s32_t esp_spiffs_erase(u32_t addr, u32_t size)
+{
+    uint32_t sectors = size / SPI_FLASH_SEC_SIZE;
+
+    for (uint32_t i = 0; i < sectors; i++) {
+        if (esp_spiffs_flash_erase_sector(addr + (SPI_FLASH_SEC_SIZE * i))
+                == ESP_SPIFFS_FLASH_ERROR) {
+            return SPIFFS_ERR_INTERNAL;
+        }
+    }
+
+    return SPIFFS_OK;
+}
+
+void esp_spiffs_init()
+{
+    work_buf.size = 2 * SPIFFS_CFG_LOG_PAGE_SZ();
+    fds_buf.size = SPIFFS_buffer_bytes_for_filedescs(&fs, ESP_SPIFFS_FD_NUMBER);
+    cache_buf.size= SPIFFS_buffer_bytes_for_cache(&fs, ESP_SPIFFS_CACHE_PAGES);
+
+    work_buf.buf = malloc(work_buf.size);
+    fds_buf.buf = malloc(fds_buf.size);
+    cache_buf.buf = malloc(cache_buf.size);
+}
+
+void esp_spiffs_deinit()
+{
+    free(work_buf.buf);
+    work_buf.buf = 0;
+
+    free(fds_buf.buf);
+    fds_buf.buf = 0;
+
+    free(cache_buf.buf);
+    cache_buf.buf = 0;
+}
+
+int32_t esp_spiffs_mount()
+{
+    spiffs_config config = {0};
+
+    config.hal_read_f = esp_spiffs_read;
+    config.hal_write_f = esp_spiffs_write;
+    config.hal_erase_f = esp_spiffs_erase;
+
+    config.fh_ix_offset = 3;
+
+    printf("SPIFFS size: %d\n", SPIFFS_SIZE);
+    printf("SPIFFS memory, work_buf_size=%d, fds_buf_size=%d, cache_buf_size=%d\n",
+            work_buf.size, fds_buf.size, cache_buf.size);
+
+    int32_t err = SPIFFS_mount(&fs, &config, (uint8_t*)work_buf.buf,
+            (uint8_t*)fds_buf.buf, fds_buf.size,
+            cache_buf.buf, cache_buf.size, 0);
+
+    if (err != SPIFFS_OK) {
+        printf("Error spiffs mount: %d\n", err);
+    }
+
+    return err;
+}
+
+// This implementation replaces implementation in core/newlib_syscals.c
+long _write_r(struct _reent *r, int fd, const char *ptr, int len )
+{
+    if(fd != r->_stdout->_file) {
+        return SPIFFS_write(&fs, (spiffs_file)fd, (char*)ptr, len);
+    }
+    for(int i = 0; i < len; i++) {
+        /* Auto convert CR to CRLF, ignore other LFs (compatible with Espressif SDK behaviour) */
+        if(ptr[i] == '\r')
+            continue;
+        if(ptr[i] == '\n')
+            uart_putc(0, '\r');
+        uart_putc(0, ptr[i]);
+    }
+    return len;
+}
+
+// This implementation replaces implementation in core/newlib_syscals.c
+long _read_r( struct _reent *r, int fd, char *ptr, int len )
+{
+    int ch, i;
+
+    if(fd != r->_stdin->_file) {
+        return SPIFFS_read(&fs, (spiffs_file)fd, ptr, len);
+    }
+    uart_rxfifo_wait(0, 1);
+    for(i = 0; i < len; i++) {
+        ch = uart_getc_nowait(0);
+        if (ch < 0) break;
+        ptr[i] = ch;
+    }
+    return i;
+}
+
+int _open_r(struct _reent *r, const char *pathname, int flags, int mode)
+{
+    uint32_t spiffs_flags = SPIFFS_RDONLY;
+
+    if (flags & O_CREAT)    spiffs_flags |= SPIFFS_CREAT;
+    if (flags & O_APPEND)   spiffs_flags |= SPIFFS_APPEND;
+    if (flags & O_TRUNC)    spiffs_flags |= SPIFFS_TRUNC;
+    if (flags & O_RDONLY)   spiffs_flags |= SPIFFS_RDONLY;
+    if (flags & O_WRONLY)   spiffs_flags |= SPIFFS_WRONLY;
+    if (flags & O_EXCL)     spiffs_flags |= SPIFFS_EXCL; 
+    /* if (flags & O_DIRECT)   spiffs_flags |= SPIFFS_DIRECT; no support in newlib */
+
+    return SPIFFS_open(&fs, pathname, spiffs_flags, mode);
+}
+
+int _close_r(struct _reent *r, int fd)
+{
+    return SPIFFS_close(&fs, (spiffs_file)fd);
+}
+
+int _unlink_r(struct _reent *r, const char *path)
+{
+    return SPIFFS_remove(&fs, path);
+}
+
+int _fstat_r(struct _reent *r, int fd, void *buf)
+{
+    spiffs_stat s;
+    struct stat *sb = (struct stat*)buf;
+
+    int result = SPIFFS_fstat(&fs, (spiffs_file)fd, &s);
+    sb->st_size = s.size;
+
+    return result;
+}
+
+int _stat_r(struct _reent *r, const char *pathname, void *buf)
+{
+    spiffs_stat s;
+    struct stat *sb = (struct stat*)buf;
+
+    int result = SPIFFS_stat(&fs, pathname, &s);
+    sb->st_size = s.size;
+
+    return result;
+}
+
+off_t _lseek_r(struct _reent *r, int fd, off_t offset, int whence)
+{
+    return SPIFFS_lseek(&fs, (spiffs_file)fd, offset, whence);
+}
diff --git a/extras/spiffs/esp_spiffs.h b/extras/spiffs/esp_spiffs.h
new file mode 100644
index 0000000..60dc7f0
--- /dev/null
+++ b/extras/spiffs/esp_spiffs.h
@@ -0,0 +1,39 @@
+/**
+ * ESP8266 SPIFFS HAL configuration.
+ *
+ * Part of esp-open-rtos
+ * Copyright (c) 2016 sheinz https://github.com/sheinz
+ * MIT License
+ */
+#ifndef __ESP_SPIFFS_H__
+#define __ESP_SPIFFS_H__
+
+#include "spiffs.h"
+
+extern spiffs fs;
+
+/**
+ * Prepare for SPIFFS mount.
+ *
+ * The function allocates all the necessary buffers.
+ */
+void esp_spiffs_init();
+
+/**
+ * Free all memory buffers that were used by SPIFFS.
+ *
+ * The function should be called after SPIFFS unmount if the file system is not
+ * going to need any more.
+ */
+void esp_spiffs_deinit();
+
+/**
+ * Mount SPIFFS.
+ *
+ * esp_spiffs_init must be called first.
+ *
+ * Return SPIFFS return code.
+ */
+int32_t esp_spiffs_mount();
+
+#endif  // __ESP_SPIFFS_H__
diff --git a/extras/spiffs/esp_spiffs_flash.c b/extras/spiffs/esp_spiffs_flash.c
new file mode 100644
index 0000000..f0a6049
--- /dev/null
+++ b/extras/spiffs/esp_spiffs_flash.c
@@ -0,0 +1,273 @@
+/**
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016 sheinz (https://github.com/sheinz)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "esp_spiffs_flash.h"
+#include "flashchip.h"
+#include "espressif/spi_flash.h"
+#include "FreeRTOS.h"
+#include "esp/rom.h"
+#include "esp/spi_regs.h"
+#include <string.h>
+
+/**
+ * Note about Wait_SPI_Idle.
+ *
+ * Each write/erase flash operation sets BUSY bit in flash status register.
+ * If attempt to access flash while BUSY bit is set operation will fail.
+ * Function Wait_SPI_Idle loops until this bit is not cleared.
+ *
+ * The approach in the following code is that each write function that is
+ * accessible from the outside should leave flash in Idle state.
+ * The read operations doesn't set BUSY bit in a flash. So they do not wait.
+ * They relay that previous operation is completely finished.
+ *
+ * This approach is different from ESP8266 bootrom where Wait_SPI_Idle is
+ * called where it needed and not.
+ */
+
+#define SPI_WRITE_MAX_SIZE  64
+
+// 64 bytes read causes hang
+// http://bbs.espressif.com/viewtopic.php?f=6&t=2439
+#define SPI_READ_MAX_SIZE   60
+
+
+/**
+ * Copy unaligned data to 4-byte aligned destination buffer.
+ *
+ * @param words Number of 4-byte words to write.
+ *
+ * @see unaligned_memcpy.S
+ */
+void memcpy_unaligned_src(volatile uint32_t *dst, uint8_t *src, uint8_t words);
+
+/**
+ * Copy 4-byte aligned source data to unaligned destination buffer.
+ *
+ * @param bytes Number of byte to copy to dst.
+ *
+ * @see unaligned_memcpy.S
+ */
+void memcpy_unaligned_dst(uint8_t *dst, volatile uint32_t *src, uint8_t bytes);
+
+
+/**
+ * Low level SPI flash write. Write block of data up to 64 bytes.
+ */
+static inline void IRAM spi_write_data(sdk_flashchip_t *chip, uint32_t addr,
+        uint8_t *buf, uint32_t size)
+{
+    uint32_t words = size >> 2;
+    if (size & 0b11) {
+        words++;
+    }
+
+    Wait_SPI_Idle(chip);  // wait for previous write to finish
+
+    SPI(0).ADDR = (addr & 0x00FFFFFF) | (size << 24);
+
+    memcpy_unaligned_src(SPI(0).W, buf, words);
+
+    SPI_write_enable(chip);
+
+    SPI(0).CMD = SPI_CMD_PP;
+    while (SPI(0).CMD) {}
+}
+
+/**
+ * Write a page of flash. Data block should not cross page boundary.
+ */
+static uint32_t IRAM spi_write_page(sdk_flashchip_t *flashchip, uint32_t dest_addr,
+    uint8_t *buf, uint32_t size)
+{
+    // check if block to write doesn't cross page boundary
+    if (flashchip->page_size < size + (dest_addr % flashchip->page_size)) {
+        return ESP_SPIFFS_FLASH_ERROR;
+    }
+
+    if (size < 1) {
+        return ESP_SPIFFS_FLASH_OK;
+    }
+
+    while (size >= SPI_WRITE_MAX_SIZE) {
+        spi_write_data(flashchip, dest_addr, buf, SPI_WRITE_MAX_SIZE);
+
+        size -= SPI_WRITE_MAX_SIZE;
+        dest_addr += SPI_WRITE_MAX_SIZE;
+        buf += SPI_WRITE_MAX_SIZE;
+
+        if (size < 1) {
+            return ESP_SPIFFS_FLASH_OK;
+        }
+    }
+
+    spi_write_data(flashchip, dest_addr, buf, size);
+
+    return ESP_SPIFFS_FLASH_OK;
+}
+
+/**
+ * Split block of data into pages and write pages.
+ */
+static uint32_t IRAM spi_write(uint32_t addr, uint8_t *dst, uint32_t size)
+{
+    if (sdk_flashchip.chip_size < (addr + size)) {
+        return ESP_SPIFFS_FLASH_ERROR;
+    }
+
+    uint32_t write_bytes_to_page = sdk_flashchip.page_size -
+        (addr % sdk_flashchip.page_size);  // TODO: place for optimization
+
+    if (size < write_bytes_to_page) {
+        if (spi_write_page(&sdk_flashchip, addr, dst, size)) {
+            return ESP_SPIFFS_FLASH_ERROR;
+        }
+    } else {
+        if (spi_write_page(&sdk_flashchip, addr, dst, write_bytes_to_page)) {
+            return ESP_SPIFFS_FLASH_ERROR;
+        }
+
+        uint32_t offset = write_bytes_to_page;
+        uint32_t pages_to_write = (size - offset) / sdk_flashchip.page_size;
+        for (uint8_t i = 0; i != pages_to_write; i++) {
+            if (spi_write_page(&sdk_flashchip, addr + offset,
+                        dst + offset, sdk_flashchip.page_size)) {
+                return ESP_SPIFFS_FLASH_ERROR;
+            }
+            offset += sdk_flashchip.page_size;
+        }
+
+        if (spi_write_page(&sdk_flashchip, addr + offset,
+                    dst + offset, size - offset)) {
+            return ESP_SPIFFS_FLASH_ERROR;
+        }
+    }
+
+    return ESP_SPIFFS_FLASH_OK;
+}
+
+uint32_t IRAM esp_spiffs_flash_write(uint32_t addr, uint8_t *buf, uint32_t size)
+{
+    uint32_t result = ESP_SPIFFS_FLASH_ERROR;
+
+    if (buf) {
+        vPortEnterCritical();
+        Cache_Read_Disable();
+
+        result = spi_write(addr, buf, size);
+
+        // make sure all write operations is finished before exiting
+        Wait_SPI_Idle(&sdk_flashchip);
+
+        Cache_Read_Enable(0, 0, 1);
+        vPortExitCritical();
+    }
+
+    return result;
+}
+
+/**
+ * Read SPI flash up to 64 bytes.
+ */
+static inline void IRAM read_block(sdk_flashchip_t *chip, uint32_t addr,
+        uint8_t *buf, uint32_t size)
+{
+    SPI(0).ADDR = (addr & 0x00FFFFFF) | (size << 24);
+    SPI(0).CMD = SPI_CMD_READ;
+
+    while (SPI(0).CMD) {};
+
+    memcpy_unaligned_dst(buf, SPI(0).W, size);
+}
+
+/**
+ * Read SPI flash data. Data region doesn't need to be page aligned.
+ */
+static inline uint32_t IRAM read_data(sdk_flashchip_t *flashchip, uint32_t addr,
+        uint8_t *dst, uint32_t size)
+{
+    if (size < 1) {
+        return ESP_SPIFFS_FLASH_OK;
+    }
+
+    if ((addr + size) > flashchip->chip_size) {
+        return ESP_SPIFFS_FLASH_ERROR;
+    }
+
+    while (size >= SPI_READ_MAX_SIZE) {
+        read_block(flashchip, addr, dst, SPI_READ_MAX_SIZE);
+        dst += SPI_READ_MAX_SIZE;
+        size -= SPI_READ_MAX_SIZE;
+        addr += SPI_READ_MAX_SIZE;
+    }
+
+    if (size > 0) {
+        read_block(flashchip, addr, dst, size);
+    }
+
+    return ESP_SPIFFS_FLASH_OK;
+}
+
+uint32_t IRAM esp_spiffs_flash_read(uint32_t dest_addr, uint8_t *buf, uint32_t size)
+{
+    uint32_t result = ESP_SPIFFS_FLASH_ERROR;
+
+    if (buf) {
+        vPortEnterCritical();
+        Cache_Read_Disable();
+
+        result = read_data(&sdk_flashchip, dest_addr, buf, size);
+
+        Cache_Read_Enable(0, 0, 1);
+        vPortExitCritical();
+    }
+
+    return result;
+}
+
+uint32_t IRAM esp_spiffs_flash_erase_sector(uint32_t addr)
+{
+    if ((addr + sdk_flashchip.sector_size) > sdk_flashchip.chip_size) {
+        return ESP_SPIFFS_FLASH_ERROR;
+    }
+
+    if (addr & 0xFFF) {
+        return ESP_SPIFFS_FLASH_ERROR;
+    }
+
+    vPortEnterCritical();
+    Cache_Read_Disable();
+
+    SPI_write_enable(&sdk_flashchip);
+
+    SPI(0).ADDR = addr & 0x00FFFFFF;
+    SPI(0).CMD = SPI_CMD_SE;
+    while (SPI(0).CMD) {};
+
+    Wait_SPI_Idle(&sdk_flashchip);
+
+    Cache_Read_Enable(0, 0, 1);
+    vPortExitCritical();
+
+    return ESP_SPIFFS_FLASH_OK;
+}
diff --git a/extras/spiffs/esp_spiffs_flash.h b/extras/spiffs/esp_spiffs_flash.h
new file mode 100644
index 0000000..abfe864
--- /dev/null
+++ b/extras/spiffs/esp_spiffs_flash.h
@@ -0,0 +1,64 @@
+/**
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016 sheinz (https://github.com/sheinz)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef __ESP_SPIFFS_FLASH_H__
+#define __ESP_SPIFFS_FLASH_H__
+
+#include <stdint.h>
+#include "common_macros.h"
+
+#define ESP_SPIFFS_FLASH_OK        0
+#define ESP_SPIFFS_FLASH_ERROR     1
+
+/**
+ * Read data from SPI flash.
+ *
+ * @param addr Address to read from. Can be not aligned.
+ * @param buf Buffer to read to. Doesn't have to be aligned.
+ * @param size Size of data to read. Buffer size must be >= than data size.
+ *
+ * @return ESP_SPIFFS_FLASH_OK or ESP_SPIFFS_FLASH_ERROR
+ */
+uint32_t IRAM esp_spiffs_flash_read(uint32_t addr, uint8_t *buf, uint32_t size);
+
+/**
+ * Write data to SPI flash.
+ *
+ * @param addr Address to write to. Can be not aligned.
+ * @param buf Buffer of data to write to flash. Doesn't have to be aligned.
+ * @param size Size of data to write. Buffer size must be >= than data size.
+ *
+ * @return ESP_SPIFFS_FLASH_OK or ESP_SPIFFS_FLASH_ERROR
+ */
+uint32_t IRAM esp_spiffs_flash_write(uint32_t addr, uint8_t *buf, uint32_t size);
+
+/**
+ * Erase a sector.
+ *
+ * @param addr Address of sector to erase. Must be sector aligned.
+ *
+ * @return ESP_SPIFFS_FLASH_OK or ESP_SPIFFS_FLASH_ERROR
+ */
+uint32_t IRAM esp_spiffs_flash_erase_sector(uint32_t addr);
+
+#endif  // __ESP_SPIFFS_FLASH_H__
diff --git a/extras/spiffs/mkspiffs/Makefile b/extras/spiffs/mkspiffs/Makefile
new file mode 100644
index 0000000..0b8b49e
--- /dev/null
+++ b/extras/spiffs/mkspiffs/Makefile
@@ -0,0 +1,45 @@
+# Check if SPIFFS_SIZE defined only if not cleaning
+ifneq ($(MAKECMDGOALS),clean)
+ifndef SPIFFS_SIZE
+define ERROR_MSG
+Variable SPIFFS_SIZE is not defined.
+Cannot build mkspiffs without SPIFFS_SIZE.
+Please specify it in your application Makefile.
+
+endef
+$(error $(ERROR_MSG))	
+endif
+endif
+
+# explicitly use gcc as in xtensa build environment it might be set to
+# cross compiler
+CC = gcc
+
+SOURCES := spiffs_hydrogen.c
+SOURCES += spiffs_cache.c
+SOURCES += spiffs_gc.c
+SOURCES += spiffs_check.c
+SOURCES += spiffs_nucleus.c
+SOURCES += mkspiffs.c
+
+OBJECTS := $(SOURCES:.c=.o)
+
+VPATH = ../spiffs/src
+
+CFLAGS += -I..
+CFLAGS += -DSPIFFS_BASE_ADDR=0   # for image base addr is start of the image
+CFLAGS += -DSPIFFS_SIZE=$(SPIFFS_SIZE)
+
+all: mkspiffs
+
+$(OBJECTS): $(SOURCES)
+
+$(OBJECTS): ../spiffs_config.h
+
+mkspiffs: $(OBJECTS)
+
+clean:
+	@rm -f mkspiffs
+	@rm -f *.o
+
+.PHONY: all clean
diff --git a/extras/spiffs/mkspiffs/README.md b/extras/spiffs/mkspiffs/README.md
new file mode 100644
index 0000000..5f6943d
--- /dev/null
+++ b/extras/spiffs/mkspiffs/README.md
@@ -0,0 +1,34 @@
+# mkspiffs Create spiffs image
+
+mkspiffs is a command line utility to create an image of SPIFFS in order
+to write to flash.
+
+## Usage
+
+mkspiffs will be built automatically if you include the following line in your
+makefile:
+
+```
+$(eval $(call make_spiffs_image,files))
+```
+
+where *files* is the directory with files that should go into SPIFFS image.
+
+Or you can build mkspiffs manually with:
+
+```
+make SPIFFS_SIZE=0x100000
+```
+
+mkspiffs cannot be built without specifying SPIFFS size because it uses the
+same SPIFFS sources as the firmware. And for the firmware SPIFFS size is
+compile time defined.
+
+Please note that if you change SPIFFS_SIZE you need to rebuild mkspiffs.
+The easiest way is to run `make clean` for you project.
+
+To manually generate SPIFFS image from directory, run:
+
+```
+mkspiffs DIRECTORY IMAGE_NAME
+```
diff --git a/extras/spiffs/mkspiffs/mkspiffs.c b/extras/spiffs/mkspiffs/mkspiffs.c
new file mode 100644
index 0000000..a1a0513
--- /dev/null
+++ b/extras/spiffs/mkspiffs/mkspiffs.c
@@ -0,0 +1,241 @@
+/**
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016 sheinz (https://github.com/sheinz)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/dir.h>
+#include <dirent.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include "spiffs_config.h"
+#include "../spiffs/src/spiffs.h"
+
+static spiffs fs;
+static void *image = 0;
+static void *work_buf = 0;
+static void *fds_buf = 0;
+static void *cache_buf = 0;
+
+static void print_usage(const char *prog_name, const char *error_msg)
+{
+    if (error_msg) {
+        printf("Error: %s\n", error_msg);
+    }
+    printf("Usage: ");
+    printf("\t%s DIRECTORY IMAGE_NAME\n\n", prog_name);
+    printf("Example:\n");
+    printf("\t%s ./my_files spiffs.img\n\n", prog_name);
+}
+
+static s32_t _read_data(u32_t addr, u32_t size, u8_t *dst)
+{
+    memcpy(dst, (uint8_t*)image + addr, size);
+    return SPIFFS_OK;
+}
+
+static s32_t _write_data(u32_t addr, u32_t size, u8_t *src)
+{
+    uint32_t i;
+    uint8_t *dst = image + addr;
+
+    for (i = 0; i < size; i++) {
+        dst[i] &= src[i];  // mimic NOR flash, flip only 1 to 0
+    }
+    return  SPIFFS_OK;
+}
+
+static s32_t _erase_data(u32_t addr, u32_t size)
+{
+    memset((uint8_t*)image + addr, 0xFF, size);
+    return SPIFFS_OK;
+}
+
+static bool init_spiffs(bool allocate_mem)
+{
+    spiffs_config config = {0};
+    printf("Initializing SPIFFS, size=%d\n", SPIFFS_SIZE);
+
+    config.hal_read_f = _read_data;
+    config.hal_write_f = _write_data;
+    config.hal_erase_f = _erase_data;
+
+    int workBufSize = 2 * SPIFFS_CFG_LOG_PAGE_SZ();
+    int fdsBufSize = SPIFFS_buffer_bytes_for_filedescs(&fs, 5);
+    int cacheBufSize = SPIFFS_buffer_bytes_for_cache(&fs, 5);
+
+    if (allocate_mem) {
+        image = malloc(SPIFFS_SIZE);
+        work_buf = malloc(workBufSize);
+        fds_buf = malloc(fdsBufSize);
+        cache_buf = malloc(cacheBufSize);
+        printf("spiffs memory, work_buf_size=%d, fds_buf_size=%d, cache_buf_size=%d\n",
+                workBufSize, fdsBufSize, cacheBufSize);
+    }
+
+    int32_t err = SPIFFS_mount(&fs, &config, work_buf, fds_buf, fdsBufSize,
+            cache_buf, cacheBufSize, 0);
+
+    return err == SPIFFS_OK;
+}
+
+static bool format_spiffs()
+{
+    SPIFFS_unmount(&fs);
+
+    if (SPIFFS_format(&fs) == SPIFFS_OK) {
+        printf("Format complete\n"); 
+    } else {
+        printf("Failed to format SPIFFS\n");
+        return false;
+    }
+
+    if (!init_spiffs(false)) {
+        printf("Failed to mount SPIFFS\n");
+        return false;
+    }
+    return true;
+}
+
+static void spiffs_free()
+{
+    free(image); 
+    image = NULL;
+
+    free(work_buf);
+    work_buf = NULL;
+
+    free(fds_buf);
+    fds_buf = NULL;
+
+    free(cache_buf);
+    cache_buf = NULL;
+}
+
+static bool process_file(const char *src_file, const char *dst_file)
+{
+    int fd;
+    const int buf_size = 256;
+    uint8_t buf[buf_size];
+    int data_len;
+    
+    fd = open(src_file, O_RDONLY);
+    if (fd < 0) {
+        printf("Error openning file: %s\n", src_file);
+    }
+    
+    spiffs_file out_fd = SPIFFS_open(&fs, dst_file, 
+            SPIFFS_O_CREAT | SPIFFS_O_WRONLY, 0);
+    while ((data_len = read(fd, buf, buf_size)) != 0) {
+        if (SPIFFS_write(&fs, out_fd, buf, data_len) != data_len) {
+            printf("Error writing to SPIFFS file\n");
+            break;
+        }       
+    }
+    SPIFFS_close(&fs, out_fd);
+    close(fd);
+    return true;
+}
+
+static bool process_directory(const char *direcotry)
+{
+    DIR *dp;
+    struct dirent *ep;
+    char path[256];
+
+    dp = opendir(direcotry);
+    if (dp != NULL) {
+        while ((ep = readdir(dp)) != 0) {
+            if (!strcmp(ep->d_name, ".") || 
+                !strcmp(ep->d_name, "..")) {
+                continue;
+            }
+            if (ep->d_type != DT_REG) {
+                continue;  // not a regular file
+            }
+            sprintf(path, "%s/%s", direcotry, ep->d_name);
+            printf("Processing file %s\n", path);
+            if (!process_file(path, ep->d_name)) {
+                printf("Error processing file\n");   
+                break;
+            }
+        }
+        closedir(dp);
+    } else {
+        printf("Error reading direcotry: %s\n", direcotry);
+    }
+    return true;
+}
+
+static bool write_image(const char *out_file)
+{
+    int fd;
+    int size = SPIFFS_SIZE;
+    uint8_t *p = (uint8_t*)image;
+    fd = open(out_file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+    if (fd < 0) {
+        printf("Error creating file %s\n", out_file);
+        return false;
+    }
+
+    printf("Writing image to file: %s\n", out_file);
+
+    while (size != 0) {
+        write(fd, p, SPIFFS_CFG_LOG_PAGE_SZ());
+        p += SPIFFS_CFG_LOG_PAGE_SZ();
+        size -= SPIFFS_CFG_LOG_PAGE_SZ();
+    }
+
+    close(fd);
+    return true;
+}
+
+int main(int argc, char *argv[])
+{
+    int result = 0;
+    
+    if (argc != 3) {
+        print_usage(argv[0], NULL);
+        return -1;
+    }
+
+    init_spiffs(/*allocate_mem=*/true);
+
+    if (format_spiffs()) {
+        if (process_directory(argv[1])) {
+            if (!write_image(argv[2])) {
+                printf("Error writing image\n");
+            }       
+        } else {
+            printf("Error processing direcotry\n");
+        }
+    } else {
+        printf("Error formating spiffs\n");
+    }
+      
+    spiffs_free();
+    return result;
+}
diff --git a/extras/spiffs/spiffs b/extras/spiffs/spiffs
new file mode 160000
index 0000000..c6e94fd
--- /dev/null
+++ b/extras/spiffs/spiffs
@@ -0,0 +1 @@
+Subproject commit c6e94fdca5c1601b90c027167f8d453c48e482c4
diff --git a/extras/spiffs/spiffs_config.h b/extras/spiffs/spiffs_config.h
new file mode 100644
index 0000000..b782d48
--- /dev/null
+++ b/extras/spiffs/spiffs_config.h
@@ -0,0 +1,263 @@
+/*
+ * spiffs_config.h
+ *
+ *  Created on: Jul 3, 2013
+ *      Author: petera
+ */
+
+#ifndef SPIFFS_CONFIG_H_
+#define SPIFFS_CONFIG_H_
+
+// ----------- 8< ------------
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <ctype.h>
+// #include <FreeRTOS.h>    // for vPortEnterCritical/vPortExitCritical
+// ----------- >8 ------------
+
+typedef signed int s32_t;
+typedef unsigned int u32_t;
+typedef signed short s16_t;
+typedef unsigned short u16_t;
+typedef signed char s8_t;
+typedef unsigned char u8_t;
+
+// compile time switches
+
+// Set generic spiffs debug output call.
+#ifndef SPIFFS_DBG
+#define SPIFFS_DBG(...) //printf(__VA_ARGS__)
+#endif
+// Set spiffs debug output call for garbage collecting.
+#ifndef SPIFFS_GC_DBG
+#define SPIFFS_GC_DBG(...) //printf(__VA_ARGS__)
+#endif
+// Set spiffs debug output call for caching.
+#ifndef SPIFFS_CACHE_DBG
+#define SPIFFS_CACHE_DBG(...) //printf(__VA_ARGS__)
+#endif
+// Set spiffs debug output call for system consistency checks.
+#ifndef SPIFFS_CHECK_DBG
+#define SPIFFS_CHECK_DBG(...) //printf(__VA_ARGS__)
+#endif
+
+// Enable/disable API functions to determine exact number of bytes
+// for filedescriptor and cache buffers. Once decided for a configuration,
+// this can be disabled to reduce flash.
+#ifndef SPIFFS_BUFFER_HELP
+#define SPIFFS_BUFFER_HELP              1
+#endif
+
+// Enables/disable memory read caching of nucleus file system operations.
+// If enabled, memory area must be provided for cache in SPIFFS_mount.
+#ifndef  SPIFFS_CACHE
+#define SPIFFS_CACHE                    1
+#endif
+#if SPIFFS_CACHE
+// Enables memory write caching for file descriptors in hydrogen
+#ifndef  SPIFFS_CACHE_WR
+#define SPIFFS_CACHE_WR                 1
+#endif
+
+// Enable/disable statistics on caching. Debug/test purpose only.
+#ifndef  SPIFFS_CACHE_STATS
+#define SPIFFS_CACHE_STATS              0
+#endif
+#endif
+
+// Always check header of each accessed page to ensure consistent state.
+// If enabled it will increase number of reads, will increase flash.
+#ifndef SPIFFS_PAGE_CHECK
+#define SPIFFS_PAGE_CHECK               1
+#endif
+
+// Define maximum number of gc runs to perform to reach desired free pages.
+#ifndef SPIFFS_GC_MAX_RUNS
+#define SPIFFS_GC_MAX_RUNS              3
+#endif
+
+// Enable/disable statistics on gc. Debug/test purpose only.
+#ifndef SPIFFS_GC_STATS
+#define SPIFFS_GC_STATS                 0
+#endif
+
+// Garbage collecting examines all pages in a block which and sums up
+// to a block score. Deleted pages normally gives positive score and
+// used pages normally gives a negative score (as these must be moved).
+// To have a fair wear-leveling, the erase age is also included in score,
+// whose factor normally is the most positive.
+// The larger the score, the more likely it is that the block will
+// picked for garbage collection.
+
+// Garbage collecting heuristics - weight used for deleted pages.
+#ifndef SPIFFS_GC_HEUR_W_DELET
+#define SPIFFS_GC_HEUR_W_DELET          (5)
+#endif
+// Garbage collecting heuristics - weight used for used pages.
+#ifndef SPIFFS_GC_HEUR_W_USED
+#define SPIFFS_GC_HEUR_W_USED           (-1)
+#endif
+// Garbage collecting heuristics - weight used for time between
+// last erased and erase of this block.
+#ifndef SPIFFS_GC_HEUR_W_ERASE_AGE
+#define SPIFFS_GC_HEUR_W_ERASE_AGE      (50)
+#endif
+
+// Object name maximum length. Note that this length include the
+// zero-termination character, meaning maximum string of characters
+// can at most be SPIFFS_OBJ_NAME_LEN - 1.
+#ifndef SPIFFS_OBJ_NAME_LEN
+#define SPIFFS_OBJ_NAME_LEN             (32)
+#endif
+
+// Size of buffer allocated on stack used when copying data.
+// Lower value generates more read/writes. No meaning having it bigger
+// than logical page size.
+#ifndef SPIFFS_COPY_BUFFER_STACK
+#define SPIFFS_COPY_BUFFER_STACK        (64)
+#endif
+
+// Enable this to have an identifiable spiffs filesystem. This will look for
+// a magic in all sectors to determine if this is a valid spiffs system or
+// not on mount point. If not, SPIFFS_format must be called prior to mounting
+// again.
+#ifndef SPIFFS_USE_MAGIC
+#define SPIFFS_USE_MAGIC                (1)
+#endif
+
+#if SPIFFS_USE_MAGIC
+// Only valid when SPIFFS_USE_MAGIC is enabled. If SPIFFS_USE_MAGIC_LENGTH is
+// enabled, the magic will also be dependent on the length of the filesystem.
+// For example, a filesystem configured and formatted for 4 megabytes will not
+// be accepted for mounting with a configuration defining the filesystem as 2
+// megabytes.
+#ifndef SPIFFS_USE_MAGIC_LENGTH
+#define SPIFFS_USE_MAGIC_LENGTH         (1)
+#endif
+#endif
+
+// SPIFFS_LOCK and SPIFFS_UNLOCK protects spiffs from reentrancy on api level
+// These should be defined on a multithreaded system
+
+// define this to enter a mutex if you're running on a multithreaded system
+#ifndef SPIFFS_LOCK
+#define SPIFFS_LOCK(fs)        // vPortEnterCritical()
+#endif
+// define this to exit a mutex if you're running on a multithreaded system
+#ifndef SPIFFS_UNLOCK
+#define SPIFFS_UNLOCK(fs)      // vPortExitCritical()
+#endif
+
+// Enable if only one spiffs instance with constant configuration will exist
+// on the target. This will reduce calculations, flash and memory accesses.
+// Parts of configuration must be defined below instead of at time of mount.
+#ifndef SPIFFS_SINGLETON
+#define SPIFFS_SINGLETON 1
+#endif
+
+#if SPIFFS_SINGLETON
+// Instead of giving parameters in config struct, singleton build must
+// give parameters in defines below.
+#ifndef SPIFFS_CFG_PHYS_SZ
+#define SPIFFS_CFG_PHYS_SZ(ignore)        (SPIFFS_SIZE)
+#endif
+#ifndef SPIFFS_CFG_PHYS_ERASE_SZ
+#define SPIFFS_CFG_PHYS_ERASE_SZ(ignore)  (4*1024)
+#endif
+#ifndef SPIFFS_CFG_PHYS_ADDR
+#define SPIFFS_CFG_PHYS_ADDR(ignore)      (SPIFFS_BASE_ADDR)
+#endif
+#ifndef SPIFFS_CFG_LOG_PAGE_SZ
+#define SPIFFS_CFG_LOG_PAGE_SZ(ignore)    (256)
+#endif
+#ifndef SPIFFS_CFG_LOG_BLOCK_SZ
+#define SPIFFS_CFG_LOG_BLOCK_SZ(ignore)   (8*1024)
+#endif
+#endif
+
+// Enable this if your target needs aligned data for index tables
+#ifndef SPIFFS_ALIGNED_OBJECT_INDEX_TABLES
+#define SPIFFS_ALIGNED_OBJECT_INDEX_TABLES       1
+#endif
+
+// Enable this if you want the HAL callbacks to be called with the spiffs struct
+#ifndef SPIFFS_HAL_CALLBACK_EXTRA
+#define SPIFFS_HAL_CALLBACK_EXTRA         0
+#endif
+
+// Enable this if you want to add an integer offset to all file handles
+// (spiffs_file). This is useful if running multiple instances of spiffs on
+// same target, in order to recognise to what spiffs instance a file handle
+// belongs.
+// NB: This adds config field fh_ix_offset in the configuration struct when
+// mounting, which must be defined.
+#ifndef SPIFFS_FILEHDL_OFFSET
+#define SPIFFS_FILEHDL_OFFSET                 1
+#endif
+
+// Enable this to compile a read only version of spiffs.
+// This will reduce binary size of spiffs. All code comprising modification
+// of the file system will not be compiled. Some config will be ignored.
+// HAL functions for erasing and writing to spi-flash may be null. Cache
+// can be disabled for even further binary size reduction (and ram savings).
+// Functions modifying the fs will return SPIFFS_ERR_RO_NOT_IMPL.
+// If the file system cannot be mounted due to aborted erase operation and
+// SPIFFS_USE_MAGIC is enabled, SPIFFS_ERR_RO_ABORTED_OPERATION will be
+// returned.
+// Might be useful for e.g. bootloaders and such.
+#ifndef SPIFFS_READ_ONLY
+#define SPIFFS_READ_ONLY                      0
+#endif
+
+// Set SPIFFS_TEST_VISUALISATION to non-zero to enable SPIFFS_vis function
+// in the api. This function will visualize all filesystem using given printf
+// function.
+#ifndef SPIFFS_TEST_VISUALISATION
+#define SPIFFS_TEST_VISUALISATION         1
+#endif
+#if SPIFFS_TEST_VISUALISATION
+#ifndef spiffs_printf
+#define spiffs_printf(...)                printf(__VA_ARGS__)
+#endif
+// spiffs_printf argument for a free page
+#ifndef SPIFFS_TEST_VIS_FREE_STR
+#define SPIFFS_TEST_VIS_FREE_STR          "_"
+#endif
+// spiffs_printf argument for a deleted page
+#ifndef SPIFFS_TEST_VIS_DELE_STR
+#define SPIFFS_TEST_VIS_DELE_STR          "/"
+#endif
+// spiffs_printf argument for an index page for given object id
+#ifndef SPIFFS_TEST_VIS_INDX_STR
+#define SPIFFS_TEST_VIS_INDX_STR(id)      "i"
+#endif
+// spiffs_printf argument for a data page for given object id
+#ifndef SPIFFS_TEST_VIS_DATA_STR
+#define SPIFFS_TEST_VIS_DATA_STR(id)      "d"
+#endif
+#endif
+
+// Types depending on configuration such as the amount of flash bytes
+// given to spiffs file system in total (spiffs_file_system_size),
+// the logical block size (log_block_size), and the logical page size
+// (log_page_size)
+
+// Block index type. Make sure the size of this type can hold
+// the highest number of all blocks - i.e. spiffs_file_system_size / log_block_size
+typedef u16_t spiffs_block_ix;
+// Page index type. Make sure the size of this type can hold
+// the highest page number of all pages - i.e. spiffs_file_system_size / log_page_size
+typedef u16_t spiffs_page_ix;
+// Object id type - most significant bit is reserved for index flag. Make sure the
+// size of this type can hold the highest object id on a full system,
+// i.e. 2 + (spiffs_file_system_size / (2*log_page_size))*2
+typedef u16_t spiffs_obj_id;
+// Object span index type. Make sure the size of this type can
+// hold the largest possible span index on the system -
+// i.e. (spiffs_file_system_size / log_page_size) - 1
+typedef u16_t spiffs_span_ix;
+
+#endif /* SPIFFS_CONFIG_H_ */
diff --git a/extras/spiffs/unaligned_memcpy.S b/extras/spiffs/unaligned_memcpy.S
new file mode 100644
index 0000000..b96c92c
--- /dev/null
+++ b/extras/spiffs/unaligned_memcpy.S
@@ -0,0 +1,112 @@
+/**
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016 sheinz (https://github.com/sheinz)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+        .text
+        .section .iram1.text, "x"
+        .literal_position
+
+/**
+ * Copy unaligned data to 4-byte aligned buffer.
+ */
+        .align  4
+        .global memcpy_unaligned_src
+        .type   memcpy_unaligned_src, @function
+memcpy_unaligned_src:
+/* a2: dst, a3: src, a4: size */
+        ssa8l       a3
+        srli        a3, a3, 2
+        slli        a3, a3, 2
+        beqz        a4, u_src_end
+        l32i        a6, a3, 0
+u_src_loop:
+        l32i        a7, a3, 4
+        src         a8, a7, a6
+        memw
+        s32i        a8, a2, 0
+        mov         a6, a7
+        addi        a3, a3, 4
+        addi        a2, a2, 4
+        addi        a4, a4, -1
+        bnez        a4, u_src_loop
+u_src_end:
+        movi a2, 0
+        ret.n
+
+
+/**
+ * Copy data from 4-byte aligned source to unaligned destination buffer.
+ */
+        .align  4
+        .global memcpy_unaligned_dst
+        .type   memcpy_unaligned_dst, @function
+memcpy_unaligned_dst:
+/* a2: dst, a3: src, a4: size */
+        beqz.n       a4, u_dst_end
+        extui        a5, a4, 0, 2
+        beqz.n       a5, aligned_dst_loop
+u_dst_loop:
+        /* Load data word */
+        memw
+        l32i.n       a5, a3, 0
+
+        /* Save byte number 0 */
+        s8i          a5, a2, 0
+        addi.n       a4, a4, -1
+        beqz         a4, u_dst_end
+        addi.n       a2, a2, 1
+
+        /* Shift and save byte number 1 */
+        srli         a5, a5, 8
+        s8i          a5, a2, 0
+        addi.n       a4, a4, -1
+        beqz         a4, u_dst_end
+        addi.n       a2, a2, 1
+
+        /* Shift and save byte number 2 */
+        srli         a5, a5, 8
+        s8i          a5, a2, 0
+        addi.n       a4, a4, -1
+        beqz         a4, u_dst_end
+        addi.n       a2, a2, 1
+
+        /* Shift and save byte number 3 */
+        srli         a5, a5, 8
+        s8i          a5, a2, 0
+        addi.n       a4, a4, -1
+        addi.n       a2, a2, 1
+
+        /* Next word */
+        addi.n       a3, a3, 4
+        bnez.n       a4, u_dst_loop
+        ret.n
+aligned_dst_loop:
+        memw
+        l32i        a5, a3, 0
+        s32i        a5, a2, 0
+        addi.n      a3, a3, 4
+        addi.n      a2, a2, 4
+        addi.n      a4, a4, -4
+        bnez.n      a4, aligned_dst_loop
+u_dst_end:   ret.n
+