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
This commit is contained in:
Scott Shawcroft 2025-03-12 09:43:04 -07:00
parent ca0eec3f1a
commit d0c840094a
No known key found for this signature in database
13 changed files with 257 additions and 62 deletions

View file

@ -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

View file

@ -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);

View file

@ -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 ""

View file

@ -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)

View file

@ -90,20 +90,18 @@ 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;

View file

@ -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) {

View file

@ -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);

View file

@ -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
}

View file

@ -5,3 +5,6 @@
// SPDX-License-Identifier: MIT
#pragma once
void sdcardio_init(void);
void automount_sd_card(void);

View file

@ -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) {

View file

@ -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;

View file

@ -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) {
fs_user_mount_t *root = filesystem_circuitpy();
if (lun == 0) {
return root;
}
#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;
}
}
#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;
}
// 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;
}
fs_user_mount_t *vfs = mounts[(i - lun) % LUN_COUNT]->obj;
return vfs;
}
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;

View file

@ -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