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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/environment.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Environment Variables
CircuitPython provides support for environment variables. These values
can be examined by user code, and are also used as settings by CircuitPython during startup.

CircuitPython looks for a file called ``settings.toml`` at the ``CIRCUITPY`` drive root
CircuitPython looks for a file called ``settings.toml`` at the **CIRCUITPY** drive root
to find the values of environment variables,
The file format is a subset of the `TOML config file language <https://toml.io>`__.

Expand Down
14 changes: 7 additions & 7 deletions docs/troubleshooting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,30 @@ variety of errors that can happen, what they mean and how to fix them.
File system issues
------------------

If your host computer starts complaining that your ``CIRCUITPY`` drive is corrupted
If your host computer starts complaining that your **CIRCUITPY** drive is corrupted
or files cannot be overwritten or deleted, then you will have to erase it completely.
When CircuitPython restarts it will create a fresh empty ``CIRCUITPY`` filesystem.
When CircuitPython restarts it will create a fresh empty **CIRCUITPY** filesystem.

Corruption often happens on Windows when the ``CIRCUITPY`` disk is not safely ejected
Corruption often happens on Windows when the **CIRCUITPY** disk is not safely ejected
before being reset by the button or being disconnected from USB. This can also
happen on Linux and Mac OSX but it's less likely.

.. caution:: To erase and re-create ``CIRCUITPY`` (for example, to correct a corrupted filesystem),
.. caution:: To erase and re-create **CIRCUITPY** (for example, to correct a corrupted filesystem),
follow one of the procedures below. It's important to note that **any files stored on the**
``CIRCUITPY`` **drive will be erased. Back up your code if possible before continuing!**
**CIRCUITPY** **drive will be erased. Back up your code if possible before continuing!**

REPL Erase Method
^^^^^^^^^^^^^^^^^
This is the recommended method of erasing your board. If you are having trouble accessing the
``CIRCUITPY`` drive or the REPL, consider first putting your board into
**CIRCUITPY** drive or the REPL, consider first putting your board into
`safe mode <https://learn.adafruit.com/welcome-to-circuitpython/troubleshooting#safe-mode-3105351-22>`_.

**To erase any board if you have access to the REPL:**

#. Connect to the CircuitPython REPL using a terminal program.
#. Type ``import storage`` into the REPL.
#. Then, type ``storage.erase_filesystem()`` into the REPL.
#. The ``CIRCUITPY`` drive will be erased and the board will restart with an empty ``CIRCUITPY`` drive.
#. The **CIRCUITPY** drive will be erased and the board will restart with an empty **CIRCUITPY** drive.

Erase File Method
^^^^^^^^^^^^^^^^^
Expand Down
2 changes: 1 addition & 1 deletion ports/espressif/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Connecting to the ESP32-C3

**USB Connection:**

On ESP32-C3 REV3 chips, a USB Serial/JTAG Controller is available. Note: This USB connection cannot be used for a ``CIRCUITPY`` drive.
On ESP32-C3 REV3 chips, a USB Serial/JTAG Controller is available. Note: This USB connection cannot be used for a **CIRCUITPY** drive.

Depending on the board you have, the USB port may or may not be connected to native USB.

Expand Down
2 changes: 1 addition & 1 deletion shared-bindings/dualbank/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
//| This module is unavailable as the flash is only large enough for one app partition.
//|
//| Boards with flash ``>2MB``:
//| This module is enabled/disabled at runtime based on whether the ``CIRCUITPY`` drive
//| This module is enabled/disabled at runtime based on whether the **CIRCUITPY** drive
//| is extended or not. See `storage.erase_filesystem()` for more information.
//|
//| .. code-block:: python
Expand Down
116 changes: 93 additions & 23 deletions shared-bindings/storage/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(storage_umount_obj, storage_umount);
//| ) -> None:
//| """Remounts the given path with new parameters.
//|
//| This can always be done from boot.py. After boot, it can only be done when the host computer
//| This can always be done from ``boot.py``. After boot, it can only be done when the host computer
//| doesn't have write access and CircuitPython isn't currently writing to the filesystem. An
//| exception will be raised if this is the case. Some host OSes allow you to eject a drive which
//| will allow for remounting.
Expand Down Expand Up @@ -140,24 +140,24 @@ static mp_obj_t storage_getmount(const mp_obj_t mnt_in) {
MP_DEFINE_CONST_FUN_OBJ_1(storage_getmount_obj, storage_getmount);

//| def erase_filesystem(extended: Optional[bool] = None) -> None:
//| """Erase and re-create the ``CIRCUITPY`` filesystem.
//| """Erase and re-create the **CIRCUITPY** filesystem.
//|
//| On boards that present USB-visible ``CIRCUITPY`` drive (e.g., SAMD21 and SAMD51),
//| On boards that present USB-visible **CIRCUITPY** drive (e.g., SAMD21 and SAMD51),
//| then call `microcontroller.reset()` to restart CircuitPython and have the
//| host computer remount CIRCUITPY.
//| host computer remount **CIRCUITPY**.
//|
//| This function can be called from the REPL when ``CIRCUITPY``
//| This function can be called from the REPL when **CIRCUITPY**
//| has become corrupted.
//|
//| :param bool extended: On boards that support ``dualbank`` module
//| and the ``extended`` parameter, the ``CIRCUITPY`` storage can be
//| and the ``extended`` parameter, the **CIRCUITPY** storage can be
//| extended by setting this to `True`. If this isn't provided or
//| set to `None` (default), the existing configuration will be used.
//|
//| .. note:: New firmware starts with storage extended. In case of an existing
//| filesystem (e.g. uf2 load), the existing extension setting is preserved.
//|
//| .. warning:: All the data on ``CIRCUITPY`` will be lost, and
//| .. warning:: All the data on **CIRCUITPY** will be lost, and
//| CircuitPython will restart on certain boards."""
//| ...
//|
Expand Down Expand Up @@ -187,9 +187,11 @@ static mp_obj_t storage_erase_filesystem(size_t n_args, const mp_obj_t *pos_args
MP_DEFINE_CONST_FUN_OBJ_KW(storage_erase_filesystem_obj, 0, storage_erase_filesystem);

//| def disable_usb_drive() -> None:
//| """Disable presenting ``CIRCUITPY`` as a USB mass storage device.
//| By default, the device is enabled and ``CIRCUITPY`` is visible.
//| Can be called in ``boot.py``, before USB is connected."""
//| """Disable presenting **CIRCUITPY** as a USB mass storage device.
//| By default, the device is enabled and **CIRCUITPY** is visible, if USB is available.
//| Must called in ``boot.py``, before USB is connected.
// If you want to disable the USB drive after `boot.py` has run, see `unsafe_disable_usb_drive()`.
//| """
//| ...
//|
//|
Expand All @@ -205,15 +207,82 @@ static mp_obj_t storage_disable_usb_drive(void) {
}
MP_DEFINE_CONST_FUN_OBJ_0(storage_disable_usb_drive_obj, storage_disable_usb_drive);

//| def unsafe_disable_usb_drive() -> None:
//| """Disable presenting **CIRCUITPY** as a USB mass storage device even if in use.
//| By default, the device is enabled and **CIRCUITPY** is visible.
//| After the call, **CIRCUITPY** will be read/write to your code and from the REPL but not appear over USB.
//|
//| Unlike `disable_usb_drive()`, `unsafe_disable_usb_drive()` can be called
//| after ``code.py`` starts or from the REPL, after USB has started.
//|
//| .. warning:: If ``unsafe_disable_usb_drive()`` is called when the host is in the middle of writing **CIRCUITPY**,
//| filesystem corruption can occur.
//| It is similar to the sudden physical removal of a USB drive.
//| Before calling ``unsafe_disable_usb_drive()``,
//| make sure the host has finished any writes to **CIRCUITPY**.
//|
//| * On Windows, do one of these:
//|
//| * Eject ("Safely Remove") the **CIRCUITPY** drive.
//| * Use a "sync" program, such as `Sysinternals Sync <https://learn.microsoft.com/en-us/sysinternals/downloads/sync>`__.
//| * Programmatically call ``_commit()`` or ``_flushall()`` or similar.
//|
//| * On Linux or macOS, do one of these:
//|
//| * Eject (unmount) the **CIRCUITPY** drive.
//| * Type ``sync`` in a terminal.
//| * Programmatically call ``sync()`` or ``fsync()``.
//|
//| * If none of the above are possible or convenient, wait several seconds to allow any writes to complete.
//| This can be unreliable, as the interval to wait depends on the host operating system
//| and how the drive is mounted.
//| In some operating systems, you can specify that the drive be mounted as "sync on write",
//| so that all writes happen immediately.
//|
//| When `unsafe_disable_usb_drive()` is called after USB has started,
//| the **CIRCUITPY** USB drive logical unit (LUN) will report as "not ready",
//| causing the host to unmount it.
//| The drive can be made ready and available again by calling `enable_usb_drive()`.
//| When `disable_usb_drive` is called after ``code.py`` starts or in the REPL,
//| the call will delay 2.5 seconds before returning,
//| so that host has time to detect that the drive is not ready.
//| The host polls the device approximately every one or two seconds.
//|
//| When the USB drive is disabled, **CIRCUITPY** becomes read/write, and can be written
//| from user code or the REPL. This is easier than arranging for a `remount()` in ``boot.py``.
//| Code editors and file uploaders can use this feature to write files via the REPL.
//|
//| If `unsafe_disable_usb_drive()` is called in ``boot.py``, it is identical to calling
//| `disable_usb_drive()`.
//| """
//| ...
//|
//|
static mp_obj_t storage_unsafe_disable_usb_drive(void) {
#if CIRCUITPY_USB_DEVICE && CIRCUITPY_USB_MSC
if (!common_hal_storage_unsafe_disable_usb_drive()) {
#else
if (true) {
#endif
mp_raise_RuntimeError(MP_ERROR_TEXT("Cannot change USB devices now"));
}
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_0(storage_unsafe_disable_usb_drive_obj, storage_unsafe_disable_usb_drive);

//| def enable_usb_drive() -> None:
//| """Enabled presenting ``CIRCUITPY`` as a USB mass storage device.
//| By default, the device is enabled and ``CIRCUITPY`` is visible,
//| so you do not normally need to call this function.
//| Can be called in ``boot.py``, before USB is connected.
//| """Enable presenting **CIRCUITPY** as a USB mass storage device.
//| By default, the device is enabled and **CIRCUITPY** is visible,
//| so you do not normally need to call this function in ``boot.py``.
//|
//| If you call `enable_usb_drive()` after ``code.py`` starts or in the REPL,
//| you can reverse the effect of a previous `unsafe_disable_usb_drive()`.
//| The **CIRCUITPY** drive will reappear to the host, and become read-only again
//| if it was previously read-only.
//|
//| If you enable too many devices at once, you will run out of USB endpoints.
//| If you enable too many USB devices at once, you will run out of USB endpoints.
//| The number of available endpoints varies by microcontroller.
//| CircuitPython will go into safe mode after running boot.py to inform you if
//| CircuitPython will go into safe mode after running ``boot.py`` to inform you if
//| not enough endpoints are available.
//| """
//| ...
Expand All @@ -234,13 +303,14 @@ MP_DEFINE_CONST_FUN_OBJ_0(storage_enable_usb_drive_obj, storage_enable_usb_drive
static const mp_rom_map_elem_t storage_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_storage) },

{ MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&storage_mount_obj) },
{ MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&storage_umount_obj) },
{ MP_ROM_QSTR(MP_QSTR_remount), MP_ROM_PTR(&storage_remount_obj) },
{ MP_ROM_QSTR(MP_QSTR_getmount), MP_ROM_PTR(&storage_getmount_obj) },
{ MP_ROM_QSTR(MP_QSTR_erase_filesystem), MP_ROM_PTR(&storage_erase_filesystem_obj) },
{ MP_ROM_QSTR(MP_QSTR_disable_usb_drive), MP_ROM_PTR(&storage_disable_usb_drive_obj) },
{ MP_ROM_QSTR(MP_QSTR_enable_usb_drive), MP_ROM_PTR(&storage_enable_usb_drive_obj) },
{ MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&storage_mount_obj) },
{ MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&storage_umount_obj) },
{ MP_ROM_QSTR(MP_QSTR_remount), MP_ROM_PTR(&storage_remount_obj) },
{ MP_ROM_QSTR(MP_QSTR_getmount), MP_ROM_PTR(&storage_getmount_obj) },
{ MP_ROM_QSTR(MP_QSTR_erase_filesystem), MP_ROM_PTR(&storage_erase_filesystem_obj) },
{ MP_ROM_QSTR(MP_QSTR_disable_usb_drive), MP_ROM_PTR(&storage_disable_usb_drive_obj) },
{ MP_ROM_QSTR(MP_QSTR_enable_usb_drive), MP_ROM_PTR(&storage_enable_usb_drive_obj) },
{ MP_ROM_QSTR(MP_QSTR_unsafe_disable_usb_drive), MP_ROM_PTR(&storage_unsafe_disable_usb_drive_obj) },

//| class VfsFat:
//| def __init__(self, block_device: BlockDevice) -> None:
Expand Down
1 change: 1 addition & 0 deletions shared-bindings/storage/__init__.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ mp_obj_t common_hal_storage_getmount(const char *path);
MP_NORETURN void common_hal_storage_erase_filesystem(bool extended);

bool common_hal_storage_disable_usb_drive(void);
bool common_hal_storage_unsafe_disable_usb_drive(void);
bool common_hal_storage_enable_usb_drive(void);
25 changes: 21 additions & 4 deletions shared-module/storage/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
#include "tusb.h"

// Is the MSC device enabled?
bool storage_usb_is_enabled;
static volatile bool storage_usb_is_enabled;

void storage_usb_set_defaults(void) {
storage_usb_is_enabled = CIRCUITPY_USB_MSC_ENABLED_DEFAULT;
Expand All @@ -38,16 +38,29 @@ bool storage_usb_enabled(void) {
}

static bool usb_drive_set_enabled(bool enabled) {
// We can't change the descriptors once we're connected.
// We can't change the descriptors once we're connected, but we can make the LUN be ready or not ready.
storage_usb_is_enabled = enabled;
if (tud_connected()) {
return false;
// The TEST UNIT READY callback in usb_msc_flash.c checks the value of storage_usb_is_enabled.
// If it's false, TEST UNIT READY will report "not ready"
// Linux and macOS send a TEST UNIT READY poll about every 1.1 seconds or faster.
// Windows polls every 2.1 seconds or so.
// So wait long enough for host to send a TEST UNIT READY and receive a reply.
mp_hal_delay_ms(2500);
}
filesystem_set_internal_writable_by_usb(enabled);
storage_usb_is_enabled = enabled;
return true;
}

bool common_hal_storage_disable_usb_drive(void) {
if (tud_connected()) {
// Complain if already connected. Use `storage.unsafe_disable_usb_drive()` in that case.
return false;
}
return usb_drive_set_enabled(false);
}

bool common_hal_storage_unsafe_disable_usb_drive(void) {
return usb_drive_set_enabled(false);
}

Expand All @@ -59,6 +72,10 @@ bool common_hal_storage_disable_usb_drive(void) {
return false;
}

bool common_hal_storage_unsafe_disable_usb_drive(void) {
return false;
}

bool common_hal_storage_enable_usb_drive(void) {
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion supervisor/shared/usb/usb_msc_flash.c
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ bool tud_msc_test_unit_ready_cb(uint8_t lun) {
return false;
}

if (ejected[lun] || eject_once[lun]
if (ejected[lun] || eject_once[lun] || (lun == 0 && !storage_usb_enabled())
#ifdef SDCARD_LUN
|| (lun == SDCARD_LUN && !sdcard_usb_enabled())
#endif
Expand Down
Loading