From d0c840094a3c10693d2c345fa25ab9bffd45773c Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Wed, 12 Mar 2025 09:43:04 -0700 Subject: [PATCH] Automount SD cards And make them available over USB MSC. They can be remount to make them writable (slowly) from the host PC. Fixes #9954. Fixes #8678. Fixes #3477 --- extmod/vfs.c | 23 +++- extmod/vfs.h | 13 -- locale/circuitpython.pot | 11 +- .../boards/adafruit_fruit_jam/mpconfigboard.h | 7 ++ shared-module/os/__init__.c | 22 ++-- shared-module/sdcardio/SDCard.c | 13 +- shared-module/sdcardio/SDCard.h | 2 + shared-module/sdcardio/__init__.c | 115 ++++++++++++++++++ shared-module/sdcardio/__init__.h | 3 + shared-module/storage/__init__.c | 15 ++- supervisor/shared/filesystem.c | 10 +- supervisor/shared/usb/usb_msc_flash.c | 81 ++++++++---- supervisor/usb.h | 4 +- 13 files changed, 257 insertions(+), 62 deletions(-) diff --git a/extmod/vfs.c b/extmod/vfs.c index e7230e5ca0..4deb8a4428 100644 --- a/extmod/vfs.c +++ b/extmod/vfs.c @@ -47,6 +47,11 @@ #include "extmod/vfs_posix.h" #endif + +#if CIRCUITPY_SDCARDIO +#include "shared-module/sdcardio/__init__.h" +#endif + // For mp_vfs_proxy_call, the maximum number of additional args that can be passed. // A fixed maximum size is used to avoid the need for a costly variable array. #define PROXY_MAX_ARGS (2) @@ -67,6 +72,10 @@ mp_vfs_mount_t *mp_vfs_lookup_path(const char *path, const char **path_out) { // path is "" or "/" so return virtual root return MP_VFS_ROOT; } + // CIRCUITPY-CHANGE: Try and automount the SD card. + #if CIRCUITPY_SDCARDIO + automount_sd_card(); + #endif for (mp_vfs_mount_t *vfs = MP_STATE_VM(vfs_mount_table); vfs != NULL; vfs = vfs->next) { size_t len = vfs->len - 1; if (len == 0) { @@ -367,8 +376,18 @@ mp_obj_t mp_vfs_getcwd(void) { } MP_DEFINE_CONST_FUN_OBJ_0(mp_vfs_getcwd_obj, mp_vfs_getcwd); -// CIRCUITPY-CHANGE: accessible from shared-module/os/__init__.c -mp_obj_t mp_vfs_ilistdir_it_iternext(mp_obj_t self_in) { +typedef struct _mp_vfs_ilistdir_it_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + union { + mp_vfs_mount_t *vfs; + mp_obj_t iter; + } cur; + bool is_str; + bool is_iter; +} mp_vfs_ilistdir_it_t; + +static mp_obj_t mp_vfs_ilistdir_it_iternext(mp_obj_t self_in) { mp_vfs_ilistdir_it_t *self = MP_OBJ_TO_PTR(self_in); if (self->is_iter) { // continue delegating to root dir diff --git a/extmod/vfs.h b/extmod/vfs.h index 65cf4ece98..e75801db90 100644 --- a/extmod/vfs.h +++ b/extmod/vfs.h @@ -94,19 +94,6 @@ typedef struct _mp_vfs_mount_t { struct _mp_vfs_mount_t *next; } mp_vfs_mount_t; -// CIRCUITPY-CHANGE: allow outside use of ilistdir_it_iternext -typedef struct _mp_vfs_ilistdir_it_t { - mp_obj_base_t base; - mp_fun_1_t iternext; - union { - mp_vfs_mount_t *vfs; - mp_obj_t iter; - } cur; - bool is_str; - bool is_iter; -} mp_vfs_ilistdir_it_t; - -mp_obj_t mp_vfs_ilistdir_it_iternext(mp_obj_t self_in); void mp_vfs_blockdev_init(mp_vfs_blockdev_t *self, mp_obj_t bdev); int mp_vfs_blockdev_read(mp_vfs_blockdev_t *self, size_t block_num, size_t num_blocks, uint8_t *buf); int mp_vfs_blockdev_read_ext(mp_vfs_blockdev_t *self, size_t block_num, size_t block_off, size_t len, uint8_t *buf); diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 167fb20ff9..5e636d8500 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -789,7 +789,7 @@ msgid "Cannot record to a file" msgstr "" #: shared-module/storage/__init__.c -msgid "Cannot remount '/' when visible via USB." +msgid "Cannot remount path when visible via USB." msgstr "" #: shared-bindings/digitalio/DigitalInOut.c @@ -1975,6 +1975,7 @@ msgstr "" #: shared-bindings/displayio/TileGrid.c #: shared-bindings/memorymonitor/AllocationSize.c #: shared-bindings/pulseio/PulseIn.c +#: shared-bindings/tilepalettemapper/TilePaletteMapper.c msgid "Slices not supported" msgstr "" @@ -2042,7 +2043,9 @@ msgstr "" msgid "Tile height must exactly divide bitmap height" msgstr "" -#: shared-bindings/displayio/TileGrid.c shared-module/displayio/TileGrid.c +#: shared-bindings/displayio/TileGrid.c +#: shared-bindings/tilepalettemapper/TilePaletteMapper.c +#: shared-module/displayio/TileGrid.c msgid "Tile index out of bounds" msgstr "" @@ -4277,7 +4280,9 @@ msgstr "" msgid "unreadable attribute" msgstr "" -#: shared-bindings/displayio/TileGrid.c shared-bindings/vectorio/VectorShape.c +#: shared-bindings/displayio/TileGrid.c +#: shared-bindings/tilepalettemapper/TilePaletteMapper.c +#: shared-bindings/vectorio/VectorShape.c msgid "unsupported %q type" msgstr "" diff --git a/ports/raspberrypi/boards/adafruit_fruit_jam/mpconfigboard.h b/ports/raspberrypi/boards/adafruit_fruit_jam/mpconfigboard.h index fb643deead..395aa820a2 100644 --- a/ports/raspberrypi/boards/adafruit_fruit_jam/mpconfigboard.h +++ b/ports/raspberrypi/boards/adafruit_fruit_jam/mpconfigboard.h @@ -30,6 +30,13 @@ #define DEFAULT_DVI_BUS_BLUE_DN (&pin_GPIO18) #define DEFAULT_DVI_BUS_BLUE_DP (&pin_GPIO19) +#define DEFAULT_SD_SCK (&pin_GPIO34) +#define DEFAULT_SD_MOSI (&pin_GPIO35) +#define DEFAULT_SD_MISO (&pin_GPIO36) +#define DEFAULT_SD_CS (&pin_GPIO39) +#define DEFAULT_SD_CARD_DETECT (&pin_GPIO33) +#define DEFAULT_SD_CARD_INSERTED true + #define CIRCUITPY_PSRAM_CHIP_SELECT (&pin_GPIO47) // #define CIRCUITPY_CONSOLE_UART_TX (&pin_GPIO44) diff --git a/shared-module/os/__init__.c b/shared-module/os/__init__.c index 81296db854..db276d17e2 100644 --- a/shared-module/os/__init__.c +++ b/shared-module/os/__init__.c @@ -90,21 +90,19 @@ mp_obj_t common_hal_os_getcwd(void) { mp_obj_t common_hal_os_listdir(const char *path) { mp_obj_t path_out; mp_vfs_mount_t *vfs = lookup_dir_path(path, &path_out); - - mp_vfs_ilistdir_it_t iter; - mp_obj_t iter_obj = MP_OBJ_FROM_PTR(&iter); - if (vfs == MP_VFS_ROOT) { - // list the root directory - iter.base.type = &mp_type_polymorph_iter; - iter.iternext = mp_vfs_ilistdir_it_iternext; - iter.cur.vfs = MP_STATE_VM(vfs_mount_table); - iter.is_str = true; - iter.is_iter = false; - } else { - iter_obj = mp_vfs_proxy_call(vfs, MP_QSTR_ilistdir, 1, &path_out); + vfs = MP_STATE_VM(vfs_mount_table); + while (vfs != NULL) { + if (vfs->len == 1) { + break; + } + vfs = vfs->next; + } + path_out = MP_OBJ_NEW_QSTR(MP_QSTR__slash_); } + mp_obj_t iter_obj = mp_vfs_proxy_call(vfs, MP_QSTR_ilistdir, 1, &path_out); + mp_obj_t dir_list = mp_obj_new_list(0, NULL); mp_obj_t next; while ((next = mp_iternext(iter_obj)) != MP_OBJ_STOP_ITERATION) { diff --git a/shared-module/sdcardio/SDCard.c b/shared-module/sdcardio/SDCard.c index a8e6248577..bd3ea62d14 100644 --- a/shared-module/sdcardio/SDCard.c +++ b/shared-module/sdcardio/SDCard.c @@ -294,7 +294,7 @@ static mp_rom_error_text_t init_card(sdcardio_sdcard_obj_t *self) { return NULL; } -void common_hal_sdcardio_sdcard_construct(sdcardio_sdcard_obj_t *self, busio_spi_obj_t *bus, const mcu_pin_obj_t *cs, int baudrate) { +mp_rom_error_text_t sdcardio_sdcard_construct(sdcardio_sdcard_obj_t *self, busio_spi_obj_t *bus, const mcu_pin_obj_t *cs, int baudrate) { self->bus = bus; common_hal_digitalio_digitalinout_construct(&self->cs, cs); common_hal_digitalio_digitalinout_switch_to_output(&self->cs, true, DRIVE_MODE_PUSH_PULL); @@ -309,10 +309,19 @@ void common_hal_sdcardio_sdcard_construct(sdcardio_sdcard_obj_t *self, busio_spi if (result != NULL) { common_hal_digitalio_digitalinout_deinit(&self->cs); - mp_raise_OSError_msg(result); + return result; } self->baudrate = baudrate; + return NULL; +} + + +void common_hal_sdcardio_sdcard_construct(sdcardio_sdcard_obj_t *self, busio_spi_obj_t *bus, const mcu_pin_obj_t *cs, int baudrate) { + mp_rom_error_text_t result = sdcardio_sdcard_construct(self, bus, cs, baudrate); + if (result != NULL) { + mp_raise_OSError_msg(result); + } } void common_hal_sdcardio_sdcard_deinit(sdcardio_sdcard_obj_t *self) { diff --git a/shared-module/sdcardio/SDCard.h b/shared-module/sdcardio/SDCard.h index 0b315395aa..f9cd64b812 100644 --- a/shared-module/sdcardio/SDCard.h +++ b/shared-module/sdcardio/SDCard.h @@ -24,3 +24,5 @@ typedef struct { uint32_t next_block; bool in_cmd25; } sdcardio_sdcard_obj_t; + +mp_rom_error_text_t sdcardio_sdcard_construct(sdcardio_sdcard_obj_t *self, busio_spi_obj_t *bus, const mcu_pin_obj_t *cs, int baudrate); diff --git a/shared-module/sdcardio/__init__.c b/shared-module/sdcardio/__init__.c index 72d32ef2b2..f9cf57e255 100644 --- a/shared-module/sdcardio/__init__.c +++ b/shared-module/sdcardio/__init__.c @@ -3,3 +3,118 @@ // SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC // // SPDX-License-Identifier: MIT + +#include "shared-module/sdcardio/__init__.h" + +#include "extmod/vfs_fat.h" + +#include "shared-bindings/busio/SPI.h" +#include "shared-bindings/digitalio/DigitalInOut.h" +#include "shared-bindings/sdcardio/SDCard.h" + +#include "supervisor/filesystem.h" + +#ifdef DEFAULT_SD_CARD_DETECT +static digitalio_digitalinout_obj_t sd_card_detect_pin; +static sdcardio_sdcard_obj_t sdcard; + +static mp_vfs_mount_t _sdcard_vfs; +fs_user_mount_t _sdcard_usermount; + +static bool _init_error = false; +static bool _mounted = false; + +#ifdef DEFAULT_SD_MOSI +static busio_spi_obj_t busio_spi_obj; +#endif +#endif + +void sdcardio_init(void) { + #ifdef DEFAULT_SD_CARD_DETECT + sd_card_detect_pin.base.type = &digitalio_digitalinout_type; + common_hal_digitalio_digitalinout_construct(&sd_card_detect_pin, DEFAULT_SD_CARD_DETECT); + common_hal_digitalio_digitalinout_switch_to_input(&sd_card_detect_pin, PULL_UP); + common_hal_digitalio_digitalinout_never_reset(&sd_card_detect_pin); + #endif +} + +void automount_sd_card(void) { + #ifdef DEFAULT_SD_CARD_DETECT + if (common_hal_digitalio_digitalinout_get_value(&sd_card_detect_pin) != DEFAULT_SD_CARD_INSERTED) { + // No card. + _init_error = false; + if (_mounted) { + // Unmount the card. + mp_vfs_mount_t *cur = MP_STATE_VM(vfs_mount_table); + if (cur == &_sdcard_vfs) { + MP_STATE_VM(vfs_mount_table) = cur->next; + } else { + while (cur->next != &_sdcard_vfs && cur != NULL) { + cur = cur->next; + } + if (cur != NULL) { + cur->next = _sdcard_vfs.next; + } + } + _sdcard_vfs.next = NULL; + + #ifdef DEFAULT_SD_MOSI + common_hal_busio_spi_deinit(&busio_spi_obj); + #endif + _mounted = false; + } + return; + } else if (_init_error || _mounted) { + // We've already tried and failed to init the card. Don't try again. + return; + } + + busio_spi_obj_t *spi_obj; + #ifndef DEFAULT_SD_MOSI + spi_obj = MP_OBJ_TO_PTR(common_hal_board_create_spi(0)); + #else + spi_obj = &busio_spi_obj; + spi_obj->base.type = &busio_spi_type; + common_hal_busio_spi_construct(spi_obj, DEFAULT_SD_SCK, DEFAULT_SD_MOSI, DEFAULT_SD_MISO, false); + common_hal_busio_spi_never_reset(spi_obj); + #endif + sdcard.base.type = &sdcardio_SDCard_type; + mp_rom_error_text_t error = sdcardio_sdcard_construct(&sdcard, spi_obj, DEFAULT_SD_CS, 25000000); + if (error != NULL) { + // Failed to communicate with the card. + _init_error = true; + } + common_hal_digitalio_digitalinout_never_reset(&sdcard.cs); + + fs_user_mount_t *vfs = &_sdcard_usermount; + vfs->base.type = &mp_fat_vfs_type; + vfs->fatfs.drv = vfs; + + // Initialise underlying block device + vfs->blockdev.block_size = FF_MIN_SS; // default, will be populated by call to MP_BLOCKDEV_IOCTL_BLOCK_SIZE + mp_vfs_blockdev_init(&vfs->blockdev, &sdcard); + + // mount the block device so the VFS methods can be used + FRESULT res = f_mount(&vfs->fatfs); + if (res != FR_OK) { + _mounted = false; + _init_error = true; + common_hal_sdcardio_sdcard_deinit(&sdcard); + #ifdef DEFAULT_SD_MOSI + common_hal_busio_spi_deinit(spi_obj); + #endif + return; + } + + filesystem_set_concurrent_write_protection(vfs, true); + filesystem_set_writable_by_usb(vfs, false); + + mp_vfs_mount_t *sdcard_vfs = &_sdcard_vfs; + sdcard_vfs->str = "/sd"; + sdcard_vfs->len = 3; + sdcard_vfs->obj = MP_OBJ_FROM_PTR(&_sdcard_usermount); + sdcard_vfs->next = MP_STATE_VM(vfs_mount_table); + MP_STATE_VM(vfs_mount_table) = sdcard_vfs; + _mounted = true; + #endif +} diff --git a/shared-module/sdcardio/__init__.h b/shared-module/sdcardio/__init__.h index c9069db9fd..59b4cf892f 100644 --- a/shared-module/sdcardio/__init__.h +++ b/shared-module/sdcardio/__init__.h @@ -5,3 +5,6 @@ // SPDX-License-Identifier: MIT #pragma once + +void sdcardio_init(void); +void automount_sd_card(void); diff --git a/shared-module/storage/__init__.c b/shared-module/storage/__init__.c index 2bf265c1dc..1f4ddefefe 100644 --- a/shared-module/storage/__init__.c +++ b/shared-module/storage/__init__.c @@ -174,18 +174,23 @@ mp_obj_t common_hal_storage_getmount(const char *mount_path) { } void common_hal_storage_remount(const char *mount_path, bool readonly, bool disable_concurrent_write_protection) { - if (strcmp(mount_path, "/") != 0) { + const char *path_under_mount; + fs_user_mount_t *fs_usermount = filesystem_for_path(mount_path, &path_under_mount); + if (path_under_mount[0] != 0 && strcmp(mount_path, "/") != 0) { mp_raise_OSError(MP_EINVAL); } #if CIRCUITPY_USB_DEVICE && CIRCUITPY_USB_MSC - if (!usb_msc_ejected() && storage_usb_is_enabled) { - mp_raise_RuntimeError(MP_ERROR_TEXT("Cannot remount '/' when visible via USB.")); + if (!blockdev_lock(fs_usermount)) { + mp_raise_RuntimeError(MP_ERROR_TEXT("Cannot remount path when visible via USB.")); } #endif - filesystem_set_internal_writable_by_usb(readonly); - filesystem_set_internal_concurrent_write_protection(!disable_concurrent_write_protection); + filesystem_set_writable_by_usb(fs_usermount, readonly); + filesystem_set_concurrent_write_protection(fs_usermount, !disable_concurrent_write_protection); + blockdev_unlock(fs_usermount); + + usb_msc_remount(fs_usermount); } void common_hal_storage_erase_filesystem(bool extended) { diff --git a/supervisor/shared/filesystem.c b/supervisor/shared/filesystem.c index 3532db4bdc..0df600e77f 100644 --- a/supervisor/shared/filesystem.c +++ b/supervisor/shared/filesystem.c @@ -15,6 +15,10 @@ #include "supervisor/flash.h" #include "supervisor/linker.h" +#if CIRCUITPY_SDCARDIO +#include "shared-module/sdcardio/__init__.h" +#endif + static mp_vfs_mount_t _circuitpy_vfs; static fs_user_mount_t _circuitpy_usermount; @@ -214,6 +218,10 @@ bool filesystem_init(bool create_allowed, bool force_create) { supervisor_flash_update_extended(); #endif + #if CIRCUITPY_SDCARDIO + sdcardio_init(); + #endif + return true; } @@ -288,7 +296,7 @@ fs_user_mount_t *filesystem_for_path(const char *path_in, const char **path_unde // because otherwise the path will be adjusted by os.getcwd() when it's looked up. if (strlen(vfs->str) != 1) { // Remove the mount point directory name, such as "/sd". - path_under_mount += strlen(vfs->str); + *path_under_mount += strlen(vfs->str); } } return fs_mount; diff --git a/supervisor/shared/usb/usb_msc_flash.c b/supervisor/shared/usb/usb_msc_flash.c index 2b92035d97..166880da0c 100644 --- a/supervisor/shared/usb/usb_msc_flash.c +++ b/supervisor/shared/usb/usb_msc_flash.c @@ -21,12 +21,25 @@ #define MSC_FLASH_BLOCK_SIZE 512 #if CIRCUITPY_SAVES_PARTITION_SIZE > 0 -#define LUN_COUNT 2 +#define SAVES_COUNT 1 +#define SAVES_LUN (1) #else -#define LUN_COUNT 1 +#define SAVES_COUNT 0 #endif +#if CIRCUITPY_SDCARDIO +#include "shared-module/sdcardio/__init__.h" + +#define SDCARD_COUNT 1 +#define SDCARD_LUN (1 + SAVES_COUNT) +#else +#define SDCARD_COUNT 0 +#endif + +#define LUN_COUNT (1 + SAVES_COUNT + SDCARD_COUNT) + static bool ejected[LUN_COUNT]; +static bool eject_once[LUN_COUNT]; static bool locked[LUN_COUNT]; #include "tusb.h" @@ -103,24 +116,36 @@ size_t usb_msc_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *desc return sizeof(usb_msc_descriptor_template); } -// The root FS is always at the end of the list. +// We hardcode LUN -> mount mapping so that it doesn't changes with saves and +// SD card appearing and disappearing. static fs_user_mount_t *get_vfs(int lun) { - // Keep a history of the mounts we pass so we can search back. - mp_vfs_mount_t *mounts[LUN_COUNT]; - mp_vfs_mount_t *current_mount = MP_STATE_VM(vfs_mount_table); - if (current_mount == NULL) { - return NULL; + fs_user_mount_t *root = filesystem_circuitpy(); + if (lun == 0) { + return root; } - // i is the last entry filled - size_t i = 0; - mounts[i] = current_mount; - while (current_mount->next != NULL) { - current_mount = current_mount->next; - i = (i + 1) % LUN_COUNT; - mounts[i] = current_mount; + #ifdef SAVES_LUN + if (lun == SAVES_LUN) { + const char *path_under_mount; + fs_user_mount_t *saves = filesystem_for_path("/saves", &path_under_mount); + if (saves != root) { + return saves; + } } - fs_user_mount_t *vfs = mounts[(i - lun) % LUN_COUNT]->obj; - return vfs; + #endif + #ifdef SDCARD_LUN + if (lun == SDCARD_LUN) { + const char *path_under_mount; + fs_user_mount_t *sdcard = filesystem_for_path("/sd", &path_under_mount); + if (sdcard != root) { + return sdcard; + } else { + // Clear any ejected state so that a re-insert causes it to reappear. + ejected[SDCARD_LUN] = false; + locked[SDCARD_LUN] = false; + } + } + #endif + return NULL; } static void _usb_msc_uneject(void) { @@ -136,7 +161,7 @@ void usb_msc_mount(void) { void usb_msc_umount(void) { for (uint8_t i = 0; i < LUN_COUNT; i++) { - fs_user_mount_t *vfs = get_vfs(i + 1); + fs_user_mount_t *vfs = get_vfs(i); if (vfs == NULL) { continue; } @@ -145,12 +170,15 @@ void usb_msc_umount(void) { } } -bool usb_msc_ejected(void) { - bool all_ejected = true; +void usb_msc_remount(fs_user_mount_t *fs_mount) { for (uint8_t i = 0; i < LUN_COUNT; i++) { - all_ejected &= ejected[i]; + fs_user_mount_t *vfs = get_vfs(i); + if (vfs == NULL || vfs != fs_mount) { + continue; + } + ejected[i] = false; + eject_once[i] = true; } - return all_ejected; } uint8_t tud_msc_get_maxlun_cb(void) { @@ -295,11 +323,18 @@ bool tud_msc_test_unit_ready_cb(uint8_t lun) { return false; } + #if CIRCUITPY_SDCARDIO + if (lun == SDCARD_LUN) { + automount_sd_card(); + } + #endif + fs_user_mount_t *current_mount = get_vfs(lun); if (current_mount == NULL) { return false; } - if (ejected[lun]) { + if (ejected[lun] || eject_once[lun]) { + eject_once[lun] = false; // Set 0x3a for media not present. tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3A, 0x00); return false; diff --git a/supervisor/usb.h b/supervisor/usb.h index 435ecd02cd..10f033a361 100644 --- a/supervisor/usb.h +++ b/supervisor/usb.h @@ -65,7 +65,9 @@ size_t usb_msc_descriptor_length(void); size_t usb_msc_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *descriptor_counts, uint8_t *current_interface_string); void usb_msc_mount(void); void usb_msc_umount(void); -bool usb_msc_ejected(void); + +#include "extmod/vfs_fat.h" +void usb_msc_remount(fs_user_mount_t *fs_mount); #endif #if CIRCUITPY_USB_KEYBOARD_WORKFLOW