diff --git a/.gitmodules b/.gitmodules index 3880ea60a0df2..0f11669ba2cc7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -30,3 +30,6 @@ [submodule "lib/mynewt-nimble"] path = lib/mynewt-nimble url = https://github.com/apache/mynewt-nimble.git +[submodule "lib/littlefs"] + path = lib/littlefs + url = https://github.com/ARMmbed/littlefs.git diff --git a/.travis.yml b/.travis.yml index 7633b1a94c723..4e7d01e62497c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ jobs: - sudo apt-get install libnewlib-arm-none-eabi - arm-none-eabi-gcc --version script: - - git submodule update --init lib/lwip lib/mbedtls lib/stm32lib lib/mynewt-nimble + - git submodule update --init lib/lwip lib/mbedtls lib/stm32lib lib/mynewt-nimble lib/littlefs - make ${MAKEOPTS} -C mpy-cross - make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_F091RC - make ${MAKEOPTS} -C ports/stm32 BOARD=PYBV11 MICROPY_PY_WIZNET5K=5200 MICROPY_PY_CC3K=1 @@ -66,7 +66,7 @@ jobs: - gcc --version - python3 --version script: - - git submodule update --init lib/axtls lib/berkeley-db-1.xx lib/libffi + - git submodule update --init lib/axtls lib/berkeley-db-1.xx lib/libffi lib/littlefs - make ${MAKEOPTS} -C mpy-cross - make ${MAKEOPTS} -C ports/unix deplibs - make ${MAKEOPTS} -C ports/unix coverage @@ -87,7 +87,7 @@ jobs: - stage: test env: NAME="unix port build and tests" script: - - git submodule update --init lib/axtls lib/berkeley-db-1.xx lib/libffi + - git submodule update --init lib/axtls lib/berkeley-db-1.xx lib/libffi lib/littlefs - make ${MAKEOPTS} -C mpy-cross - make ${MAKEOPTS} -C ports/unix deplibs - make ${MAKEOPTS} -C ports/unix diff --git a/extmod/vfs_littlefs.c b/extmod/vfs_littlefs.c new file mode 100644 index 0000000000000..fcd289e63803d --- /dev/null +++ b/extmod/vfs_littlefs.c @@ -0,0 +1,521 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 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 +#include + +#include "py/runtime.h" +#include "py/stream.h" +#include "py/binary.h" +#include "py/objarray.h" +#include "py/mperrno.h" +#include "extmod/vfs.h" +#include "extmod/vfs_littlefs.h" + +#if MICROPY_VFS && MICROPY_VFS_LITTLEFS + +size_t strspn(const char *s, const char *accept) { + const char *ss = s; + while (*s && strchr(accept, *s) != NULL) { + ++s; + } + return s - ss; +} + +size_t strcspn(const char *s, const char *reject) { + const char *ss = s; + while (*s && strchr(reject, *s) == NULL) { + ++s; + } + return s - ss; +} + +STATIC int dev_ioctl(const struct lfs_config *c, int cmd, int arg) { + mp_obj_t dest[4]; + mp_load_method(MP_OBJ_FROM_PTR(c->context), MP_QSTR_ioctl, dest); + dest[2] = MP_OBJ_NEW_SMALL_INT(cmd); + dest[3] = MP_OBJ_NEW_SMALL_INT(arg); + return mp_obj_get_int(mp_call_method_n_kw(2, 0, dest)); +} + +STATIC int dev_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { + mp_obj_vfs_littlefs_t *vfs = c->context; + + mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, size, buffer}; + + mp_obj_t dest[5]; + mp_load_method(MP_OBJ_FROM_PTR(vfs), MP_QSTR_readblocks, dest); + dest[2] = MP_OBJ_NEW_SMALL_INT(block); + dest[3] = MP_OBJ_NEW_SMALL_INT(off); + dest[4] = MP_OBJ_FROM_PTR(&ar); + mp_call_method_n_kw(3, 0, dest); + + // TODO handle error return + + return 0; +} + +STATIC int dev_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { + mp_obj_vfs_littlefs_t *vfs = c->context; + + mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, size, (void*)buffer}; + + mp_obj_t dest[5]; + mp_load_method(MP_OBJ_FROM_PTR(vfs), MP_QSTR_writeblocks, dest); + dest[2] = MP_OBJ_NEW_SMALL_INT(block); + dest[3] = MP_OBJ_NEW_SMALL_INT(off); + dest[4] = MP_OBJ_FROM_PTR(&ar); + mp_call_method_n_kw(3, 0, dest); + + return 0; +} + +STATIC int dev_erase(const struct lfs_config *c, lfs_block_t block) { + dev_ioctl(c, 4, block); // erase + // TODO handle error return + return 0; +} + +STATIC int dev_sync(const struct lfs_config *c) { + dev_ioctl(c, 0, 0); // sync + // TODO handle error return + return 0; +} +#if (LFS_VERSION >= 0x00020000) +static uint8_t __attribute__ ((aligned (64))) lookahead_buffer[128/8]; +#endif + +STATIC void init_config(struct lfs_config *config, mp_obj_t bdev) { + memset(config, 0, sizeof(*config)); + + config->context = MP_OBJ_TO_PTR(bdev); + + config->read = dev_read; + config->prog = dev_prog; + config->erase = dev_erase; + config->sync = dev_sync; + + int bs = dev_ioctl(config, 1, 0); // get block size + int bc = dev_ioctl(config, 2, 0); // get block count + int rws = dev_ioctl(config, 3, 0); // get read/write size + + config->read_size = rws; + config->prog_size = rws; + config->block_size = bs; + config->block_count = bc; + config->read_buffer = m_new(uint8_t, config->read_size); + config->prog_buffer = m_new(uint8_t, config->prog_size); + +#if (LFS_VERSION >= 0x00020000) + config->block_cycles = 100; + config->cache_size = rws; + config->lookahead_size = 128; + // config->lookahead_buffer = m_new(uint8_t, config->lookahead_size / 8); + config->lookahead_buffer = lookahead_buffer; + +#else + config->lookahead = 128; + config->lookahead_buffer = m_new(uint8_t, config->lookahead / 8); +#endif +} + +const char *mp_obj_vfs_littlefs_make_path(mp_obj_vfs_littlefs_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->cur_dir); + if (l > 0) { + vstr_add_str(&self->cur_dir, path); + path = vstr_null_terminated_str(&self->cur_dir); + self->cur_dir.len = l; + } + } + return path; +} + +STATIC void vfs_littlefs_OSError(int lfs_error) { + int oserr = 0; + switch(lfs_error) { + case LFS_ERR_OK: + // No Error + return; + + case LFS_ERR_IO: + oserr = MP_EIO; + break; + + case LFS_ERR_CORRUPT: + oserr = MP_EPERM; + break; + + case LFS_ERR_NOENT: + oserr = MP_ENOENT; + break; + + case LFS_ERR_EXIST: + oserr = MP_EEXIST; + break; + + case LFS_ERR_NOTDIR: + oserr = MP_ENOTDIR; + break; + + case LFS_ERR_ISDIR: + oserr = MP_EISDIR; + break; + + case LFS_ERR_NOTEMPTY: + oserr = MP_EACCES; + break; + + case LFS_ERR_BADF: + oserr = MP_EBADF; + break; + + case LFS_ERR_FBIG: + oserr = MP_EFBIG; + break; + + case LFS_ERR_INVAL: + oserr = MP_EINVAL; + break; + + case LFS_ERR_NOSPC: + oserr = MP_ENOSPC; + break; + + case LFS_ERR_NOMEM: + oserr = MP_ENOBUFS; + break; + + default: + oserr = MP_ESRCH; + + } + mp_raise_OSError(oserr); +} + +STATIC mp_obj_t vfs_littlefs_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + (void)type; + mp_arg_check_num(n_args, n_kw, 1, 1, false); + mp_obj_vfs_littlefs_t *self = m_new0(mp_obj_vfs_littlefs_t, 1); + self->base.type = &mp_type_vfs_littlefs; + vstr_init(&self->cur_dir, 16); + vstr_add_byte(&self->cur_dir, '/'); + init_config(&self->config, args[0]); + int ret = lfs_mount(&self->lfs, &self->config); + if (ret < 0) { + vfs_littlefs_OSError(ret); + } + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t vfs_littlefs_mkfs(mp_obj_t bdev) { + struct lfs_config config; + lfs_t lfs; + init_config(&config, bdev); + int ret = lfs_format(&lfs, &config); + if (ret < 0) { + vfs_littlefs_OSError(ret); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(vfs_littlefs_mkfs_fun_obj, vfs_littlefs_mkfs); +STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(vfs_littlefs_mkfs_obj, MP_ROM_PTR(&vfs_littlefs_mkfs_fun_obj)); + +// Implementation of mp_vfs_littlefs_file_open is provided in vfs_littlefs_file.c +STATIC MP_DEFINE_CONST_FUN_OBJ_3(vfs_littlefs_open_obj, mp_vfs_littlefs_file_open); + +typedef struct _mp_vfs_littlefs_ilistdir_it_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + bool is_str; + mp_obj_vfs_littlefs_t *vfs; + lfs_dir_t dir; +} mp_vfs_littlefs_ilistdir_it_t; + +STATIC mp_obj_t mp_vfs_littlefs_ilistdir_it_iternext(mp_obj_t self_in) { + mp_vfs_littlefs_ilistdir_it_t *self = MP_OBJ_TO_PTR(self_in); + + struct lfs_info info; + for (;;) { + int ret = lfs_dir_read(&self->vfs->lfs, &self->dir, &info); + if (ret == 0) { + lfs_dir_close(&self->vfs->lfs, &self->dir); + return MP_OBJ_STOP_ITERATION; + } + if (!(info.name[0] == '.' && (info.name[1] == '\0' + || (info.name[1] == '.' && info.name[2] == '\0')))) { + break; + } + } + + // 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(info.name, strlen(info.name)); + } else { + t->items[0] = mp_obj_new_bytes((const byte*)info.name, strlen(info.name)); + } + t->items[1] = MP_OBJ_NEW_SMALL_INT(info.type == LFS_TYPE_REG ? MP_S_IFREG : MP_S_IFDIR); + t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // no inode number + + return MP_OBJ_FROM_PTR(t); +} + +STATIC mp_obj_t vfs_littlefs_ilistdir_func(size_t n_args, const mp_obj_t *args) { + mp_obj_vfs_littlefs_t *self = MP_OBJ_TO_PTR(args[0]); + bool is_str_type = true; + const char *path; + if (n_args == 2) { + if (mp_obj_get_type(args[1]) == &mp_type_bytes) { + is_str_type = false; + } + path = mp_obj_vfs_littlefs_make_path(self, args[1]); + } else { + path = vstr_null_terminated_str(&self->cur_dir); + } + + mp_vfs_littlefs_ilistdir_it_t *iter = m_new_obj(mp_vfs_littlefs_ilistdir_it_t); + iter->base.type = &mp_type_polymorph_iter; + iter->iternext = mp_vfs_littlefs_ilistdir_it_iternext; + iter->is_str = is_str_type; + iter->vfs = self; + int ret = lfs_dir_open(&self->lfs, &iter->dir, path); + if (ret < 0) { + vfs_littlefs_OSError(ret); + } + return MP_OBJ_FROM_PTR(iter); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(vfs_littlefs_ilistdir_obj, 1, 2, vfs_littlefs_ilistdir_func); + +STATIC mp_obj_t vfs_littlefs_remove(mp_obj_t self_in, mp_obj_t path_in) { + mp_obj_vfs_littlefs_t *self = MP_OBJ_TO_PTR(self_in); + const char *path = mp_obj_vfs_littlefs_make_path(self, path_in); + int ret = lfs_remove(&self->lfs, path); + if (ret < 0) { + vfs_littlefs_OSError(ret); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(vfs_littlefs_remove_obj, vfs_littlefs_remove); + +STATIC mp_obj_t vfs_littlefs_rmdir(mp_obj_t self_in, mp_obj_t path_in) { + mp_obj_vfs_littlefs_t *self = MP_OBJ_TO_PTR(self_in); + const char *path = mp_obj_vfs_littlefs_make_path(self, path_in); + int ret = lfs_remove(&self->lfs, path); + if (ret < 0) { + vfs_littlefs_OSError(ret); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(vfs_littlefs_rmdir_obj, vfs_littlefs_rmdir); + +STATIC mp_obj_t vfs_littlefs_rename(mp_obj_t self_in, mp_obj_t path_old_in, mp_obj_t path_new_in) { + mp_obj_vfs_littlefs_t *self = MP_OBJ_TO_PTR(self_in); + const char *path_old = mp_obj_vfs_littlefs_make_path(self, path_old_in); + vstr_t path_new; + vstr_init(&path_new, vstr_len(&self->cur_dir)); + vstr_add_strn(&path_new, vstr_str(&self->cur_dir), vstr_len(&self->cur_dir)); + vstr_add_str(&path_new, mp_obj_str_get_str(path_new_in)); + int ret = lfs_rename(&self->lfs, path_old, vstr_null_terminated_str(&path_new)); + vstr_clear(&path_new); + if (ret < 0) { + vfs_littlefs_OSError(ret); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(vfs_littlefs_rename_obj, vfs_littlefs_rename); + +STATIC mp_obj_t vfs_littlefs_mkdir(mp_obj_t self_in, mp_obj_t path_o) { + mp_obj_vfs_littlefs_t *self = MP_OBJ_TO_PTR(self_in); + const char *path = mp_obj_vfs_littlefs_make_path(self, path_o); + int ret = lfs_mkdir(&self->lfs, path); + if (ret < 0) { + vfs_littlefs_OSError(ret); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(vfs_littlefs_mkdir_obj, vfs_littlefs_mkdir); + +STATIC mp_obj_t vfs_littlefs_chdir(mp_obj_t self_in, mp_obj_t path_in) { + mp_obj_vfs_littlefs_t *self = MP_OBJ_TO_PTR(self_in); + const char *path = mp_obj_str_get_str(path_in); + if (path[0] == '/') { + // change absolute + vstr_reset(&self->cur_dir); + } + vstr_add_str(&self->cur_dir, path); + if (vstr_len(&self->cur_dir) == 1) { + // at root, it exists + } else { + // not at root, check it exists + struct lfs_info info; + int ret = lfs_stat(&self->lfs, vstr_null_terminated_str(&self->cur_dir), &info); + if (ret < 0 || info.type != LFS_TYPE_DIR) { + mp_raise_OSError(-MP_ENOENT); + } + // add trailing / to make it easy to build paths + vstr_add_byte(&self->cur_dir, '/'); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(vfs_littlefs_chdir_obj, vfs_littlefs_chdir); + +STATIC mp_obj_t vfs_littlefs_getcwd(mp_obj_t self_in) { + mp_obj_vfs_littlefs_t *self = MP_OBJ_TO_PTR(self_in); + if (vstr_len(&self->cur_dir) == 1) { + return MP_OBJ_NEW_QSTR(MP_QSTR__slash_); + } else { + // don't include trailing / + return mp_obj_new_str(self->cur_dir.buf, self->cur_dir.len - 1); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(vfs_littlefs_getcwd_obj, vfs_littlefs_getcwd); + +STATIC mp_obj_t vfs_littlefs_stat(mp_obj_t self_in, mp_obj_t path_in) { + mp_obj_vfs_littlefs_t *self = MP_OBJ_TO_PTR(self_in); + const char *path = mp_obj_str_get_str(path_in); + struct lfs_info info; + int ret = lfs_stat(&self->lfs, path, &info); + if (ret < 0) { + vfs_littlefs_OSError(ret); + } + + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); + t->items[0] = MP_OBJ_NEW_SMALL_INT(info.type == LFS_TYPE_REG ? MP_S_IFREG : MP_S_IFDIR); // 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(info.size); // st_size + t->items[7] = MP_OBJ_NEW_SMALL_INT(0); // st_atime + t->items[8] = MP_OBJ_NEW_SMALL_INT(0); // st_mtime + t->items[9] = MP_OBJ_NEW_SMALL_INT(0); // st_ctime + + return MP_OBJ_FROM_PTR(t); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(vfs_littlefs_stat_obj, vfs_littlefs_stat); + +STATIC int lfs_traverse_cb(void *data, lfs_block_t bl) { + (void)bl; + uint32_t *n = (uint32_t*)data; + *n += 1; + return LFS_ERR_OK; +} + +STATIC mp_obj_t vfs_littlefs_statvfs(mp_obj_t self_in, mp_obj_t path_in) { + (void)path_in; + mp_obj_vfs_littlefs_t *self = MP_OBJ_TO_PTR(self_in); + uint32_t n_used_blocks = 0; +#if (LFS_VERSION >= 0x00020000) + int ret = lfs_fs_traverse(&self->lfs, lfs_traverse_cb, &n_used_blocks); +#else + int ret = lfs_traverse(&self->lfs, lfs_traverse_cb, &n_used_blocks); +#endif + if (ret < 0) { + vfs_littlefs_OSError(ret); + } + + mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); + t->items[0] = MP_OBJ_NEW_SMALL_INT(self->lfs.cfg->block_size); // f_bsize + t->items[1] = t->items[0]; // f_frsize + t->items[2] = MP_OBJ_NEW_SMALL_INT(self->lfs.cfg->block_count); // f_blocks + t->items[3] = MP_OBJ_NEW_SMALL_INT(self->lfs.cfg->block_count - n_used_blocks); // 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(LFS_NAME_MAX); // f_namemax + + return MP_OBJ_FROM_PTR(t); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(vfs_littlefs_statvfs_obj, vfs_littlefs_statvfs); + +STATIC mp_obj_t vfs_littlefs_mount(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs) { + (void)self_in; + (void)readonly; + (void)mkfs; + // already called lfs_mount in vfs_littlefs_make_new + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(vfs_littlefs_mount_obj, vfs_littlefs_mount); + +STATIC mp_obj_t vfs_littlefs_umount(mp_obj_t self_in) { + mp_obj_vfs_littlefs_t *self = MP_OBJ_TO_PTR(self_in); + int ret = lfs_unmount(&self->lfs); + if (ret < 0) { + vfs_littlefs_OSError(ret); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(vfs_littlefs_umount_obj, vfs_littlefs_umount); + +STATIC const mp_rom_map_elem_t vfs_littlefs_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_mkfs), MP_ROM_PTR(&vfs_littlefs_mkfs_obj) }, + { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&vfs_littlefs_open_obj) }, + { MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&vfs_littlefs_ilistdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&vfs_littlefs_mkdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&vfs_littlefs_rmdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&vfs_littlefs_chdir_obj) }, + { MP_ROM_QSTR(MP_QSTR_getcwd), MP_ROM_PTR(&vfs_littlefs_getcwd_obj) }, + { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&vfs_littlefs_remove_obj) }, + { MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&vfs_littlefs_rename_obj) }, + { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&vfs_littlefs_stat_obj) }, + { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&vfs_littlefs_statvfs_obj) }, + { MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&vfs_littlefs_mount_obj) }, + { MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&vfs_littlefs_umount_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(vfs_littlefs_locals_dict, vfs_littlefs_locals_dict_table); + +STATIC mp_import_stat_t vfs_littlefs_import_stat(void *self_in, const char *path) { + mp_obj_vfs_littlefs_t *self = self_in; + struct lfs_info info; + int ret = lfs_stat(&self->lfs, path, &info); + if (ret == 0) { + if (info.type == LFS_TYPE_REG) { + return MP_IMPORT_STAT_FILE; + } else { + return MP_IMPORT_STAT_DIR; + } + } + return MP_IMPORT_STAT_NO_EXIST; +} + +STATIC const mp_vfs_proto_t vfs_littlefs_proto = { + .import_stat = vfs_littlefs_import_stat, +}; + +const mp_obj_type_t mp_type_vfs_littlefs = { + { &mp_type_type }, + .name = MP_QSTR_VfsLittle, + .make_new = vfs_littlefs_make_new, + .protocol = &vfs_littlefs_proto, + .locals_dict = (mp_obj_dict_t*)&vfs_littlefs_locals_dict, +}; + +#endif // MICROPY_VFS && MICROPY_VFS_LITTLEFS diff --git a/extmod/vfs_littlefs.h b/extmod/vfs_littlefs.h new file mode 100644 index 0000000000000..240f35d51255f --- /dev/null +++ b/extmod/vfs_littlefs.h @@ -0,0 +1,46 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 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. + */ +#ifndef MICROPY_INCLUDED_EXTMOD_VFS_LITTLEFS_H +#define MICROPY_INCLUDED_EXTMOD_VFS_LITTLEFS_H + +#include "py/obj.h" +#include "lib/littlefs/lfs.h" + +typedef struct _mp_obj_vfs_littlefs_t { + mp_obj_base_t base; + vstr_t cur_dir; + struct lfs_config config; + lfs_t lfs; +} mp_obj_vfs_littlefs_t; + +extern const mp_obj_type_t mp_type_vfs_littlefs; +extern const mp_obj_type_t mp_type_vfs_littlefs_fileio; +extern const mp_obj_type_t mp_type_vfs_littlefs_textio; + +const char *mp_obj_vfs_littlefs_make_path(mp_obj_vfs_littlefs_t *self, mp_obj_t path_in); +mp_obj_t mp_vfs_littlefs_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in); + +#endif // MICROPY_INCLUDED_EXTMOD_VFS_LITTLEFS_H diff --git a/extmod/vfs_littlefs_file.c b/extmod/vfs_littlefs_file.c new file mode 100644 index 0000000000000..521ba57f028c4 --- /dev/null +++ b/extmod/vfs_littlefs_file.c @@ -0,0 +1,228 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 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 +#include + +#include "py/runtime.h" +#include "py/stream.h" +#include "py/mperrno.h" +#include "extmod/vfs.h" +#include "extmod/vfs_littlefs.h" + +#if MICROPY_VFS && MICROPY_VFS_LITTLEFS + +typedef struct _vfs_littlefs_file_t { + mp_obj_base_t base; + mp_obj_vfs_littlefs_t *vfs; + lfs_file_t file; + struct lfs_file_config cfg; + uint8_t file_buffer[0]; +} vfs_littlefs_file_t; + +STATIC void vfs_littlefs_file_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)self_in; + (void)kind; + mp_printf(print, "", mp_obj_get_type_str(self_in)); +} + +mp_obj_t mp_vfs_littlefs_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in) { + mp_obj_vfs_littlefs_t *self = MP_OBJ_TO_PTR(self_in); + + int flags = 0; + const mp_obj_type_t *type = &mp_type_vfs_littlefs_textio; + const char *mode_str = mp_obj_str_get_str(mode_in); + for (; *mode_str; ++mode_str) { + int new_flags = 0; + switch (*mode_str) { + case 'r': + new_flags = LFS_O_RDONLY; + break; + case 'w': + new_flags = LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC; + break; + case 'x': + new_flags = LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL; + break; + case 'a': + new_flags = LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND; + break; + case '+': + flags |= LFS_O_RDWR; + break; + #if MICROPY_PY_IO_FILEIO + case 'b': + type = &mp_type_vfs_littlefs_fileio; + break; + #endif + case 't': + type = &mp_type_vfs_littlefs_textio; + break; + } + if (new_flags) { + if (flags) { + mp_raise_ValueError(NULL); + } + flags = new_flags; + } + } + if (flags == 0) { + flags = LFS_O_RDONLY; + } + + vfs_littlefs_file_t *o = m_new_obj_var_with_finaliser(vfs_littlefs_file_t, uint8_t, self->lfs.cfg->prog_size); + o->base.type = type; + o->vfs = self; + #if !MICROPY_GC_CONSERVATIVE_CLEAR + memset(&o->file, 0, sizeof(o->file)); + memset(&o->cfg, 0, sizeof(o->cfg)); + #endif + o->cfg.buffer = &o->file_buffer[0]; + + const char *path = mp_obj_vfs_littlefs_make_path(self, path_in); + int ret = lfs_file_opencfg(&self->lfs, &o->file, path, flags, &o->cfg); + if (ret < 0) { + mp_raise_OSError(-ret); + } + + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_obj_t vfs_littlefs_file___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(vfs_littlefs_file___exit___obj, 4, 4, vfs_littlefs_file___exit__); + +STATIC mp_uint_t vfs_littlefs_file_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) { + vfs_littlefs_file_t *self = MP_OBJ_TO_PTR(self_in); + lfs_ssize_t sz = lfs_file_read(&self->vfs->lfs, &self->file, buf, size); + if (sz < 0) { + *errcode = -sz; + return MP_STREAM_ERROR; + } + return sz; +} + +STATIC mp_uint_t vfs_littlefs_file_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { + vfs_littlefs_file_t *self = MP_OBJ_TO_PTR(self_in); + lfs_ssize_t sz = lfs_file_write(&self->vfs->lfs, &self->file, buf, size); + if (sz < 0) { + *errcode = -sz; + return MP_STREAM_ERROR; + } + return sz; +} + +STATIC mp_uint_t vfs_littlefs_file_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { + vfs_littlefs_file_t *self = MP_OBJ_TO_PTR(self_in); + + if (request == MP_STREAM_SEEK) { + struct mp_stream_seek_t *s = (struct mp_stream_seek_t*)(uintptr_t)arg; + int res = lfs_file_seek(&self->vfs->lfs, &self->file, s->offset, s->whence); + if (res < 0) { + *errcode = -res; + return MP_STREAM_ERROR; + } + res = lfs_file_tell(&self->vfs->lfs, &self->file); + if (res < 0) { + *errcode = -res; + return MP_STREAM_ERROR; + } + s->offset = res; + return 0; + } else if (request == MP_STREAM_FLUSH) { + int res = lfs_file_sync(&self->vfs->lfs, &self->file); + if (res < 0) { + *errcode = -res; + return MP_STREAM_ERROR; + } + return 0; + } else if (request == MP_STREAM_CLOSE) { + int res = lfs_file_close(&self->vfs->lfs, &self->file); + if (res < 0) { + *errcode = -res; + return MP_STREAM_ERROR; + } + return 0; + } else { + *errcode = MP_EINVAL; + return MP_STREAM_ERROR; + } +} + +STATIC const mp_rom_map_elem_t vfs_littlefs_file_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(&vfs_littlefs_file___exit___obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(vfs_littlefs_file_locals_dict, vfs_littlefs_file_locals_dict_table); + +#if MICROPY_PY_IO_FILEIO +STATIC const mp_stream_p_t vfs_littlefs_fileio_stream_p = { + .read = vfs_littlefs_file_read, + .write = vfs_littlefs_file_write, + .ioctl = vfs_littlefs_file_ioctl, +}; + +const mp_obj_type_t mp_type_vfs_littlefs_fileio = { + { &mp_type_type }, + .name = MP_QSTR_FileIO, + .print = vfs_littlefs_file_print, + .getiter = mp_identity_getiter, + .iternext = mp_stream_unbuffered_iter, + .protocol = &vfs_littlefs_fileio_stream_p, + .locals_dict = (mp_obj_dict_t*)&vfs_littlefs_file_locals_dict, +}; +#endif + +STATIC const mp_stream_p_t vfs_littlefs_textio_stream_p = { + .read = vfs_littlefs_file_read, + .write = vfs_littlefs_file_write, + .ioctl = vfs_littlefs_file_ioctl, + .is_text = true, +}; + +const mp_obj_type_t mp_type_vfs_littlefs_textio = { + { &mp_type_type }, + .name = MP_QSTR_TextIOWrapper, + .print = vfs_littlefs_file_print, + .getiter = mp_identity_getiter, + .iternext = mp_stream_unbuffered_iter, + .protocol = &vfs_littlefs_textio_stream_p, + .locals_dict = (mp_obj_dict_t*)&vfs_littlefs_file_locals_dict, +}; + +#endif // MICROPY_VFS && MICROPY_VFS_LITTLEFS diff --git a/lib/littlefs b/lib/littlefs new file mode 160000 index 0000000000000..7e110b44c0e79 --- /dev/null +++ b/lib/littlefs @@ -0,0 +1 @@ +Subproject commit 7e110b44c0e796dc56e2fe86587762d685653029 diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 1a4e3c6902701..9599d28b348c1 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -119,6 +119,9 @@ endif # Options for mpy-cross MPY_CROSS_FLAGS += -march=armv7m +# for littlefs +CFLAGS += -DLFS_NO_DEBUG -DLFS_NO_WARN -DLFS_NO_ERROR -DLFS_NO_ASSERT -DLFS_NO_MALLOC + SRC_LIB = $(addprefix lib/,\ libc/string0.c \ oofatfs/ff.c \ @@ -132,6 +135,8 @@ SRC_LIB = $(addprefix lib/,\ utils/interrupt_char.c \ utils/sys_stdio_mphal.c \ utils/mpirq.c \ + littlefs/lfs.c \ + littlefs/lfs_util.c \ ) ifeq ($(MICROPY_FLOAT_IMPL),double) @@ -291,6 +296,7 @@ SRC_C = \ servo.c \ dac.c \ adc.c \ + pyblittlefs.c \ $(wildcard $(BOARD_DIR)/*.c) SRC_O = \ diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 2da3626f1294b..07b8df95fe8fd 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -617,6 +617,11 @@ void stm32_main(uint32_t reset_mode) { } #endif + #if MICROPY_HW_ENABLE_NATIVE_LFS + extern int pyb_littlefs_mount(const char *mount); + pyb_littlefs_mount(NULL); + #endif + #if MICROPY_HW_ENABLE_USB // if the SD card isn't used as the USB MSC medium then use the internal flash if (pyb_usb_storage_medium == PYB_USB_STORAGE_MEDIUM_NONE) { diff --git a/ports/stm32/moduos.c b/ports/stm32/moduos.c index ead2380b33253..523c6573ae2b9 100644 --- a/ports/stm32/moduos.c +++ b/ports/stm32/moduos.c @@ -36,6 +36,7 @@ #include "extmod/misc.h" #include "extmod/vfs.h" #include "extmod/vfs_fat.h" +#include "extmod/vfs_littlefs.h" #include "genhdr/mpversion.h" #include "rng.h" #include "usb.h" @@ -83,12 +84,19 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(os_uname_obj, os_uname); /// \function sync() /// Sync all filesystems. STATIC mp_obj_t os_sync(void) { - #if MICROPY_VFS_FAT for (mp_vfs_mount_t *vfs = MP_STATE_VM(vfs_mount_table); vfs != NULL; vfs = vfs->next) { + #if MICROPY_VFS_LITTLEFS + if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t*)vfs->obj)->type), MP_OBJ_FROM_PTR(&mp_type_vfs_littlefs))) { + // struct lfs_config config = ((mp_obj_vfs_littlefs_t*)vfs->obj)->config; + // config.sync(&config); + continue; + } + #endif + #if MICROPY_VFS_FAT // this assumes that vfs->obj is fs_user_mount_t with block device functions disk_ioctl(MP_OBJ_TO_PTR(vfs->obj), CTRL_SYNC, NULL); + #endif } - #endif return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_0(mod_os_sync_obj, os_sync); @@ -174,6 +182,9 @@ STATIC const mp_rom_map_elem_t os_module_globals_table[] = { #if MICROPY_VFS_FAT { MP_ROM_QSTR(MP_QSTR_VfsFat), MP_ROM_PTR(&mp_fat_vfs_type) }, #endif + #if MICROPY_VFS_LITTLEFS + { MP_ROM_QSTR(MP_QSTR_VfsLittle), MP_ROM_PTR(&mp_type_vfs_littlefs) }, + #endif }; STATIC MP_DEFINE_CONST_DICT(os_module_globals, os_module_globals_table); diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 5806755116f95..56d764d49ffea 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -87,6 +87,7 @@ #ifndef MICROPY_VFS_FAT #define MICROPY_VFS_FAT (1) #endif +#define MICROPY_VFS_LITTLEFS (1) // control over Python builtins #define MICROPY_PY_FUNCTION_ATTRS (1) diff --git a/ports/stm32/pyblittlefs.c b/ports/stm32/pyblittlefs.c new file mode 100644 index 0000000000000..bb76c022b39ab --- /dev/null +++ b/ports/stm32/pyblittlefs.c @@ -0,0 +1,184 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 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 +#include + +#include "py/runtime.h" +#include "py/mperrno.h" +#include "extmod/vfs.h" +#include "extmod/vfs_littlefs.h" +#include "drivers/memory/spiflash.h" +#include "storage.h" + +#if MICROPY_VFS && MICROPY_VFS_LITTLEFS && MICROPY_HW_ENABLE_NATIVE_LFS + +// This is the mp_spiflash_t object that is used for the storage +#define SPIFLASH &spi_bdev.spiflash + +// SPI flash size +#ifdef MICROPY_HW_SPIFLASH_SIZE_BITS +#define SPIFLASH_SIZE_BYTES (MICROPY_HW_SPIFLASH_SIZE_BITS/8) +#else +#define SPIFLASH_SIZE_BYTES (1024 * 1024) +#endif + +// This is the starting block within SPIFLASH to start the filesystem +#ifdef MICROPY_VFS_LITTLEFS_START_OFFSET_BYTES +#define START_BLOCK (MICROPY_VFS_LITTLEFS_START_OFFSET_BYTES / MP_SPIFLASH_ERASE_BLOCK_SIZE) +#else +#define START_BLOCK 0 +#endif + +// This is the number of blocks within SPIFLASH for the lfs filesystem +#define NUM_BLOCKS ((SPIFLASH_SIZE_BYTES / MP_SPIFLASH_ERASE_BLOCK_SIZE)-START_BLOCK) + +STATIC int dev_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { + mp_spiflash_t *spiflash = c->context; + + block += START_BLOCK; + + // we must disable USB irqs to prevent MSC contention with SPI flash + uint32_t basepri = raise_irq_pri(IRQ_PRI_OTG_FS); + mp_spiflash_read(spiflash, block * MP_SPIFLASH_ERASE_BLOCK_SIZE + off, size, buffer); + restore_irq_pri(basepri); + + return 0; +} + +STATIC int dev_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { + mp_spiflash_t *spiflash = c->context; + + block += START_BLOCK; + + // we must disable USB irqs to prevent MSC contention with SPI flash + uint32_t basepri = raise_irq_pri(IRQ_PRI_OTG_FS); + mp_spiflash_write(spiflash, block * MP_SPIFLASH_ERASE_BLOCK_SIZE + off, size, buffer); + restore_irq_pri(basepri); + + return 0; +} + +STATIC int dev_erase(const struct lfs_config *c, lfs_block_t block) { + mp_spiflash_t *spiflash = c->context; + + block += START_BLOCK; + + // we must disable USB irqs to prevent MSC contention with SPI flash + uint32_t basepri = raise_irq_pri(IRQ_PRI_OTG_FS); + mp_spiflash_erase_block(spiflash, block * MP_SPIFLASH_ERASE_BLOCK_SIZE); + restore_irq_pri(basepri); + + return 0; +} + +// Sync the state of the underlying block device. Negative error codes +// are propogated to the user. +STATIC int dev_sync(const struct lfs_config *c) { + (void)c; + return 0; +} + +#if (LFS_VERSION >= 0x00020000) +static uint8_t __attribute__ ((aligned (64))) lookahead_buffer[128/8]; +#endif + +STATIC void init_config(struct lfs_config *config) { + config->context = SPIFLASH; + + config->read = dev_read; + config->prog = dev_prog; + config->erase = dev_erase; + config->sync = dev_sync; + + config->read_size = 128; + config->prog_size = 128; + config->block_size = MP_SPIFLASH_ERASE_BLOCK_SIZE; + config->block_count = NUM_BLOCKS; + config->read_buffer = m_new(uint8_t, config->read_size); + config->prog_buffer = m_new(uint8_t, config->prog_size); +#if (LFS_VERSION >= 0x00020000) + config->block_cycles = 100; + config->cache_size = 128; +// static uint8_t __attribute__ ((aligned (64))) lookahead_buffer[128/8]; +// config->lookahead_buffer = lookahead_buffer; + config->lookahead_size = 128; + config->lookahead_buffer = lookahead_buffer; + +#else + config->lookahead = 128; + config->lookahead_buffer = m_new(uint8_t, config->lookahead / 8); +#endif +} + +int pyb_littlefs_mount(const char * mount) { + // create vfs object + mp_obj_vfs_littlefs_t *lfs = m_new_obj_maybe(mp_obj_vfs_littlefs_t); + mp_vfs_mount_t *vfs = m_new_obj_maybe(mp_vfs_mount_t); + if (lfs == NULL || vfs == NULL) { + return -MP_ENOMEM; + } + + lfs->base.type = &mp_type_vfs_littlefs; + vstr_init(&lfs->cur_dir, 16); + init_config(&lfs->config); + + int ret = lfs_mount(&lfs->lfs, &lfs->config); + if (ret == LFS_ERR_CORRUPT) { + // couldn't mount, format it and try to mount again + ret = lfs_format(&lfs->lfs, &lfs->config); + ret = lfs_mount(&lfs->lfs, &lfs->config); + } + + if (ret < 0) { + // fail + m_del_obj(mp_obj_vfs_littlefs_t, lfs); + m_del_obj(mp_vfs_mount_t, vfs); + return ret; + } + + // mounted via littlefs, now mount in the VFS + if (mount != NULL) { + vfs->str = mount; + vfs->len = strlen(mount); + } else { + vfs->str = "/lfs"; + vfs->len = 4; + } + + vfs->obj = MP_OBJ_FROM_PTR(lfs); + vfs->next = NULL; + for (mp_vfs_mount_t **m = &MP_STATE_VM(vfs_mount_table);; m = &(*m)->next) { + if (*m == NULL) { + *m = vfs; + break; + } + } + + return 0; +} + +#endif // MICROPY_VFS && MICROPY_VFS_LITTLEFS && MICROPY_HW_ENABLE_NATIVE_LFS diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 41552bf5c99ae..bf53e5d076d63 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -159,10 +159,17 @@ LIB_SRC_C = $(addprefix lib/,\ timeutils/timeutils.c \ ) +# for littlefs +CFLAGS += -DLFS_NO_DEBUG -DLFS_NO_WARN -DLFS_NO_ERROR -DLFS_NO_ASSERT -DLFS_NO_MALLOC +$(BUILD)/lib/littlefs/lfs.o: CFLAGS += -Wno-unused-parameter -Wno-shadow +$(BUILD)/lib/littlefs/lfs_util.o: CFLAGS += -Wno-unused-parameter + # FatFS VFS support LIB_SRC_C += $(addprefix lib/,\ oofatfs/ff.c \ oofatfs/ffunicode.c \ + littlefs/lfs.c \ + littlefs/lfs_util.c \ ) OBJ = $(PY_O) diff --git a/ports/unix/moduos_vfs.c b/ports/unix/moduos_vfs.c index e9ac8e1f88244..eae93dea60ea7 100644 --- a/ports/unix/moduos_vfs.c +++ b/ports/unix/moduos_vfs.c @@ -30,6 +30,7 @@ #include "extmod/vfs.h" #include "extmod/vfs_posix.h" #include "extmod/vfs_fat.h" +#include "extmod/vfs_littlefs.h" #if MICROPY_VFS @@ -71,6 +72,9 @@ STATIC const mp_rom_map_elem_t uos_vfs_module_globals_table[] = { #if MICROPY_VFS_FAT { MP_ROM_QSTR(MP_QSTR_VfsFat), MP_ROM_PTR(&mp_fat_vfs_type) }, #endif + #if MICROPY_VFS_LITTLEFS + { MP_ROM_QSTR(MP_QSTR_VfsLittle), MP_ROM_PTR(&mp_type_vfs_littlefs) }, + #endif }; STATIC MP_DEFINE_CONST_DICT(uos_vfs_module_globals, uos_vfs_module_globals_table); diff --git a/ports/unix/mpconfigport_coverage.h b/ports/unix/mpconfigport_coverage.h index afd364649077c..e20c951f1ad09 100644 --- a/ports/unix/mpconfigport_coverage.h +++ b/ports/unix/mpconfigport_coverage.h @@ -57,6 +57,7 @@ #define MICROPY_VFS_POSIX (1) #undef MICROPY_VFS_FAT #define MICROPY_VFS_FAT (1) +#define MICROPY_VFS_LITTLEFS (1) #define MICROPY_PY_FRAMEBUF (1) #define MICROPY_PY_COLLECTIONS_NAMEDTUPLE__ASDICT (1) #define MICROPY_PY_UCRYPTOLIB (1) diff --git a/py/py.mk b/py/py.mk index 5669e33fccfc4..94ad76646dd8e 100644 --- a/py/py.mk +++ b/py/py.mk @@ -190,6 +190,8 @@ PY_EXTMOD_O_BASENAME = \ extmod/vfs_fat.o \ extmod/vfs_fat_diskio.o \ extmod/vfs_fat_file.o \ + extmod/vfs_littlefs.o \ + extmod/vfs_littlefs_file.o \ extmod/utime_mphal.o \ extmod/uos_dupterm.o \ lib/embed/abort_.o \ diff --git a/tests/extmod/vfs_littlefs.py b/tests/extmod/vfs_littlefs.py new file mode 100644 index 0000000000000..4c6060f1d9a10 --- /dev/null +++ b/tests/extmod/vfs_littlefs.py @@ -0,0 +1,108 @@ +# Test for VfsLittle using a RAM device + +try: + import uos + uos.VfsLittle +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +class RAMFS: + ERASE_BLOCK_SIZE = 1024 + + def __init__(self, blocks): + self.data = bytearray(blocks * self.ERASE_BLOCK_SIZE) + + def readblocks(self, block, off, buf): + #print("readblocks(%d, %d, %x(%d), %d)" % (block, off, id(buf), len(buf), off + len(buf))) + addr = block * self.ERASE_BLOCK_SIZE + off + for i in range(len(buf)): + buf[i] = self.data[addr + i] + + def writeblocks(self, block, off, buf): + #print("writeblocks(%d, %d, %x(%d), %d)" % (block, off, id(buf), len(buf), off + len(buf))) + addr = block * self.ERASE_BLOCK_SIZE + off + for i in range(len(buf)): + self.data[addr + i] = buf[i] + + def ioctl(self, op, arg): + #print("ioctl(%d, %r)" % (op, arg)) + if op == 0: # sync + return 0 + if op == 1: # erase block size + return self.ERASE_BLOCK_SIZE + if op == 2: # erase block count + return len(self.data) // self.ERASE_BLOCK_SIZE + if op == 3: # read/write size + return 128 + if op == 4: # erase a block + return 0 + +bdev = RAMFS(30) + +# mkfs +uos.VfsLittle.mkfs(bdev) + +# construction +vfs = uos.VfsLittle(bdev) + +# statvfs +print(vfs.statvfs('/')) + +# open, write close +f = vfs.open('test', 'w') +f.write('littlefs') +f.close() + +# statvfs after creating a file +print(vfs.statvfs('/')) + +# ilistdir +print(list(vfs.ilistdir())) +print(list(vfs.ilistdir('/'))) +print(list(vfs.ilistdir(b'/'))) + +# mkdir, rmdir +vfs.mkdir('testdir') +print(list(vfs.ilistdir())) +print(list(vfs.ilistdir('testdir'))) +vfs.rmdir('testdir') +print(list(vfs.ilistdir())) +vfs.mkdir('testdir') + +# stat a file +print(vfs.stat('test')) + +# stat a dir +print(vfs.stat('testdir')) + +# read +with vfs.open('test', 'r') as f: + print(f.read()) + +# create large file +with vfs.open('testbig', 'w') as f: + data = 'large012' * 32 * 16 + print('data length:', len(data)) + for i in range(4): + print('write', i) + f.write(data) + +# stat after creating large file +print(vfs.statvfs('/')) + +# rename +vfs.rename('testbig', 'testbig2') +print(list(vfs.ilistdir())) + +# remove +vfs.remove('testbig2') +print(list(vfs.ilistdir())) + +# getcwd, chdir +print(vfs.getcwd()) +vfs.chdir('/testdir') +print(vfs.getcwd()) +vfs.chdir('/') +print(vfs.getcwd()) +vfs.rmdir('testdir') diff --git a/tests/extmod/vfs_littlefs.py.exp b/tests/extmod/vfs_littlefs.py.exp new file mode 100644 index 0000000000000..c83d38d401264 --- /dev/null +++ b/tests/extmod/vfs_littlefs.py.exp @@ -0,0 +1,22 @@ +(1024, 1024, 30, 26, 26, 0, 0, 0, 0, 255) +(1024, 1024, 30, 25, 25, 0, 0, 0, 0, 255) +[('test', 32768, 0)] +[('test', 32768, 0)] +[(b'test', 32768, 0)] +[('test', 32768, 0), ('testdir', 16384, 0)] +[] +[('test', 32768, 0)] +(32768, 0, 0, 0, 0, 0, 8, 0, 0, 0) +(16384, 0, 0, 0, 0, 0, 0, 0, 0, 0) +littlefs +data length: 4096 +write 0 +write 1 +write 2 +write 3 +(1024, 1024, 30, 6, 6, 0, 0, 0, 0, 255) +[('test', 32768, 0), ('testdir', 16384, 0), ('testbig2', 32768, 0)] +[('test', 32768, 0), ('testdir', 16384, 0)] +/ +/testdir +/ diff --git a/tests/extmod/vfs_littlefs_error.py b/tests/extmod/vfs_littlefs_error.py new file mode 100644 index 0000000000000..867b86868f836 --- /dev/null +++ b/tests/extmod/vfs_littlefs_error.py @@ -0,0 +1,98 @@ +# Test for VfsLittle using a RAM device, testing error handling + +try: + import uos + uos.VfsLittle +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +class RAMFS: + ERASE_BLOCK_SIZE = 1024 + + def __init__(self, blocks): + self.data = bytearray(blocks * self.ERASE_BLOCK_SIZE) + + def readblocks(self, block, off, buf): + #print("readblocks(%d, %d, %x(%d), %d)" % (block, off, id(buf), len(buf), off + len(buf))) + addr = block * self.ERASE_BLOCK_SIZE + off + for i in range(len(buf)): + buf[i] = self.data[addr + i] + + def writeblocks(self, block, off, buf): + #print("writeblocks(%d, %d, %x(%d), %d)" % (block, off, id(buf), len(buf), off + len(buf))) + addr = block * self.ERASE_BLOCK_SIZE + off + for i in range(len(buf)): + self.data[addr + i] = buf[i] + + def ioctl(self, op, arg): + #print("ioctl(%d, %r)" % (op, arg)) + if op == 0: # sync + return 0 + if op == 1: # erase block size + return self.ERASE_BLOCK_SIZE + if op == 2: # erase block count + return len(self.data) // self.ERASE_BLOCK_SIZE + if op == 3: # read/write size + return 128 + if op == 4: # erase a block + return 0 + +bdev = RAMFS(30) + +# mkfs +uos.VfsLittle.mkfs(bdev) + +# construction +vfs = uos.VfsLittle(bdev) +with open('testfile', 'w') as f: + f.write('test') +vfs.mkdir('testdir') + +# ilistdir +try: + vfs.ilistdir('noexist') +except OSError: + print('ilistdir OSError') + +# remove +try: + vfs.remove('noexist') +except OSError: + print('remove OSError') + +# rmdir +try: + vfs.rmdir('noexist') +except OSError: + print('rmdir OSError') + +# rename +try: + vfs.rename('noexist', 'somethingelse') +except OSError: + print('rename OSError') + +# mkdir +try: + vfs.mkdir('testdir') +except OSError: + print('mkdir OSError') + +# chdir to nonexistent +try: + vfs.chdir('noexist') +except OSError: + print('chdir OSError') + +# chdir to file +try: + vfs.chdir('testfile') +except OSError: + print('chdir OSError') + +# stat +try: + vfs.stat('noexist') +except OSError: + print('stat OSError') diff --git a/tests/extmod/vfs_littlefs_error.py.exp b/tests/extmod/vfs_littlefs_error.py.exp new file mode 100644 index 0000000000000..9a6af645de037 --- /dev/null +++ b/tests/extmod/vfs_littlefs_error.py.exp @@ -0,0 +1,8 @@ +ilistdir OSError +remove OSError +rmdir OSError +rename OSError +mkdir OSError +chdir OSError +chdir OSError +stat OSError diff --git a/tests/extmod/vfs_littlefs_file.py b/tests/extmod/vfs_littlefs_file.py new file mode 100644 index 0000000000000..8fc6e50e98e42 --- /dev/null +++ b/tests/extmod/vfs_littlefs_file.py @@ -0,0 +1,118 @@ +# Test for VfsLittle using a RAM device, file IO + +try: + import uos + uos.VfsLittle +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +class RAMFS: + ERASE_BLOCK_SIZE = 1024 + + def __init__(self, blocks): + self.data = bytearray(blocks * self.ERASE_BLOCK_SIZE) + + def readblocks(self, block, off, buf): + #print("readblocks(%d, %d, %x(%d), %d)" % (block, off, id(buf), len(buf), off + len(buf))) + addr = block * self.ERASE_BLOCK_SIZE + off + for i in range(len(buf)): + buf[i] = self.data[addr + i] + + def writeblocks(self, block, off, buf): + #print("writeblocks(%d, %d, %x(%d), %d)" % (block, off, id(buf), len(buf), off + len(buf))) + addr = block * self.ERASE_BLOCK_SIZE + off + for i in range(len(buf)): + self.data[addr + i] = buf[i] + + def ioctl(self, op, arg): + #print("ioctl(%d, %r)" % (op, arg)) + if op == 0: # sync + return 0 + if op == 1: # erase block size + return self.ERASE_BLOCK_SIZE + if op == 2: # erase block count + return len(self.data) // self.ERASE_BLOCK_SIZE + if op == 3: # read/write size + return 128 + if op == 4: # erase a block + return 0 + +bdev = RAMFS(30) + +# mkfs +uos.VfsLittle.mkfs(bdev) + +# construction +vfs = uos.VfsLittle(bdev) + +# create text, print, write, close +f = vfs.open('test.txt', 'wt') +print(f) +f.write('littlefs') +f.close() + +# close already-closed file +f.close() + +# create binary, print, write, flush, close +f = vfs.open('test.bin', 'wb') +print(f) +f.write('littlefs') +f.flush() +f.close() + +# create for append +f = vfs.open('test.bin', 'ab') +f.write('more') +f.close() + +# create exclusive +f = vfs.open('test2.bin', 'xb') +f.close() + +# create exclusive with error +try: + vfs.open('test2.bin', 'x') +except OSError: + print('open OSError') + +# read default +with vfs.open('test.txt', '') as f: + print(f.read()) + +# read text +with vfs.open('test.txt', 'rt') as f: + print(f.read()) + +# read binary +with vfs.open('test.bin', 'rb') as f: + print(f.read()) + +# create read and write +with vfs.open('test.bin', 'r+b') as f: + print(f.read(8)) + f.write('MORE') +with vfs.open('test.bin', 'rb') as f: + print(f.read()) + +# seek and tell +f = vfs.open('test.txt', 'r') +print(f.tell()) +f.seek(3, 0) +print(f.tell()) +f.close() + +# open nonexistent +try: + vfs.open('noexist', 'r') +except OSError: + print('open OSError') + +# open multiple files at the same time +f1 = vfs.open('test.txt', '') +f2 = vfs.open('test.bin', 'b') +print(f1.read()) +print(f2.read()) +f1.close() +f2.close() diff --git a/tests/extmod/vfs_littlefs_file.py.exp b/tests/extmod/vfs_littlefs_file.py.exp new file mode 100644 index 0000000000000..0b7908df1abf8 --- /dev/null +++ b/tests/extmod/vfs_littlefs_file.py.exp @@ -0,0 +1,13 @@ + + +open OSError +littlefs +littlefs +b'littlefsmore' +b'littlefs' +b'littlefsMORE' +0 +3 +open OSError +littlefs +b'littlefsMORE' diff --git a/tests/extmod/vfs_littlefs_mount.py b/tests/extmod/vfs_littlefs_mount.py new file mode 100644 index 0000000000000..36d917b1e56c3 --- /dev/null +++ b/tests/extmod/vfs_littlefs_mount.py @@ -0,0 +1,69 @@ +# Test for VfsLittle using a RAM device, with mount/umount + +try: + import uos + uos.VfsLittle +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +class RAMFS: + ERASE_BLOCK_SIZE = 1024 + + def __init__(self, blocks): + self.data = bytearray(blocks * self.ERASE_BLOCK_SIZE) + + def readblocks(self, block, off, buf): + #print("readblocks(%d, %d, %x(%d), %d)" % (block, off, id(buf), len(buf), off + len(buf))) + addr = block * self.ERASE_BLOCK_SIZE + off + for i in range(len(buf)): + buf[i] = self.data[addr + i] + + def writeblocks(self, block, off, buf): + #print("writeblocks(%d, %d, %x(%d), %d)" % (block, off, id(buf), len(buf), off + len(buf))) + addr = block * self.ERASE_BLOCK_SIZE + off + for i in range(len(buf)): + self.data[addr + i] = buf[i] + + def ioctl(self, op, arg): + #print("ioctl(%d, %r)" % (op, arg)) + if op == 0: # sync + return 0 + if op == 1: # erase block size + return self.ERASE_BLOCK_SIZE + if op == 2: # erase block count + return len(self.data) // self.ERASE_BLOCK_SIZE + if op == 3: # read/write size + return 128 + if op == 4: # erase a block + return 0 + +bdev = RAMFS(30) + +# initialise path +import sys +sys.path.clear() +sys.path.append('/lfs') + +# mkfs +uos.VfsLittle.mkfs(bdev) + +# construction +vfs = uos.VfsLittle(bdev) + +# mount +uos.mount(vfs, '/lfs') + +# import +with open('/lfs/lfsmod.py', 'w') as f: + f.write('print("hello from lfs")\n') +import lfsmod + +# import package +uos.mkdir('/lfs/lfspkg') +with open('/lfs/lfspkg/__init__.py', 'w') as f: + f.write('print("package")\n') +import lfspkg + +# umount +uos.umount('/lfs') diff --git a/tests/extmod/vfs_littlefs_mount.py.exp b/tests/extmod/vfs_littlefs_mount.py.exp new file mode 100644 index 0000000000000..2a0ca4d41918c --- /dev/null +++ b/tests/extmod/vfs_littlefs_mount.py.exp @@ -0,0 +1,2 @@ +hello from lfs +package