diff --git a/extmod/vfs_fat.c b/extmod/vfs_fat.c
index dee4f92988786c6a6432e4aa854bdff9945f9e62..41c32c6b6ac2e98823894828fd3c6533bb10c970 100644
--- a/extmod/vfs_fat.c
+++ b/extmod/vfs_fat.c
@@ -91,7 +91,7 @@ STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(fat_vfs_mkfs_obj, MP_ROM_PTR(&fat_vfs_mk
 
 STATIC MP_DEFINE_CONST_FUN_OBJ_3(fat_vfs_open_obj, fatfs_builtin_open_self);
 
-STATIC mp_obj_t fat_vfs_listdir_func(size_t n_args, const mp_obj_t *args) {
+STATIC mp_obj_t fat_vfs_ilistdir_func(size_t n_args, const mp_obj_t *args) {
     mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(args[0]);
     bool is_str_type = true;
     const char *path;
@@ -104,9 +104,9 @@ STATIC mp_obj_t fat_vfs_listdir_func(size_t n_args, const mp_obj_t *args) {
         path = "";
     }
 
-    return fat_vfs_listdir2(self, path, is_str_type);
+    return fat_vfs_ilistdir2(self, path, is_str_type);
 }
-STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fat_vfs_listdir_obj, 1, 2, fat_vfs_listdir_func);
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fat_vfs_ilistdir_obj, 1, 2, fat_vfs_ilistdir_func);
 
 STATIC mp_obj_t fat_vfs_remove_internal(mp_obj_t vfs_in, mp_obj_t path_in, mp_int_t attr) {
     mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in);
@@ -321,7 +321,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(fat_vfs_umount_obj, vfs_fat_umount);
 STATIC const mp_rom_map_elem_t fat_vfs_locals_dict_table[] = {
     { MP_ROM_QSTR(MP_QSTR_mkfs), MP_ROM_PTR(&fat_vfs_mkfs_obj) },
     { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&fat_vfs_open_obj) },
-    { MP_ROM_QSTR(MP_QSTR_listdir), MP_ROM_PTR(&fat_vfs_listdir_obj) },
+    { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&fat_vfs_ilistdir_obj) },
     { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&fat_vfs_mkdir_obj) },
     { MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&fat_vfs_rmdir_obj) },
     { MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&fat_vfs_chdir_obj) },
diff --git a/extmod/vfs_fat.h b/extmod/vfs_fat.h
index 7eb865254f51a8986129955ebdfb06bb99f660b0..6c7c05a9ac483a0a34aaea68b3ddbc689597918f 100644
--- a/extmod/vfs_fat.h
+++ b/extmod/vfs_fat.h
@@ -57,4 +57,4 @@ mp_import_stat_t fat_vfs_import_stat(struct _fs_user_mount_t *vfs, const char *p
 mp_obj_t fatfs_builtin_open_self(mp_obj_t self_in, mp_obj_t path, mp_obj_t mode);
 MP_DECLARE_CONST_FUN_OBJ_KW(mp_builtin_open_obj);
 
-mp_obj_t fat_vfs_listdir2(struct _fs_user_mount_t *vfs, const char *path, bool is_str_type);
+mp_obj_t fat_vfs_ilistdir2(struct _fs_user_mount_t *vfs, const char *path, bool is_str_type);
diff --git a/extmod/vfs_fat_misc.c b/extmod/vfs_fat_misc.c
index 19db99c7f7ec31b89a9d11ebc5043d68046ee307..5b906189f2bc5e3262cff54631dd9b028e972b69 100644
--- a/extmod/vfs_fat_misc.c
+++ b/extmod/vfs_fat_misc.c
@@ -34,51 +34,64 @@
 #include "extmod/vfs_fat.h"
 #include "py/lexer.h"
 
-// TODO: actually, the core function should be ilistdir()
-
-mp_obj_t fat_vfs_listdir2(fs_user_mount_t *vfs, const char *path, bool is_str_type) {
-    FRESULT res;
-    FILINFO fno;
+typedef struct _mp_vfs_fat_ilistdir_it_t {
+    mp_obj_base_t base;
+    mp_fun_1_t iternext;
+    bool is_str;
     FF_DIR dir;
+} mp_vfs_fat_ilistdir_it_t;
 
-    res = f_opendir(&vfs->fatfs, &dir, path);
-    if (res != FR_OK) {
-        mp_raise_OSError(fresult_to_errno_table[res]);
-    }
-
-    mp_obj_t dir_list = mp_obj_new_list(0, NULL);
+STATIC mp_obj_t mp_vfs_fat_ilistdir_it_iternext(mp_obj_t self_in) {
+    mp_vfs_fat_ilistdir_it_t *self = MP_OBJ_TO_PTR(self_in);
 
     for (;;) {
-        res = f_readdir(&dir, &fno);                   /* Read a directory item */
-        if (res != FR_OK || fno.fname[0] == 0) break;  /* Break on error or end of dir */
-        if (fno.fname[0] == '.' && fno.fname[1] == 0) continue;             /* Ignore . entry */
-        if (fno.fname[0] == '.' && fno.fname[1] == '.' && fno.fname[2] == 0) continue;             /* Ignore .. entry */
-
+        FILINFO fno;
+        FRESULT res = f_readdir(&self->dir, &fno);
         char *fn = fno.fname;
+        if (res != FR_OK || fn[0] == 0) {
+            // stop on error or end of dir
+            break;
+        }
+        if (fn[0] == '.' && (fn[1] == 0 || (fn[1] == '.' && fn[2] == 0))) {
+            // skip . and ..
+            continue;
+        }
 
-        /*
+        // make 3-tuple with info about this entry
+        mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL));
+        if (self->is_str) {
+            t->items[0] = mp_obj_new_str(fn, strlen(fn), false);
+        } else {
+            t->items[0] = mp_obj_new_bytes((const byte*)fn, strlen(fn));
+        }
         if (fno.fattrib & AM_DIR) {
             // dir
+            t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFDIR);
         } else {
             // file
+            t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFREG);
         }
-        */
+        t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // no inode number
 
-        // make a string object for this entry
-        mp_obj_t entry_o;
-        if (is_str_type) {
-            entry_o = mp_obj_new_str(fn, strlen(fn), false);
-        } else {
-            entry_o = mp_obj_new_bytes((const byte*)fn, strlen(fn));
-        }
-
-        // add the entry to the list
-        mp_obj_list_append(dir_list, entry_o);
+        return MP_OBJ_FROM_PTR(t);
     }
 
-    f_closedir(&dir);
+    // ignore error because we may be closing a second time
+    f_closedir(&self->dir);
 
-    return dir_list;
+    return MP_OBJ_STOP_ITERATION;
+}
+
+mp_obj_t fat_vfs_ilistdir2(fs_user_mount_t *vfs, const char *path, bool is_str_type) {
+    mp_vfs_fat_ilistdir_it_t *iter = m_new_obj(mp_vfs_fat_ilistdir_it_t);
+    iter->base.type = &mp_type_polymorph_iter;
+    iter->iternext = mp_vfs_fat_ilistdir_it_iternext;
+    iter->is_str = is_str_type;
+    FRESULT res = f_opendir(&vfs->fatfs, &iter->dir, path);
+    if (res != FR_OK) {
+        mp_raise_OSError(fresult_to_errno_table[res]);
+    }
+    return MP_OBJ_FROM_PTR(iter);
 }
 
 mp_import_stat_t fat_vfs_import_stat(fs_user_mount_t *vfs, const char *path) {