From 287ae82e837e41648a57b89df3ffed823f2e55cf Mon Sep 17 00:00:00 2001
From: swym <0xfd000000@gmail.com>
Date: Thu, 18 Jul 2019 18:47:42 +0200
Subject: [PATCH] First implementation of file IO via libff in epicardium &
 pycarddium

---
 epicardium/epicardium.h      |  24 +++
 epicardium/modules/fatfs.c   | 279 +++++++++++++++++++++++++++++------
 epicardium/modules/modules.h |   5 +-
 pycardium/meson.build        |   1 +
 pycardium/modules/fat_file.c | 273 ++++++++++++++++++++++++++++++++++
 pycardium/modules/qstrdefs.h |  21 +++
 pycardium/mpconfigport.h     |   1 +
 pycardium/mphalport.c        |   7 -
 8 files changed, 560 insertions(+), 51 deletions(-)
 create mode 100644 pycardium/modules/fat_file.c

diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index 470a41853..4456a0bbf 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -48,6 +48,14 @@ typedef unsigned int size_t;
 #define API_DISP_RECT          0x16
 #define API_DISP_CIRC          0x17
 #define API_DISP_PIXEL         0x18
+
+#define API_FILE_OPEN          0x30
+#define API_FILE_CLOSE         0x31
+#define API_FILE_READ          0x32
+#define API_FILE_WRITE         0x34
+#define API_FILE_FLUSH         0x35
+#define API_FILE_SEEK          0x36 //NYI
+#define API_FILE_TELL          0x37 //NYI
 /* clang-format on */
 
 typedef uint32_t api_int_id_t;
@@ -439,4 +447,20 @@ API(API_LIGHT_SENSOR_GET, int epic_light_sensor_get(uint16_t* value));
  */
 API(API_LIGHT_SENSOR_STOP, int epic_light_sensor_stop());
 
+/**
+ * File
+ * ====
+ * 
+ * Except for epic_open, which models C stdio's fopen function, close, read and write
+ * model close (3), read (3) and write(3)
+ * All file-related functions return >= 0 on success and -Exyz on failure, with error
+ * codes from errno.h (EIO, EINVAL etc.)
+ * 
+ */
+API(API_FILE_OPEN,  int32_t epic_open(const char* filename, const char* modeString));
+API(API_FILE_CLOSE, int32_t epic_close(int32_t fd));
+API(API_FILE_READ,  int32_t epic_read(int32_t fd, void* buf, uint32_t nbytes));
+API(API_FILE_WRITE, int32_t epic_write(int32_t fd, const void* buf, uint32_t nbytes));
+API(API_FILE_FLUSH, int32_t epic_flush(int32_t fd));
+
 #endif /* _EPICARDIUM_H */
diff --git a/epicardium/modules/fatfs.c b/epicardium/modules/fatfs.c
index fc76bb553..84622d264 100644
--- a/epicardium/modules/fatfs.c
+++ b/epicardium/modules/fatfs.c
@@ -2,68 +2,118 @@
  * support routines for FatFs
  */
 
-#include <stddef.h> //NULL
+#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"
+
+#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");
+    _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");
+
+// 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,
+};
+
+enum FatObjectType { FO_Nil, FO_File, FO_Dir };
+struct FatObject {
+    enum FatObjectType type;
+    union {
+        FIL file;
+        DIR dir;
+    };
+};
 
 static bool mount(void);
+static int
+get_fat_object(int i, enum FatObjectType expected, struct FatObject **res);
 
 DIR dir;
 FATFS FatFs;
+static struct FatObject s_openedObjects[EPIC_FAT_MAX_OPENED];
+
+#if (EPIC_FAT_STATIC_SEMAPHORE == 1)
+StaticSemaphore_t xSemaphoreBuffer;
+#endif
 
 static volatile struct {
-	bool initiaized;
+    bool initiaized;
 } s_state = {
-	.initiaized = false,
+    .initiaized = false,
 };
 
 void fatfs_init()
 {
-	if (mount()) {
-		s_state.initiaized = true;
-		printf("FatFs mounted\n");
-	}
+    if (mount()) {
+        s_state.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;
+    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;
+    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 */
 /*------------------------------------------------------------------------*/
@@ -79,8 +129,13 @@ static bool mount()
  */
 int ff_cre_syncobj(BYTE vol, FF_SYNC_t *sobj)
 {
-	*sobj = xSemaphoreCreateMutex();
-	return (int)(*sobj != NULL);
+#if (EPIC_FAT_STATIC_SEMAPHORE == 1)
+    *sobj = xSemaphoreCreateMutexStatic(&xSemaphoreBuffer);
+#else
+    *sobj = xSemaphoreCreateMutex();
+#endif //EPIC_FAT_STATIC_SEMAPHORE
+
+    return (int)(*sobj != NULL);
 }
 
 /*------------------------------------------------------------------------*/
@@ -98,10 +153,9 @@ int ff_cre_syncobj(BYTE vol, FF_SYNC_t *sobj)
  */
 int ff_del_syncobj(FF_SYNC_t sobj)
 {
-	printf("%s\n", __PRETTY_FUNCTION__);
-	/* FreeRTOS */
-	vSemaphoreDelete(sobj);
-	return 1;
+    /* FreeRTOS */
+    vSemaphoreDelete(sobj);
+    return 1;
 }
 
 /*------------------------------------------------------------------------*/
@@ -118,8 +172,8 @@ int ff_del_syncobj(FF_SYNC_t sobj)
  */
 int ff_req_grant(FF_SYNC_t sobj)
 {
-	/* FreeRTOS */
-	return (int)(xSemaphoreTake(sobj, FF_FS_TIMEOUT) == pdTRUE);
+    /* FreeRTOS */
+    return (int)(xSemaphoreTake(sobj, FF_FS_TIMEOUT) == pdTRUE);
 }
 
 /*------------------------------------------------------------------------*/
@@ -130,6 +184,145 @@ int ff_req_grant(FF_SYNC_t sobj)
 
 void ff_rel_grant(FF_SYNC_t sobj)
 {
-	/* FreeRTOS */
-	xSemaphoreGive(sobj);
+    /* FreeRTOS */
+    xSemaphoreGive(sobj);
+}
+
+int get_fat_object(int i, enum FatObjectType expected, struct FatObject **res)
+{
+    if (i < 0 || i >= EPIC_FAT_MAX_OPENED) {
+        *res = NULL;
+        return EBADF;
+    }
+    if (s_openedObjects[i].type != expected) {
+        *res = NULL;
+        return EBADF;
+    }
+    *res = &s_openedObjects[i];
+    return 0;
+}
+
+int32_t epic_open(const char *filename, const char *modeString)
+{
+    struct FatObject *o = NULL;
+    const char *mode_s  = modeString;
+    int i;
+    int mode = 0;
+
+    //find free object to use
+    for (i = 0; i < EPIC_FAT_MAX_OPENED; ++i) {
+        if (s_openedObjects[i].type == FO_Nil) {
+            break;
+        }
+    }
+    if (i == EPIC_FAT_MAX_OPENED) {
+        return -fresult_to_errno_table[FR_TOO_MANY_OPEN_FILES];
+    }
+    o = &s_openedObjects[i];
+
+    while (*mode_s) {
+        switch (*mode_s++) {
+        case 'r':
+            mode |= FA_READ;
+            break;
+        case 'w':
+            mode |= FA_WRITE | FA_CREATE_ALWAYS;
+            break;
+        case 'x':
+            mode |= FA_WRITE | FA_CREATE_NEW;
+            break;
+        case 'a':
+            mode |= FA_WRITE | FA_OPEN_ALWAYS;
+            break;
+        case '+':
+            mode |= FA_READ | FA_WRITE;
+            break;
+        }
+    }
+
+    int res = f_open(&o->file, filename, mode);
+    if (res != FR_OK) {
+        return -fresult_to_errno_table[res];
+    }
+    o->type = FO_File;
+
+    // for 'a' mode, we must begin at the end of the file
+    if ((mode & FA_OPEN_ALWAYS) != 0) {
+        f_lseek(&o->file, f_size(&o->file));
+    }
+
+    return i;
+}
+
+int32_t epic_close(int32_t fd)
+{
+    int res;
+    struct FatObject *o;
+    res = get_fat_object(fd, FO_File, &o);
+    if (res) {
+        return -res;
+    }
+
+    res = f_close(&o->file);
+    if (res != FR_OK) {
+        return -fresult_to_errno_table[res];
+    }
+
+    o->type = FO_Nil;
+    return 0;
 }
+
+
+int32_t epic_read(int32_t fd, void *buf, uint32_t nbytes)
+{
+    unsigned int nread = 0;
+
+    int res;
+    struct FatObject *o;
+    res = get_fat_object(fd, FO_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 nread;
+}
+
+int32_t epic_write(int32_t fd, const void *buf, uint32_t nbytes)
+{
+    unsigned int nwritten = 0;
+
+    int res;
+    struct FatObject *o;
+    res = get_fat_object(fd, FO_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 nwritten;
+}
+
+int32_t epic_flush(int32_t fd) {
+
+    int res;
+    struct FatObject *o;
+    res = get_fat_object(fd, FO_File, &o);
+    if (res) {
+        return -res;
+    }
+    res = f_sync(&o->file);
+    if (res != FR_OK) {
+        return -fresult_to_errno_table[res];
+    }
+
+    return 0;
+}
+
diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h
index 214545f60..f02298e36 100644
--- a/epicardium/modules/modules.h
+++ b/epicardium/modules/modules.h
@@ -1,7 +1,10 @@
 #ifndef MODULES_H
 #define MODULES_H
 
-/* FatFS */
+/* ---------- FAT fs ------------------------------------------------------ */
+/* max no. of descriptors (file & directory) that can be open at a time */
+#define EPIC_FAT_MAX_OPENED 16
+#define EPIC_FAT_STATIC_SEMAPHORE 1
 void fatfs_init(void);
 
 /* ---------- Serial ------------------------------------------------------- */
diff --git a/pycardium/meson.build b/pycardium/meson.build
index 895be888e..453fc752e 100644
--- a/pycardium/meson.build
+++ b/pycardium/meson.build
@@ -7,6 +7,7 @@ modsrc = files(
   'modules/utime.c',
   'modules/vibra.c',
   'modules/light_sensor.c'
+  'modules/fat_file.c',
 )
 
 #################################
diff --git a/pycardium/modules/fat_file.c b/pycardium/modules/fat_file.c
new file mode 100644
index 000000000..711217b4d
--- /dev/null
+++ b/pycardium/modules/fat_file.c
@@ -0,0 +1,273 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2013, 2014 Damien P. George
+ *
+ * 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 <stdio.h>
+
+#include "py/mpconfig.h"
+#include "py/runtime.h"
+#include "py/builtin.h"
+#include "py/stream.h"
+#include "py/mperrno.h"
+#include "epicardium.h"
+
+extern const mp_obj_type_t mp_type_fat_textio;
+#if MICROPY_PY_IO_FILEIO
+extern const mp_obj_type_t mp_type_fat_fileio;
+#endif
+
+typedef struct _pyb_file_obj_t {
+	mp_obj_base_t base;
+	int fd;
+} pyb_file_obj_t;
+
+STATIC void
+file_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind)
+{
+	(void)kind;
+	mp_printf(
+		print,
+		"<io.%s %p>",
+		mp_obj_get_type_str(self_in),
+		MP_OBJ_TO_PTR(self_in)
+	);
+}
+
+STATIC mp_uint_t
+file_obj_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode)
+{
+	pyb_file_obj_t *self = MP_OBJ_TO_PTR(self_in);
+	int res              = epic_read(self->fd, buf, size);
+	if (res < 0) {
+		*errcode = -res;
+		return MP_STREAM_ERROR;
+	}
+	return res;
+}
+
+STATIC mp_uint_t
+file_obj_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode)
+{
+	pyb_file_obj_t *self = MP_OBJ_TO_PTR(self_in);
+	int res              = epic_write(self->fd, buf, size);
+	if (res < 0) {
+		*errcode = -res;
+		return MP_STREAM_ERROR;
+	}
+	return res;
+}
+
+STATIC mp_obj_t file_obj___exit__(size_t n_args, const mp_obj_t *args)
+{
+	(void)n_args;
+	return mp_stream_close(args[0]);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(
+	file_obj___exit___obj, 4, 4, file_obj___exit__
+);
+
+STATIC mp_uint_t
+file_obj_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode)
+{
+	pyb_file_obj_t *self = MP_OBJ_TO_PTR(o_in);
+    int res;
+    switch (request) {
+        case MP_STREAM_FLUSH:
+            res = epic_flush(self->fd);
+            if (res < 0) {
+                *errcode = -res;
+                return MP_STREAM_ERROR;
+            }
+            return 0;
+        case MP_STREAM_CLOSE:
+            res = epic_close(self->fd);
+            if (res < 0) {
+                *errcode = -res;
+                return MP_STREAM_ERROR;
+            }
+            return 0;
+    }
+    //every valid case returns either success or error, so this is EINVAL land:
+    *errcode = MP_EINVAL;
+    return MP_STREAM_ERROR;
+	// if (request == MP_STREAM_SEEK) {
+	//     struct mp_stream_seek_t *s = (struct mp_stream_seek_t*)(uintptr_t)arg;
+
+	//     switch (s->whence) {
+	//         case 0: // SEEK_SET
+	//             f_lseek(&self->fp, s->offset);
+	//             break;
+
+	//         case 1: // SEEK_CUR
+	//             f_lseek(&self->fp, f_tell(&self->fp) + s->offset);
+	//             break;
+
+	//         case 2: // SEEK_END
+	//             f_lseek(&self->fp, f_size(&self->fp) + s->offset);
+	//             break;
+	//     }
+
+	//     s->offset = f_tell(&self->fp);
+	//     return 0;
+}
+
+// Note: encoding is ignored for now; it's also not a valid kwarg for CPython's FileIO,
+// but by adding it here we can use one single mp_arg_t array for open() and FileIO's constructor
+STATIC const mp_arg_t file_open_args[] = {
+	{ MP_QSTR_file,
+	  MP_ARG_OBJ | MP_ARG_REQUIRED,
+	  { .u_rom_obj = MP_ROM_PTR(&mp_const_none_obj) } },
+	{ MP_QSTR_mode, MP_ARG_OBJ, { .u_obj = MP_OBJ_NEW_QSTR(MP_QSTR_r) } },
+	{ MP_QSTR_encoding,
+	  MP_ARG_OBJ | MP_ARG_KW_ONLY,
+	  { .u_rom_obj = MP_ROM_PTR(&mp_const_none_obj) } },
+};
+#define FILE_OPEN_NUM_ARGS MP_ARRAY_SIZE(file_open_args)
+
+STATIC mp_obj_t file_open(const mp_obj_type_t *type, mp_arg_val_t *args)
+{
+	const char *modeString = mp_obj_str_get_str(args[1].u_obj);
+	const char *mode_s     = modeString;
+	// modes r w x a + are handled on epicardium side, binary / text
+	// is relevant for python type so look for these here
+	while (*mode_s) {
+		switch (*mode_s++) {
+#if MICROPY_PY_IO_FILEIO
+		case 'b':
+			type = &mp_type_fat_fileio;
+			break;
+#endif
+		case 't':
+			type = &mp_type_fat_textio;
+			break;
+		}
+	}
+
+	pyb_file_obj_t *o = m_new_obj_with_finaliser(pyb_file_obj_t);
+	o->base.type      = type;
+
+	const char *fname = mp_obj_str_get_str(args[0].u_obj);
+	int res           = epic_open(fname, modeString);
+	if (res < 0) {
+		m_del_obj(pyb_file_obj_t, o);
+		mp_raise_OSError(-res);
+	}
+    o->fd = res;
+
+	return MP_OBJ_FROM_PTR(o);
+}
+
+STATIC mp_obj_t file_obj_make_new(
+	const mp_obj_type_t *type,
+	size_t n_args,
+	size_t n_kw,
+	const mp_obj_t *args
+) {
+	mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS];
+	mp_arg_parse_all_kw_array(
+		n_args,
+		n_kw,
+		args,
+		FILE_OPEN_NUM_ARGS,
+		file_open_args,
+		arg_vals
+	);
+	return file_open(type, arg_vals);
+}
+
+// TODO gc hook to close the file if not already closed
+
+STATIC const mp_rom_map_elem_t rawfile_locals_dict_table[] = {
+	{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_readline),
+	  MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_readlines),
+	  MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_seek), MP_ROM_PTR(&mp_stream_seek_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_tell), MP_ROM_PTR(&mp_stream_tell_obj) },
+	{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_stream_close_obj) },
+	{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) },
+	{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&file_obj___exit___obj) },
+};
+
+STATIC MP_DEFINE_CONST_DICT(rawfile_locals_dict, rawfile_locals_dict_table);
+
+#if MICROPY_PY_IO_FILEIO
+STATIC const mp_stream_p_t fileio_stream_p = {
+	.read  = file_obj_read,
+	.write = file_obj_write,
+	.ioctl = file_obj_ioctl,
+};
+
+const mp_obj_type_t mp_type_fat_fileio = {
+	{ &mp_type_type },
+	.name        = MP_QSTR_FileIO,
+	.print       = file_obj_print,
+	.make_new    = file_obj_make_new,
+	.getiter     = mp_identity_getiter,
+	.iternext    = mp_stream_unbuffered_iter,
+	.protocol    = &fileio_stream_p,
+	.locals_dict = (mp_obj_dict_t *)&rawfile_locals_dict,
+};
+#endif
+
+STATIC const mp_stream_p_t textio_stream_p = {
+	.read    = file_obj_read,
+	.write   = file_obj_write,
+	.ioctl   = file_obj_ioctl,
+	.is_text = true,
+};
+
+const mp_obj_type_t mp_type_fat_textio = {
+	{ &mp_type_type },
+	.name        = MP_QSTR_TextIOWrapper,
+	.print       = file_obj_print,
+	.make_new    = file_obj_make_new,
+	.getiter     = mp_identity_getiter,
+	.iternext    = mp_stream_unbuffered_iter,
+	.protocol    = &textio_stream_p,
+	.locals_dict = (mp_obj_dict_t *)&rawfile_locals_dict,
+};
+
+// Factory function for I/O stream classes
+mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs)
+{
+	// TODO: analyze buffering args and instantiate appropriate type
+	mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS];
+	mp_arg_parse_all(
+		n_args,
+		args,
+		kwargs,
+		FILE_OPEN_NUM_ARGS,
+		file_open_args,
+		arg_vals
+	);
+	return file_open(&mp_type_fat_textio, arg_vals);
+}
+MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open);
diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h
index 3dda54d66..e8ac9dac5 100644
--- a/pycardium/modules/qstrdefs.h
+++ b/pycardium/modules/qstrdefs.h
@@ -46,3 +46,24 @@ Q(light_sensor)
 Q(start)
 Q(get_reading)
 Q(stop)
+
+/* file */
+Q(__del__)
+Q(__enter__)
+Q(__exit__)
+Q(close)
+Q(encoding)
+Q(file)
+Q(FileIO)
+Q(flush)
+Q(mode)
+Q(r)
+Q(read)
+Q(readinto)
+Q(readline)
+Q(readlines)
+Q(seek)
+Q(tell)
+Q(TextIOWrapper)
+Q(write)
+
diff --git a/pycardium/mpconfigport.h b/pycardium/mpconfigport.h
index 6bfb2c671..9bed1f600 100644
--- a/pycardium/mpconfigport.h
+++ b/pycardium/mpconfigport.h
@@ -76,3 +76,4 @@ typedef long mp_off_t;
 /* For some reason, we need to define readline history manually */
 #define MICROPY_PORT_ROOT_POINTERS \
     const char *readline_hist[16];
+
diff --git a/pycardium/mphalport.c b/pycardium/mphalport.c
index 95d4bffa9..7c14df44e 100644
--- a/pycardium/mphalport.c
+++ b/pycardium/mphalport.c
@@ -132,10 +132,3 @@ mp_import_stat_t mp_import_stat(const char *path)
 	return MP_IMPORT_STAT_NO_EXIST;
 }
 
-mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs)
-{
-	/* TODO: Once fs is implemented, get this working as well */
-	mp_raise_NotImplementedError("FS is not yet implemented");
-	return mp_const_none;
-}
-MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open);
-- 
GitLab