diff --git a/docs/workflows.md b/docs/workflows.md index b829643293ddf..4cfa1950011f2 100644 --- a/docs/workflows.md +++ b/docs/workflows.md @@ -165,6 +165,13 @@ Returns a JSON representation of the directory. * `403 Forbidden` - No `CIRCUITPY_WEB_API_PASSWORD` set * `404 Not Found` - Missing directory +Returns directory information: +* `free`: Count of free blocks on the disk holding this directory. +* `total`: Total blocks that make up the disk holding this directory. +* `block_size`: Size of a block in bytes. +* `writable`: True when CircuitPython and the web workflow can write to the disk. USB may claim a disk instead. +* `files`: Array of objects. One for each file. + Returns information about each file in the directory: * `name` - File name. No trailing `/` on directory names @@ -179,14 +186,20 @@ curl -v -u :passw0rd -H "Accept: application/json" -L --location-trusted http:// ``` ```json -[ - { - "name": "world.txt", - "directory": false, - "modified_ns": 946934328000000000, - "file_size": 12 - } -] +{ + "free": 451623, + "total": 973344, + "block_size": 32768, + "writable": true, + "files": [ + { + "name": "world.txt", + "directory": false, + "modified_ns": 946934328000000000, + "file_size": 12 + } + ] +} ``` ##### PUT @@ -373,10 +386,10 @@ curl -v -L http://circuitpython.local/cp/devices.json Returns information about the attached disk(s). A list of objects, one per disk. * `root`: Filesystem path to the root of the disk. -* `free`: Count of free bytes on the disk. +* `free`: Count of free blocks on the disk. +* `total`: Total blocks that make up the disk. * `block_size`: Size of a block in bytes. * `writable`: True when CircuitPython and the web workflow can write to the disk. USB may claim a disk instead. -* `total`: Total bytes that make up the disk. Example: ```sh @@ -405,7 +418,7 @@ This is an authenticated endpoint in both modes. Returns information about the device. -* `web_api_version`: Between `1` and `3`. This versions the rest of the API and new versions may not be backwards compatible. See below for more info. +* `web_api_version`: Between `1` and `4`. This versions the rest of the API and new versions may not be backwards compatible. See below for more info. * `version`: CircuitPython build version. * `build_date`: CircuitPython build date. * `board_name`: Human readable name of the board. @@ -467,3 +480,5 @@ Only one WebSocket at a time is supported. * `1` - Initial version. * `2` - Added `/cp/diskinfo.json`. * `3` - Changed `/cp/diskinfo.json` to return a list in preparation for multi-disk support. +* `4` - Changed directory json to an object with additional data. File list is under `files` and is + the same as the old format. diff --git a/extmod/vfs.h b/extmod/vfs.h index 49c6d87e58afb..4e6af208e4d09 100644 --- a/extmod/vfs.h +++ b/extmod/vfs.h @@ -48,6 +48,8 @@ #define MP_BLOCKDEV_FLAG_USB_WRITABLE (0x0010) // Bit set when the above flag is checked before opening a file for write. #define MP_BLOCKDEV_FLAG_CONCURRENT_WRITE_PROTECTED (0x0020) +// Bit set when something has claimed the right to mutate the blockdev. +#define MP_BLOCKDEV_FLAG_LOCKED (0x0040) // constants for block protocol ioctl #define MP_BLOCKDEV_IOCTL_INIT (1) diff --git a/extmod/vfs_blockdev.c b/extmod/vfs_blockdev.c index 57c83b42899a6..f459dfbce0a54 100644 --- a/extmod/vfs_blockdev.c +++ b/extmod/vfs_blockdev.c @@ -30,12 +30,41 @@ #include "py/mperrno.h" #include "extmod/vfs.h" +#if CIRCUITPY_SDCARDIO +#include "shared-bindings/sdcardio/SDCard.h" +#endif +#if CIRCUITPY_SDIOIO +#include "shared-bindings/sdioio/SDCard.h" +#endif + + #if MICROPY_VFS void mp_vfs_blockdev_init(mp_vfs_blockdev_t *self, mp_obj_t bdev) { mp_load_method(bdev, MP_QSTR_readblocks, self->readblocks); mp_load_method_maybe(bdev, MP_QSTR_writeblocks, self->writeblocks); mp_load_method_maybe(bdev, MP_QSTR_ioctl, self->u.ioctl); + + // CIRCUITPY-CHANGE: Support native SD cards. + #if CIRCUITPY_SDCARDIO + if (mp_obj_get_type(bdev) == &sdcardio_SDCard_type) { + self->flags |= MP_BLOCKDEV_FLAG_NATIVE | MP_BLOCKDEV_FLAG_HAVE_IOCTL; + self->readblocks[0] = mp_const_none; + self->readblocks[1] = bdev; + self->readblocks[2] = (mp_obj_t)sdcardio_sdcard_readblocks; // native version + self->writeblocks[0] = mp_const_none; + self->writeblocks[1] = bdev; + self->writeblocks[2] = (mp_obj_t)sdcardio_sdcard_writeblocks; // native version + self->u.ioctl[0] = mp_const_none; + self->u.ioctl[1] = bdev; + self->u.ioctl[2] = (mp_obj_t)sdcardio_sdcard_ioctl; // native version + } + #endif + #if CIRCUITPY_SDIOIO + if (mp_obj_get_type(bdev) == &sdioio_SDCard_type) { + // TODO: Enable native blockdev for SDIO too. + } + #endif if (self->u.ioctl[0] != MP_OBJ_NULL) { // Device supports new block protocol, so indicate it self->flags |= MP_BLOCKDEV_FLAG_HAVE_IOCTL; @@ -48,8 +77,8 @@ 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) { if (self->flags & MP_BLOCKDEV_FLAG_NATIVE) { - mp_uint_t (*f)(uint8_t *, uint32_t, uint32_t) = (void *)(uintptr_t)self->readblocks[2]; - return f(buf, block_num, num_blocks); + mp_uint_t (*f)(mp_obj_t self, uint8_t *, uint32_t, uint32_t) = (void *)(uintptr_t)self->readblocks[2]; + return f(self->readblocks[1], buf, block_num, num_blocks); } else { mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, num_blocks *self->block_size, buf}; self->readblocks[2] = MP_OBJ_NEW_SMALL_INT(block_num); @@ -80,8 +109,8 @@ int mp_vfs_blockdev_write(mp_vfs_blockdev_t *self, size_t block_num, size_t num_ } if (self->flags & MP_BLOCKDEV_FLAG_NATIVE) { - mp_uint_t (*f)(const uint8_t *, uint32_t, uint32_t) = (void *)(uintptr_t)self->writeblocks[2]; - return f(buf, block_num, num_blocks); + mp_uint_t (*f)(mp_obj_t self, const uint8_t *, uint32_t, uint32_t) = (void *)(uintptr_t)self->writeblocks[2]; + return f(self->writeblocks[1], buf, block_num, num_blocks); } else { mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, num_blocks *self->block_size, (void *)buf}; self->writeblocks[2] = MP_OBJ_NEW_SMALL_INT(block_num); @@ -112,6 +141,15 @@ int mp_vfs_blockdev_write_ext(mp_vfs_blockdev_t *self, size_t block_num, size_t mp_obj_t mp_vfs_blockdev_ioctl(mp_vfs_blockdev_t *self, uintptr_t cmd, uintptr_t arg) { if (self->flags & MP_BLOCKDEV_FLAG_HAVE_IOCTL) { + if (self->flags & MP_BLOCKDEV_FLAG_NATIVE) { + size_t out_value; + bool (*f)(mp_obj_t self, uint32_t, uint32_t, size_t *) = (void *)(uintptr_t)self->u.ioctl[2]; + bool b = f(self->u.ioctl[1], cmd, arg, &out_value); + if (!b) { + return mp_const_none; + } + return MP_OBJ_NEW_SMALL_INT(out_value); + } // New protocol with ioctl self->u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(cmd); self->u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(arg); diff --git a/extmod/vfs_fat.h b/extmod/vfs_fat.h index cd003e2fe1887..dbee2947c9a78 100644 --- a/extmod/vfs_fat.h +++ b/extmod/vfs_fat.h @@ -34,6 +34,7 @@ typedef struct _fs_user_mount_t { mp_obj_base_t base; mp_vfs_blockdev_t blockdev; FATFS fatfs; + int8_t lock_count; } fs_user_mount_t; extern const byte fresult_to_errno_table[20]; diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index b40368673e67b..6fb965842f496 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -1388,6 +1388,10 @@ msgstr "" msgid "Missing jmp_pin. %q[%u] jumps on pin" msgstr "" +#: shared-module/storage/__init__.c +msgid "Mount point missing. Create first (maybe via USB)" +msgstr "" + #: shared-bindings/busio/UART.c shared-bindings/displayio/Group.c msgid "Must be a %q subclass." msgstr "" diff --git a/ports/espressif/supervisor/internal_flash.c b/ports/espressif/supervisor/internal_flash.c index d6e96732bac43..5ec875bcea78d 100644 --- a/ports/espressif/supervisor/internal_flash.c +++ b/ports/espressif/supervisor/internal_flash.c @@ -60,7 +60,8 @@ STATIC uint32_t _cache_lba = 0xffffffff; #define SECSIZE(fs) ((fs)->ssize) #endif // FF_MAX_SS == FF_MIN_SS STATIC DWORD fatfs_bytes(void) { - FATFS *fatfs = filesystem_circuitpy(); + fs_user_mount_t *fs_mount = filesystem_circuitpy(); + FATFS *fatfs = &fs_mount->fatfs; return (fatfs->csize * SECSIZE(fatfs)) * (fatfs->n_fatent - 2); } STATIC bool storage_extended = true; diff --git a/shared-bindings/sdcardio/SDCard.c b/shared-bindings/sdcardio/SDCard.c index 26db3217fc212..7f5f07ddd65fe 100644 --- a/shared-bindings/sdcardio/SDCard.c +++ b/shared-bindings/sdcardio/SDCard.c @@ -131,7 +131,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(sdcardio_sdcard_deinit_obj, sdcardio_sdcard_deinit); //| //| :return: None""" -STATIC mp_obj_t sdcardio_sdcard_readblocks(mp_obj_t self_in, mp_obj_t start_block_in, mp_obj_t buf_in) { +STATIC mp_obj_t _sdcardio_sdcard_readblocks(mp_obj_t self_in, mp_obj_t start_block_in, mp_obj_t buf_in) { uint32_t start_block = mp_obj_get_int(start_block_in); mp_buffer_info_t bufinfo; mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_WRITE); @@ -143,7 +143,7 @@ STATIC mp_obj_t sdcardio_sdcard_readblocks(mp_obj_t self_in, mp_obj_t start_bloc return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_3(sdcardio_sdcard_readblocks_obj, sdcardio_sdcard_readblocks); +MP_DEFINE_CONST_FUN_OBJ_3(sdcardio_sdcard_readblocks_obj, _sdcardio_sdcard_readblocks); //| def sync(self) -> None: //| """Ensure all blocks written are actually committed to the SD card @@ -171,7 +171,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(sdcardio_sdcard_sync_obj, sdcardio_sdcard_sync); //| :return: None""" //| -STATIC mp_obj_t sdcardio_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t start_block_in, mp_obj_t buf_in) { +STATIC mp_obj_t _sdcardio_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t start_block_in, mp_obj_t buf_in) { uint32_t start_block = mp_obj_get_int(start_block_in); mp_buffer_info_t bufinfo; mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ); @@ -182,7 +182,7 @@ STATIC mp_obj_t sdcardio_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t start_blo } return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_3(sdcardio_sdcard_writeblocks_obj, sdcardio_sdcard_writeblocks); +MP_DEFINE_CONST_FUN_OBJ_3(sdcardio_sdcard_writeblocks_obj, _sdcardio_sdcard_writeblocks); STATIC const mp_rom_map_elem_t sdcardio_sdcard_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&sdcardio_sdcard_count_obj) }, diff --git a/shared-bindings/sdcardio/SDCard.h b/shared-bindings/sdcardio/SDCard.h index 5986d5b8142b7..2e274b9d54f66 100644 --- a/shared-bindings/sdcardio/SDCard.h +++ b/shared-bindings/sdcardio/SDCard.h @@ -27,4 +27,19 @@ #pragma once +#include "shared-module/sdcardio/SDCard.h" + extern const mp_obj_type_t sdcardio_SDCard_type; + +void common_hal_sdcardio_sdcard_construct(sdcardio_sdcard_obj_t *self, busio_spi_obj_t *spi, const mcu_pin_obj_t *cs, int baudrate); +void common_hal_sdcardio_sdcard_deinit(sdcardio_sdcard_obj_t *self); +void common_hal_sdcardio_sdcard_check_for_deinit(sdcardio_sdcard_obj_t *self); +int common_hal_sdcardio_sdcard_get_blockcount(sdcardio_sdcard_obj_t *self); +int common_hal_sdcardio_sdcard_readblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *buf); +int common_hal_sdcardio_sdcard_sync(sdcardio_sdcard_obj_t *self); +int common_hal_sdcardio_sdcard_writeblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *buf); + +// Used by native vfs blockdev. +mp_uint_t sdcardio_sdcard_readblocks(mp_obj_t self_in, uint8_t *buf, uint32_t start_block, uint32_t buflen); +mp_uint_t sdcardio_sdcard_writeblocks(mp_obj_t self_in, uint8_t *buf, uint32_t start_block, uint32_t buflen); +bool sdcardio_sdcard_ioctl(mp_obj_t self_in, size_t cmd, size_t arg, mp_int_t *out_value); diff --git a/shared-bindings/sdioio/SDCard.c b/shared-bindings/sdioio/SDCard.c index d06d76825cf11..2fb878aa081e5 100644 --- a/shared-bindings/sdioio/SDCard.c +++ b/shared-bindings/sdioio/SDCard.c @@ -164,7 +164,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(sdioio_sdcard_count_obj, sdioio_sdcard_count); //| :param ~circuitpython_typing.WriteableBuffer buf: The buffer to write into. Length must be multiple of 512. //| //| :return: None""" -STATIC mp_obj_t sdioio_sdcard_readblocks(mp_obj_t self_in, mp_obj_t start_block_in, mp_obj_t buf_in) { +STATIC mp_obj_t _sdioio_sdcard_readblocks(mp_obj_t self_in, mp_obj_t start_block_in, mp_obj_t buf_in) { uint32_t start_block = mp_obj_get_int(start_block_in); mp_buffer_info_t bufinfo; mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_WRITE); @@ -176,7 +176,7 @@ STATIC mp_obj_t sdioio_sdcard_readblocks(mp_obj_t self_in, mp_obj_t start_block_ return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_3(sdioio_sdcard_readblocks_obj, sdioio_sdcard_readblocks); +MP_DEFINE_CONST_FUN_OBJ_3(sdioio_sdcard_readblocks_obj, _sdioio_sdcard_readblocks); //| def writeblocks(self, start_block: int, buf: ReadableBuffer) -> None: //| """Write one or more blocks to the card @@ -185,7 +185,7 @@ MP_DEFINE_CONST_FUN_OBJ_3(sdioio_sdcard_readblocks_obj, sdioio_sdcard_readblocks //| :param ~circuitpython_typing.ReadableBuffer buf: The buffer to read from. Length must be multiple of 512. //| //| :return: None""" -STATIC mp_obj_t sdioio_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t start_block_in, mp_obj_t buf_in) { +STATIC mp_obj_t _sdioio_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t start_block_in, mp_obj_t buf_in) { uint32_t start_block = mp_obj_get_int(start_block_in); mp_buffer_info_t bufinfo; mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ); @@ -197,7 +197,7 @@ STATIC mp_obj_t sdioio_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t start_block return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_3(sdioio_sdcard_writeblocks_obj, sdioio_sdcard_writeblocks); +MP_DEFINE_CONST_FUN_OBJ_3(sdioio_sdcard_writeblocks_obj, _sdioio_sdcard_writeblocks); //| frequency: int //| """The actual SDIO bus frequency. This may not match the frequency diff --git a/shared-module/os/getenv.c b/shared-module/os/getenv.c index 9c9bad1c05146..d3408e8a2c9ac 100644 --- a/shared-module/os/getenv.c +++ b/shared-module/os/getenv.c @@ -62,8 +62,12 @@ STATIC bool open_file(const char *name, file_arg *active_file) { return false; } #else - FATFS *fs = filesystem_circuitpy(); - FRESULT result = f_open(fs, active_file, name, FA_READ); + fs_user_mount_t *fs_mount = filesystem_circuitpy(); + if (fs_mount == NULL) { + return false; + } + FATFS *fatfs = &fs_mount->fatfs; + FRESULT result = f_open(fatfs, active_file, name, FA_READ); return result == FR_OK; #endif } diff --git a/shared-module/sdcardio/SDCard.c b/shared-module/sdcardio/SDCard.c index 0ff156f8f89fb..20c5afef0e019 100644 --- a/shared-module/sdcardio/SDCard.c +++ b/shared-module/sdcardio/SDCard.c @@ -26,6 +26,8 @@ // This implementation largely follows the structure of adafruit_sdcard.py +#include "extmod/vfs.h" + #include "shared-bindings/busio/SPI.h" #include "shared-bindings/digitalio/DigitalInOut.h" #include "shared-bindings/sdcardio/SDCard.h" @@ -358,23 +360,24 @@ STATIC int readinto(sdcardio_sdcard_obj_t *self, void *buf, size_t size) { return 0; } -STATIC int readblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *buf) { - uint32_t nblocks = buf->len / 512; +mp_uint_t sdcardio_sdcard_readblocks(mp_obj_t self_in, uint8_t *buf, uint32_t start_block, uint32_t nblocks) { + sdcardio_sdcard_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (!lock_and_configure_bus(self)) { + return MP_EAGAIN; + } + int r = 0; + size_t buflen = 512 * nblocks; if (nblocks == 1) { // Use CMD17 to read a single block - return block_cmd(self, 17, start_block, buf->buf, buf->len, true, true); + r = block_cmd(self, 17, start_block, buf, buflen, true, true); } else { // Use CMD18 to read multiple blocks - int r = block_cmd(self, 18, start_block, NULL, 0, true, true); - if (r < 0) { - return r; - } - - uint8_t *ptr = buf->buf; - while (nblocks--) { + r = block_cmd(self, 18, start_block, NULL, 0, true, true); + uint8_t *ptr = buf; + while (nblocks-- && r >= 0) { r = readinto(self, ptr, 512); - if (r < 0) { - return r; + if (r != 0) { + break; } ptr += 512; } @@ -387,12 +390,13 @@ STATIC int readblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buff uint8_t single_byte; common_hal_busio_spi_read(self->bus, &single_byte, 1, 0xff); if (single_byte & 0x80) { - return r; + break; } r = single_byte; } } - return 0; + extraclock_and_unlock_bus(self); + return r; } int common_hal_sdcardio_sdcard_readblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *buf) { @@ -401,10 +405,7 @@ int common_hal_sdcardio_sdcard_readblocks(sdcardio_sdcard_obj_t *self, uint32_t mp_raise_ValueError(MP_ERROR_TEXT("Buffer length must be a multiple of 512")); } - lock_and_configure_bus(self); - int r = readblocks(self, start_block, buf); - extraclock_and_unlock_bus(self); - return r; + return sdcardio_sdcard_readblocks(MP_OBJ_FROM_PTR(self), buf->buf, start_block, buf->len / 512); } STATIC int _write(sdcardio_sdcard_obj_t *self, uint8_t token, void *buf, size_t size) { @@ -452,17 +453,20 @@ STATIC int _write(sdcardio_sdcard_obj_t *self, uint8_t token, void *buf, size_t return 0; } -STATIC int writeblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *buf) { +mp_uint_t sdcardio_sdcard_writeblocks(mp_obj_t self_in, uint8_t *buf, uint32_t start_block, uint32_t nblocks) { + sdcardio_sdcard_obj_t *self = MP_OBJ_TO_PTR(self_in); common_hal_sdcardio_check_for_deinit(self); - uint32_t nblocks = buf->len / 512; - DEBUG_PRINT("cmd25? %d next_block %d start_block %d\n", self->in_cmd25, self->next_block, start_block); + if (!lock_and_configure_bus(self)) { + return MP_EAGAIN; + } if (!self->in_cmd25 || start_block != self->next_block) { DEBUG_PRINT("entering CMD25 at %d\n", (int)start_block); // Use CMD25 to write multiple block int r = block_cmd(self, 25, start_block, NULL, 0, true, true); if (r < 0) { + extraclock_and_unlock_bus(self); return r; } self->in_cmd25 = true; @@ -470,17 +474,19 @@ STATIC int writeblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buf self->next_block = start_block; - uint8_t *ptr = buf->buf; + uint8_t *ptr = buf; while (nblocks--) { int r = _write(self, TOKEN_CMD25, ptr, 512); if (r < 0) { self->in_cmd25 = false; + extraclock_and_unlock_bus(self); return r; } self->next_block++; ptr += 512; } + extraclock_and_unlock_bus(self); return 0; } @@ -498,7 +504,29 @@ int common_hal_sdcardio_sdcard_writeblocks(sdcardio_sdcard_obj_t *self, uint32_t mp_raise_ValueError(MP_ERROR_TEXT("Buffer length must be a multiple of 512")); } lock_and_configure_bus(self); - int r = writeblocks(self, start_block, buf); + int r = sdcardio_sdcard_writeblocks(MP_OBJ_FROM_PTR(self), buf->buf, start_block, buf->len / 512); extraclock_and_unlock_bus(self); return r; } + +bool sdcardio_sdcard_ioctl(mp_obj_t self_in, size_t cmd, size_t arg, mp_int_t *out_value) { + sdcardio_sdcard_obj_t *self = MP_OBJ_TO_PTR(self_in); + *out_value = 0; + switch (cmd) { + case MP_BLOCKDEV_IOCTL_DEINIT: + common_hal_sdcardio_sdcard_sync(self); + break; // TODO properly + case MP_BLOCKDEV_IOCTL_SYNC: + common_hal_sdcardio_sdcard_sync(self); + break; + case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: + *out_value = common_hal_sdcardio_sdcard_get_blockcount(self); + break; + case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: + *out_value = 512; + break; + default: + return false; + } + return true; +} diff --git a/shared-module/sdcardio/SDCard.h b/shared-module/sdcardio/SDCard.h index ff89b89819315..38644021065ea 100644 --- a/shared-module/sdcardio/SDCard.h +++ b/shared-module/sdcardio/SDCard.h @@ -44,11 +44,3 @@ typedef struct { uint32_t next_block; bool in_cmd25; } sdcardio_sdcard_obj_t; - -void common_hal_sdcardio_sdcard_construct(sdcardio_sdcard_obj_t *self, busio_spi_obj_t *spi, const mcu_pin_obj_t *cs, int baudrate); -void common_hal_sdcardio_sdcard_deinit(sdcardio_sdcard_obj_t *self); -void common_hal_sdcardio_sdcard_check_for_deinit(sdcardio_sdcard_obj_t *self); -int common_hal_sdcardio_sdcard_get_blockcount(sdcardio_sdcard_obj_t *self); -int common_hal_sdcardio_sdcard_readblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *buf); -int common_hal_sdcardio_sdcard_sync(sdcardio_sdcard_obj_t *self); -int common_hal_sdcardio_sdcard_writeblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *buf); diff --git a/shared-module/storage/__init__.c b/shared-module/storage/__init__.c index 5dc071435ceac..489efa0da0ca1 100644 --- a/shared-module/storage/__init__.c +++ b/shared-module/storage/__init__.c @@ -177,15 +177,16 @@ void common_hal_storage_mount(mp_obj_t vfs_obj, const char *mount_path, bool rea args[0] = readonly ? mp_const_true : mp_const_false; args[1] = mp_const_false; // Don't make the file system automatically when mounting. - // Check that there's no file or directory with the same name as the mount point. + // Check that there's file or directory with the same name as the mount point. // But it's ok to mount '/' in any case. if (strcmp(vfs->str, "/") != 0) { nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { common_hal_os_stat(mount_path); nlr_pop(); - // Something with the same name exists. - mp_raise_OSError(MP_EEXIST); + } else { + // Something with the same name doesn't exists. + mp_raise_RuntimeError(MP_ERROR_TEXT("Mount point missing. Create first (maybe via USB)")); } } diff --git a/supervisor/filesystem.h b/supervisor/filesystem.h index 3954291513e4b..77470f46973cb 100644 --- a/supervisor/filesystem.h +++ b/supervisor/filesystem.h @@ -24,8 +24,7 @@ * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_SUPERVISOR_FILESYSTEM_H -#define MICROPY_INCLUDED_SUPERVISOR_FILESYSTEM_H +#pragma once #include @@ -45,6 +44,16 @@ void filesystem_set_concurrent_write_protection(fs_user_mount_t *vfs, bool concu bool filesystem_is_writable_by_python(fs_user_mount_t *vfs); bool filesystem_is_writable_by_usb(fs_user_mount_t *vfs); -FATFS *filesystem_circuitpy(void); +fs_user_mount_t *filesystem_circuitpy(void); +fs_user_mount_t *filesystem_for_path(const char *path_in, const char **path_under_mount); +bool filesystem_native_fatfs(fs_user_mount_t *fs_mount); -#endif // MICROPY_INCLUDED_SUPERVISOR_FILESYSTEM_H +// We have two levels of locking. filesystem_* calls grab a shared blockdev lock to allow +// CircuitPython's fatfs code edit the blocks. blockdev_* class grab a lock to mutate blocks +// directly, excluding any filesystem_* locks. + +bool filesystem_lock(fs_user_mount_t *fs_mount); +void filesystem_unlock(fs_user_mount_t *fs_mount); + +bool blockdev_lock(fs_user_mount_t *fs_mount); +void blockdev_unlock(fs_user_mount_t *fs_mount); diff --git a/supervisor/shared/background_callback.c b/supervisor/shared/background_callback.c index cceda7d4132df..776435ac5570f 100644 --- a/supervisor/shared/background_callback.c +++ b/supervisor/shared/background_callback.c @@ -126,13 +126,21 @@ void background_callback_reset() { background_callback_t *cb = (background_callback_t *)callback_head; while (cb) { background_callback_t *next = cb->next; - if (gc_ptr_on_heap((void *)cb)) { + cb->next = NULL; + // Unlink any callbacks that are allocated on the python heap or if they + // reference data on the python heap. The python heap will be disappear + // soon after this. + if (gc_ptr_on_heap((void *)cb) || gc_ptr_on_heap(cb->data)) { + cb->prev = NULL; // Used to indicate a callback isn't queued. + } else { + // Set .next of the previous callback. *previous_next = cb; + // Set our .next for the next callback. previous_next = &cb->next; - cb->next = NULL; + // Set our prev to the last callback. + cb->prev = new_tail; + // Now we're the tail of the list. new_tail = cb; - } else { - memset(cb, 0, sizeof(*cb)); } cb = next; } diff --git a/supervisor/shared/bluetooth/file_transfer.c b/supervisor/shared/bluetooth/file_transfer.c index ae24c29899f87..2962532982ae7 100644 --- a/supervisor/shared/bluetooth/file_transfer.c +++ b/supervisor/shared/bluetooth/file_transfer.c @@ -151,6 +151,7 @@ STATIC uint64_t truncate_time(uint64_t input_time, DWORD *fattime) { // Used by read and write. STATIC FIL active_file; +STATIC fs_user_mount_t *active_mount; STATIC uint8_t _process_read(const uint8_t *raw_buf, size_t command_len) { struct read_command *command = (struct read_command *)raw_buf; size_t header_size = sizeof(struct read_command); @@ -170,11 +171,19 @@ STATIC uint8_t _process_read(const uint8_t *raw_buf, size_t command_len) { return THIS_COMMAND; } - char *path = (char *)((uint8_t *)command) + header_size; - path[command->path_length] = '\0'; + char *full_path = (char *)((uint8_t *)command) + header_size; + full_path[command->path_length] = '\0'; - FATFS *fs = filesystem_circuitpy(); - FRESULT result = f_open(fs, &active_file, path, FA_READ); + const char *mount_path; + active_mount = filesystem_for_path(full_path, &mount_path); + if (active_mount == NULL || !filesystem_native_fatfs(active_mount)) { + response.status = STATUS_ERROR; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, response_size, NULL, 0); + return ANY_COMMAND; + } + + FATFS *fs = &active_mount->fatfs; + FRESULT result = f_open(fs, &active_file, mount_path, FA_READ); if (result != FR_OK) { response.status = STATUS_ERROR; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, response_size, NULL, 0); @@ -250,22 +259,6 @@ STATIC uint8_t _process_read_pacing(const uint8_t *raw_buf, size_t command_len) STATIC size_t total_write_length; STATIC uint64_t _truncated_time; -// Returns true if usb is active and replies with an error if so. If not, it grabs -// the USB mass storage lock and returns false. Make sure to release the lock with -// usb_msc_unlock() when the transaction is complete. -STATIC bool _usb_active(void *response, size_t response_size) { - // Check to see if USB has already been mounted. If not, then we "eject" from USB until we're done. - #if CIRCUITPY_USB && CIRCUITPY_USB_MSC - if (storage_usb_enabled() && !usb_msc_lock()) { - // Status is always the second byte of the response. - ((uint8_t *)response)[1] = STATUS_ERROR_READONLY; - common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)response, response_size, NULL, 0); - return true; - } - #endif - return false; -} - STATIC uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) { struct write_command *command = (struct write_command *)raw_buf; size_t header_size = sizeof(struct write_command); @@ -284,23 +277,31 @@ STATIC uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) { } total_write_length = command->total_length; - char *path = (char *)command->path; - path[command->path_length] = '\0'; - if (_usb_active(&response, sizeof(struct write_pacing))) { + char *full_path = (char *)command->path; + full_path[command->path_length] = '\0'; + + const char *mount_path; + active_mount = filesystem_for_path(full_path, &mount_path); + if (active_mount == NULL || !filesystem_native_fatfs(active_mount)) { + response.status = STATUS_ERROR; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); + return ANY_COMMAND; + } + if (!filesystem_lock(active_mount)) { + response.status = STATUS_ERROR_READONLY; + common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); return ANY_COMMAND; } - FATFS *fs = filesystem_circuitpy(); + FATFS *fs = &active_mount->fatfs; DWORD fattime; _truncated_time = truncate_time(command->modification_time, &fattime); override_fattime(fattime); - FRESULT result = f_open(fs, &active_file, path, FA_WRITE | FA_OPEN_ALWAYS); + FRESULT result = f_open(fs, &active_file, mount_path, FA_WRITE | FA_OPEN_ALWAYS); if (result != FR_OK) { response.status = STATUS_ERROR; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); - #if CIRCUITPY_USB_MSC - usb_msc_unlock(); - #endif + filesystem_unlock(active_mount); override_fattime(0); return ANY_COMMAND; } @@ -315,9 +316,7 @@ STATIC uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) { f_truncate(&active_file); f_close(&active_file); override_fattime(0); - #if CIRCUITPY_USB_MSC - usb_msc_unlock(); - #endif + filesystem_unlock(active_mount); } response.offset = offset; response.free_space = chunk_size; @@ -342,9 +341,7 @@ STATIC uint8_t _process_write_data(const uint8_t *raw_buf, size_t command_len) { // TODO: throw away any more packets of path. response.status = STATUS_ERROR; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); - #if CIRCUITPY_USB_MSC - usb_msc_unlock(); - #endif + filesystem_unlock(active_mount); override_fattime(0); return ANY_COMMAND; } @@ -360,9 +357,7 @@ STATIC uint8_t _process_write_data(const uint8_t *raw_buf, size_t command_len) { // TODO: throw away any more packets of path. response.status = STATUS_ERROR; common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0); - #if CIRCUITPY_USB_MSC - usb_msc_unlock(); - #endif + filesystem_unlock(active_mount); override_fattime(0); return ANY_COMMAND; } @@ -377,9 +372,7 @@ STATIC uint8_t _process_write_data(const uint8_t *raw_buf, size_t command_len) { f_truncate(&active_file); f_close(&active_file); override_fattime(0); - #if CIRCUITPY_USB_MSC - usb_msc_unlock(); - #endif + filesystem_unlock(active_mount); // Don't reload until everything is written out of the packet buffer. common_hal_bleio_packet_buffer_flush(&_transfer_packet_buffer); return ANY_COMMAND; @@ -399,30 +392,19 @@ STATIC uint8_t _process_delete(const uint8_t *raw_buf, size_t command_len) { common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct delete_status), NULL, 0); return ANY_COMMAND; } - if (_usb_active(&response, sizeof(struct delete_status))) { - return ANY_COMMAND; - } // We need to receive another packet to have the full path. if (command_len < header_size + command->path_length) { return THIS_COMMAND; } - FATFS *fs = filesystem_circuitpy(); - char *path = (char *)((uint8_t *)command) + header_size; - path[command->path_length] = '\0'; - FILINFO file; - FRESULT result = f_stat(fs, path, &file); - if (result == FR_OK) { - if ((file.fattrib & AM_DIR) != 0) { - result = supervisor_workflow_delete_directory_contents(fs, path); - } - if (result == FR_OK) { - result = f_unlink(fs, path); - } - } - #if CIRCUITPY_USB_MSC - usb_msc_unlock(); - #endif - if (result != FR_OK) { + + char *full_path = (char *)((uint8_t *)command) + header_size; + full_path[command->path_length] = '\0'; + + FRESULT result = supervisor_workflow_delete_recursive(full_path); + + if (result == FR_WRITE_PROTECTED) { + response.status = STATUS_ERROR_READONLY; + } if (result != FR_OK) { response.status = STATUS_ERROR; } common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct delete_status), NULL, 0); @@ -456,25 +438,16 @@ STATIC uint8_t _process_mkdir(const uint8_t *raw_buf, size_t command_len) { common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct mkdir_status), NULL, 0); return ANY_COMMAND; } - if (_usb_active(&response, sizeof(struct mkdir_status))) { - return ANY_COMMAND; - } // We need to receive another packet to have the full path. if (command_len < header_size + command->path_length) { return THIS_COMMAND; } - FATFS *fs = filesystem_circuitpy(); - char *path = (char *)command->path; - _terminate_path(path, command->path_length); + char *full_path = (char *)command->path; + _terminate_path(full_path, command->path_length); DWORD fattime; response.truncated_time = truncate_time(command->modification_time, &fattime); - override_fattime(fattime); - FRESULT result = supervisor_workflow_mkdir_parents(fs, path); - override_fattime(0); - #if CIRCUITPY_USB_MSC - usb_msc_unlock(); - #endif + FRESULT result = supervisor_workflow_mkdir(fattime, full_path); if (result != FR_OK) { response.status = STATUS_ERROR; } @@ -520,12 +493,21 @@ STATIC uint8_t _process_listdir(uint8_t *raw_buf, size_t command_len) { return THIS_COMMAND; } - FATFS *fs = filesystem_circuitpy(); - char *path = (char *)&command->path; - _terminate_path(path, command->path_length); - // mp_printf(&mp_plat_print, "list %s\n", path); + char *full_path = (char *)&command->path; + _terminate_path(full_path, command->path_length); + + const char *mount_path; + active_mount = filesystem_for_path(full_path, &mount_path); + if (active_mount == NULL || !filesystem_native_fatfs(active_mount)) { + entry->command = LISTDIR_ENTRY; + entry->status = STATUS_ERROR_NO_FILE; + send_listdir_entry_header(entry, max_packet_size); + return ANY_COMMAND; + } + FATFS *fs = &active_mount->fatfs; + FF_DIR dir; - FRESULT res = f_opendir(fs, &dir, path); + FRESULT res = f_opendir(fs, &dir, mount_path); entry->command = LISTDIR_ENTRY; entry->status = STATUS_OK; @@ -601,27 +583,21 @@ STATIC uint8_t _process_move(const uint8_t *raw_buf, size_t command_len) { common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct move_status), NULL, 0); return ANY_COMMAND; } - if (_usb_active(&response, sizeof(struct move_status))) { - return ANY_COMMAND; - } // We need to receive another packet to have the full path. if (command_len < header_size + total_path_length) { return THIS_COMMAND; } - FATFS *fs = filesystem_circuitpy(); + char *old_path = (char *)command->paths; old_path[command->old_path_length] = '\0'; char *new_path = old_path + command->old_path_length + 1; new_path[command->new_path_length] = '\0'; - // mp_printf(&mp_plat_print, "move %s to %s\n", old_path, new_path); - - FRESULT result = f_rename(fs, old_path, new_path); - #if CIRCUITPY_USB_MSC - usb_msc_unlock(); - #endif - if (result != FR_OK) { + FRESULT result = supervisor_workflow_move(old_path, new_path); + if (result == FR_WRITE_PROTECTED) { + response.status = STATUS_ERROR_READONLY; + } else if (result != FR_OK) { response.status = STATUS_ERROR; } common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct move_status), NULL, 0); diff --git a/supervisor/shared/filesystem.c b/supervisor/shared/filesystem.c index 2eff0b47aab88..e55ba1933ced9 100644 --- a/supervisor/shared/filesystem.c +++ b/supervisor/shared/filesystem.c @@ -221,9 +221,56 @@ bool filesystem_present(void) { return _mp_vfs.len > 0; } -FATFS *filesystem_circuitpy(void) { +fs_user_mount_t *filesystem_circuitpy(void) { if (!filesystem_present()) { return NULL; } - return &_internal_vfs.fatfs; + return &_internal_vfs; +} + +fs_user_mount_t *filesystem_for_path(const char *path_in, const char **path_under_mount) { + mp_vfs_mount_t *vfs = mp_vfs_lookup_path(path_in, path_under_mount); + if (vfs == MP_VFS_NONE) { + return NULL; + } + fs_user_mount_t *fs_mount; + *path_under_mount = path_in; + if (vfs == MP_VFS_ROOT) { + fs_mount = filesystem_circuitpy(); + } else { + fs_mount = MP_OBJ_TO_PTR(vfs->obj); + *path_under_mount += strlen(vfs->str); + } + return fs_mount; +} + +bool filesystem_native_fatfs(fs_user_mount_t *fs_mount) { + return fs_mount->base.type == &mp_fat_vfs_type && (fs_mount->blockdev.flags & MP_BLOCKDEV_FLAG_NATIVE) != 0; +} + +bool filesystem_lock(fs_user_mount_t *fs_mount) { + if (fs_mount->lock_count == 0 && !blockdev_lock(fs_mount)) { + return false; + } + fs_mount->lock_count += 1; + return true; +} + +void filesystem_unlock(fs_user_mount_t *fs_mount) { + fs_mount->lock_count -= 1; + if (fs_mount->lock_count == 0) { + blockdev_unlock(fs_mount); + } +} + +bool blockdev_lock(fs_user_mount_t *fs_mount) { + if ((fs_mount->blockdev.flags & MP_BLOCKDEV_FLAG_LOCKED) != 0) { + return false; + } + fs_mount->blockdev.flags |= MP_BLOCKDEV_FLAG_LOCKED; + return true; +} + +void blockdev_unlock(fs_user_mount_t *fs_mount) { + fs_mount->blockdev.flags &= ~MP_BLOCKDEV_FLAG_LOCKED; } diff --git a/supervisor/shared/flash.c b/supervisor/shared/flash.c index 448a7e1802c7b..33602d37ef179 100644 --- a/supervisor/shared/flash.c +++ b/supervisor/shared/flash.c @@ -87,7 +87,7 @@ static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_blo buf[15] = num_blocks >> 24; } -static mp_uint_t flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks) { +static mp_uint_t flash_read_blocks(mp_obj_t self, uint8_t *dest, uint32_t block_num, uint32_t num_blocks) { if (block_num == 0) { // fake the MBR so we can decide on our own partition table @@ -117,7 +117,7 @@ static mp_uint_t flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t n static volatile bool filesystem_dirty = false; -static mp_uint_t flash_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks) { +static mp_uint_t flash_write_blocks(mp_obj_t self, const uint8_t *src, uint32_t block_num, uint32_t num_blocks) { if (block_num == 0) { if (num_blocks > 1) { return 1; // error @@ -150,7 +150,7 @@ void PLACE_IN_ITCM(supervisor_flash_flush)(void) { STATIC mp_obj_t supervisor_flash_obj_readblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_WRITE); - mp_uint_t ret = flash_read_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FILESYSTEM_BLOCK_SIZE); + mp_uint_t ret = flash_read_blocks(self, bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FILESYSTEM_BLOCK_SIZE); return MP_OBJ_NEW_SMALL_INT(ret); } STATIC MP_DEFINE_CONST_FUN_OBJ_3(supervisor_flash_obj_readblocks_obj, supervisor_flash_obj_readblocks); @@ -158,13 +158,15 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_3(supervisor_flash_obj_readblocks_obj, supervisor STATIC mp_obj_t supervisor_flash_obj_writeblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ); - mp_uint_t ret = flash_write_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FILESYSTEM_BLOCK_SIZE); + mp_uint_t ret = flash_write_blocks(self, bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FILESYSTEM_BLOCK_SIZE); return MP_OBJ_NEW_SMALL_INT(ret); } STATIC MP_DEFINE_CONST_FUN_OBJ_3(supervisor_flash_obj_writeblocks_obj, supervisor_flash_obj_writeblocks); -static bool flash_ioctl(size_t cmd, mp_int_t *out_value) { - *out_value = 0; +STATIC bool flash_ioctl(mp_obj_t self_in, size_t cmd, size_t arg, mp_int_t *out_value) { + if (out_value != NULL) { + *out_value = 0; + } switch (cmd) { case MP_BLOCKDEV_IOCTL_INIT: supervisor_flash_init(); @@ -189,8 +191,9 @@ static bool flash_ioctl(size_t cmd, mp_int_t *out_value) { STATIC mp_obj_t supervisor_flash_obj_ioctl(mp_obj_t self, mp_obj_t cmd_in, mp_obj_t arg_in) { mp_int_t cmd = mp_obj_get_int(cmd_in); + mp_int_t arg = mp_obj_get_int(arg_in); mp_int_t out_value; - if (flash_ioctl(cmd, &out_value)) { + if (flash_ioctl(self, cmd, arg, &out_value)) { return MP_OBJ_NEW_SMALL_INT(out_value); } return mp_const_none; diff --git a/supervisor/shared/usb/usb_msc_flash.c b/supervisor/shared/usb/usb_msc_flash.c index f212d55270c91..b82576faec822 100644 --- a/supervisor/shared/usb/usb_msc_flash.c +++ b/supervisor/shared/usb/usb_msc_flash.c @@ -41,30 +41,45 @@ #define MSC_FLASH_BLOCK_SIZE 512 static bool ejected[1] = {true}; +static bool locked[1] = {false}; -// Lock to track if something else is using the filesystem when USB is plugged in. If so, the drive -// will be made available once the lock is released. -static bool _usb_msc_lock = false; -static bool _usb_connected_while_locked = false; +// The root FS is always at the end of the list. +static fs_user_mount_t *get_vfs(int lun) { + // TODO(tannewt): Return the mount which matches the lun where 0 is the end + // and is counted in reverse. + if (lun > 0) { + return NULL; + } + mp_vfs_mount_t *current_mount = MP_STATE_VM(vfs_mount_table); + if (current_mount == NULL) { + return NULL; + } + while (current_mount->next != NULL) { + current_mount = current_mount->next; + } + return current_mount->obj; +} STATIC void _usb_msc_uneject(void) { for (uint8_t i = 0; i < sizeof(ejected); i++) { ejected[i] = false; + locked[i] = false; } } void usb_msc_mount(void) { - // Reset the ejection tracking every time we're plugged into USB. This allows for us to battery - // power the device, eject, unplug and plug it back in to get the drive. - if (_usb_msc_lock) { - _usb_connected_while_locked = true; - return; - } _usb_msc_uneject(); - _usb_connected_while_locked = false; } void usb_msc_umount(void) { + for (uint8_t i = 0; i < sizeof(ejected); i++) { + fs_user_mount_t *vfs = get_vfs(i + 1); + if (vfs == NULL) { + continue; + } + blockdev_unlock(vfs); + locked[i] = false; + } } bool usb_msc_ejected(void) { @@ -75,42 +90,6 @@ bool usb_msc_ejected(void) { return all_ejected; } -bool usb_msc_lock(void) { - if ((storage_usb_enabled() && !usb_msc_ejected()) || _usb_msc_lock) { - return false; - } - _usb_msc_lock = true; - return true; -} - -void usb_msc_unlock(void) { - if (!_usb_msc_lock) { - // Mismatched unlock. - return; - } - if (_usb_connected_while_locked) { - _usb_msc_uneject(); - } - _usb_msc_lock = false; -} - -// The root FS is always at the end of the list. -static fs_user_mount_t *get_vfs(int lun) { - // TODO(tannewt): Return the mount which matches the lun where 0 is the end - // and is counted in reverse. - if (lun > 0) { - return NULL; - } - mp_vfs_mount_t *current_mount = MP_STATE_VM(vfs_mount_table); - if (current_mount == NULL) { - return NULL; - } - while (current_mount->next != NULL) { - current_mount = current_mount->next; - } - return current_mount->obj; -} - // Callback invoked when received an SCSI command not in built-in list below // - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, TEST_UNIT_READY, START_STOP_UNIT, MODE_SENSE6, REQUEST_SENSE // - READ10 and WRITE10 have their own callbacks @@ -164,6 +143,11 @@ bool tud_msc_is_writable_cb(uint8_t lun) { if (vfs->blockdev.writeblocks[0] == MP_OBJ_NULL || !filesystem_is_writable_by_usb(vfs)) { return false; } + // Lock the blockdev once we say we're writable. + if (!locked[lun] && !blockdev_lock(vfs)) { + return false; + } + locked[lun] = true; return true; } @@ -267,7 +251,9 @@ bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, boo if (disk_ioctl(current_mount, CTRL_SYNC, NULL) != RES_OK) { return false; } else { + blockdev_unlock(current_mount); ejected[lun] = true; + locked[lun] = false; } } else { // We can only load if it hasn't been ejected. diff --git a/supervisor/shared/web_workflow/static/directory.html b/supervisor/shared/web_workflow/static/directory.html index a8ffa24dfe64e..a6634439b061a 100644 --- a/supervisor/shared/web_workflow/static/directory.html +++ b/supervisor/shared/web_workflow/static/directory.html @@ -20,5 +20,6 @@

 


+📁  + diff --git a/supervisor/shared/web_workflow/static/directory.js b/supervisor/shared/web_workflow/static/directory.js index 2068ca38ea72c..0161f09160932 100644 --- a/supervisor/shared/web_workflow/static/directory.js +++ b/supervisor/shared/web_workflow/static/directory.js @@ -4,7 +4,28 @@ let dirs = document.getElementById("dirs"); var url_base = window.location; var current_path; -var editable = undefined; + +// From: https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string +function humanFileSize(bytes) { + const thresh = 1000; + + if (Math.abs(bytes) < thresh) { + return bytes + ' B'; + } + + const units = ['kB', 'MB', 'GB', 'TB']; + let u = -1; + const r = 10; + + do { + bytes /= thresh; + ++u; + } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1); + + + return bytes.toFixed(1) + ' ' + units[u]; +} + function compareValues(a, b) { if (a.directory == b.directory && a.name.toLowerCase() === b.name.toLowerCase()) { @@ -42,21 +63,20 @@ async function refresh_list() { var path = document.querySelector("#path"); path.textContent = current_path; var template = document.querySelector('#row'); + var disk_usage = document.querySelector('#usage'); - if (editable === undefined) { - const status = await fetch(new URL("/fs/", url_base), - { - method: "OPTIONS", - credentials: "include" - } - ); - editable = status.headers.get("Access-Control-Allow-Methods").includes("DELETE"); - new_directory_name.disabled = !editable; - set_upload_enabled(editable); - if (!editable) { - let usbwarning = document.querySelector("#usbwarning"); - usbwarning.style.display = "block"; - } + let used = humanFileSize((data.total - data.free) * data.block_size); + let total = humanFileSize(data.total * data.block_size); + disk_usage.textContent = `${used} out of ${total}`; + + let editable = data.writable; + new_directory_name.disabled = !editable; + set_upload_enabled(editable); + let usbwarning = document.querySelector("#usbwarning"); + if (!editable) { + usbwarning.style.display = "block"; + } else { + usbwarning.style.display = "none"; } if (current_path != "/") { @@ -74,9 +94,9 @@ async function refresh_list() { new_children.push(clone); } - data.sort(compareValues); + data.files.sort(compareValues); - for (const f of data) { + for (const f of data.files) { // Clone the new row and insert it into the table var clone = template.content.cloneNode(true); var td = clone.querySelectorAll("td"); @@ -106,7 +126,7 @@ async function refresh_list() { text_file = true; } td[0].textContent = icon; - td[1].textContent = f.file_size; + td[1].textContent = humanFileSize(f.file_size); var path = clone.querySelector("a.path"); path.href = file_path; path.textContent = f.name; diff --git a/supervisor/shared/web_workflow/web_workflow.c b/supervisor/shared/web_workflow/web_workflow.c index 68eaeec069e30..cfd065b240697 100644 --- a/supervisor/shared/web_workflow/web_workflow.c +++ b/supervisor/shared/web_workflow/web_workflow.c @@ -525,17 +525,6 @@ static bool _origin_ok(_request *request) { return false; } -STATIC bool _usb_active(void) { - // Check to see if USB has already been mounted. If not, then we "eject" from USB until we're done. - #if CIRCUITPY_USB && CIRCUITPY_USB_MSC - if (storage_usb_enabled() && !usb_msc_lock()) { - return true; - } - #endif - return false; -} - - static const char *OK_JSON = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: application/json\r\n"; static void _cors_header(socketpool_socket_obj_t *socket, _request *request) { @@ -574,13 +563,7 @@ static void _reply_access_control(socketpool_socket_obj_t *socket, _request *req "Content-Length: 0\r\n", "Access-Control-Expose-Headers: Access-Control-Allow-Methods\r\n", "Access-Control-Allow-Headers: X-Timestamp, X-Destination, Content-Type, Authorization\r\n", - "Access-Control-Allow-Methods:GET, OPTIONS", NULL); - if (!_usb_active()) { - _send_str(socket, ", PUT, DELETE, MOVE"); - #if CIRCUITPY_USB_MSC - usb_msc_unlock(); - #endif - } + "Access-Control-Allow-Methods:GET, OPTIONS, PUT, DELETE, MOVE", NULL); _send_str(socket, "\r\n"); _cors_header(socket, request); _send_final_str(socket, "\r\n"); @@ -687,17 +670,47 @@ static void _reply_redirect(socketpool_socket_obj_t *socket, _request *request, } #endif -static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *request, FF_DIR *dir, const char *request_path, const char *path) { +static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *request, fs_user_mount_t *fs_mount, FF_DIR *dir, const char *request_path, const char *path) { + FILINFO file_info; + char *fn = file_info.fname; + FRESULT res = f_readdir(dir, &file_info); + if (res != FR_OK) { + _reply_missing(socket, request); + return; + } + socketpool_socket_send(socket, (const uint8_t *)OK_JSON, strlen(OK_JSON)); _cors_header(socket, request); _send_str(socket, "\r\n"); mp_print_t _socket_print = {socket, _print_chunk}; - _send_chunk(socket, "["); + + // Send mount info. + DWORD free_clusters = 0; + FATFS *fatfs = &fs_mount->fatfs; + f_getfree(fatfs, &free_clusters); + size_t ssize; + #if FF_MAX_SS != FF_MIN_SS + ssize = fatfs->ssize; + #else + ssize = FF_MIN_SS; + #endif + uint32_t cluster_size = fatfs->csize * ssize; + uint32_t total_clusters = fatfs->n_fatent - 2; + + const char *writable = "false"; + if (filesystem_is_writable_by_python(fs_mount)) { + writable = "true"; + } + mp_printf(&_socket_print, + "{\"free\": %u, " + "\"total\": %u, " + "\"block_size\": %u, " + "\"writable\": %s, ", free_clusters, total_clusters, cluster_size, writable); + + // Send file list + _send_chunk(socket, "\"files\": ["); bool first = true; - FILINFO file_info; - char *fn = file_info.fname; - FRESULT res = f_readdir(dir, &file_info); while (res == FR_OK && fn[0] != 0) { if (!first) { _send_chunk(socket, ","); @@ -733,7 +746,7 @@ static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *req first = false; res = f_readdir(dir, &file_info); } - _send_chunk(socket, "]"); + _send_chunk(socket, "]}"); _send_chunk(socket, ""); } @@ -851,7 +864,7 @@ static void _reply_with_version_json(socketpool_socket_obj_t *socket, _request * _update_encoded_ip(); // Note: this leverages the fact that C concats consecutive string literals together. mp_printf(&_socket_print, - "{\"web_api_version\": 3, " + "{\"web_api_version\": 4, " "\"version\": \"" MICROPY_GIT_TAG "\", " "\"build_date\": \"" MICROPY_BUILD_DATE "\", " "\"board_name\": \"%s\", " @@ -880,27 +893,46 @@ static void _reply_with_diskinfo_json(socketpool_socket_obj_t *socket, _request _cors_header(socket, request); _send_str(socket, "\r\n"); mp_print_t _socket_print = {socket, _print_chunk}; + _send_chunk(socket, "["); - DWORD free_clusters; - FATFS *fs = filesystem_circuitpy(); - FRESULT blk_result = f_getfree(fs, &free_clusters); - uint16_t block_size; - if (blk_result == FR_OK) { - disk_ioctl(fs, GET_SECTOR_SIZE, &block_size); - } - - uint16_t total_size = fs->n_fatent - 2; + mp_vfs_mount_t *vfs = MP_STATE_VM(vfs_mount_table); + size_t i = 0; + while (vfs != NULL) { + if (i > 0) { + _send_chunk(socket, ","); + } + fs_user_mount_t *fs = MP_OBJ_TO_PTR(vfs->obj); + // Skip non-fat and non-native block file systems. + if (fs->base.type != &mp_fat_vfs_type || (fs->blockdev.flags & MP_BLOCKDEV_FLAG_NATIVE) == 0) { + vfs = vfs->next; + continue; + } + DWORD free_clusters = 0; + FATFS *fatfs = &fs->fatfs; + f_getfree(fatfs, &free_clusters); + size_t ssize; + #if FF_MAX_SS != FF_MIN_SS + ssize = fatfs->ssize; + #else + ssize = FF_MIN_SS; + #endif + size_t block_size = fatfs->csize * ssize; + size_t total_size = fatfs->n_fatent - 2; - const char *writable = "false"; - if (!_usb_active()) { - writable = "true"; + const char *writable = "false"; + if (filesystem_is_writable_by_python(fs)) { + writable = "true"; + } + mp_printf(&_socket_print, + "{\"root\": \"%s\", " + "\"free\": %u, " + "\"total\": %u, " + "\"block_size\": %u, " + "\"writable\": %s}", vfs->str, free_clusters, total_size, block_size, writable); + i++; + vfs = vfs->next; } - mp_printf(&_socket_print, - "[{\"root\": \"/\", " - "\"free\": %d, " - "\"block_size\": %d, " - "\"writable\": %s, " - "\"total\": %d}]", free_clusters * block_size, block_size, writable, total_size * block_size); + _send_chunk(socket, "]"); // Empty chunk signals the end of the response. _send_chunk(socket, ""); @@ -937,10 +969,10 @@ STATIC void _discard_incoming(socketpool_socket_obj_t *socket, size_t amount) { } } -static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *request, FATFS *fs, const TCHAR *path) { +static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *request, fs_user_mount_t *fs_mount, const TCHAR *path) { FIL active_file; - if (_usb_active()) { + if (!filesystem_lock(fs_mount)) { _discard_incoming(socket, request->content_length); _reply_conflict(socket, request); return; @@ -951,6 +983,7 @@ static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *req override_fattime(fattime); } + FATFS* fs = &fs_mount->fatfs; FRESULT result = f_open(fs, &active_file, path, FA_WRITE); bool new_file = false; size_t old_length = 0; @@ -963,18 +996,14 @@ static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *req if (result == FR_NO_PATH) { override_fattime(0); - #if CIRCUITPY_USB_MSC - usb_msc_unlock(); - #endif + filesystem_unlock(fs_mount); _discard_incoming(socket, request->content_length); _reply_missing(socket, request); return; } if (result != FR_OK) { override_fattime(0); - #if CIRCUITPY_USB_MSC - usb_msc_unlock(); - #endif + filesystem_unlock(fs_mount); _discard_incoming(socket, request->content_length); _reply_server_error(socket, request); return; @@ -994,9 +1023,7 @@ static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *req f_unlink(fs, path); } override_fattime(0); - #if CIRCUITPY_USB_MSC - usb_msc_unlock(); - #endif + filesystem_unlock(fs_mount); // Too large. if (request->expect) { _reply_expectation_failed(socket, request); @@ -1034,9 +1061,7 @@ static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *req } f_close(&active_file); - #if CIRCUITPY_USB_MSC - usb_msc_unlock(); - #endif + filesystem_unlock(fs_mount); override_fattime(0); if (error) { @@ -1164,8 +1189,28 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { _decode_percents(request->path); char *path = request->path + 3; + const char *path_out = NULL; size_t pathlen = strlen(path); - FATFS *fs = filesystem_circuitpy(); + + mp_vfs_mount_t *vfs = mp_vfs_lookup_path(path, &path_out); + if (vfs == MP_VFS_NONE) { + _reply_missing(socket, request); + return false; + } + fs_user_mount_t *fs_mount; + if (vfs == MP_VFS_ROOT) { + fs_mount = filesystem_circuitpy(); + } else { + fs_mount = MP_OBJ_TO_PTR(vfs->obj); + // Skip non-fat and non-native block file systems. + if (!filesystem_native_fatfs(fs_mount)) { + _reply_missing(socket, request); + return false; + } + path += strlen(vfs->str); + pathlen = strlen(path); + } + FATFS *fs = &fs_mount->fatfs; // Trailing / is a directory. bool directory = false; if (path[pathlen - 1] == '/') { @@ -1178,25 +1223,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { // Delete is almost identical for files and directories so share the // implementation. if (strcasecmp(request->method, "DELETE") == 0) { - if (_usb_active()) { - _reply_conflict(socket, request); - return false; - } - - FILINFO file; - FRESULT result = f_stat(fs, path, &file); - if (result == FR_OK) { - if ((file.fattrib & AM_DIR) != 0) { - result = supervisor_workflow_delete_directory_contents(fs, path); - } - if (result == FR_OK) { - result = f_unlink(fs, path); - } - } - - #if CIRCUITPY_USB_MSC - usb_msc_unlock(); - #endif + FRESULT result = supervisor_workflow_delete_recursive(path); if (result == FR_NO_PATH || result == FR_NO_FILE) { _reply_missing(socket, request); } else if (result != FR_OK) { @@ -1206,11 +1233,6 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { return true; } } else if (strcasecmp(request->method, "MOVE") == 0) { - if (_usb_active()) { - _reply_conflict(socket, request); - return false; - } - _decode_percents(request->destination); char *destination = request->destination + 3; size_t destinationlen = strlen(destination); @@ -1218,11 +1240,10 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { destination[destinationlen - 1] = '\0'; } - FRESULT result = f_rename(fs, path, destination); - #if CIRCUITPY_USB_MSC - usb_msc_unlock(); - #endif - if (result == FR_EXIST) { // File exists and won't be overwritten. + FRESULT result = supervisor_workflow_move(path, destination); + if (result == FR_WRITE_PROTECTED) { + _reply_conflict(socket, request); + } else if (result == FR_EXIST) { // File exists and won't be overwritten. _reply_precondition_failed(socket, request); } else if (result == FR_NO_PATH || result == FR_NO_FILE) { // Missing higher directories or target file. _reply_missing(socket, request); @@ -1245,7 +1266,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { return false; } if (request->json) { - _reply_directory_json(socket, request, &dir, request->path, path); + _reply_directory_json(socket, request, fs_mount, &dir, request->path, path); } else if (pathlen == 1) { _REPLY_STATIC(socket, request, directory_html); } else { @@ -1254,22 +1275,14 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { f_closedir(&dir); } else if (strcasecmp(request->method, "PUT") == 0) { - if (_usb_active()) { - _reply_conflict(socket, request); - return false; - } - + DWORD fattime = 0; if (request->timestamp_ms > 0) { - DWORD fattime; truncate_time(request->timestamp_ms * 1000000, &fattime); - override_fattime(fattime); } - FRESULT result = supervisor_workflow_mkdir_parents(fs, path); - override_fattime(0); - #if CIRCUITPY_USB_MSC - usb_msc_unlock(); - #endif - if (result == FR_EXIST) { + FRESULT result = supervisor_workflow_mkdir_parents(fattime, path); + if (result == FR_WRITE_PROTECTED) { + _reply_conflict(socket, request); + } else if (result == FR_EXIST) { _reply_no_content(socket, request); } else if (result == FR_NO_PATH) { _reply_missing(socket, request); @@ -1293,7 +1306,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) { f_close(&active_file); } else if (strcasecmp(request->method, "PUT") == 0) { - _write_file_and_reply(socket, request, fs, path); + _write_file_and_reply(socket, request, fs_mount, path); return true; } } diff --git a/supervisor/shared/workflow.c b/supervisor/shared/workflow.c index 10af20f0cf6dc..ad51e80b904d0 100644 --- a/supervisor/shared/workflow.c +++ b/supervisor/shared/workflow.c @@ -29,6 +29,8 @@ #include "py/mpstate.h" #include "py/stackctrl.h" #include "supervisor/background_callback.h" +#include "supervisor/fatfs.h" +#include "supervisor/filesystem.h" #include "supervisor/workflow.h" #include "supervisor/serial.h" #include "supervisor/shared/workflow.h" @@ -118,24 +120,65 @@ void supervisor_workflow_start(void) { #endif } -FRESULT supervisor_workflow_mkdir_parents(FATFS *fs, char *path) { +FRESULT supervisor_workflow_move(const char *old_path, const char *new_path) { + const char *old_mount_path; + const char *new_mount_path; + fs_user_mount_t* active_mount = filesystem_for_path(old_path, &old_mount_path); + fs_user_mount_t* new_mount = filesystem_for_path(new_path, &new_mount_path); + if (active_mount == NULL || new_mount == NULL || active_mount != new_mount || !filesystem_native_fatfs(active_mount)) { + return FR_NO_PATH; + } + if (!filesystem_lock(active_mount)) { + return FR_WRITE_PROTECTED; + } + FATFS *fs = &active_mount->fatfs; + + FRESULT result = f_rename(fs, old_path, new_path); + filesystem_unlock(active_mount); + return result; +} + +FRESULT supervisor_workflow_mkdir(DWORD fattime, const char* full_path) { + const char *mount_path; + fs_user_mount_t* active_mount = filesystem_for_path(full_path, &mount_path); + if (active_mount == NULL || !filesystem_native_fatfs(active_mount)) { + return FR_NO_PATH; + } + if (!filesystem_lock(active_mount)) { + return FR_WRITE_PROTECTED; + } + FATFS *fs = &active_mount->fatfs; + + override_fattime(fattime); + FRESULT result = f_mkdir(fs, mount_path); + override_fattime(0); + filesystem_unlock(active_mount); + return result; +} + +FRESULT supervisor_workflow_mkdir_parents(DWORD fattime, char *path) { + override_fattime(fattime); FRESULT result = FR_OK; // Make parent directories. for (size_t j = 1; j < strlen(path); j++) { if (path[j] == '/') { path[j] = '\0'; - result = f_mkdir(fs, path); + result = supervisor_workflow_mkdir(fattime, path); path[j] = '/'; if (result != FR_OK && result != FR_EXIST) { - return result; + break; } } } // Make the target directory. - return f_mkdir(fs, path); + if (result != FR_OK && result != FR_EXIST) { + result = supervisor_workflow_mkdir(fattime, path); + } + override_fattime(0); + return result; } -FRESULT supervisor_workflow_delete_directory_contents(FATFS *fs, const TCHAR *path) { +STATIC FRESULT supervisor_workflow_delete_directory_contents(FATFS *fs, const TCHAR *path) { FF_DIR dir; FILINFO file_info; // Check the stack since we're putting paths on it. @@ -174,3 +217,27 @@ FRESULT supervisor_workflow_delete_directory_contents(FATFS *fs, const TCHAR *pa f_closedir(&dir); return res; } + +FRESULT supervisor_workflow_delete_recursive(const char* full_path) { + const char *mount_path; + fs_user_mount_t* active_mount = filesystem_for_path(full_path, &mount_path); + if (active_mount == NULL || !filesystem_native_fatfs(active_mount)) { + return FR_NO_PATH; + } + if (!filesystem_lock(active_mount)) { + return FR_WRITE_PROTECTED; + } + FATFS *fs = &active_mount->fatfs; + FILINFO file; + FRESULT result = f_stat(fs, full_path, &file); + if (result == FR_OK) { + if ((file.fattrib & AM_DIR) != 0) { + result = supervisor_workflow_delete_directory_contents(fs, full_path); + } + if (result == FR_OK) { + result = f_unlink(fs, full_path); + } + } + filesystem_unlock(active_mount); + return result; +} diff --git a/supervisor/shared/workflow.h b/supervisor/shared/workflow.h index df483e0ebc59e..885e84c71ce9e 100644 --- a/supervisor/shared/workflow.h +++ b/supervisor/shared/workflow.h @@ -31,5 +31,7 @@ extern bool supervisor_workflow_connecting(void); // File system helpers for workflow code. -FRESULT supervisor_workflow_mkdir_parents(FATFS *fs, char *path); -FRESULT supervisor_workflow_delete_directory_contents(FATFS *fs, const TCHAR *path); +FRESULT supervisor_workflow_move(const char *old_path, const char *new_path); +FRESULT supervisor_workflow_mkdir(DWORD fattime, const char* full_path); +FRESULT supervisor_workflow_mkdir_parents(DWORD fattime, char *path); +FRESULT supervisor_workflow_delete_recursive(const char* full_path); diff --git a/supervisor/usb.h b/supervisor/usb.h index 4a1eda3917bef..2e73412a9c49d 100644 --- a/supervisor/usb.h +++ b/supervisor/usb.h @@ -24,8 +24,7 @@ * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_SUPERVISOR_USB_H -#define MICROPY_INCLUDED_SUPERVISOR_USB_H +#pragma once #include #include @@ -85,11 +84,6 @@ void usb_setup_with_vm(void); void usb_msc_mount(void); void usb_msc_umount(void); bool usb_msc_ejected(void); - -// Locking MSC prevents presenting the drive on plug-in when in use by something -// else (likely BLE.) -bool usb_msc_lock(void); -void usb_msc_unlock(void); #endif #if CIRCUITPY_USB_KEYBOARD_WORKFLOW @@ -102,5 +96,3 @@ void usb_keyboard_detach(uint8_t dev_addr, uint8_t interface); void usb_keyboard_attach(uint8_t dev_addr, uint8_t interface); void usb_keymap_set(const uint8_t *buf, size_t len); #endif - -#endif // MICROPY_INCLUDED_SUPERVISOR_USB_H