diff --git a/epicardium/main.c b/epicardium/main.c
index c90d008248e0562c184701099be9ff59d7639b60..50e9438cb3a11c87d58ded7d2fc5eaee650ad961 100644
--- a/epicardium/main.c
+++ b/epicardium/main.c
@@ -15,6 +15,7 @@
 #include "modules/modules.h"
 #include "modules/log.h"
 #include "modules/stream.h"
+#include "modules/filesystem.h"
 #include "api/interrupt-sender.h"
 
 #include <Heart.h>
diff --git a/epicardium/modules/fatfs.c b/epicardium/modules/fatfs.c
deleted file mode 100644
index 1962f4939781097178602e7646c5f1af0f6886b9..0000000000000000000000000000000000000000
--- a/epicardium/modules/fatfs.c
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * support routines for FatFs
- */
-
-#include <errno.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdbool.h>
-#include <stdlib.h>
-
-#include <ff.h>
-
-#include <FreeRTOS.h>
-#include <semphr.h>
-
-#include "modules.h"
-#include "epicardium.h"
-
-#ifndef EPIC_FAT_STATIC_SEMAPHORE
-#define EPIC_FAT_STATIC_SEMAPHORE 0
-#endif
-
-static const TCHAR *rcstrings =
-	_T("OK\0DISK_ERR\0INT_ERR\0NOT_READY\0NO_FILE\0NO_PATH\0INVALID_NAME\0")
-	_T("DENIED\0EXIST\0INVALID_OBJECT\0WRITE_PROTECTED\0INVALID_DRIVE\0")
-	_T("NOT_ENABLED\0NO_FILESYSTEM\0MKFS_ABORTED\0TIMEOUT\0LOCKED\0")
-	_T("NOT_ENOUGH_CORE\0TOO_MANY_OPEN_FILES\0INVALID_PARAMETER\0");
-
-static bool mount(void);
-
-DIR dir;
-FATFS FatFs;
-
-#if (EPIC_FAT_STATIC_SEMAPHORE == 1)
-StaticSemaphore_t xSemaphoreBuffer;
-#endif
-
-static volatile bool s_fatfs_initiaized = false;
-
-void fatfs_init()
-{
-	if (mount()) {
-		s_fatfs_initiaized = true;
-		printf("FatFs mounted\n");
-	}
-}
-
-const char *f_get_rc_string(FRESULT rc)
-{
-	FRESULT i;
-	const char *p = rcstrings;
-
-	for (i = 0; i != rc && *p; i++) {
-		while (*p++)
-			;
-	}
-	return p;
-}
-
-static bool mount()
-{
-	FRESULT res;
-	res = f_mount(&FatFs, "/", 0);
-	if (res != FR_OK) {
-		printf("f_mount error %s\n", f_get_rc_string(res));
-		return false;
-	}
-
-	res = f_opendir(&dir, "0:");
-	if (res != FR_OK) {
-		printf("f_opendir error %s\n", f_get_rc_string(res));
-		return false;
-	}
-
-	return true;
-}
-
-/*------------------------------------------------------------------------*/
-/* Create a Synchronization Object */
-/*------------------------------------------------------------------------*/
-/* This function is called in f_mount() function to create a new
-/  synchronization object for the volume, such as semaphore and mutex.
-/  When a 0 is returned, the f_mount() function fails with FR_INT_ERR.
-*/
-
-/*
- * Return value:
- *   - 1: Function succeeded
- *   - 0: Could not create the sync object
- */
-int ff_cre_syncobj(BYTE vol, FF_SYNC_t *sobj)
-{
-#if (EPIC_FAT_STATIC_SEMAPHORE == 1)
-	*sobj = xSemaphoreCreateMutexStatic(&xSemaphoreBuffer);
-#else
-	*sobj = xSemaphoreCreateMutex();
-#endif //EPIC_FAT_STATIC_SEMAPHORE
-
-	return (int)(*sobj != NULL);
-}
-
-/*------------------------------------------------------------------------*/
-/* Delete a Synchronization Object                                        */
-/*------------------------------------------------------------------------*/
-/* This function is called in f_mount() function to delete a synchronization
-/  object that created with ff_cre_syncobj() function. When a 0 is returned,
-/  the f_mount() function fails with FR_INT_ERR.
-*/
-
-/*
- * Return value:
- *   - 1: Function succeeded
- *   - 0: Could not delete due to an error
- */
-int ff_del_syncobj(FF_SYNC_t sobj)
-{
-	/* FreeRTOS */
-	vSemaphoreDelete(sobj);
-	return 1;
-}
-
-/*------------------------------------------------------------------------*/
-/* Request Grant to Access the Volume                                     */
-/*------------------------------------------------------------------------*/
-/* This function is called on entering file functions to lock the volume.
-/  When a 0 is returned, the file function fails with FR_TIMEOUT.
-*/
-
-/*
- * Return value:
- *   - 1: Got a grant to access the volume
- *   - 0: Could not get a grant
- */
-int ff_req_grant(FF_SYNC_t sobj)
-{
-	/* FreeRTOS */
-	return (int)(xSemaphoreTake(sobj, FF_FS_TIMEOUT) == pdTRUE);
-}
-
-/*------------------------------------------------------------------------*/
-/* Release Grant to Access the Volume                                     */
-/*------------------------------------------------------------------------*/
-/* This function is called on leaving file functions to unlock the volume.
-*/
-
-void ff_rel_grant(FF_SYNC_t sobj)
-{
-	/* FreeRTOS */
-	xSemaphoreGive(sobj);
-}
diff --git a/epicardium/modules/fatfs_fileops.c b/epicardium/modules/fatfs_fileops.c
deleted file mode 100644
index de083fcb432581d8a79129d82a8828637fe57dfc..0000000000000000000000000000000000000000
--- a/epicardium/modules/fatfs_fileops.c
+++ /dev/null
@@ -1,300 +0,0 @@
-#include <errno.h>
-#include <stddef.h>
-#include <stdbool.h>
-#include <stdio.h>
-
-#include <ff.h>
-
-#include "modules.h"
-#include "epicardium.h"
-
-#define EPIC_FAT_FD_GENERATION_BITS (31 - (EPIC_FAT_FD_INDEX_BITS))
-#define EPIC_FAT_MAX_OPENED (1 << (EPIC_FAT_FD_INDEX_BITS))
-#define EPIC_FAT_FD_INDEX_MASK (uint32_t)((1u << EPIC_FAT_FD_INDEX_BITS) - 1)
-#define EPIC_FAT_FD_INDEX(fd) ((uint32_t)(fd)&EPIC_FAT_FD_INDEX_MASK)
-#define EPIC_FAT_FD_GENERATION(fd) ((uint32_t)(fd) >> EPIC_FAT_FD_INDEX_BITS)
-#define EPIC_FAT_FD_MAX_GENERATION                                             \
-	(uint32_t)((1u << EPIC_FAT_FD_GENERATION_BITS) - 1)
-#define EPIC_FAT_FD(idx, gen)                                                  \
-	(int)(((uint32_t)(gen) << EPIC_FAT_FD_INDEX_BITS) |                    \
-	      ((uint32_t)(idx)&EPIC_FAT_FD_INDEX_MASK))
-
-// this table converts from FRESULT to POSIX errno
-const int fresult_to_errno_table[20] = {
-	[FR_OK]                  = 0,
-	[FR_DISK_ERR]            = EIO,
-	[FR_INT_ERR]             = EIO,
-	[FR_NOT_READY]           = EBUSY,
-	[FR_NO_FILE]             = ENOENT,
-	[FR_NO_PATH]             = ENOENT,
-	[FR_INVALID_NAME]        = EINVAL,
-	[FR_DENIED]              = EACCES,
-	[FR_EXIST]               = EEXIST,
-	[FR_INVALID_OBJECT]      = EINVAL,
-	[FR_WRITE_PROTECTED]     = EROFS,
-	[FR_INVALID_DRIVE]       = ENODEV,
-	[FR_NOT_ENABLED]         = ENODEV,
-	[FR_NO_FILESYSTEM]       = ENODEV,
-	[FR_MKFS_ABORTED]        = EIO,
-	[FR_TIMEOUT]             = EIO,
-	[FR_LOCKED]              = EIO,
-	[FR_NOT_ENOUGH_CORE]     = ENOMEM,
-	[FR_TOO_MANY_OPEN_FILES] = EMFILE,
-	[FR_INVALID_PARAMETER]   = EINVAL,
-};
-
-struct FatObject {
-	uint32_t generation;
-	enum epic_stat_type type;
-	union {
-		FIL file;
-		DIR dir;
-	};
-};
-
-static int
-get_fat_object(int i, enum epic_stat_type expected, struct FatObject **res);
-
-static struct FatObject s_openedObjects[EPIC_FAT_MAX_OPENED];
-static uint32_t s_fatfs_generationCount = 1;
-
-int get_fat_object(int fd, enum epic_stat_type expected, struct FatObject **res)
-{
-	uint32_t index      = EPIC_FAT_FD_INDEX(fd);
-	uint32_t generation = EPIC_FAT_FD_GENERATION(fd);
-	if (index >= EPIC_FAT_MAX_OPENED) {
-		*res = NULL;
-		return EBADF;
-	}
-	if (generation >= EPIC_FAT_FD_MAX_GENERATION) {
-		*res = NULL;
-		return EBADF;
-	}
-	if (s_openedObjects[index].type != expected) {
-		*res = NULL;
-		return EBADF;
-	}
-	if (s_openedObjects[index].generation != generation) {
-		*res = NULL;
-		return EBADF;
-	}
-	*res = &s_openedObjects[index];
-	return 0;
-}
-
-/* here we're trying to mirror glibc's behaviour:
- * any combination of rwax parses but only the first of those flags wins:
- *    - rw, ra, rr all open read-only
- * a `+` at any position but the first turns into read-write
- * any other character at any position yields EINVAL
- */
-static inline bool parse_mode(const char *mstring, int *mode)
-{
-	switch (mstring[0]) {
-	case 'r':
-		*mode = FA_READ;
-		break;
-	case 'w':
-		*mode = FA_CREATE_ALWAYS | FA_WRITE;
-		break;
-	case 'x':
-		//in constrast to FA_CREATE_ALWAYS, FA_CREATE_NEW fails for existing files
-		*mode = FA_WRITE | FA_CREATE_NEW;
-		break;
-	case 'a':
-		//in constrast to FA_CREATE_ALWAYS, FA_CREATE_NEW fails for existing files
-		*mode = FA_WRITE | FA_OPEN_APPEND;
-		break;
-	default:
-		return false;
-	}
-	while (*mstring) {
-		switch (*mstring++) {
-		case '+': //turns any of r,w,x into read&write
-			*mode |= FA_READ | FA_WRITE;
-			break;
-		case 'r': //fallthrough intentional
-		case 'w': //fallthrough intentional
-		case 'a': //fallthrough intentional
-		case 'x': //fallthrough intentional
-		case 'b': //fallthrough intentional
-			break;
-		default:
-			return false;
-		}
-	}
-	return true;
-}
-
-int epic_file_open(const char *filename, const char *modeString)
-{
-	struct FatObject *o = NULL;
-	uint32_t index, generation;
-	int mode = 0;
-	int res;
-
-	//find free object to use
-	for (index = 0; index < EPIC_FAT_MAX_OPENED; ++index) {
-		if (s_openedObjects[index].type == EPICSTAT_NONE) {
-			break;
-		}
-	}
-	if (index == EPIC_FAT_MAX_OPENED) {
-		return -fresult_to_errno_table[FR_TOO_MANY_OPEN_FILES];
-	}
-	generation = s_fatfs_generationCount++;
-	if (generation == EPIC_FAT_FD_MAX_GENERATION) {
-		s_fatfs_generationCount = 1;
-	}
-	o = &s_openedObjects[index];
-
-	if (!parse_mode(modeString, &mode)) {
-		return -EINVAL;
-	}
-
-	res = f_open(&o->file, filename, mode);
-	if (res != FR_OK) {
-		return -fresult_to_errno_table[res];
-	}
-	o->type       = EPICSTAT_FILE;
-	o->generation = generation;
-
-	// for 'a' mode, we must begin at the end of the file
-	if ((mode & FA_OPEN_APPEND) != 0) {
-		f_lseek(&o->file, f_size(&o->file));
-	}
-
-	return EPIC_FAT_FD(index, generation);
-}
-
-int epic_file_close(int fd)
-{
-	int res;
-	struct FatObject *o;
-	res = get_fat_object(fd, EPICSTAT_FILE, &o);
-	if (res) {
-		return -res;
-	}
-
-	res = f_close(&o->file);
-	if (res != FR_OK) {
-		return -fresult_to_errno_table[res];
-	}
-
-	o->type       = EPICSTAT_NONE;
-	o->generation = 0;
-	return 0;
-}
-
-int epic_file_read(int fd, void *buf, size_t nbytes)
-{
-	unsigned int nread = 0;
-
-	int res;
-	struct FatObject *o;
-	res = get_fat_object(fd, EPICSTAT_FILE, &o);
-	if (res) {
-		return -res;
-	}
-
-	res = f_read(&o->file, buf, nbytes, &nread);
-	if (res != FR_OK) {
-		return -fresult_to_errno_table[res];
-	}
-
-	return (int)nread;
-}
-
-int epic_file_write(int fd, const void *buf, size_t nbytes)
-{
-	unsigned int nwritten = 0;
-
-	int res;
-	struct FatObject *o;
-	res = get_fat_object(fd, EPICSTAT_FILE, &o);
-	if (res) {
-		return -res;
-	}
-	res = f_write(&o->file, buf, nbytes, &nwritten);
-	if (res != FR_OK) {
-		return -fresult_to_errno_table[res];
-	}
-
-	return (int)nwritten;
-}
-
-int epic_file_flush(int fd)
-{
-	int res;
-	struct FatObject *o;
-	res = get_fat_object(fd, EPICSTAT_FILE, &o);
-	if (res) {
-		return -res;
-	}
-	res = f_sync(&o->file);
-	if (res != FR_OK) {
-		return -fresult_to_errno_table[res];
-	}
-
-	return 0;
-}
-
-int epic_file_seek(int fd, long offset, int whence)
-{
-	int res;
-	struct FatObject *o;
-	res = get_fat_object(fd, EPICSTAT_FILE, &o);
-	if (res) {
-		return -res;
-	}
-	switch (whence) {
-	case SEEK_SET:
-		res = f_lseek(&o->file, offset);
-		break;
-
-	case SEEK_CUR:
-		res = f_lseek(&o->file, f_tell(&o->file) + offset);
-		break;
-
-	case SEEK_END:
-		res = f_lseek(&o->file, f_size(&o->file) + offset);
-		break;
-	default:
-		return -EINVAL;
-	}
-	if (res != FR_OK) {
-		return -fresult_to_errno_table[res];
-	}
-
-	return 0;
-}
-
-int epic_file_tell(int fd)
-{
-	int res;
-	struct FatObject *o;
-	res = get_fat_object(fd, EPICSTAT_FILE, &o);
-	if (res) {
-		return -res;
-	}
-	//f_tell simply accesses fp->fptr so no errors are expected - return directly
-	return f_tell(&o->file);
-}
-
-int epic_file_stat(const char *filename, struct epic_stat *stat)
-{
-	int res;
-	FILINFO finfo;
-	res = f_stat(filename, &finfo);
-	if (res != FR_OK) {
-		return -fresult_to_errno_table[res];
-	}
-
-	if (finfo.fattrib & AM_DIR) {
-		stat->type = EPICSTAT_DIR;
-	} else {
-		stat->type = EPICSTAT_FILE;
-	}
-
-	return 0;
-}
diff --git a/epicardium/modules/filesystem.h b/epicardium/modules/filesystem.h
new file mode 100644
index 0000000000000000000000000000000000000000..5d4e1bcbb3c356cdf7931460c3194ebc9f6ffc75
--- /dev/null
+++ b/epicardium/modules/filesystem.h
@@ -0,0 +1,46 @@
+#ifndef EPICARDIUM_MODULE_FILESYSTEM_INCLUDED
+#define EPICARDIUM_MODULE_FILESYSTEM_INCLUDED
+
+/* ---------- FAT fs ------------------------------------------------------ */
+
+#include <stdbool.h>
+#include "epicardium.h"
+
+/* Number of bits to use for indexing into our internal pool of files/directories
+ * This indirectly specifies the size of the pool as 1^EPIC_FAT_FD_INDEX_BITS
+ * Increase if number of open file descriptors is not enough, but be aware of
+ * memory usage of the pool!
+ */
+#define EPIC_FAT_FD_INDEX_BITS 6
+#define EPIC_FAT_STATIC_SEMAPHORE 1
+
+#define EPIC_FAT_MAX_OPENED (1 << (EPIC_FAT_FD_INDEX_BITS))
+
+typedef struct EpicFileSystem EpicFileSystem;
+
+/**
+ * module initialization - to be called once at startup before any FreeRTOS tasks
+ * have been started
+ *
+ * calls fatfs_attach
+ */
+void fatfs_init(void);
+
+/**
+ * initialize and mount the FLASH storage
+ */
+int fatfs_attach(void);
+
+/** close all opened FDs, sync and deinitialize FLASH layer */
+void fatfs_detach(void);
+
+/**
+ * lock global filesystem
+ *
+ * Upon successful return, the filesystem has to be re-locked with epic_fs_unlock_global
+ * In case of error, the filesystem will be left in a locked state.
+ */
+bool efs_lock_global(EpicFileSystem** fs, int* ec);
+void efs_unlock_global(EpicFileSystem* fs);
+
+#endif//EPICARDIUM_MODULE_FILESYSTEM_INCLUDED
diff --git a/epicardium/modules/filesystem_fat.c b/epicardium/modules/filesystem_fat.c
new file mode 100644
index 0000000000000000000000000000000000000000..f6908812bcc69588224975df7817cd9523211059
--- /dev/null
+++ b/epicardium/modules/filesystem_fat.c
@@ -0,0 +1,480 @@
+/*
+ * Implementation of efs_ API functions for a FatFS specific
+ * EpicFileSystem
+ */
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <ff.h>
+#include <diskio.h>
+
+#include <FreeRTOS.h>
+#include <semphr.h>
+
+#include "modules/filesystem.h"
+#include "epicardium.h"
+#include "card10.h"
+#include "modules/log.h"
+
+#define SSLOG_INFO(...) LOG_INFO("fatfs", __VA_ARGS__)
+#define SSLOG_ERR(...) LOG_ERR("fatfs", __VA_ARGS__)
+
+#ifndef EPIC_FAT_STATIC_SEMAPHORE
+#define EPIC_FAT_STATIC_SEMAPHORE 0
+#endif
+
+/* clang-format off */
+#define EPIC_FAT_FD_GENERATION_BITS   (31 - (EPIC_FAT_FD_INDEX_BITS))
+#define EPIC_FAT_FD_INDEX_MASK        (uint32_t)((1u << EPIC_FAT_FD_INDEX_BITS) - 1)
+#define EPIC_FAT_FD_INDEX(fd)         ((uint32_t)(fd)&EPIC_FAT_FD_INDEX_MASK)
+#define EPIC_FAT_FD_GENERATION(fd)    ((uint32_t)(fd) >> EPIC_FAT_FD_INDEX_BITS)
+#define EPIC_FAT_FD_MAX_GENERATION    (uint32_t)((1u << EPIC_FAT_FD_GENERATION_BITS) - 1)
+#define EPIC_FAT_FD(idx, gen)         (int)(((uint32_t)(gen) << EPIC_FAT_FD_INDEX_BITS) \
+                                      | ((uint32_t)(idx)&EPIC_FAT_FD_INDEX_MASK))
+/* clang-format on */
+
+struct FatObject {
+	uint32_t generation;
+	enum epic_stat_type type;
+	union {
+		FIL file;
+		DIR dir;
+	};
+};
+
+struct EpicFileSystem {
+	struct FatObject pool[EPIC_FAT_MAX_OPENED];
+	uint32_t generationCount;
+	bool initialized;
+	FATFS FatFs;
+};
+
+// this table converts from FRESULT to POSIX errno
+static const int s_libffToErrno[20];
+
+static const char *f_get_rc_string(FRESULT rc);
+static bool globalLockAccquire();
+static void globalLockRelease();
+static void efs_close_all(EpicFileSystem *fs);
+
+static bool efs_get_opened(
+	EpicFileSystem *fs,
+	int i,
+	enum epic_stat_type expected,
+	struct FatObject **res,
+	int *rc
+);
+
+static EpicFileSystem s_globalFileSystem;
+
+#if (EPIC_FAT_STATIC_SEMAPHORE == 1)
+static StaticSemaphore_t s_globalLockBuffer;
+#endif
+
+static SemaphoreHandle_t s_globalLock = NULL;
+
+bool globalLockAccquire()
+{
+	return (int)(xSemaphoreTake(s_globalLock, FF_FS_TIMEOUT) == pdTRUE);
+}
+
+void globalLockRelease()
+{
+	xSemaphoreGive(s_globalLock);
+}
+
+void fatfs_init()
+{
+	static volatile bool s_initCalled = false;
+	//this has to be called vefore any tasks have been started!
+	// ...not 100% water-tight though, since tick count might be zero even after vTaskStartScheduler
+	// has been called...
+	assert(xTaskGetTickCount() == configINITIAL_TICK_COUNT);
+	assert(!s_initCalled);
+	s_initCalled = true;
+
+#if (EPIC_FAT_STATIC_SEMAPHORE == 1)
+	s_globalLock = xSemaphoreCreateMutexStatic(&s_globalLockBuffer);
+#else
+	s_globalLock = xSemaphoreCreateMutex();
+#endif
+	fatfs_attach();
+}
+
+/*
+ * NOTE about attach/detach:
+ *
+ * while in detach, we're calling diskio_deinitialize (a function that is
+ * originally not present in libff's diskio.h), we do not need to call
+ * diskio_initialize in attach, since it will implicitly be called by
+ * any f_ operation, via libff's find_volume for volumes that have not
+ * been mounted yet.
+ *
+ */
+int fatfs_attach()
+{
+	FRESULT ff_res;
+	int rc = 0;
+	if (globalLockAccquire()) {
+		EpicFileSystem *fs = &s_globalFileSystem;
+		if (!fs->initialized) {
+			ff_res = f_mount(&fs->FatFs, "/", 0);
+			if (ff_res == FR_OK) {
+				fs->initialized = true;
+				SSLOG_INFO("FatFs mounted\n");
+			} else {
+				SSLOG_ERR(
+					"f_mount error %s\n",
+					f_get_rc_string(ff_res)
+				);
+				rc = -s_libffToErrno[ff_res];
+			}
+		}
+
+		globalLockRelease();
+	} else {
+		SSLOG_ERR("Failed to lock\n");
+	}
+	return rc;
+}
+
+void fatfs_detach()
+{
+	FRESULT ff_res;
+	int rc;
+	EpicFileSystem *fs;
+	if (efs_lock_global(&fs, &rc)) {
+		efs_close_all(fs);
+
+		//unmount by passing NULL as fs object, will destroy our sync object via ff_del_syncobj
+		ff_res = f_mount(NULL, "/", 0);
+		if (ff_res != FR_OK) {
+			SSLOG_ERR(
+				"f_mount (unmount) error %s\n",
+				f_get_rc_string(ff_res)
+			);
+		}
+
+		fs->initialized = false;
+		disk_deinitialize();
+		SSLOG_INFO("detached\n");
+		efs_unlock_global(fs);
+	}
+}
+
+const char *f_get_rc_string(FRESULT rc)
+{
+	static const TCHAR *rcstrings =
+		_T("OK\0DISK_ERR\0INT_ERR\0NOT_READY\0NO_FILE\0NO_PATH\0INVALID_NAME\0")
+		_T("DENIED\0EXIST\0INVALID_OBJECT\0WRITE_PROTECTED\0INVALID_DRIVE\0")
+		_T("NOT_ENABLED\0NO_FILESYSTEM\0MKFS_ABORTED\0TIMEOUT\0LOCKED\0")
+		_T("NOT_ENOUGH_CORE\0TOO_MANY_OPEN_FILES\0INVALID_PARAMETER\0");
+
+	FRESULT i;
+	const char *p = rcstrings;
+
+	for (i = 0; i != rc && *p; i++) {
+		while (*p++)
+			;
+	}
+	return p;
+}
+
+bool efs_lock_global(EpicFileSystem **fs, int *rc)
+{
+	*fs = NULL;
+	if (!globalLockAccquire()) {
+		*rc = -EBUSY;
+		return false;
+	}
+	if (!s_globalFileSystem.initialized) {
+		globalLockRelease();
+		*rc = -ENODEV;
+		return false;
+	}
+	*fs = &s_globalFileSystem;
+	*rc = 0;
+	return true;
+}
+
+void efs_unlock_global(EpicFileSystem *fs)
+{
+	(void)fs;
+	globalLockRelease();
+}
+
+bool efs_get_opened(
+	EpicFileSystem *fs,
+	int fd,
+	enum epic_stat_type expected,
+	struct FatObject **obj,
+	int *rc
+) {
+	uint32_t index      = EPIC_FAT_FD_INDEX(fd);
+	uint32_t generation = EPIC_FAT_FD_GENERATION(fd);
+
+	*obj = NULL;
+	*rc  = -EBADF;
+
+	if (index >= EPIC_FAT_MAX_OPENED ||
+	    generation >= EPIC_FAT_FD_MAX_GENERATION) {
+		return false;
+	}
+	if (fs->pool[index].type != expected ||
+	    fs->pool[index].generation != generation) {
+		return false;
+	}
+
+	*obj = &fs->pool[index];
+	*rc  = 0;
+	return true;
+}
+
+/* here we're trying to mirror glibc's behaviour:
+ * any combination of rwax parses but only the first of those flags wins:
+ *    - rw, ra, rr all open read-only
+ * a `+` at any position but the first turns into read-write
+ * any other character at any position yields EINVAL
+ */
+static inline bool parse_mode(const char *mstring, int *mode)
+{
+	switch (mstring[0]) {
+	case 'r':
+		*mode = FA_READ;
+		break;
+	case 'w':
+		*mode = FA_CREATE_ALWAYS | FA_WRITE;
+		break;
+	case 'x':
+		//in constrast to FA_CREATE_ALWAYS, FA_CREATE_NEW fails for existing files
+		*mode = FA_WRITE | FA_CREATE_NEW;
+		break;
+	case 'a':
+		//in constrast to FA_CREATE_ALWAYS, FA_CREATE_NEW fails for existing files
+		*mode = FA_WRITE | FA_OPEN_APPEND;
+		break;
+	default:
+		return false;
+	}
+	while (*mstring) {
+		switch (*mstring++) {
+		case '+': //turns any of r,w,x into read&write
+			*mode |= FA_READ | FA_WRITE;
+			break;
+		case 'r': //fallthrough intentional
+		case 'w': //fallthrough intentional
+		case 'a': //fallthrough intentional
+		case 'x': //fallthrough intentional
+		case 'b': //fallthrough intentional
+			break;
+		default:
+			return false;
+		}
+	}
+	return true;
+}
+
+int efs_open(EpicFileSystem *fs, const char *filename, const char *modeString)
+{
+	struct FatObject *o = NULL;
+	uint32_t index, generation;
+	int mode = 0;
+	int res;
+
+	//find free object to use
+	for (index = 0; index < EPIC_FAT_MAX_OPENED; ++index) {
+		if (fs->pool[index].type == EPICSTAT_NONE) {
+			break;
+		}
+	}
+	if (index == EPIC_FAT_MAX_OPENED) {
+		return -s_libffToErrno[FR_TOO_MANY_OPEN_FILES];
+	}
+
+	generation = fs->generationCount++;
+	if (generation == EPIC_FAT_FD_MAX_GENERATION) {
+		fs->generationCount = 1;
+	}
+	o = &fs->pool[index];
+
+	if (!parse_mode(modeString, &mode)) {
+		return -EINVAL;
+	}
+
+	res = f_open(&o->file, filename, mode);
+	if (res != FR_OK) {
+		return -s_libffToErrno[res];
+	}
+
+	o->type       = EPICSTAT_FILE;
+	o->generation = generation;
+
+	// for 'a' mode, we must begin at the end of the file
+	if ((mode & FA_OPEN_APPEND) != 0) {
+		f_lseek(&o->file, f_size(&o->file));
+	}
+
+	return EPIC_FAT_FD(index, generation);
+}
+
+int efs_close(EpicFileSystem *fs, int fd)
+{
+	int res;
+	struct FatObject *o;
+	if (efs_get_opened(fs, fd, EPICSTAT_FILE, &o, &res)) {
+		res = f_close(&o->file);
+		if (res != FR_OK) {
+			return -s_libffToErrno[res];
+		}
+
+		o->type       = EPICSTAT_NONE;
+		o->generation = 0;
+	}
+	return res;
+}
+
+void efs_close_all(EpicFileSystem *fs)
+{
+	for (int i = 0; i < EPIC_FAT_MAX_OPENED; ++i) {
+		switch (fs->pool[i].type) {
+		case EPICSTAT_FILE:
+			f_close(&fs->pool[i].file);
+			break;
+		case EPICSTAT_DIR:
+			//NYI
+			break;
+		case EPICSTAT_NONE:
+			break;
+		}
+		fs->pool[i].type       = EPICSTAT_NONE;
+		fs->pool[i].generation = 0;
+	}
+}
+
+int efs_read(EpicFileSystem *fs, int fd, void *buf, size_t nbytes)
+{
+	unsigned int nread = 0;
+
+	int res;
+	struct FatObject *o;
+	if (efs_get_opened(fs, fd, EPICSTAT_FILE, &o, &res)) {
+		res = f_read(&o->file, buf, nbytes, &nread);
+		if (res != FR_OK) {
+			return -s_libffToErrno[res];
+		}
+		res = (int)nread;
+	}
+
+	return res;
+}
+
+int efs_write(EpicFileSystem *fs, int fd, const void *buf, size_t nbytes)
+{
+	unsigned int nwritten = 0;
+
+	int res;
+	struct FatObject *o;
+	if (efs_get_opened(fs, fd, EPICSTAT_FILE, &o, &res)) {
+		res = f_write(&o->file, buf, nbytes, &nwritten);
+		if (res != FR_OK) {
+			res = -s_libffToErrno[res];
+		} else {
+			res = (int)nwritten;
+		}
+	}
+	return res;
+}
+
+int efs_flush(EpicFileSystem *fs, int fd)
+{
+	int res = 0;
+	struct FatObject *o;
+	if (efs_get_opened(fs, fd, EPICSTAT_FILE, &o, &res)) {
+		res = f_sync(&o->file);
+		if (res != FR_OK) {
+			res = -s_libffToErrno[res];
+		}
+	}
+
+	return res;
+}
+
+int efs_seek(EpicFileSystem *fs, int fd, long offset, int whence)
+{
+	int res = 0;
+	struct FatObject *o;
+	if (efs_get_opened(fs, fd, EPICSTAT_FILE, &o, &res)) {
+		switch (whence) {
+		case SEEK_SET:
+			res = f_lseek(&o->file, offset);
+			break;
+
+		case SEEK_CUR:
+			res = f_lseek(&o->file, f_tell(&o->file) + offset);
+			break;
+
+		case SEEK_END:
+			res = f_lseek(&o->file, f_size(&o->file) + offset);
+			break;
+		default:
+			return -EINVAL;
+		}
+		res = -s_libffToErrno[res];
+	}
+	return res;
+}
+
+int efs_tell(EpicFileSystem *fs, int fd)
+{
+	int res;
+	struct FatObject *o;
+	if (efs_get_opened(fs, fd, EPICSTAT_FILE, &o, &res)) {
+		//f_tell simply accesses fp->fptr so no errors are expected - return directly
+		res = f_tell(&o->file);
+	}
+	return res;
+}
+
+int efs_stat(EpicFileSystem *fs, const char *filename, struct epic_stat *stat)
+{
+	int res = 0;
+	FILINFO finfo;
+	res = f_stat(filename, &finfo);
+	if (res == 0) {
+		if (finfo.fattrib & AM_DIR) {
+			stat->type = EPICSTAT_DIR;
+		} else {
+			stat->type = EPICSTAT_FILE;
+		}
+	}
+	return -s_libffToErrno[res];
+}
+
+static const int s_libffToErrno[20] = {
+	[FR_OK]                  = 0,
+	[FR_DISK_ERR]            = EIO,
+	[FR_INT_ERR]             = EIO,
+	[FR_NOT_READY]           = EBUSY,
+	[FR_NO_FILE]             = ENOENT,
+	[FR_NO_PATH]             = ENOENT,
+	[FR_INVALID_NAME]        = EINVAL,
+	[FR_DENIED]              = EACCES,
+	[FR_EXIST]               = EEXIST,
+	[FR_INVALID_OBJECT]      = EINVAL,
+	[FR_WRITE_PROTECTED]     = EROFS,
+	[FR_INVALID_DRIVE]       = ENODEV,
+	[FR_NOT_ENABLED]         = ENODEV,
+	[FR_NO_FILESYSTEM]       = ENODEV,
+	[FR_MKFS_ABORTED]        = EIO,
+	[FR_TIMEOUT]             = EIO,
+	[FR_LOCKED]              = EIO,
+	[FR_NOT_ENOUGH_CORE]     = ENOMEM,
+	[FR_TOO_MANY_OPEN_FILES] = EMFILE,
+	[FR_INVALID_PARAMETER]   = EINVAL,
+};
diff --git a/epicardium/modules/filesystem_fileops.c b/epicardium/modules/filesystem_fileops.c
new file mode 100644
index 0000000000000000000000000000000000000000..c285c0e6129fda0e2c35711231c3cae6f5d67179
--- /dev/null
+++ b/epicardium/modules/filesystem_fileops.c
@@ -0,0 +1,110 @@
+/**
+ * Implemention of the epicardium epic_file_ api that operates on
+ * the global EpicFileSystem instance
+ *
+ * All functions lock & unlock the global FS object
+ *
+ */
+
+#include "modules/filesystem.h"
+#include "epicardium.h"
+
+extern int
+efs_open(EpicFileSystem *fs, const char *filename, const char *modeString);
+extern int efs_close(EpicFileSystem *fs, int fd);
+extern int efs_read(EpicFileSystem *fs, int fd, void *buf, size_t nbytes);
+extern int
+efs_write(EpicFileSystem *fs, int fd, const void *buf, size_t nbytes);
+extern int efs_flush(EpicFileSystem *fs, int fd);
+extern int efs_seek(EpicFileSystem *fs, int fd, long offset, int whence);
+extern int efs_tell(EpicFileSystem *fs, int fd);
+extern int
+efs_stat(EpicFileSystem *fs, const char *filename, struct epic_stat *stat);
+
+int epic_file_open(const char *filename, const char *mode)
+{
+	EpicFileSystem *fs;
+	int res;
+	if (efs_lock_global(&fs, &res)) {
+		res = efs_open(fs, filename, mode);
+		efs_unlock_global(fs);
+	}
+	return res;
+}
+
+int epic_file_close(int fd)
+{
+	EpicFileSystem *fs;
+	int res;
+	if (efs_lock_global(&fs, &res)) {
+		res = efs_close(fs, fd);
+		efs_unlock_global(fs);
+	}
+	return res;
+}
+
+int epic_file_read(int fd, void *buf, size_t nbytes)
+{
+	EpicFileSystem *fs;
+	int res;
+	if (efs_lock_global(&fs, &res)) {
+		res = efs_read(fs, fd, buf, nbytes);
+		efs_unlock_global(fs);
+	}
+	return res;
+}
+
+int epic_file_write(int fd, const void *buf, size_t nbytes)
+{
+	EpicFileSystem *fs;
+	int res;
+	if (efs_lock_global(&fs, &res)) {
+		res = efs_write(fs, fd, buf, nbytes);
+		efs_unlock_global(fs);
+	}
+	return res;
+}
+
+int epic_file_flush(int fd)
+{
+	EpicFileSystem *fs;
+	int res;
+	if (efs_lock_global(&fs, &res)) {
+		res = efs_flush(fs, fd);
+		efs_unlock_global(fs);
+	}
+	return res;
+}
+
+int epic_file_seek(int fd, long offset, int whence)
+{
+	EpicFileSystem *fs;
+	int res;
+	if (efs_lock_global(&fs, &res)) {
+		res = efs_seek(fs, fd, offset, whence);
+		efs_unlock_global(fs);
+	}
+	return res;
+}
+
+int epic_file_tell(int fd)
+{
+	EpicFileSystem *fs;
+	int res;
+	if (efs_lock_global(&fs, &res)) {
+		res = efs_tell(fs, fd);
+		efs_unlock_global(fs);
+	}
+	return res;
+}
+
+int epic_file_stat(const char *filename, struct epic_stat *stat)
+{
+	EpicFileSystem *fs;
+	int res;
+	if (efs_lock_global(&fs, &res)) {
+		res = efs_stat(fs, filename, stat);
+		efs_unlock_global(fs);
+	}
+	return res;
+}
diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build
index 113e3cb8ecaebc932a65ce9e6124cc2ff3be60ce..29aa39536f25454ab867a18c4867185aa08a9384 100644
--- a/epicardium/modules/meson.build
+++ b/epicardium/modules/meson.build
@@ -1,7 +1,7 @@
 module_sources = files(
   'display.c',
-  'fatfs.c',
-  'fatfs_fileops.c',
+  'filesystem_fat.c',
+  'filesystem_fileops.c',
   'leds.c',
   'log.c',
   'pmic.c',
diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h
index 182fdae7e1bee27bce1b3d56d94f35bce9eb979e..083923852e730f3b054a1527b50c7be98737a294 100644
--- a/epicardium/modules/modules.h
+++ b/epicardium/modules/modules.h
@@ -1,16 +1,6 @@
 #ifndef MODULES_H
 #define MODULES_H
 
-/* ---------- FAT fs ------------------------------------------------------ */
-/* Number of bits to use for indexing into our internal pool of files/directories
- * This indirectly specifies the size of the pool as 2^EPIC_FAT_FD_INDEX_BITS
- * Increase if number of open file descriptors is not enough, but be aware of
- * memory usage of the pool!
- */
-#define EPIC_FAT_FD_INDEX_BITS 4
-#define EPIC_FAT_STATIC_SEMAPHORE 1
-void fatfs_init(void);
-
 /* ---------- Serial ------------------------------------------------------- */
 #define SERIAL_READ_BUFFER_SIZE 128
 void vSerialTask(void *pvParameters);
diff --git a/lib/ff13/Source/diskio.c b/lib/ff13/Source/diskio.c
index 6945883624234c067a31809b559f199ac0768f4d..eb1ab11d8a1bf8351fde713152f84798cc9c66ba 100644
--- a/lib/ff13/Source/diskio.c
+++ b/lib/ff13/Source/diskio.c
@@ -7,20 +7,22 @@
 /* storage control modules to the FatFs module with a defined API.       */
 /*-----------------------------------------------------------------------*/
 
-#include "diskio.h"     /* FatFs lower layer API */
+#include <stdbool.h>
 
+#include "diskio.h" /* FatFs lower layer API */
 /* Definitions of physical drive number for each drive */
-#define DEV_FLASH       0   /* Example: Map MMC/SD card to physical drive 1 */
-#define DEV_SD          1   /* Example: Map MMC/SD card to physical drive 1 */
+#define DEV_FLASH 0 /* Example: Map MMC/SD card to physical drive 1 */
+#define DEV_SD 1    /* Example: Map MMC/SD card to physical drive 1 */
 
-#define SDHC            0
+#define SDHC 0
 
-#define SECTOR_SIZE     512UL
+#define SECTOR_SIZE 512UL
 
 /*local vaiables*/
 static uint8_t rtc_en;
+static bool s_diskio_initialized = false;
 
-#if SDHC
+#if SDHC //TODO: implement deinitialization with SDHC as well
 /* # of times to check for a card, should be > 1 to detect both SD and MMC */
 #define INIT_CARD_RETRIES 10
 
@@ -38,8 +40,8 @@ extern int mx25_init(void);
 extern int mx25_start(void);
 extern int mx25_stop(void);
 extern uint32_t mx25_size(void);
-extern int mx25_read(uint32_t lba, uint8_t* buffer);
-extern int mx25_write(uint32_t lba, uint8_t* buffer);
+extern int mx25_read(uint32_t lba, uint8_t *buffer);
+extern int mx25_write(uint32_t lba, uint8_t *buffer);
 extern int mx25_sync(void);
 extern int mx25_ready(void);
 
@@ -47,316 +49,328 @@ extern int mx25_ready(void);
 /* Get Drive Status                                                      */
 /*-----------------------------------------------------------------------*/
 
-DSTATUS disk_status (
-    BYTE pdrv       /* Physical drive nmuber to identify the drive */
-)
-{
-#if 0
-    #define STA_NOINIT		0x01	/* Drive not initialized */
-    #define STA_NODISK		0x02	/* No medium in the drive */
-    #define STA_PROTECT		0x04	/* Write protected */
-#endif
+DSTATUS disk_status(BYTE pdrv /* Physical drive nmuber to identify the drive */
+) {
 
-    DSTATUS status = 0;
-    if(pdrv == 0) {
-        if(mx25_ready()) {
-            status = RES_OK;
-         }
-    }
+	DSTATUS status = 0;
+	if (!s_diskio_initialized) {
+		status = STA_NOINIT | STA_NODISK;
+	} else if (pdrv == 0) {
+		if (mx25_ready()) {
+			status = RES_OK;
+		}
+	}
 
 #if SDHC
-    if(pdrv == 1) {
-        if (!SDHC_Card_Inserted()) {
-            init_done = 0;
-            status = STA_NOINIT | STA_NODISK;
-        }
-    }
+	if (pdrv == 1) {
+		if (!SDHC_Card_Inserted()) {
+			init_done = 0;
+			status    = STA_NOINIT | STA_NODISK;
+		}
+	}
 #endif
-    return status;
+	return status;
 }
 
-
-
 /*-----------------------------------------------------------------------*/
 /* Inidialize a Drive                                                    */
 /*-----------------------------------------------------------------------*/
 
-DSTATUS disk_initialize (
-    BYTE pdrv               /* Physical drive nmuber to identify the drive */
-)
-{
-    DSTATUS status = STA_NOINIT;
+DSTATUS
+disk_initialize(BYTE pdrv /* Physical drive nmuber to identify the drive */
+) {
+	DSTATUS status = STA_NOINIT;
 
-    rtc_en = 0;
+	rtc_en               = 0;
+	s_diskio_initialized = true;
 #if (FF_FS_NORTC == 0)
-    //Initialize RTC
-    if (MXC_RTC->cn & MXC_F_RTC_CN_WE) {
-        rtc_en = 1;
-    } else {
-        start_time_sec = (FF_NORTC_YEAR-1980)*SEC_IN_YEAR_AVG;
-        start_time_sec += FF_NORTC_MON*SEC_IN_MONTH_AVG;
-        start_time_sec += FF_NORTC_MDAY*SEC_IN_DAY;
-        if(RTC_init(MXC_RTC, start_time_sec, 0) == E_NO_ERROR) {
-            rtc_en = 1;
-        }
-    }
+	//Initialize RTC
+	if (MXC_RTC->cn & MXC_F_RTC_CN_WE) {
+		rtc_en = 1;
+	} else {
+		start_time_sec = (FF_NORTC_YEAR - 1980) * SEC_IN_YEAR_AVG;
+		start_time_sec += FF_NORTC_MON * SEC_IN_MONTH_AVG;
+		start_time_sec += FF_NORTC_MDAY * SEC_IN_DAY;
+		if (RTC_init(MXC_RTC, start_time_sec, 0) == E_NO_ERROR) {
+			rtc_en = 1;
+		}
+	}
 #endif
 
-    if(pdrv == 0) {
-        if(mx25_start() == 0) {
-            status = RES_OK;
-        }
-    }
+	if (pdrv == 0) {
+		if (mx25_start() == 0) {
+			status = RES_OK;
+		}
+	}
 
 #if SDHC
-    if(pdrv == 1) {
-        if (SDHC_Card_Inserted() && (SDHC_Lib_InitCard(INIT_CARD_RETRIES) == E_NO_ERROR)) {
-            /* Card initialized and ready for work */
-            init_done = 1;
-            status = 0;
-        } else {
-            status = STA_NOINIT;
-        }
-    }
+	if (pdrv == 1) {
+		if (SDHC_Card_Inserted() &&
+		    (SDHC_Lib_InitCard(INIT_CARD_RETRIES) == E_NO_ERROR)) {
+			/* Card initialized and ready for work */
+			init_done = 1;
+			status    = 0;
+		} else {
+			status = STA_NOINIT;
+		}
+	}
 #endif
 
-    return status;
+	return status;
 }
 
-
+void disk_deinitialize()
+{
+	if (s_diskio_initialized) {
+		mx25_sync();
+		mx25_stop(); //XXX: or should we not?
+		s_diskio_initialized = false;
+	}
+}
 
 /*-----------------------------------------------------------------------*/
 /* Read Sector(s)                                                        */
 /*-----------------------------------------------------------------------*/
 
-DRESULT disk_read (
-    BYTE pdrv,      /* Physical drive nmuber to identify the drive */
-    BYTE *buff,     /* Data buffer to store read data */
-    DWORD sector,   /* Start sector in LBA */
-    UINT count      /* Number of sectors to read */
-)
-{
-    DRESULT status = RES_ERROR;
-
-    if(pdrv == 0) {
-        int sector_offset;
-        status = RES_OK;
-        for(sector_offset = 0; sector_offset < count; sector_offset++) {
-            if(mx25_read(sector + sector_offset, (uint8_t*)buff + SECTOR_SIZE * sector_offset) == 1) {
-                status = RES_ERROR;
-                break;
-            }
-        }
-    }
+DRESULT disk_read(
+	BYTE pdrv,    /* Physical drive nmuber to identify the drive */
+	BYTE *buff,   /* Data buffer to store read data */
+	DWORD sector, /* Start sector in LBA */
+	UINT count    /* Number of sectors to read */
+) {
+	DRESULT status = RES_ERROR;
+
+	if (!s_diskio_initialized) {
+		status = STA_NOINIT | STA_NODISK;
+	} else if (pdrv == 0) {
+		int sector_offset;
+		status = RES_OK;
+		for (sector_offset = 0; sector_offset < count;
+		     sector_offset++) {
+			if (mx25_read(
+				    sector + sector_offset,
+				    (uint8_t *)buff +
+					    SECTOR_SIZE * sector_offset) == 1) {
+				status = RES_ERROR;
+				break;
+			}
+		}
+	}
 #if SDHC
-    if(pdrv == 1) {
-        if (SDHC_Lib_Read(buff, sector, count, SDHC_LIB_SINGLE_DATA) == E_NO_ERROR) {
-            status = RES_OK;
-        }
-    }
+	if (pdrv == 1) {
+		if (SDHC_Lib_Read(buff, sector, count, SDHC_LIB_SINGLE_DATA) ==
+		    E_NO_ERROR) {
+			status = RES_OK;
+		}
+	}
 #endif
 
-    return status;
+	return status;
 }
 
-
-
 /*-----------------------------------------------------------------------*/
 /* Write Sector(s)                                                       */
 /*-----------------------------------------------------------------------*/
 
-DRESULT disk_write (
-    BYTE pdrv,          /* Physical drive nmuber to identify the drive */
-    const BYTE *buff,   /* Data to be written */
-    DWORD sector,       /* Start sector in LBA */
-    UINT count          /* Number of sectors to write */
-)
-{
-    DRESULT status = RES_ERROR;
-
-    if(pdrv == 0) {
-        int sector_offset;
-        status = RES_OK;
-        for(sector_offset = 0; sector_offset < count; sector_offset++) {
-            if(mx25_write(sector + sector_offset, (uint8_t*)buff + SECTOR_SIZE * sector_offset) == 1) {
-                status = RES_ERROR;
-                break;
-            }
-        }
-    }
+DRESULT disk_write(
+	BYTE pdrv,        /* Physical drive nmuber to identify the drive */
+	const BYTE *buff, /* Data to be written */
+	DWORD sector,     /* Start sector in LBA */
+	UINT count        /* Number of sectors to write */
+) {
+	DRESULT status = RES_ERROR;
+
+	if (!s_diskio_initialized) {
+		status = STA_NOINIT | STA_NODISK;
+	} else if (pdrv == 0) {
+		int sector_offset;
+		status = RES_OK;
+		for (sector_offset = 0; sector_offset < count;
+		     sector_offset++) {
+			if (mx25_write(
+				    sector + sector_offset,
+				    (uint8_t *)buff +
+					    SECTOR_SIZE * sector_offset) == 1) {
+				status = RES_ERROR;
+				break;
+			}
+		}
+	}
 
 #if SDHC
-    if(pdrv == 1) {
-        if (SDHC_Lib_Write(sector, (void *)buff, count, SDHC_LIB_SINGLE_DATA) != E_NO_ERROR) {
-            status = RES_ERROR;
-        } else {
-            status = RES_OK;
-        }
-    }
+	if (pdrv == 1) {
+		if (SDHC_Lib_Write(
+			    sector, (void *)buff, count, SDHC_LIB_SINGLE_DATA) !=
+		    E_NO_ERROR) {
+			status = RES_ERROR;
+		} else {
+			status = RES_OK;
+		}
+	}
 #endif
 
-    return status;
+	return status;
 }
 
-
-
 /*-----------------------------------------------------------------------*/
 /* Miscellaneous Functions                                               */
 /*-----------------------------------------------------------------------*/
 
-DRESULT disk_ioctl (
-    BYTE pdrv,      /* Physical drive nmuber (0..) */
-    BYTE cmd,       /* Control code */
-    void *buff      /* Buffer to send/receive control data */
-)
-{
-    DRESULT status = RES_PARERR;
-
-    if(pdrv == 0) {
-        switch(cmd) {
-            case CTRL_SYNC:
-                /* Mandatory */
-                status = mx25_sync();
-                break;
-            case GET_SECTOR_COUNT:
-                /* Mandatory */
-                *((DWORD *)buff) = mx25_size() / SECTOR_SIZE;
-                status = RES_OK;
-                break;
-            case GET_BLOCK_SIZE:
-                /* Mandatory */
-                *((DWORD *)buff) = SECTOR_SIZE;
-                status = RES_OK;
-                break;
-            default:
-                status = RES_PARERR;
-                break;
-        }
-    }
+DRESULT disk_ioctl(
+	BYTE pdrv, /* Physical drive nmuber (0..) */
+	BYTE cmd,  /* Control code */
+	void *buff /* Buffer to send/receive control data */
+) {
+	DRESULT status = RES_PARERR;
+
+	if (!s_diskio_initialized) {
+		status = STA_NOINIT | STA_NODISK;
+	} else if (pdrv == 0) {
+		switch (cmd) {
+		case CTRL_SYNC:
+			/* Mandatory */
+			status = mx25_sync();
+			break;
+		case GET_SECTOR_COUNT:
+			/* Mandatory */
+			*((DWORD *)buff) = mx25_size() / SECTOR_SIZE;
+			status           = RES_OK;
+			break;
+		case GET_BLOCK_SIZE:
+			/* Mandatory */
+			*((DWORD *)buff) = SECTOR_SIZE;
+			status           = RES_OK;
+			break;
+		default:
+			status = RES_PARERR;
+			break;
+		}
+	}
 
 #if SDHC
-    if(pdrv == 1) {
-        switch(cmd) {
-            case CTRL_SYNC:
-                /* Mandatory */
-                status = ctrl_sync(buff);
-                break;
-            case GET_SECTOR_COUNT:
-                /* Mandatory */
-                status = get_sector_count(buff);
-                break;
-            case GET_BLOCK_SIZE:
-                /* Mandatory */
-                status = get_block_size(buff);
-                break;
-            case MMC_GET_CSD:
-                /* Optional */
-                status = mmc_get_csd(buff);
-                break;
-            default:
-                status = RES_PARERR;
-                break;
-        }
-    }
+	if (pdrv == 1) {
+		switch (cmd) {
+		case CTRL_SYNC:
+			/* Mandatory */
+			status = ctrl_sync(buff);
+			break;
+		case GET_SECTOR_COUNT:
+			/* Mandatory */
+			status = get_sector_count(buff);
+			break;
+		case GET_BLOCK_SIZE:
+			/* Mandatory */
+			status = get_block_size(buff);
+			break;
+		case MMC_GET_CSD:
+			/* Optional */
+			status = mmc_get_csd(buff);
+			break;
+		default:
+			status = RES_PARERR;
+			break;
+		}
+	}
 #endif
-    return status;
+	return status;
 }
 
-DWORD get_fattime(void) {
-    if(rtc_en) {
-        DWORD result;
-        uint32_t seconds;
-        uint8_t year, month, day, hour, minute, half_seconds;
-
-        //Convert RTC Seconds to time
-        seconds = MXC_RTC->sec + (FF_RTC_EPOCH_DELTA);
-        year = seconds/SEC_IN_YEAR_AVG;    //year from epoch
-        seconds = seconds%SEC_IN_YEAR_AVG; //seconds from Jan 1, $year
-        month = seconds/SEC_IN_MONTH_AVG;
-        seconds = seconds%SEC_IN_MONTH_AVG;
-        day = seconds/SEC_IN_DAY;        //hours from 12:00am
-        seconds = seconds%SEC_IN_DAY;
-        hour = seconds/SEC_IN_HOUR;
-        seconds = seconds%SEC_IN_HOUR;
-        minute = seconds/SEC_IN_MINUTE;
-        seconds = seconds%SEC_IN_MINUTE;
-        half_seconds = seconds*2;
-
-        /* Mask bits for inclusion in result */
-        year &= 0x7F;
-        month &= 0x0F;
-        day &= 0x1F;
-        hour &= 0x1F;
-        minute &= 0x3F;
-        half_seconds &= 0x1F;
-
-        /* Add fields into 32bit result */
-        result = year<<25;
-        result |= month<<21;
-        result |= day<<16;
-        result |= hour<<11;
-        result |= minute<<5;
-        result |= half_seconds;
-        return result;
-    }
-    else {
-        return RES_NOTRDY;
-    }
+DWORD get_fattime(void)
+{
+	if (rtc_en) {
+		DWORD result;
+		uint32_t seconds;
+		uint8_t year, month, day, hour, minute, half_seconds;
+
+		//Convert RTC Seconds to time
+		seconds = MXC_RTC->sec + (FF_RTC_EPOCH_DELTA);
+		year    = seconds / SEC_IN_YEAR_AVG; //year from epoch
+		seconds = seconds % SEC_IN_YEAR_AVG; //seconds from Jan 1, $year
+		month   = seconds / SEC_IN_MONTH_AVG;
+		seconds = seconds % SEC_IN_MONTH_AVG;
+		day     = seconds / SEC_IN_DAY; //hours from 12:00am
+		seconds = seconds % SEC_IN_DAY;
+		hour    = seconds / SEC_IN_HOUR;
+		seconds = seconds % SEC_IN_HOUR;
+		minute  = seconds / SEC_IN_MINUTE;
+		seconds = seconds % SEC_IN_MINUTE;
+		half_seconds = seconds * 2;
+
+		/* Mask bits for inclusion in result */
+		year &= 0x7F;
+		month &= 0x0F;
+		day &= 0x1F;
+		hour &= 0x1F;
+		minute &= 0x3F;
+		half_seconds &= 0x1F;
+
+		/* Add fields into 32bit result */
+		result = year << 25;
+		result |= month << 21;
+		result |= day << 16;
+		result |= hour << 11;
+		result |= minute << 5;
+		result |= half_seconds;
+		return result;
+	} else {
+		return RES_NOTRDY;
+	}
 }
 
 #if SDHC
 static DRESULT ctrl_sync(void *buff)
 {
-    return RES_OK;
+	return RES_OK;
 }
 
 static DRESULT get_sector_count(void *buff)
 {
-    DRESULT status = RES_ERROR;
+	DRESULT status = RES_ERROR;
 
-    mxc_sdhc_csd_regs_t csd;
+	mxc_sdhc_csd_regs_t csd;
 
-    if (init_done) {
-            if (SDHC_Lib_GetCSD(&csd) == E_NO_ERROR) {
-            *((DWORD *)buff) = SDHC_Lib_GetCapacity(&csd) / FF_MIN_SS;
-            status = RES_OK;
-        }
-    } else {
-        status = RES_NOTRDY;
-    }
+	if (init_done) {
+		if (SDHC_Lib_GetCSD(&csd) == E_NO_ERROR) {
+			*((DWORD *)buff) =
+				SDHC_Lib_GetCapacity(&csd) / FF_MIN_SS;
+			status = RES_OK;
+		}
+	} else {
+		status = RES_NOTRDY;
+	}
 
-    return status;
+	return status;
 }
 
 static DRESULT get_block_size(void *buff)
 {
-    DRESULT status = RES_ERROR;
-
-    mxc_sdhc_csd_regs_t csd;
-    if (init_done) {
-            if (SDHC_Lib_GetCSD(&csd) == E_NO_ERROR) {
-            *((DWORD *)buff) = SDHC_Lib_GetBlockSize(&csd);
-            status = RES_OK;
-        }
-    } else {
-        status = RES_NOTRDY;
-    }
-
-    return status;
+	DRESULT status = RES_ERROR;
+
+	mxc_sdhc_csd_regs_t csd;
+	if (init_done) {
+		if (SDHC_Lib_GetCSD(&csd) == E_NO_ERROR) {
+			*((DWORD *)buff) = SDHC_Lib_GetBlockSize(&csd);
+			status           = RES_OK;
+		}
+	} else {
+		status = RES_NOTRDY;
+	}
+
+	return status;
 }
 
 static DRESULT mmc_get_csd(void *buff)
 {
-    DRESULT status = RES_ERROR;
+	DRESULT status = RES_ERROR;
 
-    if (init_done) {
-            if (SDHC_Lib_GetCSD(buff) == E_NO_ERROR) {
-            status = RES_OK;
-        }
-    } else {
-        status = RES_NOTRDY;
-    }
+	if (init_done) {
+		if (SDHC_Lib_GetCSD(buff) == E_NO_ERROR) {
+			status = RES_OK;
+		}
+	} else {
+		status = RES_NOTRDY;
+	}
 
-    return status;
+	return status;
 }
 #endif
diff --git a/lib/ff13/Source/diskio.h b/lib/ff13/Source/diskio.h
index 40551fbd2e878654a9a6622d26ec916331888592..5dc221e2f8e635e2b6a980856ddde5984c49814b 100644
--- a/lib/ff13/Source/diskio.h
+++ b/lib/ff13/Source/diskio.h
@@ -34,6 +34,7 @@ typedef enum {
 /* Prototypes for disk control functions */
 
 DSTATUS disk_initialize (BYTE pdrv);
+void disk_deinitialize(void);
 DSTATUS disk_status (BYTE pdrv);
 DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
 DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
diff --git a/lib/ff13/Source/ffconf.h b/lib/ff13/Source/ffconf.h
index 3a14f3dcb3cfcda8c403bda34cb8ddd8ba321148..f4b54f334c0f5c21bb59e1f0b554e5330efa9351 100644
--- a/lib/ff13/Source/ffconf.h
+++ b/lib/ff13/Source/ffconf.h
@@ -248,10 +248,10 @@
 /      lock control is independent of re-entrancy. */
 
 
-#define FF_FS_REENTRANT	1
+#define FF_FS_REENTRANT	0
 #define FF_FS_TIMEOUT	1000
 //in FreeRTOS, SemaphoreHandle_t is a typedef for QueueHandle_t, which is just a typedef for:
-#define FF_SYNC_t		struct QueueDefinition *
+//#define FF_SYNC_t		struct QueueDefinition *
 
 /* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
 /  module itself. Note that regardless of this option, file access to different
diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h
index dedc57c05513debe59a7441493dcf7d86ec0060d..23fb73e5a347c5825f1fa34e4db8119a137e0a7e 100644
--- a/pycardium/modules/qstrdefs.h
+++ b/pycardium/modules/qstrdefs.h
@@ -61,6 +61,7 @@ Q(encoding)
 Q(file)
 Q(FileIO)
 Q(flush)
+Q(usbstorage)
 Q(mode)
 Q(r)
 Q(read)
diff --git a/pycardium/mpconfigport.h b/pycardium/mpconfigport.h
index 233c01bbb82dd986e9ec54c53275ffef96631269..a7fb16f674dcc20e393345b963c654e7d034b999 100644
--- a/pycardium/mpconfigport.h
+++ b/pycardium/mpconfigport.h
@@ -36,6 +36,7 @@
 #define MICROPY_PY_URE_SUB                  (1)
 #define MICROPY_PY_UTIME_MP_HAL             (1)
 #define MICROPY_PY_IO_FILEIO                (1)
+#define MICROPY_PY_UERRNO                   (1)
 
 /* Modules */
 #define MODULE_UTIME_ENABLED                (1)