diff options
Diffstat (limited to 'ports/zephyr')
| -rw-r--r-- | ports/zephyr/CMakeLists.txt | 22 | ||||
| -rw-r--r-- | ports/zephyr/README.md | 44 | ||||
| -rw-r--r-- | ports/zephyr/boards/rpi_pico.conf | 10 | ||||
| -rw-r--r-- | ports/zephyr/boards/rpi_pico.overlay | 14 | ||||
| -rw-r--r-- | ports/zephyr/modules/_boot.py | 36 | ||||
| -rw-r--r-- | ports/zephyr/modzephyr.c | 3 | ||||
| -rw-r--r-- | ports/zephyr/modzephyr.h | 4 | ||||
| -rw-r--r-- | ports/zephyr/mpconfigport.h | 5 | ||||
| -rw-r--r-- | ports/zephyr/zephyr_filesystem.c | 782 |
9 files changed, 914 insertions, 6 deletions
diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index e7a55d804..9db0a95c6 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -55,6 +55,7 @@ set(MICROPY_SOURCE_PORT uart_core.c zephyr_device.c zephyr_storage.c + zephyr_filesystem.c mpthreadport.c ) list(TRANSFORM MICROPY_SOURCE_PORT PREPEND ${MICROPY_PORT_DIR}/) @@ -81,14 +82,33 @@ set(MICROPY_QSTRDEFS_PORT ${MICROPY_PORT_DIR}/qstrdefsport.h ) -set(MICROPY_SOURCE_LIB +if (CONFIG_MICROPY_VFS_FAT) + +list(APPEND MICROPY_SOURCE_LIB oofatfs/ff.c oofatfs/ffunicode.c +) + +endif() + +if (CONFIG_MICROPY_VFS_LFS1) + +list(APPEND MICROPY_SOURCE_LIB littlefs/lfs1.c littlefs/lfs1_util.c +) + +endif() + +if (CONFIG_MICROPY_VFS_LFS2) + +list(APPEND MICROPY_SOURCE_LIB littlefs/lfs2.c littlefs/lfs2_util.c ) + +endif() + list(TRANSFORM MICROPY_SOURCE_LIB PREPEND ${MICROPY_DIR}/lib/) set(MICROPY_SOURCE_QSTR diff --git a/ports/zephyr/README.md b/ports/zephyr/README.md index eedcdcb58..f3fcd8f70 100644 --- a/ports/zephyr/README.md +++ b/ports/zephyr/README.md @@ -208,3 +208,47 @@ run the following after you built an image with the previous command: $ west build -t run +File Systems +------------ + +The Zephyr Micropython port provides 2 options for handling filesystems on the device: +The first is the Micropython filesystem management, which uses Micropython's filesystem code and +relies on zephyr's FlashArea API, this is enabled by default when +`CONFIG_FLASH` and `CONFIG_FLASH_MAP` are turned on. +The second option is using Zephyr's Filesystem management: +This relies on Zephyr's File System features and enables sharing access between zephyr and +micropython code. Several configuration options must be enabled: + + CONFIG_FLASH_MAP=y # Requirement for the file system subsystem + CONFIG_FILE_SYSTEM=y # Enables the file system subsystem + CONFIG_FILE_SYSTEM_LITTLEFS=y # Enables the littlefs support in zephyr + CONFIG_FILE_SYSTEM_MKFS=y # Enables the ability to create new file systems + +Then, a fstab must be added to the dts overlay, for example: + + + fstab { + compatible = "zephyr,fstab"; + lfs2: lfs2 { + compatible = "zephyr,fstab,littlefs"; + mount-point = "/flash"; + partition = <&storage_partition>; + read-size=<16>; + prog-size=<4096>; + cache-size=<4096>; + lookahead-size=<32>; + block-cycles=<4>; + }; + }; + +It is then possible to use the FS like a normal Micropython filesystem: + + import vfs, zephyr + zfs = zephyr.FileSystem(zephyr.FileSystem.fstab()[0]) + vfs.mount(zfs, "/zephyr") + +You may disable Micropython's File system code to save space: + + CONFIG_MICROPY_VFS_FAT=n + CONFIG_MICROPY_VFS_LFS1=n + CONFIG_MICROPY_VFS_LFS2=n diff --git a/ports/zephyr/boards/rpi_pico.conf b/ports/zephyr/boards/rpi_pico.conf index 683279ddc..6a0be1c37 100644 --- a/ports/zephyr/boards/rpi_pico.conf +++ b/ports/zephyr/boards/rpi_pico.conf @@ -18,3 +18,13 @@ CONFIG_SPI=y # MicroPython config. CONFIG_MICROPY_HEAP_SIZE=196608 + +# File System Configuration +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_LITTLEFS=y +CONFIG_FILE_SYSTEM_MKFS=y +CONFIG_MICROPY_VFS_FAT=n +CONFIG_MICROPY_VFS_LFS1=n +CONFIG_MICROPY_VFS_LFS2=n +# Default heap for littlefs is too small +CONFIG_FS_LITTLEFS_FC_HEAP_SIZE=8192 diff --git a/ports/zephyr/boards/rpi_pico.overlay b/ports/zephyr/boards/rpi_pico.overlay index d63ed73bd..436e4210a 100644 --- a/ports/zephyr/boards/rpi_pico.overlay +++ b/ports/zephyr/boards/rpi_pico.overlay @@ -9,6 +9,20 @@ /* Use USB CDC ACM as the console. */ zephyr,console = &cdc_acm_uart0; }; + + fstab { + compatible = "zephyr,fstab"; + lfs: lfs { + compatible = "zephyr,fstab,littlefs"; + mount-point = "/flash"; + partition = <&storage_partition>; + read-size=<16>; + prog-size=<256>; + cache-size=<1024>; + lookahead-size=<32>; + block-cycles=<4>; + }; + }; }; /* Delete defined partitions and make a layout matching the rp2 port RPI_PICO configuration. */ diff --git a/ports/zephyr/modules/_boot.py b/ports/zephyr/modules/_boot.py index d0ba21d3f..ab3ed9c63 100644 --- a/ports/zephyr/modules/_boot.py +++ b/ports/zephyr/modules/_boot.py @@ -11,12 +11,39 @@ FlashArea = getattr(zephyr, "FlashArea", None) # DiskAccess depends on CONFIG_DISK_ACCESS DiskAccess = getattr(zephyr, "DiskAccess", None) +# zephyr.FileSystem depends on CONFIG_FILE_SYSTEM and CONFIG_FLASH_MAP +FileSystem = getattr(zephyr, "FileSystem", None) + _FLASH = const("/flash") _FLASH_LIB = const("/flash/lib") _STORAGE_KEY = const("storage") _FLASH_EXISTS = False +def mount_filesystem_flash(): + """Create a filesystem if needed on the FS partition "/flash" + and mount it on /flash. + Return True if successful, False otherwise. + """ + if _FLASH in FileSystem.fstab(): + fs = FileSystem(_FLASH) + retval = True + try: + vfs.mount(fs, _FLASH) + except OSError: + if getattr(fs, "mkfs", None): + try: + fs.mkfs() + vfs.mount(fs, _FLASH) + except OSError: + print("Error formatting flash partition") + retval = False + else: + retval = False + return retval + return False + + def create_flash_partition(): """Create an LFS2 filesystem on the partition labeled storage and mount it on /flash. @@ -54,7 +81,10 @@ def mount_all_disks(): return retval -if FlashArea and create_flash_partition(): +# Prefer FileSystem over FlashArea Access +if FileSystem and mount_filesystem_flash(): + _FLASH_EXISTS = True +elif FlashArea and create_flash_partition(): _FLASH_EXISTS = True # Prefer disks to /flash @@ -67,6 +97,6 @@ if _FLASH_EXISTS: sys.path.append(_FLASH_LIB) # Cleanup globals for boot.py/main.py -del FlashArea, DiskAccess, zephyr +del FlashArea, DiskAccess, FileSystem, zephyr del sys, vfs, os, const -del create_flash_partition, mount_all_disks, _FLASH_EXISTS +del mount_filesystem_flash, create_flash_partition, mount_all_disks, _FLASH_EXISTS diff --git a/ports/zephyr/modzephyr.c b/ports/zephyr/modzephyr.c index 08fdf5c5a..7726737e1 100644 --- a/ports/zephyr/modzephyr.c +++ b/ports/zephyr/modzephyr.c @@ -86,6 +86,9 @@ static const mp_rom_map_elem_t mp_module_time_globals_table[] = { #ifdef CONFIG_FLASH_MAP { MP_ROM_QSTR(MP_QSTR_FlashArea), MP_ROM_PTR(&zephyr_flash_area_type) }, #endif + #ifdef CONFIG_FILE_SYSTEM + { MP_ROM_QSTR(MP_QSTR_FileSystem), MP_ROM_PTR(&zephyr_filesystem_type) }, + #endif }; static MP_DEFINE_CONST_DICT(mp_module_time_globals, mp_module_time_globals_table); diff --git a/ports/zephyr/modzephyr.h b/ports/zephyr/modzephyr.h index f9b7e8eea..834ec4a31 100644 --- a/ports/zephyr/modzephyr.h +++ b/ports/zephyr/modzephyr.h @@ -37,4 +37,8 @@ extern const mp_obj_type_t zephyr_disk_access_type; extern const mp_obj_type_t zephyr_flash_area_type; #endif +#ifdef CONFIG_FILE_SYSTEM +extern const mp_obj_type_t zephyr_filesystem_type; +#endif + #endif // MICROPY_INCLUDED_ZEPHYR_MODZEPHYR_H diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index 8d5d60ed2..7f8497348 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -84,9 +84,10 @@ #endif #define MICROPY_PY_MACHINE_PWM (1) #define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/zephyr/machine_pwm.c" -#ifdef CONFIG_NETWORKING -// If we have networking, we likely want errno comfort +#if defined(CONFIG_NETWORKING) || defined(CONFIG_FILE_SYSTEM) #define MICROPY_PY_ERRNO (1) +#endif +#ifdef CONFIG_NETWORKING #define MICROPY_PY_SOCKET (1) #endif #ifdef CONFIG_BT diff --git a/ports/zephyr/zephyr_filesystem.c b/ports/zephyr/zephyr_filesystem.c new file mode 100644 index 000000000..2ca726253 --- /dev/null +++ b/ports/zephyr/zephyr_filesystem.c @@ -0,0 +1,782 @@ +/* +* This file is part of the MicroPython project, http://micropython.org/ +* +* The MIT License (MIT) +* +* Copyright (c) 2025 MASSDRIVER EI (massdriver.space) +* +* 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 "py/mpconfig.h" + +#if CONFIG_FILE_SYSTEM + +#if !MICROPY_VFS +#error "with CONFIG_FILE_SYSTEM enabled, must also enable MICROPY_VFS" +#endif + +#include <zephyr/fs/fs.h> +#include <zephyr/fs/fs_sys.h> + +#include <string.h> +#include "py/obj.h" +#include "py/objstr.h" +#include "py/runtime.h" +#include "py/stream.h" +#include "py/mperrno.h" +#include "extmod/vfs.h" +#include "shared/timeutils/timeutils.h" + +// Declare each FSTAB entry +#define FOREACH_FS_DEFINE(fs) FS_FSTAB_DECLARE_ENTRY(fs); + +#define FOREACH_FSTAB_DEFINE(n) DT_FOREACH_CHILD(n, FOREACH_FS_DEFINE) + +DT_FOREACH_STATUS_OKAY(zephyr_fstab, FOREACH_FSTAB_DEFINE) + +// Add all FSTAB entries to a table for us to use dynamically +#define FOREACH_FS(fs) FS_FSTAB_ENTRY(fs) + +#define FOREACH_FSTAB(n) DT_FOREACH_CHILD(n, FOREACH_FS) + +static struct fs_mount_t *const zephyr_fs_mounts[] = { + &DT_FOREACH_STATUS_OKAY(zephyr_fstab, FOREACH_FSTAB), +}; + +#define zephyr_fs_mounts_size sizeof(zephyr_fs_mounts) / sizeof(struct fs_mount_t *) + +typedef struct _zephyr_fs_obj_t { + mp_obj_base_t base; + struct fs_mount_t *mount; + vstr_t cur_dir; + vstr_t root_dir; +} zephyr_fs_obj_t; + +const char *zephyr_fs_make_path(zephyr_fs_obj_t *self, mp_obj_t path_in) { + const char *path = mp_obj_str_get_str(path_in); + + if (path[0] != '/') { + size_t l = vstr_len(&self->root_dir); + size_t lc = vstr_len(&self->cur_dir); + vstr_add_str(&self->root_dir, "/"); + vstr_add_strn(&self->root_dir, self->cur_dir.buf, lc); + vstr_add_str(&self->root_dir, path); + path = vstr_null_terminated_str(&self->root_dir); + self->root_dir.len = l; + } else { + size_t l = vstr_len(&self->root_dir); + vstr_add_str(&self->root_dir, path); + path = vstr_null_terminated_str(&self->root_dir); + self->root_dir.len = l; + } + return path; +} + +typedef struct _zephyr_file_obj_t { + mp_obj_base_t base; + struct fs_file_t fp; +} zephyr_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) { + zephyr_file_obj_t *self = MP_OBJ_TO_PTR(self_in); + ssize_t err; + + err = fs_read(&self->fp, buf, size); + if (err < 0) { + *errcode = -err; + return MP_STREAM_ERROR; + } + return err; +} + +static mp_uint_t file_obj_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { + zephyr_file_obj_t *self = MP_OBJ_TO_PTR(self_in); + ssize_t err; + + err = fs_write(&self->fp, buf, size); + if (err < 0) { + *errcode = -err; + return MP_STREAM_ERROR; + } + return err; +} + +static mp_uint_t file_obj_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode) { + zephyr_file_obj_t *self = MP_OBJ_TO_PTR(o_in); + int err = -EINVAL; + + if (request == MP_STREAM_SEEK) { + struct mp_stream_seek_t *s = (struct mp_stream_seek_t *)(uintptr_t)arg; + + MP_STATIC_ASSERT(FS_SEEK_SET == MP_SEEK_SET); + MP_STATIC_ASSERT(FS_SEEK_CUR == MP_SEEK_CUR); + MP_STATIC_ASSERT(FS_SEEK_END == MP_SEEK_END); + + err = fs_seek(&self->fp, s->offset, s->whence); + + if (err < 0) { + *errcode = -err; + return MP_STREAM_ERROR; + } + + off_t tell = fs_tell(&self->fp); + if (tell < 0) { + *errcode = -err; + return MP_STREAM_ERROR; + } + s->offset = tell; + return 0; + + } else if (request == MP_STREAM_FLUSH) { + err = fs_sync(&self->fp); + if (err < 0) { + *errcode = -err; + return MP_STREAM_ERROR; + } + return 0; + + } else if (request == MP_STREAM_CLOSE) { + err = fs_close(&self->fp); + if (err < 0) { + *errcode = -err; + return MP_STREAM_ERROR; + } + return 0; + + } else { + *errcode = MP_EINVAL; + return MP_STREAM_ERROR; + } +} + +static const mp_rom_map_elem_t zephyr_fs_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(&mp_stream___exit___obj) }, +}; + +static MP_DEFINE_CONST_DICT(zephyr_fs_rawfile_locals_dict, zephyr_fs_rawfile_locals_dict_table); + +static const mp_stream_p_t zephyr_fs_fileio_stream_p = { + .read = file_obj_read, + .write = file_obj_write, + .ioctl = file_obj_ioctl, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + mp_type_zephyr_fs_fileio, + MP_QSTR_FileIO, + MP_TYPE_FLAG_ITER_IS_STREAM, + print, file_obj_print, + protocol, &zephyr_fs_fileio_stream_p, + locals_dict, &zephyr_fs_rawfile_locals_dict + ); + +static const mp_stream_p_t zephyr_fs_textio_stream_p = { + .read = file_obj_read, + .write = file_obj_write, + .ioctl = file_obj_ioctl, + .is_text = true, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + mp_type_zephyr_fs_textio, + MP_QSTR_TextIOWrapper, + MP_TYPE_FLAG_ITER_IS_STREAM, + print, file_obj_print, + protocol, &zephyr_fs_textio_stream_p, + locals_dict, &zephyr_fs_rawfile_locals_dict + ); + +// Factory function for I/O stream classes +static mp_obj_t zephyr_fs_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(self_in); + const mp_obj_type_t *type = &mp_type_zephyr_fs_textio; + int flags = 0; + int err; + bool x_mode = false; + const char *mode_str = mp_obj_str_get_str(mode_in); + const char *path = zephyr_fs_make_path(self, path_in); + + for (; *mode_str; ++mode_str) { + int new_flags = 0; + switch (*mode_str) { + case 'r': + new_flags = FS_O_READ; + break; + case 'w': + new_flags = FS_O_WRITE | FS_O_CREATE | FS_O_TRUNC; + break; + case 'x': + new_flags = FS_O_WRITE; + x_mode = true; + break; + case 'a': + new_flags = FS_O_WRITE | FS_O_CREATE | FS_O_APPEND; + break; + case '+': + flags |= FS_O_RDWR; + break; + case 'b': + type = &mp_type_zephyr_fs_fileio; + break; + case 't': + type = &mp_type_zephyr_fs_textio; + break; + } + if (new_flags) { + if (flags) { + mp_raise_ValueError(NULL); + } + flags = new_flags; + } + } + + zephyr_file_obj_t *o = mp_obj_malloc_with_finaliser(zephyr_file_obj_t, type); + + err = fs_open(&o->fp, path, flags); + if (x_mode && err < 0) { + err = fs_open(&o->fp, path, flags | FS_O_CREATE); + } else if (x_mode) { + fs_close(&o->fp); + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("file %s Already exists: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + if (err < 0) { + m_del_obj(zephyr_file_obj_t, o); + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error opening file %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + + return MP_OBJ_FROM_PTR(o); +} +MP_DEFINE_CONST_FUN_OBJ_3(zephyr_fs_open_obj, zephyr_fs_open); + +typedef struct _mp_zephyr_fs_ilistdir_it_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + mp_fun_1_t finaliser; + bool is_str; + struct fs_dir_t dir; +} mp_zephyr_fs_ilistdir_it_t; + +static mp_obj_t mp_zephyr_fs_ilistdir_it_iternext(mp_obj_t self_in) { + mp_zephyr_fs_ilistdir_it_t *self = MP_OBJ_TO_PTR(self_in); + int err; + + for (;;) { + struct fs_dirent stats; + err = fs_readdir(&self->dir, &stats); + char *fn = stats.name; + if (err < 0 || stats.name[0] == 0) { + // stop on error or end of dir + break; + } + + // Note that Zephyr FS also already filters . and .., so we don't need to + + // make 4-tuple with info about this entry + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(4, NULL)); + if (self->is_str) { + t->items[0] = mp_obj_new_str_from_cstr(fn); + } else { + t->items[0] = mp_obj_new_bytes((const byte *)fn, strlen(fn)); + } + if (stats.type == FS_DIR_ENTRY_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 + t->items[3] = mp_obj_new_int_from_uint(stats.size); + + return MP_OBJ_FROM_PTR(t); + } + + // ignore error because we may be closing a second time + fs_closedir(&self->dir); + + return MP_OBJ_STOP_ITERATION; +} + +static mp_obj_t mp_zephyr_fs_ilistdir_it_del(mp_obj_t self_in) { + mp_zephyr_fs_ilistdir_it_t *self = MP_OBJ_TO_PTR(self_in); + // ignore result / error because we may be closing a second time. + fs_closedir(&self->dir); + return mp_const_none; +} + +static mp_obj_t zephyr_fs_ilistdir_func(size_t n_args, const mp_obj_t *args) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(args[0]); + bool is_str_type = true; + const char *path; + int err; + + if (n_args == 2) { + if (mp_obj_get_type(args[1]) == &mp_type_bytes) { + is_str_type = false; + } + path = zephyr_fs_make_path(self, args[1]); + } else { + path = ""; + } + + // Create a new iterator object to list the dir + mp_zephyr_fs_ilistdir_it_t *iter = mp_obj_malloc_with_finaliser(mp_zephyr_fs_ilistdir_it_t, &mp_type_polymorph_iter_with_finaliser); + iter->iternext = mp_zephyr_fs_ilistdir_it_iternext; + iter->finaliser = mp_zephyr_fs_ilistdir_it_del; + iter->is_str = is_str_type; + + err = fs_opendir(&iter->dir, path); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("could not open directory %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + + return MP_OBJ_FROM_PTR(iter); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(zephyr_fs_ilistdir_obj, 1, 2, zephyr_fs_ilistdir_func); + +static mp_obj_t zephyr_fs_mkdir(mp_obj_t vfs_in, mp_obj_t path_o) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(vfs_in); + const char *path = zephyr_fs_make_path(self, path_o); + int err; + + err = fs_mkdir(path); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("could not mkdir %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(zephyr_fs_mkdir_obj, zephyr_fs_mkdir); + + +static mp_obj_t zephyr_fs_chdir(mp_obj_t vfs_in, mp_obj_t path_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(vfs_in); + const char *path = mp_obj_str_get_str(path_in); + size_t lc = vstr_len(&self->cur_dir); + size_t l = vstr_len(&self->root_dir); + vstr_t chdir; + struct fs_dirent stats; + int err; + + + vstr_init(&chdir, 32); + vstr_add_strn(&chdir, self->root_dir.buf, l); + if (path[0] != '/') { + vstr_add_byte(&chdir, '/'); + vstr_add_strn(&chdir, self->cur_dir.buf, lc); + } + vstr_add_str(&chdir, path); + vstr_add_byte(&chdir, '/'); + + #define CWD_LEN (vstr_len(&chdir)) + size_t to = 1; + size_t from = 1; + char *cwd = vstr_str(&chdir); + while (from < CWD_LEN) { + for (; from < CWD_LEN && cwd[from] == '/'; ++from) { + // Scan for the start + } + if (from > to) { + // Found excessive slash chars, squeeze them out + vstr_cut_out_bytes(&chdir, to, from - to); + from = to; + } + for (; from < CWD_LEN && cwd[from] != '/'; ++from) { + // Scan for the next / + } + if ((from - to) == 1 && cwd[to] == '.') { + // './', ignore + vstr_cut_out_bytes(&chdir, to, ++from - to); + from = to; + } else if ((from - to) == 2 && cwd[to] == '.' && cwd[to + 1] == '.') { + // '../', skip back + if (to > 1) { + // Only skip back if not at the tip + for (--to; to > 1 && cwd[to - 1] != '/'; --to) { + // Skip back + } + } + vstr_cut_out_bytes(&chdir, to, ++from - to); + from = to; + } else { + // Normal element, keep it and just move the offset + to = ++from; + } + } + + err = fs_stat(vstr_null_terminated_str(&chdir), &stats); + vstr_cut_out_bytes(&chdir, 0, l + 1); + lc = vstr_len(&chdir); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("could not chdir to %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + + vstr_reset(&self->cur_dir); + vstr_add_strn(&self->cur_dir, chdir.buf, lc); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(zephyr_fs_chdir_obj, zephyr_fs_chdir); + +static mp_obj_t zephyr_fs_getcwd(mp_obj_t vfs_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(vfs_in); + char *path; + + if (vstr_len(&self->cur_dir) == 0) { + return MP_OBJ_NEW_QSTR(MP_QSTR__slash_); + } else { + size_t l = vstr_len(&self->root_dir); + size_t lc = vstr_len(&self->cur_dir); + vstr_add_str(&self->root_dir, "/"); + vstr_add_strn(&self->root_dir, self->cur_dir.buf, lc); + path = vstr_null_terminated_str(&self->root_dir); + self->root_dir.len = l; + return mp_obj_new_str_from_cstr(path + l); + } +} +static MP_DEFINE_CONST_FUN_OBJ_1(zephyr_fs_getcwd_obj, zephyr_fs_getcwd); + +static mp_obj_t zephyr_fs_rmdir(mp_obj_t vfs_in, mp_obj_t path_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(vfs_in); + const char *path = zephyr_fs_make_path(self, path_in); + struct fs_dirent stats; + int err; + + err = fs_stat(path, &stats); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error getting stats of Zephyr File System at %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + if (stats.type == FS_DIR_ENTRY_FILE) { + mp_raise_msg_varg(&mp_type_OSError, MP_ERROR_TEXT("not for removing files")); + return mp_const_none; + } + err = fs_unlink(path); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error removing directory at %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(zephyr_fs_rmdir_obj, zephyr_fs_rmdir); + +static mp_obj_t zephyr_fs_remove(mp_obj_t vfs_in, mp_obj_t path_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(vfs_in); + const char *path = zephyr_fs_make_path(self, path_in); + struct fs_dirent stats; + int err; + + err = fs_stat(path, &stats); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error getting stats of Zephyr File System at %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + if (stats.type == FS_DIR_ENTRY_DIR) { + mp_raise_msg_varg(&mp_type_OSError, MP_ERROR_TEXT("not for removing directories")); + return mp_const_none; + } + err = fs_unlink(path); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error removing file at %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(zephyr_fs_remove_obj, zephyr_fs_remove); + +static mp_obj_t zephyr_fs_rename(mp_obj_t vfs_in, mp_obj_t path_in, mp_obj_t path_out) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(vfs_in); + const char *old_path = zephyr_fs_make_path(self, path_in); + const char *new_path = zephyr_fs_make_path(self, path_out); + int err; + + err = fs_rename(old_path, new_path); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error renaming file from %s to %s: %q"), old_path, new_path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_3(zephyr_fs_rename_obj, zephyr_fs_rename); + +static mp_import_stat_t zephyr_fs_import_stat(void *vfs_in, const char *path) { + zephyr_fs_obj_t *self = vfs_in; + struct fs_dirent stats; + assert(self != NULL); + const char *ppath = zephyr_fs_make_path(self, mp_obj_new_str_from_cstr(path)); + int err; + + err = fs_stat(ppath, &stats); + if (err == 0) { + if (stats.type == FS_DIR_ENTRY_DIR) { + return MP_IMPORT_STAT_DIR; + } else { + return MP_IMPORT_STAT_FILE; + } + } + return MP_IMPORT_STAT_NO_EXIST; +} + +static mp_obj_t zephyr_fs_stat(mp_obj_t vfs_in, mp_obj_t path_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(vfs_in); + int err; + struct fs_dirent stats; + const char *path = zephyr_fs_make_path(self, path_in); + + err = fs_stat(path, &stats); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error getting stats of Zephyr File System at %s: %q"), path, mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); + mp_int_t mode = 0; + if (stats.type == FS_DIR_ENTRY_DIR) { + mode |= MP_S_IFDIR; + } else { + mode |= MP_S_IFREG; + } + t->items[0] = MP_OBJ_NEW_SMALL_INT(mode); // st_mode + t->items[1] = MP_OBJ_NEW_SMALL_INT(0); // st_ino + t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // st_dev + t->items[3] = MP_OBJ_NEW_SMALL_INT(0); // st_nlink + t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid + t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid + t->items[6] = mp_obj_new_int_from_uint(stats.size); // st_size + t->items[7] = mp_obj_new_int_from_uint(0); // st_atime + t->items[8] = mp_obj_new_int_from_uint(0); // st_mtime + t->items[9] = mp_obj_new_int_from_uint(0); // st_ctime + + return MP_OBJ_FROM_PTR(t); +} +static MP_DEFINE_CONST_FUN_OBJ_2(zephyr_fs_stat_obj, zephyr_fs_stat); + +static mp_obj_t zephyr_fs_statvfs(mp_obj_t vfs_in, mp_obj_t path_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(vfs_in); + (void)path_in; + int err; + struct fs_statvfs stats; + unsigned long bsize; + + err = fs_statvfs(self->mount->mnt_point, &stats); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error getting stats of Zephyr File System: %q"), mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + + if (stats.f_frsize > stats.f_bsize) { + bsize = stats.f_frsize; + } else { + bsize = stats.f_bsize; + } + + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); + + t->items[0] = MP_OBJ_NEW_SMALL_INT(bsize); // f_bsize + t->items[1] = MP_OBJ_NEW_SMALL_INT(stats.f_frsize); // f_frsize + t->items[2] = MP_OBJ_NEW_SMALL_INT(stats.f_blocks); // f_blocks + t->items[3] = MP_OBJ_NEW_SMALL_INT(stats.f_bfree); // f_bfree + t->items[4] = t->items[3]; // f_bavail + t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // f_files + t->items[6] = MP_OBJ_NEW_SMALL_INT(0); // f_ffree + t->items[7] = MP_OBJ_NEW_SMALL_INT(0); // f_favail + t->items[8] = MP_OBJ_NEW_SMALL_INT(0); // f_flags + t->items[9] = MP_OBJ_NEW_SMALL_INT(MAX_FILE_NAME); // f_namemax. + + return MP_OBJ_FROM_PTR(t); +} +static MP_DEFINE_CONST_FUN_OBJ_2(zephyr_fs_statvfs_obj, zephyr_fs_statvfs); + +static mp_obj_t zephyr_fs_umount(mp_obj_t self_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(self_in); + int err; + + err = fs_unmount(self->mount); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error un-mounting Zephyr File System: %q"), mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(zephyr_fs_umount_obj, zephyr_fs_umount); + +static mp_obj_t zephyr_fs_mount(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(self_in); + int err; + (void)readonly; + (void)mkfs; + + err = fs_mount(self->mount); + if (err == -EBUSY) { + err = fs_unmount(self->mount); + if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error un-mounting Zephyr File System: %q"), mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + err = fs_mount(self->mount); + } + + if (err == -EROFS) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("read only filesystem that requires formatting")); + return mp_const_none; + } else if (err < 0) { + mp_raise_msg_varg(&mp_type_OSError, + MP_ERROR_TEXT("error mounting Zephyr File System: %q"), mp_errno_to_str(MP_OBJ_NEW_SMALL_INT(-err))); + return mp_const_none; + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_3(zephyr_fs_mount_obj, zephyr_fs_mount); + +#if CONFIG_FILE_SYSTEM_MKFS +static mp_obj_t zephyr_fs_mkfs(mp_obj_t self_in) { + zephyr_fs_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Yes, they correspond indirectly + if (self->mount->fs->mkfs((int)self->mount->storage_dev, self->mount->fs_data, self->mount->flags) < 0) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("error formatting Zephyr File System")); + } + + return mp_const_none; +} + +static MP_DEFINE_CONST_FUN_OBJ_1(zephyr_fs_mkfs_obj, zephyr_fs_mkfs); +#endif + +static mp_obj_t zephyr_fs_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + if (!mp_obj_is_str(args[0])) { + mp_raise_ValueError(MP_ERROR_TEXT("argument must be a zephyr FSTAB mountpoint string")); + return mp_const_none; + } + + const char *_mountpoint = mp_obj_str_get_str(args[0]); + int i = 0; + + for (; i < zephyr_fs_mounts_size; i++) { + if (strcmp(_mountpoint, zephyr_fs_mounts[i]->mnt_point) == 0) { + break; + } + } + + if (i == zephyr_fs_mounts_size) { + mp_raise_ValueError(MP_ERROR_TEXT("couldn't find zephyr mountpoint")); + return mp_const_none; + } + + if (zephyr_fs_mounts[i]->fs == 0) { + if (fs_mount(zephyr_fs_mounts[i]) != 0) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("FS is invalid, try adding automount to fstab")); + return mp_const_none; + } + } + + // create new object + zephyr_fs_obj_t *fs = mp_obj_malloc(zephyr_fs_obj_t, type); + fs->mount = zephyr_fs_mounts[i]; + vstr_init(&fs->cur_dir, 32); + vstr_init(&fs->root_dir, 128); + vstr_add_str(&fs->root_dir, zephyr_fs_mounts[i]->mnt_point); + + return MP_OBJ_FROM_PTR(fs); +} + +static mp_obj_t zephyr_fs_fstab(void) { + mp_obj_t list = mp_obj_new_list(0, NULL); + + for (int i = 0; i < zephyr_fs_mounts_size; i++) { + mp_obj_list_append(list, mp_obj_new_str_from_cstr(zephyr_fs_mounts[i]->mnt_point)); + } + + return list; +} +static MP_DEFINE_CONST_FUN_OBJ_0(zephyr_fs_fstab_fun_obj, zephyr_fs_fstab); +static MP_DEFINE_CONST_STATICMETHOD_OBJ(zephyr_fs_fstab_obj, MP_ROM_PTR(&zephyr_fs_fstab_fun_obj)); + +static const mp_rom_map_elem_t zephyr_fs_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&zephyr_fs_mount_obj) }, + { MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&zephyr_fs_umount_obj) }, + { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&zephyr_fs_statvfs_obj) }, + { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&zephyr_fs_stat_obj) }, + { MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&zephyr_fs_rename_obj) }, + { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&zephyr_fs_remove_obj) }, + { MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&zephyr_fs_rmdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_getcwd), MP_ROM_PTR(&zephyr_fs_getcwd_obj) }, + { MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&zephyr_fs_chdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&zephyr_fs_mkdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&zephyr_fs_open_obj) }, + { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&zephyr_fs_ilistdir_obj) }, + #if CONFIG_FILE_SYSTEM_MKFS + { MP_ROM_QSTR(MP_QSTR_mkfs), MP_ROM_PTR(&zephyr_fs_mkfs_obj) }, + #endif + // Not part of the VFS API, used to list available File Systems + { MP_ROM_QSTR(MP_QSTR_fstab), MP_ROM_PTR(&zephyr_fs_fstab_obj) }, +}; +static MP_DEFINE_CONST_DICT(zephyr_fs_locals_dict, zephyr_fs_locals_dict_table); + +static const mp_vfs_proto_t zephyr_fs_proto = { + .import_stat = zephyr_fs_import_stat, +}; + +const mp_obj_type_t zephyr_filesystem_type; + +MP_DEFINE_CONST_OBJ_TYPE( + zephyr_filesystem_type, + MP_QSTR_FileSystem, + MP_TYPE_FLAG_NONE, + make_new, zephyr_fs_make_new, + protocol, &zephyr_fs_proto, + locals_dict, &zephyr_fs_locals_dict + ); + +#endif // CONFIG_FILE_SYSTEM |
