From 6d1686e0e9f0a99337be4af5cf8c70e68e8ebdaa Mon Sep 17 00:00:00 2001
From: swym <0xfd000000@gmail.com>
Date: Fri, 9 Aug 2019 14:50:45 +0000
Subject: [PATCH] feat(fatfs): Implement global FLASH lock

- Implement de-initialization
- Wrap filesystem operations in semaphore
- Introduce EpicFileSystem object and move epic_file_FOO(...)
  imlementations into efs_FOO(EpicFileSystem*, ...) functions.
- epic_file_FOO(...) functions are now wrappers around the _fs_
  functions, but lock and unlock the global filesystem object before &
  after calls.  This way, all efs_ functions can assume that the
  necessary lock has been acquired.
- libff: don't use FF_FS_REENTRANT, our own FS lock is enough
---
 epicardium/fs/filesystem_fat.c     | 478 +++++++++++++++++++++++++++++
 epicardium/fs/internal.h           |  41 +++
 epicardium/main.c                  |   1 +
 epicardium/meson.build             |   1 +
 epicardium/modules/fatfs.c         | 150 ---------
 epicardium/modules/fatfs_fileops.c | 300 ------------------
 epicardium/modules/fileops.c       |  97 ++++++
 epicardium/modules/filesystem.h    |  25 ++
 epicardium/modules/meson.build     |   3 +-
 epicardium/modules/modules.h       |   9 -
 lib/ff13/Source/diskio.c           |  40 ++-
 lib/ff13/Source/diskio.h           |   1 +
 lib/ff13/Source/ffconf.h           |   4 +-
 pycardium/mpconfigport.h           |   1 +
 14 files changed, 677 insertions(+), 474 deletions(-)
 create mode 100644 epicardium/fs/filesystem_fat.c
 create mode 100644 epicardium/fs/internal.h
 delete mode 100644 epicardium/modules/fatfs.c
 delete mode 100644 epicardium/modules/fatfs_fileops.c
 create mode 100644 epicardium/modules/fileops.c
 create mode 100644 epicardium/modules/filesystem.h

diff --git a/epicardium/fs/filesystem_fat.c b/epicardium/fs/filesystem_fat.c
new file mode 100644
index 00000000..a96198fe
--- /dev/null
+++ b/epicardium/fs/filesystem_fat.c
@@ -0,0 +1,478 @@
+/*
+ * 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 "fs/internal.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_MAX_OPENED           (1 << (EPIC_FAT_FD_INDEX_BITS))
+#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;
+
+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;
+	EpicFileSystem *fs;
+	if (efs_lock_global(&fs) == 0) {
+		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);
+	}
+}
+
+static 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;
+}
+
+static bool globalLockAccquire()
+{
+	return (int)(xSemaphoreTake(s_globalLock, FF_FS_TIMEOUT) == pdTRUE);
+}
+
+static void globalLockRelease()
+{
+	xSemaphoreGive(s_globalLock);
+}
+
+int efs_lock_global(EpicFileSystem **fs)
+{
+	*fs = NULL;
+	if (!globalLockAccquire()) {
+		return -EBUSY;
+	}
+	if (!s_globalFileSystem.initialized) {
+		globalLockRelease();
+		return -ENODEV;
+	}
+	*fs = &s_globalFileSystem;
+	return 0;
+}
+
+void efs_unlock_global(EpicFileSystem *fs)
+{
+	(void)fs;
+	globalLockRelease();
+}
+
+static 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/fs/internal.h b/epicardium/fs/internal.h
new file mode 100644
index 00000000..b3f3acb3
--- /dev/null
+++ b/epicardium/fs/internal.h
@@ -0,0 +1,41 @@
+#ifndef EPICARCIUM_FS_INTERNAL_H_INCLUDED
+#define EPICARCIUM_FS_INTERNAL_H_INCLUDED
+
+#include <stdint.h>
+#include <stddef.h>
+#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 4
+#define EPIC_FAT_STATIC_SEMAPHORE 1
+
+// forward declaration, actual definition is in filesystem_fat.c
+typedef struct EpicFileSystem EpicFileSystem;
+
+int efs_open(EpicFileSystem *fs, const char *filename, const char *modeString);
+int efs_close(EpicFileSystem *fs, int fd);
+int efs_read(EpicFileSystem *fs, int fd, void *buf, size_t nbytes);
+int efs_write(EpicFileSystem *fs, int fd, const void *buf, size_t nbytes);
+int efs_flush(EpicFileSystem *fs, int fd);
+int efs_seek(EpicFileSystem *fs, int fd, long offset, int whence);
+int efs_tell(EpicFileSystem *fs, int fd);
+int efs_stat(EpicFileSystem *fs, const char *filename, struct epic_stat *stat);
+/**
+ * lock global filesystem
+ * 
+ * locks the global mutex and, if the global EpicFileSystem has been initialized correctly
+ * passes it to *fs
+ *
+ * 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.
+ */
+int efs_lock_global(EpicFileSystem** fs);
+void efs_unlock_global(EpicFileSystem* fs);
+
+#endif// EPICARCIUM_FS_INTERNAL_H_INCLUDED
diff --git a/epicardium/main.c b/epicardium/main.c
index ebadaf95..89d5c07d 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/meson.build b/epicardium/meson.build
index 9b3880a8..9c8b12b8 100644
--- a/epicardium/meson.build
+++ b/epicardium/meson.build
@@ -74,6 +74,7 @@ elf = executable(
   'cdcacm.c',
   'main.c',
   'support.c',
+  'fs/filesystem_fat.c',
   module_sources,
   l0der_sources,
   ble_sources,
diff --git a/epicardium/modules/fatfs.c b/epicardium/modules/fatfs.c
deleted file mode 100644
index 1962f493..00000000
--- 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 de083fcb..00000000
--- 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/fileops.c b/epicardium/modules/fileops.c
new file mode 100644
index 00000000..fc13417b
--- /dev/null
+++ b/epicardium/modules/fileops.c
@@ -0,0 +1,97 @@
+/**
+ * Implemention of the epicardium epic_file_ api that operates on
+ * the global EpicFileSystem instance
+ *
+ * All functions lock & unlock the global FS object
+ *
+ */
+
+#include "fs/internal.h"
+
+int epic_file_open(const char *filename, const char *mode)
+{
+	EpicFileSystem *fs;
+	int res = efs_lock_global(&fs);
+	if (res == 0) {
+		res = efs_open(fs, filename, mode);
+		efs_unlock_global(fs);
+	}
+	return res;
+}
+
+int epic_file_close(int fd)
+{
+	EpicFileSystem *fs;
+	int res = efs_lock_global(&fs);
+	if (res == 0) {
+		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 = efs_lock_global(&fs);
+	if (res == 0) {
+		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 = efs_lock_global(&fs);
+	if (res == 0) {
+		res = efs_write(fs, fd, buf, nbytes);
+		efs_unlock_global(fs);
+	}
+	return res;
+}
+
+int epic_file_flush(int fd)
+{
+	EpicFileSystem *fs;
+	int res = efs_lock_global(&fs);
+	if (res == 0) {
+		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 = efs_lock_global(&fs);
+	if (res == 0) {
+		res = efs_seek(fs, fd, offset, whence);
+		efs_unlock_global(fs);
+	}
+	return res;
+}
+
+int epic_file_tell(int fd)
+{
+	EpicFileSystem *fs;
+	int res = efs_lock_global(&fs);
+	if (res == 0) {
+		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 = efs_lock_global(&fs);
+	if (res == 0) {
+		res = efs_stat(fs, filename, stat);
+		efs_unlock_global(fs);
+	}
+	return res;
+}
diff --git a/epicardium/modules/filesystem.h b/epicardium/modules/filesystem.h
new file mode 100644
index 00000000..7a638d5a
--- /dev/null
+++ b/epicardium/modules/filesystem.h
@@ -0,0 +1,25 @@
+#ifndef EPICARDIUM_MODULE_FILESYSTEM_INCLUDED
+#define EPICARDIUM_MODULE_FILESYSTEM_INCLUDED
+
+/* ---------- FAT fs ------------------------------------------------------ */
+
+#include <stdbool.h>
+#include "epicardium.h"
+
+/**
+ * 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);
+
+#endif//EPICARDIUM_MODULE_FILESYSTEM_INCLUDED
diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build
index 35b6e819..b3cf4eb2 100644
--- a/epicardium/modules/meson.build
+++ b/epicardium/modules/meson.build
@@ -1,7 +1,6 @@
 module_sources = files(
   'display.c',
-  'fatfs.c',
-  'fatfs_fileops.c',
+  'fileops.c',
   'leds.c',
   'light_sensor.c',
   'log.c',
diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h
index 5dbda13c..4a7bc255 100644
--- a/epicardium/modules/modules.h
+++ b/epicardium/modules/modules.h
@@ -2,15 +2,6 @@
 #define MODULES_H
 
 #include <stdint.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
diff --git a/lib/ff13/Source/diskio.c b/lib/ff13/Source/diskio.c
index 69458836..6b90b705 100644
--- a/lib/ff13/Source/diskio.c
+++ b/lib/ff13/Source/diskio.c
@@ -8,6 +8,7 @@
 /*-----------------------------------------------------------------------*/
 
 #include "diskio.h"     /* FatFs lower layer API */
+#include <stdbool.h>
 
 /* Definitions of physical drive number for each drive */
 #define DEV_FLASH       0   /* Example: Map MMC/SD card to physical drive 1 */
@@ -19,6 +20,7 @@
 
 /*local vaiables*/
 static uint8_t rtc_en;
+static bool s_diskio_initialized = false;
 
 #if SDHC
 /* # of times to check for a card, should be > 1 to detect both SD and MMC */
@@ -57,12 +59,14 @@ DSTATUS disk_status (
     #define STA_PROTECT		0x04	/* Write protected */
 #endif
 
-    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) {
@@ -87,7 +91,8 @@ DSTATUS disk_initialize (
 {
     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) {
@@ -123,7 +128,14 @@ DSTATUS disk_initialize (
     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)                                                        */
@@ -138,7 +150,9 @@ DRESULT disk_read (
 {
     DRESULT status = RES_ERROR;
 
-    if(pdrv == 0) {
+    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++) {
@@ -174,7 +188,9 @@ DRESULT disk_write (
 {
     DRESULT status = RES_ERROR;
 
-    if(pdrv == 0) {
+    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++) {
@@ -212,7 +228,9 @@ DRESULT disk_ioctl (
 {
     DRESULT status = RES_PARERR;
 
-    if(pdrv == 0) {
+    if (!s_diskio_initialized) {
+        status = STA_NOINIT | STA_NODISK;
+    } else if (pdrv == 0) {
         switch(cmd) {
             case CTRL_SYNC:
                 /* Mandatory */
diff --git a/lib/ff13/Source/diskio.h b/lib/ff13/Source/diskio.h
index 40551fbd..5dc221e2 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 3a14f3dc..f4b54f33 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/mpconfigport.h b/pycardium/mpconfigport.h
index 3230d432..38934682 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_DISPLAY_ENABLED              (1)
-- 
GitLab