Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/run-zephyr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
matrix:
include:
- shard: native_sim
boards: native_native_sim native_native_sim_asan
boards: native_native_sim native_native_sim_asan native_native_sim_lfs
pytest_args: tests/ --ignore=tests/bsim
- shard: nrf5340bsim
boards: native_nrf5340bsim
Expand Down
9 changes: 6 additions & 3 deletions extmod/vfs_fat.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ static mp_obj_t fat_vfs_make_new(const mp_obj_type_t *type, size_t n_args, size_

// CIRCUITPY-CHANGE
static void verify_fs_writable(fs_user_mount_t *vfs) {
if (!filesystem_is_writable_by_python(vfs)) {
if (!filesystem_is_writable_by_python((supervisor_vfs_t *)vfs)) {
mp_raise_OSError(MP_EROFS);
}
}
Expand Down Expand Up @@ -430,7 +430,10 @@ static mp_obj_t vfs_fat_mount(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs
// CIRCUITPY-CHANGE: Use MP_BLOCKDEV_FLAG_USB_WRITABLE instead of writeblocks[0] =/!= MP_OBJ_NULL
// to specify read-write.
// If readonly to Python, it's writable by USB and vice versa.
filesystem_set_writable_by_usb(self, mp_obj_is_true(readonly));
// CIRCUITPY-CHANGE: The flag helpers take a supervisor_vfs_t so they work
// for the FAT and littlefs root filesystems alike. A VfsFat is always a
// fs_user_mount_t, whose blockdev prefix matches both vfs kinds.
filesystem_set_writable_by_usb((supervisor_vfs_t *)self, mp_obj_is_true(readonly));

// check if we need to make the filesystem
FRESULT res = (self->blockdev.flags & MP_BLOCKDEV_FLAG_NO_FILESYSTEM) ? FR_NO_FILESYSTEM : FR_OK;
Expand Down Expand Up @@ -487,7 +490,7 @@ static MP_DEFINE_CONST_FUN_OBJ_3(fat_vfs_utime_obj, vfs_fat_utime);

static mp_obj_t vfs_fat_getreadonly(mp_obj_t self_in) {
fs_user_mount_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_bool(!filesystem_is_writable_by_python(self));
return mp_obj_new_bool(!filesystem_is_writable_by_python((supervisor_vfs_t *)self));
}
static MP_DEFINE_CONST_FUN_OBJ_1(fat_vfs_getreadonly_obj, vfs_fat_getreadonly);

Expand Down
2 changes: 1 addition & 1 deletion extmod/vfs_fat_diskio.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ DRESULT disk_ioctl(
// error initialising
stat = STA_NOINIT;
// CIRCUITPY-CHANGE: writability from Python check
} else if (!filesystem_is_writable_by_python(vfs)) {
} else if (!filesystem_is_writable_by_python((supervisor_vfs_t *)vfs)) {
stat = STA_PROTECT;
} else {
stat = 0;
Expand Down
2 changes: 1 addition & 1 deletion extmod/vfs_fat_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ static mp_obj_t fat_vfs_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_i
}

assert(self != NULL);
if ((mode & FA_WRITE) != 0 && !filesystem_is_writable_by_python(self)) {
if ((mode & FA_WRITE) != 0 && !filesystem_is_writable_by_python((supervisor_vfs_t *)self)) {
mp_raise_OSError(MP_EROFS);
}

Expand Down
60 changes: 49 additions & 11 deletions extmod/vfs_lfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include "py/runtime.h"
#include "py/mphal.h"
#include "supervisor/fatfs.h"

#if MICROPY_VFS && (MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2)

Expand Down Expand Up @@ -105,15 +106,6 @@ mp_obj_t mp_vfs_lfs1_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode
// Attribute ids for lfs2_attr.type.
#define LFS_ATTR_MTIME (1) // 64-bit little endian, nanoseconds since 1970/1/1

typedef struct _mp_obj_vfs_lfs2_t {
mp_obj_base_t base;
mp_vfs_blockdev_t blockdev;
bool enable_mtime;
vstr_t cur_dir;
struct lfs2_config config;
lfs2_t lfs;
} mp_obj_vfs_lfs2_t;

typedef struct _mp_obj_vfs_lfs2_file_t {
mp_obj_base_t base;
mp_obj_vfs_lfs2_t *vfs;
Expand All @@ -128,8 +120,10 @@ const char *mp_vfs_lfs2_make_path(mp_obj_vfs_lfs2_t *self, mp_obj_t path_in);
mp_obj_t mp_vfs_lfs2_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in);

static void lfs_get_mtime(uint8_t buf[8]) {
// On-disk storage of timestamps uses 1970 as the Epoch, so convert from host's Epoch.
uint64_t ns = timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(mp_hal_time_ns());
// CIRCUITPY-CHANGE: Use the same RTC-based time that get_fattime() gives
// the FAT filesystems, so every filesystem kind stamps files alike.
// On-disk storage of timestamps is 64-bit little endian, ns since 1970/1/1.
uint64_t ns = get_fattime_ns();
// Store "ns" to "buf" in little-endian format (essentially htole64).
for (size_t i = 0; i < 8; ++i) {
buf[i] = ns;
Expand All @@ -140,6 +134,50 @@ static void lfs_get_mtime(uint8_t buf[8]) {
#include "extmod/vfs_lfsx.c"
#include "extmod/vfs_lfsx_file.c"

// CIRCUITPY-CHANGE: Supervisor-facing littlefs mount. The caller prepares the
// mp_obj_vfs_lfs2_t (zeroed, with blockdev callbacks and lfs2_config geometry
// already filled in) and the port allocator for the littlefs caches. This runs
// without the VM or its GC, so no MicroPython allocations may happen here.
mp_obj_t mp_vfs_lfs2_mount_supervisor(mp_obj_vfs_lfs2_t *self, void *(*alloc)(size_t), bool format_allowed, bool *formatted_out, int *mount_err) {
struct lfs2_config *config = &self->config;

config->block_cycles = 100;
config->cache_size = MIN(config->block_size, (4 * MAX(config->read_size, config->prog_size)));
config->lookahead_size = 32;
config->read_buffer = alloc(config->cache_size);
config->prog_buffer = alloc(config->cache_size);
config->lookahead_buffer = alloc(config->lookahead_size);
if (config->read_buffer == NULL || config->prog_buffer == NULL || config->lookahead_buffer == NULL) {
if (mount_err != NULL) {
*mount_err = LFS2_ERR_NOMEM;
}
return MP_OBJ_NULL;
}

bool formatted = false;
int ret = lfs2_mount(&self->lfs, config);
if (ret < 0 && format_allowed) {
// Empty or corrupted. Format a fresh filesystem and try again.
ret = lfs2_format(&self->lfs, config);
if (ret >= 0) {
formatted = true;
ret = lfs2_mount(&self->lfs, config);
}
}
if (formatted_out != NULL && formatted) {
// Only ever set true: the caller may have formatted the filesystem
// itself (force reformat) and needs that to survive the mount.
*formatted_out = true;
}
if (mount_err != NULL) {
*mount_err = ret;
}
if (ret < 0) {
return MP_OBJ_NULL;
}
return MP_OBJ_FROM_PTR(self);
}

#endif // MICROPY_VFS_LFS2

#endif // MICROPY_VFS && (MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2)
27 changes: 27 additions & 0 deletions extmod/vfs_lfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,31 @@ extern const mp_obj_type_t mp_type_vfs_lfs2;
extern const mp_obj_type_t mp_type_vfs_lfs2_fileio;
extern const mp_obj_type_t mp_type_vfs_lfs2_textio;

// CIRCUITPY-CHANGE: Export the lfs2 VFS object so the supervisor can mount
// littlefs directly (before the VM and its GC are running) using the same
// layout the VFS methods in vfs_lfsx.c expect.
#if MICROPY_VFS_LFS2
#include "lib/littlefs/lfs2.h"
#include "extmod/vfs.h"

typedef struct _mp_obj_vfs_lfs2_t {
mp_obj_base_t base;
mp_vfs_blockdev_t blockdev;
bool enable_mtime;
vstr_t cur_dir;
struct lfs2_config config;
lfs2_t lfs;
} mp_obj_vfs_lfs2_t;

// Mount (and optionally format first) a littlefs filesystem on the
// caller-prepared lfs2_config at self->config. self must be zeroed static or
// VM-heap storage; buffers are allocated via alloc(). Returns self_in on
// success or MP_OBJ_NULL on mount failure (the errno-like lfs2 return code is
// passed out via mount_err). formatted_out is set to true when a fresh
// filesystem was formatted before mounting successfully; it is never cleared,
// so the caller must initialize it to false (and may set it true itself after
// an explicit format).
mp_obj_t mp_vfs_lfs2_mount_supervisor(mp_obj_vfs_lfs2_t *self, void *(*alloc)(size_t), bool format_allowed, bool *formatted_out, int *mount_err);
#endif

#endif // MICROPY_INCLUDED_EXTMOD_VFS_LFS_H
14 changes: 14 additions & 0 deletions extmod/vfs_lfsx.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "py/objstr.h"
#include "py/mperrno.h"
#include "extmod/vfs.h"
#include "supervisor/filesystem.h"
#include "shared/timeutils/timeutils.h"

#if !MICROPY_ENABLE_FINALISER
Expand Down Expand Up @@ -244,8 +245,18 @@ static mp_obj_t MP_VFS_LFSx(ilistdir_func)(size_t n_args, const mp_obj_t *args)
}
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(MP_VFS_LFSx(ilistdir_obj), 1, 2, MP_VFS_LFSx(ilistdir_func));

static void MP_VFS_LFSx(verify_fs_writable)(MP_OBJ_VFS_LFSx * self) {
// CIRCUITPY-CHANGE: Honor the supervisor's write protection flags so that
// storage.remount(mount, readonly=True) also applies to littlefs mounts,
// matching what the FAT VFS does.
if (!filesystem_is_writable_by_python((supervisor_vfs_t *)self)) {
mp_raise_OSError(MP_EROFS);
}
}

static mp_obj_t MP_VFS_LFSx(remove)(mp_obj_t self_in, mp_obj_t path_in) {
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
MP_VFS_LFSx(verify_fs_writable)(self);
const char *path = MP_VFS_LFSx(make_path)(self, path_in);
int ret = LFSx_API(remove)(&self->lfs, path);
if (ret < 0) {
Expand All @@ -257,6 +268,7 @@ static MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(remove_obj), MP_VFS_LFSx(remove));

static mp_obj_t MP_VFS_LFSx(rmdir)(mp_obj_t self_in, mp_obj_t path_in) {
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
MP_VFS_LFSx(verify_fs_writable)(self);
const char *path = MP_VFS_LFSx(make_path)(self, path_in);
int ret = LFSx_API(remove)(&self->lfs, path);
if (ret < 0) {
Expand All @@ -268,6 +280,7 @@ static MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(rmdir_obj), MP_VFS_LFSx(rmdir));

static mp_obj_t MP_VFS_LFSx(rename)(mp_obj_t self_in, mp_obj_t path_old_in, mp_obj_t path_new_in) {
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
MP_VFS_LFSx(verify_fs_writable)(self);
const char *path_old = MP_VFS_LFSx(make_path)(self, path_old_in);
const char *path = mp_obj_str_get_str(path_new_in);
vstr_t path_new;
Expand All @@ -287,6 +300,7 @@ static MP_DEFINE_CONST_FUN_OBJ_3(MP_VFS_LFSx(rename_obj), MP_VFS_LFSx(rename));

static mp_obj_t MP_VFS_LFSx(mkdir)(mp_obj_t self_in, mp_obj_t path_o) {
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
MP_VFS_LFSx(verify_fs_writable)(self);
const char *path = MP_VFS_LFSx(make_path)(self, path_o);
int ret = LFSx_API(mkdir)(&self->lfs, path);
if (ret < 0) {
Expand Down
6 changes: 6 additions & 0 deletions extmod/vfs_lfsx_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ mp_obj_t MP_VFS_LFSx(file_open)(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mod
flags = LFSx_MACRO(_O_RDONLY);
}

// CIRCUITPY-CHANGE: Writes honor the supervisor's write protection flags,
// like the FAT VFS does, so storage.remount() readonly applies too.
if ((flags & LFSx_MACRO(_O_WRONLY)) != 0) {
MP_VFS_LFSx(verify_fs_writable)(self);
}

#if LFS_BUILD_VERSION == 1
MP_OBJ_VFS_LFSx_FILE *o = mp_obj_malloc_var_with_finaliser(MP_OBJ_VFS_LFSx_FILE, file_buffer, uint8_t, self->lfs.cfg->prog_size, type);
#else
Expand Down
29 changes: 17 additions & 12 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -875,8 +875,8 @@ static void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) {
#endif

// Get the base filesystem.
fs_user_mount_t *vfs = filesystem_circuitpy();
FATFS *fs = &vfs->fatfs;
supervisor_vfs_t *vfs_root = filesystem_circuitpy();
fs_user_mount_t *vfs = vfs_root == NULL ? NULL : &vfs_root->fat;

// Allow boot.py access to CIRCUITPY, and allow writes to boot_out.txt.
// We can't use the regular flags for this, because they might get modified inside boot.py.
Expand Down Expand Up @@ -905,27 +905,32 @@ static void __attribute__ ((noinline)) run_boot_py(safe_mode_t safe_mode) {
supervisor_status_bar_resume();
#endif
bool write_boot_output = true;
FIL boot_output_file;
if (f_open(fs, &boot_output_file, CIRCUITPY_BOOT_OUTPUT_FILE, FA_READ) == FR_OK) {
supervisor_vfs_file_t boot_output_file;
if (supervisor_vfs_open_file(vfs_root, CIRCUITPY_BOOT_OUTPUT_FILE, SUPERVISOR_FS_OPEN_READ, 0,
&boot_output_file) == SUPERVISOR_FS_OK) {
char *file_contents = m_new(char, boot_text.alloc);
UINT chars_read;
if (f_read(&boot_output_file, file_contents, 1 + boot_text.len, &chars_read) == FR_OK) {
size_t chars_read;
if (supervisor_vfs_read_file(&boot_output_file, file_contents, 1 + boot_text.len, &chars_read) ==
SUPERVISOR_FS_OK) {
write_boot_output =
(chars_read != boot_text.len) || (memcmp(boot_text.buf, file_contents, chars_read) != 0);
}
// no need to f_close the file
supervisor_vfs_close_file(&boot_output_file);
}

if (write_boot_output) {
// Wait 1 second before opening CIRCUITPY_BOOT_OUTPUT_FILE for write,
// in case power is momentary or will fail shortly due to, say a low, battery.
mp_hal_delay_ms(1000);

f_open(fs, &boot_output_file, CIRCUITPY_BOOT_OUTPUT_FILE, FA_WRITE | FA_CREATE_ALWAYS);
UINT chars_written;
f_write(&boot_output_file, boot_text.buf, boot_text.len, &chars_written);
f_close(&boot_output_file);
filesystem_flush();
if (supervisor_vfs_open_file(vfs_root, CIRCUITPY_BOOT_OUTPUT_FILE,
SUPERVISOR_FS_OPEN_WRITE | SUPERVISOR_FS_OPEN_CREATE | SUPERVISOR_FS_OPEN_TRUNCATE, 0,
&boot_output_file) == SUPERVISOR_FS_OK) {
size_t chars_written;
supervisor_vfs_write_file(&boot_output_file, boot_text.buf, boot_text.len, &chars_written);
supervisor_vfs_close_file(&boot_output_file);
filesystem_flush();
}
}
#endif

Expand Down
4 changes: 2 additions & 2 deletions ports/espressif/boards/mixgo_ce_serial/board.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
void board_init(void) {
mp_import_stat_t stat_b = mp_import_stat("boot.py");
if (stat_b != MP_IMPORT_STAT_FILE) {
fs_user_mount_t *fs_mount = filesystem_circuitpy();
FATFS *fatfs = &fs_mount->fatfs;
supervisor_vfs_t *fs_mount = filesystem_circuitpy();
FATFS *fatfs = &fs_mount->fat.fatfs;
FIL fs;
UINT char_written = 0;
const byte buffer[] = "#Serial port upload mode\nimport storage\nstorage.remount(\"/\", False)\nstorage.disable_usb_drive()\n";
Expand Down
4 changes: 2 additions & 2 deletions ports/espressif/boards/yoto_mini_2024/board.c
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ void board_init(void) {
}
common_hal_sdioio_sdcard_never_reset(&sdmmc);

filesystem_set_concurrent_write_protection(vfs, true);
filesystem_set_writable_by_usb(vfs, false);
filesystem_set_concurrent_write_protection((supervisor_vfs_t *)vfs, true);
filesystem_set_writable_by_usb((supervisor_vfs_t *)vfs, false);

mp_vfs_mount_t *sdcard_vfs = &_sdcard_vfs;
sdcard_vfs->str = "/sd";
Expand Down
4 changes: 2 additions & 2 deletions ports/espressif/boards/yoto_player_v3/board.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ void board_init(void) {
}
common_hal_sdioio_sdcard_never_reset(&sdmmc);

filesystem_set_concurrent_write_protection(vfs, true);
filesystem_set_writable_by_usb(vfs, false);
filesystem_set_concurrent_write_protection((supervisor_vfs_t *)vfs, true);
filesystem_set_writable_by_usb((supervisor_vfs_t *)vfs, false);

mp_vfs_mount_t *sdcard_vfs = &_sdcard_vfs;
sdcard_vfs->str = "/sd";
Expand Down
4 changes: 2 additions & 2 deletions ports/espressif/supervisor/internal_flash.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ static uint32_t _cache_lba = 0xffffffff;
#define SECSIZE(fs) ((fs)->ssize)
#endif // FF_MAX_SS == FF_MIN_SS
static DWORD fatfs_bytes(void) {
fs_user_mount_t *fs_mount = filesystem_circuitpy();
FATFS *fatfs = &fs_mount->fatfs;
supervisor_vfs_t *fs_mount = filesystem_circuitpy();
FATFS *fatfs = &fs_mount->fat.fatfs;
return (fatfs->csize * SECSIZE(fatfs)) * (fatfs->n_fatent - 2);
}
static bool storage_extended = true;
Expand Down
2 changes: 1 addition & 1 deletion ports/zephyr-cp/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ clean-sim:
# Every board the test suite uses: native sim (non-asan + asan) and the bsim
# boards (tests/bsim/conftest.py parametrizes over both). Zephyr samples for
# bsim tests are built on demand by the zephyr_sample fixture.
TEST_BOARDS := native_native_sim native_native_sim_asan native_nrf5340bsim native_nrf54lm20bsim
TEST_BOARDS := native_native_sim native_native_sim_asan native_native_sim_lfs native_nrf5340bsim native_nrf54lm20bsim

# Delegate to a sub-make with BOARD set so the per-board build rule (and its
# shield args + bsim prep) applies. The targets are phony: the west builds are
Expand Down
1 change: 1 addition & 0 deletions ports/zephyr-cp/boards/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions ports/zephyr-cp/boards/board_aliases.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ cp_board_alias(renesas_ek_ra8d1 ek_ra8d1)
cp_board_alias(renesas_da14695_dk_usb da14695_dk_usb)
cp_board_alias(native_native_sim native_sim/native)
cp_board_alias(native_native_sim_asan native_sim/native)
cp_board_alias(native_native_sim_lfs native_sim/native)
cp_board_alias(native_nrf5340bsim nrf5340bsim/nrf5340/cpuapp)
cp_board_alias(native_nrf54lm20bsim nrf54lm20bsim/nrf54lm20a/cpuapp)
cp_board_alias(nordic_nrf54l15dk nrf54l15dk/nrf54l15/cpuapp)
Expand Down
Loading
Loading