diff --git a/py/py.mk b/py/py.mk
index 63770e2d245302cab8a67864a5d369022f1aa97a..61faf08920e6453172d4ad937b8333571cf11656 100644
--- a/py/py.mk
+++ b/py/py.mk
@@ -170,6 +170,7 @@ PY_O_BASENAME = \
 	../extmod/modussl.o \
 	../extmod/modurandom.o \
 	../extmod/fsusermount.o \
+	../extmod/vfs_fat.o \
 	../extmod/moduos_dupterm.o \
 
 # prepend the build destination prefix to the py object files
diff --git a/unix/Makefile b/unix/Makefile
index 49cf8bac163a7dcb88ba8c60190303c2d9ed59c0..42f0edf81138c19c17e3680ffb80b474a42d59d6 100644
--- a/unix/Makefile
+++ b/unix/Makefile
@@ -144,8 +144,14 @@ SRC_C = \
 	moduselect.c \
 	alloc.c \
 	coverage.c \
+	fatfs_port.c \
 	$(SRC_MOD)
 
+STMHAL_SRC_C = \
+	stmhal/diskio.c \
+	stmhal/ffconf.c \
+	stmhal/file.c
+
 # Include builtin package manager in the standard build (and coverage)
 ifeq ($(PROG),micropython)
 SRC_C += $(BUILD)/_frozen_upip.c
@@ -160,11 +166,14 @@ endif
 LIB_SRC_C = $(addprefix lib/,\
 	$(LIB_SRC_C_EXTRA) \
 	utils/printf.c \
+	fatfs/ff.c \
+	fatfs/option/ccsbcs.c \
 	)
 
 OBJ = $(PY_O)
 OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o))
 OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o))
+OBJ += $(addprefix $(BUILD)/, $(STMHAL_SRC_C:.c=.o))
 
 include ../py/mkrules.mk
 
diff --git a/unix/fatfs_port.c b/unix/fatfs_port.c
new file mode 100644
index 0000000000000000000000000000000000000000..c62ae2e7dcdf03fe6247878cbec10fb69fe6d860
--- /dev/null
+++ b/unix/fatfs_port.c
@@ -0,0 +1,6 @@
+#include "lib/fatfs/ff.h"
+#include "lib/fatfs/diskio.h"
+
+DWORD get_fattime(void) {
+    return 0;
+}
diff --git a/unix/modos.c b/unix/modos.c
index f6b3f0b97bd68e7667450327c7a6993603215473..0e699ec1d644bec0ba4e132aa5b7301d845cf4b0 100644
--- a/unix/modos.c
+++ b/unix/modos.c
@@ -39,6 +39,14 @@
 #include "py/objtuple.h"
 #include "extmod/misc.h"
 
+// Can't include this, as FATFS structure definition is required,
+// and FatFs header defining it conflicts with POSIX.
+//#include "extmod/fsusermount.h"
+MP_DECLARE_CONST_FUN_OBJ(fsuser_mount_obj);
+MP_DECLARE_CONST_FUN_OBJ(fsuser_umount_obj);
+MP_DECLARE_CONST_FUN_OBJ(fsuser_mkfs_obj);
+extern const mp_obj_type_t mp_fat_vfs_type;
+
 #ifdef __ANDROID__
 #define USE_STATFS 1
 #endif
@@ -228,6 +236,14 @@ STATIC const mp_rom_map_elem_t mp_module_os_globals_table[] = {
     { MP_ROM_QSTR(MP_QSTR_getenv), MP_ROM_PTR(&mod_os_getenv_obj) },
     { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&mod_os_mkdir_obj) },
     { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&mod_os_ilistdir_obj) },
+    #if MICROPY_FSUSERMOUNT
+    { MP_ROM_QSTR(MP_QSTR_vfs_mount), MP_ROM_PTR(&fsuser_mount_obj) },
+    { MP_ROM_QSTR(MP_QSTR_vfs_umount), MP_ROM_PTR(&fsuser_umount_obj) },
+    { MP_ROM_QSTR(MP_QSTR_vfs_mkfs), MP_ROM_PTR(&fsuser_mkfs_obj) },
+    #endif
+    #if MICROPY_VFS_FAT
+    { MP_ROM_QSTR(MP_QSTR_VfsFat), MP_ROM_PTR(&mp_fat_vfs_type) },
+    #endif
     #if MICROPY_PY_OS_DUPTERM
     { MP_ROM_QSTR(MP_QSTR_dupterm), MP_ROM_PTR(&mp_uos_dupterm_obj) },
     #endif
diff --git a/unix/mpconfigport.h b/unix/mpconfigport.h
index f7fdeec07c94966696488ea0d0596b3b7c48641c..719f60099302b253edf1544a29acc3fe6dc89e9c 100644
--- a/unix/mpconfigport.h
+++ b/unix/mpconfigport.h
@@ -112,6 +112,16 @@
 #define MICROPY_MACHINE_MEM_GET_READ_ADDR   mod_machine_mem_get_addr
 #define MICROPY_MACHINE_MEM_GET_WRITE_ADDR  mod_machine_mem_get_addr
 
+#define MICROPY_FATFS_ENABLE_LFN       (1)
+#define MICROPY_FATFS_RPATH            (2)
+// Can't have less than 3 values because diskio.h uses volume numbers
+// as volume types and PD_USER == 2.
+#define MICROPY_FATFS_VOLUMES          (3)
+#define MICROPY_FATFS_MAX_SS           (4096)
+#define MICROPY_FATFS_LFN_CODE_PAGE    (437) /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */
+#define MICROPY_FSUSERMOUNT            (1)
+#define MICROPY_VFS_FAT                (1)
+
 // Define to MICROPY_ERROR_REPORTING_DETAILED to get function, etc.
 // names in exception messages (may require more RAM).
 #define MICROPY_ERROR_REPORTING     (MICROPY_ERROR_REPORTING_DETAILED)
@@ -254,6 +264,8 @@ extern const struct _mp_obj_fun_builtin_t mp_builtin_open_obj;
 #define MICROPY_PORT_ROOT_POINTERS \
     const char *readline_hist[50]; \
     mp_obj_t keyboard_interrupt_obj; \
+    /* for user-mountable block device (max fixed at compile time) */ \
+    struct _fs_user_mount_t *fs_user_mount[MICROPY_FATFS_VOLUMES]; \
     void *mmap_region_head; \
 
 // We need to provide a declaration/definition of alloca()
diff --git a/unix/qstrdefsport.h b/unix/qstrdefsport.h
index 60a39c39f188ab055e7a074c5f6cfcf1ef3125cf..3aceb331fd5869b41ac15a61c33f037e1a9b9d5f 100644
--- a/unix/qstrdefsport.h
+++ b/unix/qstrdefsport.h
@@ -45,6 +45,14 @@ Q(getenv)
 Q(mkdir)
 Q(ilistdir)
 Q(errno)
+#if MICROPY_FSUSERMOUNT
+Q(vfs_mount)
+Q(vfs_umount)
+Q(vfs_mkfs)
+#endif
+#if MICROPY_VFS_FAT
+Q(VfsFat)
+#endif
 #if MICROPY_PY_OS_DUPTERM
 Q(dupterm)
 #endif