summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/library/machine.USBDevice.rst286
-rw-r--r--docs/library/machine.rst1
-rw-r--r--extmod/extmod.cmake1
-rw-r--r--extmod/extmod.mk1
-rw-r--r--extmod/machine_usb_device.c335
-rw-r--r--extmod/modmachine.c3
-rw-r--r--extmod/modmachine.h5
-rw-r--r--py/runtime.c4
-rw-r--r--shared/tinyusb/mp_usbd.c34
-rw-r--r--shared/tinyusb/mp_usbd.h101
-rw-r--r--shared/tinyusb/mp_usbd_descriptor.c112
-rw-r--r--shared/tinyusb/mp_usbd_internal.h34
-rw-r--r--shared/tinyusb/mp_usbd_runtime.c554
-rw-r--r--shared/tinyusb/tusb_config.h31
14 files changed, 1384 insertions, 118 deletions
diff --git a/docs/library/machine.USBDevice.rst b/docs/library/machine.USBDevice.rst
new file mode 100644
index 000000000..82897b280
--- /dev/null
+++ b/docs/library/machine.USBDevice.rst
@@ -0,0 +1,286 @@
+.. currentmodule:: machine
+.. _machine.USBDevice:
+
+class USBDevice -- USB Device driver
+====================================
+
+.. note:: ``machine.USBDevice`` is currently only supported on the rp2 and samd
+ ports.
+
+USBDevice provides a low-level Python API for implementing USB device functions using
+Python code. This low-level API assumes familiarity with the USB standard. It's
+not recommended to use this API directly, instead install the high-level usbd
+module from micropython-lib.
+
+.. warning:: This functionality is very new and the high-level usbd module is
+ not yet merged into micropython-lib. It can be found `here on
+ GitHub <https://github.com/micropython/micropython-lib/pull/558>`_.
+
+Terminology
+-----------
+
+- A "Runtime" USB device interface or driver is one which is defined using this
+ Python API after MicroPython initially starts up.
+
+- A "Built-in" USB device interface or driver is one that is compiled into the
+ MicroPython firmware, and is always available. Examples are USB-CDC (serial
+ port) which is usually enabled by default. Built-in USB-MSC (Mass Storage) is an
+ option on some ports.
+
+Lifecycle
+---------
+
+Managing a runtime USB interface can be tricky, especially if you are communicating
+with MicroPython over a built-in USB-CDC serial port that's part of the same USB
+device.
+
+- A MicroPython soft reset will always clear all runtime USB interfaces, which
+ results in the entire USB device disconnecting from the host. If MicroPython
+ is also providing a built-in USB-CDC serial port then this will re-appear
+ after the soft reset.
+
+ This means some functions (like ``mpremote run``) that target the USB-CDC
+ serial port will immediately fail if a runtime USB interface is active,
+ because the port goes away when ``mpremote`` triggers a soft reset. The
+ operation should succeed on the second try, as after the soft reset there is
+ no more runtime USB interface.
+
+- To configure a runtime USB device on every boot, it's recommended to place the
+ configuration code in the ``boot.py`` file on the :ref:`device VFS
+ <filesystem>`. On each reset this file is executed before the USB subsystem is
+ initialised (and before ``main.py``), so it allows the board to come up with the runtime
+ USB device immediately.
+
+- For development or debugging, it may be convenient to connect a hardware
+ serial REPL and disable the built-in USB-CDC serial port entirely. Not all ports
+ support this (currently only ``rp2``). The custom build should be configured
+ with ``#define MICROPY_HW_USB_CDC (0)`` and ``#define
+ MICROPY_HW_ENABLE_UART_REPL (1)``.
+
+Constructors
+------------
+
+.. class:: USBDevice()
+
+ Construct a USBDevice object.
+
+ .. note:: This object is a singleton, each call to this constructor
+ returns the same object reference.
+
+Methods
+-------
+
+.. method:: USBDevice.config(desc_dev, desc_cfg, desc_strs=None, open_itf_cb=None, reset_cb=None, control_xfer_cb=None, xfer_cb=None)
+
+ Configures the ``USBDevice`` singleton object with the USB runtime device
+ state and callback functions:
+
+ - ``desc_dev`` - A bytes-like object containing
+ the new USB device descriptor.
+
+ - ``desc_cfg`` - A bytes-like object containing the
+ new USB configuration descriptor.
+
+ - ``desc_strs`` - Optional object holding strings or bytes objects
+ containing USB string descriptor values. Can be a list, a dict, or any
+ object which supports subscript indexing with integer keys (USB string
+ descriptor index).
+
+ Strings are an optional USB feature, and this parameter can be unset
+ (default) if no strings are referenced in the device and configuration
+ descriptors, or if only built-in strings should be used.
+
+ Apart from index 0, all the string values should be plain ASCII. Index 0
+ is the special "languages" USB descriptor, represented as a bytes object
+ with a custom format defined in the USB standard. ``None`` can be
+ returned at index 0 in order to use a default "English" language
+ descriptor.
+
+ To fall back to providing a built-in string value for a given index, a
+ subscript lookup can return ``None``, raise ``KeyError``, or raise
+ ``IndexError``.
+
+ - ``open_itf_cb`` - This callback is called once for each interface
+ or Interface Association Descriptor in response to a Set
+ Configuration request from the USB Host (the final stage before
+ the USB device is available to the host).
+
+ The callback takes a single argument, which is a memoryview of the
+ interface or IAD descriptor that the host is accepting (including
+ all associated descriptors). It is a view into the same
+ ``desc_cfg`` object that was provided as a separate
+ argument to this function. The memoryview is only valid until the
+ callback function returns.
+
+ - ``reset_cb`` - This callback is called when the USB host performs
+ a bus reset. The callback takes no arguments. Any in-progress
+ transfers will never complete. The USB host will most likely
+ proceed to re-enumerate the USB device by calling the descriptor
+ callbacks and then ``open_itf_cb()``.
+
+ - ``control_xfer_cb`` - This callback is called one or more times
+ for each USB control transfer (device Endpoint 0). It takes two
+ arguments.
+
+ The first argument is the control transfer stage. It is one of:
+
+ - ``1`` for SETUP stage.
+ - ``2`` for DATA stage.
+ - ``3`` for ACK stage.
+
+ Second argument is a memoryview to read the USB control request
+ data for this stage. The memoryview is only valid until the
+ callback function returns.
+
+ The callback should return one of the following values:
+
+ - ``False`` to stall the endpoint and reject the transfer.
+ - ``True`` to continue the transfer to the next stage.
+ - A buffer object to provide data for this stage of the transfer.
+ This should be a writable buffer for an ``OUT`` direction transfer, or a
+ readable buffer with data for an ``IN`` direction transfer.
+
+ - ``xfer_cb`` - This callback is called whenever a non-control
+ transfer submitted by calling :func:`USBDevice.submit_xfer` completes.
+
+ The callback has three arguments:
+
+ 1. The Endpoint number for the completed transfer.
+ 2. Result value: ``True`` if the transfer succeeded, ``False``
+ otherwise.
+ 3. Number of bytes successfully transferred. In the case of a
+ "short" transfer, The result is ``True`` and ``xferred_bytes``
+ will be smaller than the length of the buffer submitted for the
+ transfer.
+
+ .. note:: If a bus reset occurs (see :func:`USBDevice.reset`),
+ ``xfer_cb`` is not called for any transfers that have not
+ already completed.
+
+.. method:: USBDevice.active(self, [value] /)
+
+ Returns the current active state of this runtime USB device as a
+ boolean. The runtime USB device is "active" when it is available to
+ interact with the host, it doesn't mean that a USB Host is actually
+ present.
+
+ If the optional ``value`` argument is set to a truthy value, then
+ the USB device will be activated.
+
+ If the optional ``value`` argument is set to a falsey value, then
+ the USB device is deactivated. While the USB device is deactivated,
+ it will not be detected by the USB Host.
+
+ To simulate a disconnect and a reconnect of the USB device, call
+ ``active(False)`` followed by ``active(True)``. This may be
+ necessary if the runtime device configuration has changed, so that
+ the host sees the new device.
+
+.. attribute:: USDBD.builtin_driver
+
+ This attribute holds the current built-in driver configuration, and must be
+ set to one of the ``USBDevice.BUILTIN_`` named constants defined on this object.
+
+ By default it holds the value :data:`USBDevice.BUILTIN_NONE`.
+
+ Runtime USB device must be inactive when setting this field. Call the
+ :func:`USBDevice.active` function to deactivate before setting if necessary
+ (and again to activate after setting).
+
+ If this value is set to any value other than :data:`USBDevice.BUILTIN_NONE` then
+ the following restrictions apply to the :func:`USBDevice.config` arguments:
+
+ - ``desc_cfg`` should begin with the built-in USB interface descriptor data
+ accessible via :data:`USBDevice.builtin_driver` attribute ``desc_cfg``.
+ Descriptors appended after the built-in configuration descriptors should use
+ interface, string and endpoint numbers starting from the max built-in values
+ defined in :data:`USBDevice.builtin_driver` attributes ``itf_max``, ``str_max`` and
+ ``ep_max``.
+
+ - The ``bNumInterfaces`` field in the built-in configuration
+ descriptor will also need to be updated if any new interfaces
+ are appended to the end of ``desc_cfg``.
+
+ - ``desc_strs`` should either be ``None`` or a list/dictionary where index
+ values less than ``USBDevice.builtin_driver.str_max`` are missing or have
+ value ``None``. This reserves those string indexes for the built-in
+ drivers. Placing a different string at any of these indexes overrides that
+ string in the built-in driver.
+
+.. method:: USBDevice.submit_xfer(self, ep, buffer /)
+
+ Submit a USB transfer on endpoint number ``ep``. ``buffer`` must be
+ an object implementing the buffer interface, with read access for
+ ``IN`` endpoints and write access for ``OUT`` endpoints.
+
+ .. note:: ``ep`` cannot be the control Endpoint number 0. Control
+ transfers are built up through successive executions of
+ ``control_xfer_cb``, see above.
+
+ Returns ``True`` if successful, ``False`` if the transfer could not
+ be queued (as USB device is not configured by host, or because
+ another transfer is queued on this endpoint.)
+
+ When the USB host completes the transfer, the ``xfer_cb`` callback
+ is called (see above).
+
+ Raises ``OSError`` with reason ``MP_EINVAL`` If the USB device is not
+ active.
+
+.. method:: USBDevice.stall(self, ep, [stall] /)
+
+ Calling this function gets or sets the STALL state of a device endpoint.
+
+ ``ep`` is the number of the endpoint.
+
+ If the optional ``stall`` parameter is set, this is a boolean flag
+ for the STALL state.
+
+ The return value is the current stall state of the endpoint (before
+ any change made by this function).
+
+ An endpoint that is set to STALL may remain stalled until this
+ function is called again, or STALL may be cleared automatically by
+ the USB host.
+
+ Raises ``OSError`` with reason ``MP_EINVAL`` If the USB device is not
+ active.
+
+Constants
+---------
+
+.. data:: USBDevice.BUILTIN_NONE
+.. data:: USBDevice.BUILTIN_DEFAULT
+.. data:: USBDevice.BUILTIN_CDC
+.. data:: USBDevice.BUILTIN_MSC
+.. data:: USBDevice.BUILTIN_CDC_MSC
+
+ These constant objects hold the built-in descriptor data which is
+ compiled into the MicroPython firmware. ``USBDevice.BUILTIN_NONE`` and
+ ``USBDevice.BUILTIN_DEFAULT`` are always present. Additional objects may be present
+ depending on the firmware build configuration and the actual built-in drivers.
+
+ .. note:: Currently at most one of ``USBDevice.BUILTIN_CDC``,
+ ``USBDevice.BUILTIN_MSC`` and ``USBDevice.BUILTIN_CDC_MSC`` is defined
+ and will be the same object as ``USBDevice.BUILTIN_DEFAULT``.
+ These constants are defined to allow run-time detection of
+ the built-in driver (if any). Support for selecting one of
+ multiple built-in driver configurations may be added in the
+ future.
+
+ These values are assigned to :data:`USBDevice.builtin_driver` to get/set the
+ built-in configuration.
+
+ Each object contains the following read-only fields:
+
+ - ``itf_max`` - One more than the highest bInterfaceNumber value used
+ in the built-in configuration descriptor.
+ - ``ep_max`` - One more than the highest bEndpointAddress value used
+ in the built-in configuration descriptor. Does not include any
+ ``IN`` flag bit (0x80).
+ - ``str_max`` - One more than the highest string descriptor index
+ value used by any built-in descriptor.
+ - ``desc_dev`` - ``bytes`` object containing the built-in USB device
+ descriptor.
+ - ``desc_cfg`` - ``bytes`` object containing the complete built-in USB
+ configuration descriptor.
diff --git a/docs/library/machine.rst b/docs/library/machine.rst
index 3f5cd6f13..532266d1d 100644
--- a/docs/library/machine.rst
+++ b/docs/library/machine.rst
@@ -265,3 +265,4 @@ Classes
machine.WDT.rst
machine.SD.rst
machine.SDCard.rst
+ machine.USBDevice.rst
diff --git a/extmod/extmod.cmake b/extmod/extmod.cmake
index 53c774013..957580d15 100644
--- a/extmod/extmod.cmake
+++ b/extmod/extmod.cmake
@@ -18,6 +18,7 @@ set(MICROPY_SOURCE_EXTMOD
${MICROPY_EXTMOD_DIR}/machine_signal.c
${MICROPY_EXTMOD_DIR}/machine_spi.c
${MICROPY_EXTMOD_DIR}/machine_uart.c
+ ${MICROPY_EXTMOD_DIR}/machine_usb_device.c
${MICROPY_EXTMOD_DIR}/machine_wdt.c
${MICROPY_EXTMOD_DIR}/modbluetooth.c
${MICROPY_EXTMOD_DIR}/modframebuf.c
diff --git a/extmod/extmod.mk b/extmod/extmod.mk
index f0428bcd0..4f5f7d53a 100644
--- a/extmod/extmod.mk
+++ b/extmod/extmod.mk
@@ -15,6 +15,7 @@ SRC_EXTMOD_C += \
extmod/machine_spi.c \
extmod/machine_timer.c \
extmod/machine_uart.c \
+ extmod/machine_usb_device.c \
extmod/machine_wdt.c \
extmod/modasyncio.c \
extmod/modbinascii.c \
diff --git a/extmod/machine_usb_device.c b/extmod/machine_usb_device.c
new file mode 100644
index 000000000..69c3f3848
--- /dev/null
+++ b/extmod/machine_usb_device.c
@@ -0,0 +1,335 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2024 Angus Gratton
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "py/mpconfig.h"
+
+#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+
+#include "mp_usbd.h"
+#include "py/mperrno.h"
+#include "py/objstr.h"
+
+// Implements the singleton runtime USB object
+//
+// Currently this implementation references TinyUSB directly.
+
+#ifndef NO_QSTR
+#include "device/usbd_pvt.h"
+#endif
+
+#define HAS_BUILTIN_DRIVERS (MICROPY_HW_USB_CDC || MICROPY_HW_USB_MSC)
+
+const mp_obj_type_t machine_usb_device_type;
+
+static mp_obj_t usb_device_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
+ (void)type;
+ (void)n_args;
+ (void)n_kw;
+ (void)args;
+
+ if (MP_STATE_VM(usbd) == MP_OBJ_NULL) {
+ mp_obj_usb_device_t *o = m_new0(mp_obj_usb_device_t, 1);
+ o->base.type = &machine_usb_device_type;
+ o->desc_dev = mp_const_none;
+ o->desc_cfg = mp_const_none;
+ o->desc_strs = mp_const_none;
+ o->open_itf_cb = mp_const_none;
+ o->reset_cb = mp_const_none;
+ o->control_xfer_cb = mp_const_none;
+ o->xfer_cb = mp_const_none;
+ for (int i = 0; i < CFG_TUD_ENDPPOINT_MAX; i++) {
+ o->xfer_data[i][0] = mp_const_none;
+ o->xfer_data[i][1] = mp_const_none;
+ }
+ o->builtin_driver = MP_OBJ_FROM_PTR(&mp_type_usb_device_builtin_none);
+ o->active = false; // Builtin USB may be active already, but runtime is inactive
+ o->trigger = false;
+ o->control_data = MP_OBJ_TO_PTR(mp_obj_new_memoryview('B', 0, NULL));
+ o->num_pend_excs = 0;
+ for (int i = 0; i < MP_USBD_MAX_PEND_EXCS; i++) {
+ o->pend_excs[i] = mp_const_none;
+ }
+
+ MP_STATE_VM(usbd) = MP_OBJ_FROM_PTR(o);
+ }
+
+ return MP_STATE_VM(usbd);
+}
+
+// Utility helper to raise an error if USB device is not active
+// (or if a change of active state is triggered but not processed.)
+static void usb_device_check_active(mp_obj_usb_device_t *usbd) {
+ if (!usbd->active || usbd->trigger) {
+ mp_raise_OSError(MP_EINVAL);
+ }
+}
+
+static mp_obj_t usb_device_submit_xfer(mp_obj_t self, mp_obj_t ep, mp_obj_t buffer) {
+ mp_obj_usb_device_t *usbd = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(self);
+ int ep_addr;
+ mp_buffer_info_t buf_info = { 0 };
+ bool result;
+
+ usb_device_check_active(usbd);
+
+ // Unmarshal arguments, raises TypeError if invalid
+ ep_addr = mp_obj_get_int(ep);
+ mp_get_buffer_raise(buffer, &buf_info, ep_addr & TUSB_DIR_IN_MASK ? MP_BUFFER_READ : MP_BUFFER_RW);
+
+ uint8_t ep_num = tu_edpt_number(ep_addr);
+ uint8_t ep_dir = tu_edpt_dir(ep_addr);
+
+ if (ep_num == 0 || ep_num >= CFG_TUD_ENDPPOINT_MAX) {
+ // TinyUSB usbd API doesn't range check arguments, so this check avoids
+ // out of bounds array access, or submitting transfers on the control endpoint.
+ //
+ // This C layer doesn't otherwise keep track of which endpoints the host
+ // is aware of (or not).
+ mp_raise_ValueError("ep");
+ }
+
+ if (!usbd_edpt_claim(USBD_RHPORT, ep_addr)) {
+ mp_raise_OSError(MP_EBUSY);
+ }
+
+ result = usbd_edpt_xfer(USBD_RHPORT, ep_addr, buf_info.buf, buf_info.len);
+
+ if (result) {
+ // Store the buffer object until the transfer completes
+ usbd->xfer_data[ep_num][ep_dir] = buffer;
+ }
+
+ return mp_obj_new_bool(result);
+}
+static MP_DEFINE_CONST_FUN_OBJ_3(usb_device_submit_xfer_obj, usb_device_submit_xfer);
+
+static mp_obj_t usb_device_active(size_t n_args, const mp_obj_t *args) {
+ mp_obj_usb_device_t *usbd = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(args[0]);
+
+ bool result = usbd->active;
+ if (n_args == 2) {
+ bool value = mp_obj_is_true(args[1]);
+
+ if (value != result) {
+ if (value
+ && !mp_usb_device_builtin_enabled(usbd)
+ && usbd->desc_dev == mp_const_none) {
+ // Only allow activating if config() has already been called to set some descriptors, or a
+ // built-in driver is enabled
+ mp_raise_OSError(MP_EINVAL);
+ }
+
+ // Any change to active state is triggered here and processed
+ // from the TinyUSB task.
+ usbd->active = value;
+ usbd->trigger = true;
+ if (value) {
+ mp_usbd_init(); // Ensure TinyUSB has initialised by this point
+ }
+ mp_usbd_schedule_task();
+ }
+ }
+
+ return mp_obj_new_bool(result);
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(usb_device_active_obj, 1, 2, usb_device_active);
+
+static mp_obj_t usb_device_stall(size_t n_args, const mp_obj_t *args) {
+ mp_obj_usb_device_t *self = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(args[0]);
+ int epnum = mp_obj_get_int(args[1]);
+
+ usb_device_check_active(self);
+
+ mp_obj_t res = mp_obj_new_bool(usbd_edpt_stalled(USBD_RHPORT, epnum));
+
+ if (n_args == 3) { // Set stall state
+ mp_obj_t stall = args[2];
+ if (mp_obj_is_true(stall)) {
+ usbd_edpt_stall(USBD_RHPORT, epnum);
+ } else {
+ usbd_edpt_clear_stall(USBD_RHPORT, epnum);
+ }
+ }
+
+ return res;
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(usb_device_stall_obj, 2, 3, usb_device_stall);
+
+// Configure the singleton USB device with all of the relevant transfer and descriptor
+// callbacks for dynamic devices.
+static mp_obj_t usb_device_config(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
+ mp_obj_usb_device_t *self = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(pos_args[0]);
+
+ enum { ARG_desc_dev, ARG_desc_cfg, ARG_desc_strs, ARG_open_itf_cb,
+ ARG_reset_cb, ARG_control_xfer_cb, ARG_xfer_cb, ARG_active };
+ static const mp_arg_t allowed_args[] = {
+ { MP_QSTR_desc_dev, MP_ARG_OBJ | MP_ARG_REQUIRED },
+ { MP_QSTR_desc_cfg, MP_ARG_OBJ | MP_ARG_REQUIRED },
+ { MP_QSTR_desc_strs, MP_ARG_OBJ | MP_ARG_REQUIRED },
+ { MP_QSTR_open_itf_cb, MP_ARG_OBJ, {.u_obj = mp_const_none} },
+ { MP_QSTR_reset_cb, MP_ARG_OBJ, {.u_obj = mp_const_none} },
+ { MP_QSTR_control_xfer_cb, MP_ARG_OBJ, {.u_obj = mp_const_none} },
+ { MP_QSTR_xfer_cb, MP_ARG_OBJ, {.u_obj = mp_const_none} },
+ };
+ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
+ mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
+
+ // Check descriptor arguments
+ mp_obj_t desc_dev = args[ARG_desc_dev].u_obj;
+ mp_obj_t desc_cfg = args[ARG_desc_cfg].u_obj;
+ mp_obj_t desc_strs = args[ARG_desc_strs].u_obj;
+ if (!MP_OBJ_TYPE_HAS_SLOT(mp_obj_get_type(desc_dev), buffer)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("desc_dev"));
+ }
+ if (!MP_OBJ_TYPE_HAS_SLOT(mp_obj_get_type(desc_cfg), buffer)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("desc_cfg"));
+ }
+ if (desc_strs != mp_const_none
+ && !MP_OBJ_TYPE_HAS_SLOT(mp_obj_get_type(desc_strs), subscr)) {
+ mp_raise_ValueError(MP_ERROR_TEXT("desc_strs"));
+ }
+
+ self->desc_dev = desc_dev;
+ self->desc_cfg = desc_cfg;
+ self->desc_strs = desc_strs;
+ self->open_itf_cb = args[ARG_open_itf_cb].u_obj;
+ self->reset_cb = args[ARG_reset_cb].u_obj;
+ self->control_xfer_cb = args[ARG_control_xfer_cb].u_obj;
+ self->xfer_cb = args[ARG_xfer_cb].u_obj;
+
+ return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_KW(usb_device_config_obj, 1, usb_device_config);
+
+static const MP_DEFINE_BYTES_OBJ(builtin_default_desc_dev_obj,
+ &mp_usbd_builtin_desc_dev, sizeof(tusb_desc_device_t));
+
+#if HAS_BUILTIN_DRIVERS
+// BUILTIN_DEFAULT Python object holds properties of the built-in USB configuration
+// (i.e. values used by the C implementation of TinyUSB devices.)
+static const MP_DEFINE_BYTES_OBJ(builtin_default_desc_cfg_obj,
+ mp_usbd_builtin_desc_cfg, MP_USBD_BUILTIN_DESC_CFG_LEN);
+
+static const mp_rom_map_elem_t usb_device_builtin_default_dict_table[] = {
+ { MP_ROM_QSTR(MP_QSTR_itf_max), MP_OBJ_NEW_SMALL_INT(USBD_ITF_BUILTIN_MAX) },
+ { MP_ROM_QSTR(MP_QSTR_ep_max), MP_OBJ_NEW_SMALL_INT(USBD_EP_BUILTIN_MAX) },
+ { MP_ROM_QSTR(MP_QSTR_str_max), MP_OBJ_NEW_SMALL_INT(USBD_STR_BUILTIN_MAX) },
+ { MP_ROM_QSTR(MP_QSTR_desc_dev), MP_ROM_PTR(&builtin_default_desc_dev_obj) },
+ { MP_ROM_QSTR(MP_QSTR_desc_cfg), MP_ROM_PTR(&builtin_default_desc_cfg_obj) },
+};
+static MP_DEFINE_CONST_DICT(usb_device_builtin_default_dict, usb_device_builtin_default_dict_table);
+
+MP_DEFINE_CONST_OBJ_TYPE(
+ mp_type_usb_device_builtin_default,
+ MP_QSTR_BUILTIN_DEFAULT,
+ MP_TYPE_FLAG_NONE,
+ locals_dict, &usb_device_builtin_default_dict
+ );
+#endif // HAS_BUILTIN_DRIVERS
+
+// BUILTIN_NONE holds properties for no enabled built-in USB device support
+static const mp_rom_map_elem_t usb_device_builtin_none_dict_table[] = {
+ { MP_ROM_QSTR(MP_QSTR_itf_max), MP_OBJ_NEW_SMALL_INT(0) },
+ { MP_ROM_QSTR(MP_QSTR_ep_max), MP_OBJ_NEW_SMALL_INT(0) },
+ { MP_ROM_QSTR(MP_QSTR_str_max), MP_OBJ_NEW_SMALL_INT(1) },
+ { MP_ROM_QSTR(MP_QSTR_desc_dev), MP_ROM_PTR(&builtin_default_desc_dev_obj) },
+ { MP_ROM_QSTR(MP_QSTR_desc_cfg), mp_const_empty_bytes },
+};
+static MP_DEFINE_CONST_DICT(usb_device_builtin_none_dict, usb_device_builtin_none_dict_table);
+
+MP_DEFINE_CONST_OBJ_TYPE(
+ mp_type_usb_device_builtin_none,
+ MP_QSTR_BUILTIN_NONE,
+ MP_TYPE_FLAG_NONE,
+ locals_dict, &usb_device_builtin_none_dict
+ );
+
+static const mp_rom_map_elem_t usb_device_locals_dict_table[] = {
+ { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&usb_device_config_obj) },
+ { MP_ROM_QSTR(MP_QSTR_submit_xfer), MP_ROM_PTR(&usb_device_submit_xfer_obj) },
+ { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&usb_device_active_obj) },
+ { MP_ROM_QSTR(MP_QSTR_stall), MP_ROM_PTR(&usb_device_stall_obj) },
+
+ // Built-in driver constants
+ { MP_ROM_QSTR(MP_QSTR_BUILTIN_NONE), MP_ROM_PTR(&mp_type_usb_device_builtin_none) },
+
+ #if !HAS_BUILTIN_DRIVERS
+ // No builtin-in drivers, so BUILTIN_DEFAULT is BUILTIN_NONE
+ { MP_ROM_QSTR(MP_QSTR_BUILTIN_DEFAULT), MP_ROM_PTR(&mp_type_usb_device_builtin_none) },
+ #else
+ { MP_ROM_QSTR(MP_QSTR_BUILTIN_DEFAULT), MP_ROM_PTR(&mp_type_usb_device_builtin_default) },
+
+ // Specific driver constant names are to support future switching of built-in drivers,
+ // but currently only one is present and it maps directly to BUILTIN_DEFAULT
+ #if MICROPY_HW_USB_CDC && !MICROPY_HW_USB_MSC
+ { MP_ROM_QSTR(MP_QSTR_BUILTIN_CDC), MP_ROM_PTR(&mp_type_usb_device_builtin_default) },
+ #endif
+ #if MICROPY_HW_USB_MSC && !MICROPY_HW_USB_CDC
+ { MP_ROM_QSTR(MP_QSTR_BUILTIN_MSC), MP_ROM_PTR(&mp_type_usb_device_builtin_default) },
+ #endif
+ #if MICROPY_HW_USB_CDC && MICROPY_HW_USB_MSC
+ { MP_ROM_QSTR(MP_QSTR_BUILTIN_CDC_MSC), MP_ROM_PTR(&mp_type_usb_device_builtin_default) },
+ #endif
+ #endif // !HAS_BUILTIN_DRIVERS
+};
+static MP_DEFINE_CONST_DICT(usb_device_locals_dict, usb_device_locals_dict_table);
+
+static void usb_device_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
+ mp_obj_usb_device_t *self = MP_OBJ_TO_PTR(self_in);
+ if (dest[0] == MP_OBJ_NULL) {
+ // Load attribute.
+ if (attr == MP_QSTR_builtin_driver) {
+ dest[0] = self->builtin_driver;
+ } else {
+ // Continue lookup in locals_dict.
+ dest[1] = MP_OBJ_SENTINEL;
+ }
+ } else if (dest[1] != MP_OBJ_NULL) {
+ // Store attribute.
+ if (attr == MP_QSTR_builtin_driver) {
+ if (self->active) {
+ mp_raise_OSError(MP_EINVAL); // Need to deactivate first
+ }
+ // Note: this value should be one of the BUILTIN_nnn constants,
+ // but not checked here to save code size in a low level API
+ self->builtin_driver = dest[1];
+ dest[0] = MP_OBJ_NULL;
+ }
+ }
+}
+
+MP_DEFINE_CONST_OBJ_TYPE(
+ machine_usb_device_type,
+ MP_QSTR_USBDevice,
+ MP_TYPE_FLAG_NONE,
+ make_new, usb_device_make_new,
+ locals_dict, &usb_device_locals_dict,
+ attr, &usb_device_attr
+ );
+
+MP_REGISTER_ROOT_POINTER(mp_obj_t usbd);
+
+#endif
diff --git a/extmod/modmachine.c b/extmod/modmachine.c
index d7e82b124..2a7e315bb 100644
--- a/extmod/modmachine.c
+++ b/extmod/modmachine.c
@@ -232,6 +232,9 @@ static const mp_rom_map_elem_t machine_module_globals_table[] = {
#if MICROPY_PY_MACHINE_UART
{ MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_uart_type) },
#endif
+ #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+ { MP_ROM_QSTR(MP_QSTR_USBDevice), MP_ROM_PTR(&machine_usb_device_type) },
+ #endif
#if MICROPY_PY_MACHINE_WDT
{ MP_ROM_QSTR(MP_QSTR_WDT), MP_ROM_PTR(&machine_wdt_type) },
#endif
diff --git a/extmod/modmachine.h b/extmod/modmachine.h
index e6b08b3fc..7c16ed302 100644
--- a/extmod/modmachine.h
+++ b/extmod/modmachine.h
@@ -213,6 +213,7 @@ extern const mp_obj_type_t machine_signal_type;
extern const mp_obj_type_t machine_spi_type;
extern const mp_obj_type_t machine_timer_type;
extern const mp_obj_type_t machine_uart_type;
+extern const mp_obj_type_t machine_usbd_type;
extern const mp_obj_type_t machine_wdt_type;
#if MICROPY_PY_MACHINE_SOFTI2C
@@ -230,6 +231,10 @@ extern const mp_machine_spi_p_t mp_machine_soft_spi_p;
extern const mp_obj_dict_t mp_machine_spi_locals_dict;
#endif
+#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+extern const mp_obj_type_t machine_usb_device_type;
+#endif
+
#if defined(MICROPY_MACHINE_MEM_GET_READ_ADDR)
uintptr_t MICROPY_MACHINE_MEM_GET_READ_ADDR(mp_obj_t addr_o, uint align);
#endif
diff --git a/py/runtime.c b/py/runtime.c
index f7e0abdb4..1836f5d92 100644
--- a/py/runtime.c
+++ b/py/runtime.c
@@ -171,6 +171,10 @@ void mp_init(void) {
MP_STATE_VM(bluetooth) = MP_OBJ_NULL;
#endif
+ #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+ MP_STATE_VM(usbd) = MP_OBJ_NULL;
+ #endif
+
#if MICROPY_PY_THREAD_GIL
mp_thread_mutex_init(&MP_STATE_VM(gil_mutex));
#endif
diff --git a/shared/tinyusb/mp_usbd.c b/shared/tinyusb/mp_usbd.c
index 74b3f0749..436314dcd 100644
--- a/shared/tinyusb/mp_usbd.c
+++ b/shared/tinyusb/mp_usbd.c
@@ -24,42 +24,42 @@
* THE SOFTWARE.
*/
-#include <stdlib.h>
-
#include "py/mpconfig.h"
-#include "py/runtime.h"
#if MICROPY_HW_ENABLE_USBDEV
+#include "mp_usbd.h"
+
#ifndef NO_QSTR
-#include "tusb.h" // TinyUSB is not available when running the string preprocessor
#include "device/dcd.h"
-#include "device/usbd.h"
-#include "device/usbd_pvt.h"
#endif
-// TinyUSB task function wrapper, as scheduled from the USB IRQ
-static void mp_usbd_task_callback(mp_sched_node_t *node);
-
-extern void __real_dcd_event_handler(dcd_event_t const *event, bool in_isr);
+#if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
void mp_usbd_task(void) {
tud_task_ext(0, false);
}
+void mp_usbd_task_callback(mp_sched_node_t *node) {
+ (void)node;
+ mp_usbd_task();
+}
+
+#endif // !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+
+extern void __real_dcd_event_handler(dcd_event_t const *event, bool in_isr);
+
// If -Wl,--wrap=dcd_event_handler is passed to the linker, then this wrapper
// will be called and allows MicroPython to schedule the TinyUSB task when
// dcd_event_handler() is called from an ISR.
TU_ATTR_FAST_FUNC void __wrap_dcd_event_handler(dcd_event_t const *event, bool in_isr) {
- static mp_sched_node_t usbd_task_node;
-
__real_dcd_event_handler(event, in_isr);
- mp_sched_schedule_node(&usbd_task_node, mp_usbd_task_callback);
+ mp_usbd_schedule_task();
}
-static void mp_usbd_task_callback(mp_sched_node_t *node) {
- (void)node;
- mp_usbd_task();
+TU_ATTR_FAST_FUNC void mp_usbd_schedule_task(void) {
+ static mp_sched_node_t usbd_task_node;
+ mp_sched_schedule_node(&usbd_task_node, mp_usbd_task_callback);
}
void mp_usbd_hex_str(char *out_str, const uint8_t *bytes, size_t bytes_len) {
@@ -72,4 +72,4 @@ void mp_usbd_hex_str(char *out_str, const uint8_t *bytes, size_t bytes_len) {
out_str[hex_len] = 0;
}
-#endif
+#endif // MICROPY_HW_ENABLE_USBDEV
diff --git a/shared/tinyusb/mp_usbd.h b/shared/tinyusb/mp_usbd.h
index 89f8bf0ee..ef3234845 100644
--- a/shared/tinyusb/mp_usbd.h
+++ b/shared/tinyusb/mp_usbd.h
@@ -27,25 +27,110 @@
#ifndef MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_H
#define MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_H
+#include "py/mpconfig.h"
+
+#if MICROPY_HW_ENABLE_USBDEV
+
#include "py/obj.h"
-#include "tusb.h"
+#include "py/objarray.h"
+#include "py/runtime.h"
-static inline void mp_usbd_init(void) {
- // Currently this is a thin wrapper around tusb_init(), however
- // runtime USB support will require this to be extended.
- tusb_init();
-}
+#ifndef NO_QSTR
+#include "tusb.h"
+#include "device/dcd.h"
+#endif
-// Call this to explicitly run the TinyUSB device task.
+// Run the TinyUSB device task
void mp_usbd_task(void);
+// Schedule a call to mp_usbd_task(), even if no USB interrupt has occurred
+void mp_usbd_schedule_task(void);
+
// Function to be implemented in port code.
// Can write a string up to MICROPY_HW_USB_DESC_STR_MAX characters long, plus terminating byte.
extern void mp_usbd_port_get_serial_number(char *buf);
-// Most ports need to write a hexadecimal serial number from a byte array, this
+// Most ports need to write a hexadecimal serial number from a byte array. This
// is a helper function for this. out_str must be long enough to hold a string of total
// length (2 * bytes_len + 1) (including NUL terminator).
void mp_usbd_hex_str(char *out_str, const uint8_t *bytes, size_t bytes_len);
+// Length of built-in configuration descriptor
+#define MP_USBD_BUILTIN_DESC_CFG_LEN (TUD_CONFIG_DESC_LEN + \
+ (CFG_TUD_CDC ? (TUD_CDC_DESC_LEN) : 0) + \
+ (CFG_TUD_MSC ? (TUD_MSC_DESC_LEN) : 0) \
+ )
+
+// Built-in USB device and configuration descriptor values
+extern const tusb_desc_device_t mp_usbd_builtin_desc_dev;
+extern const uint8_t mp_usbd_builtin_desc_cfg[MP_USBD_BUILTIN_DESC_CFG_LEN];
+
+void mp_usbd_task_callback(mp_sched_node_t *node);
+
+#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+void mp_usbd_deinit(void);
+void mp_usbd_init(void);
+
+const char *mp_usbd_runtime_string_cb(uint8_t index);
+
+// Maximum number of pending exceptions per single TinyUSB task execution
+#define MP_USBD_MAX_PEND_EXCS 2
+
+typedef struct {
+ mp_obj_base_t base;
+
+ mp_obj_t desc_dev; // Device descriptor bytes
+ mp_obj_t desc_cfg; // Configuration descriptor bytes
+ mp_obj_t desc_strs; // List/dict/similar to look up string descriptors by index
+
+ // Runtime device driver callback functions
+ mp_obj_t open_itf_cb;
+ mp_obj_t reset_cb;
+ mp_obj_t control_xfer_cb;
+ mp_obj_t xfer_cb;
+
+ mp_obj_t builtin_driver; // Points to one of mp_type_usb_device_builtin_nnn
+
+ bool active; // Has the user set the USB device active?
+ bool trigger; // Has the user requested the active state change (or re-activate)?
+
+ // Temporary pointers for xfer data in progress on each endpoint
+ // Ensuring they aren't garbage collected until the xfer completes
+ mp_obj_t xfer_data[CFG_TUD_ENDPPOINT_MAX][2];
+
+ // Pointer to a memoryview that is reused to refer to various pieces of
+ // control transfer data that are pushed to USB control transfer
+ // callbacks. Python code can't rely on the memoryview contents
+ // to remain valid after the callback returns!
+ mp_obj_array_t *control_data;
+
+ // Pointers to exceptions thrown inside Python callbacks. See
+ // usbd_callback_function_n().
+ mp_uint_t num_pend_excs;
+ mp_obj_t pend_excs[MP_USBD_MAX_PEND_EXCS];
+} mp_obj_usb_device_t;
+
+// Built-in constant objects, possible values of builtin_driver
+//
+// (Currently not possible to change built-in drivers at runtime, just enable/disable.)
+extern const mp_obj_type_t mp_type_usb_device_builtin_default;
+extern const mp_obj_type_t mp_type_usb_device_builtin_none;
+
+// Return true if any built-in driver is enabled
+inline static bool mp_usb_device_builtin_enabled(const mp_obj_usb_device_t *usbd) {
+ return usbd->builtin_driver != MP_OBJ_FROM_PTR(&mp_type_usb_device_builtin_none);
+}
+
+#else // Static USBD drivers only
+
+static inline void mp_usbd_init(void) {
+ // Without runtime USB support, this can be a thin wrapper wrapper around tusb_init()
+ extern bool tusb_init(void);
+ tusb_init();
+}
+
+#endif
+
+#endif // MICROPY_HW_ENABLE_USBDEV
+
#endif // MICROPY_INCLUDED_SHARED_TINYUSB_USBD_H
diff --git a/shared/tinyusb/mp_usbd_descriptor.c b/shared/tinyusb/mp_usbd_descriptor.c
index 72a265217..be3473b6b 100644
--- a/shared/tinyusb/mp_usbd_descriptor.c
+++ b/shared/tinyusb/mp_usbd_descriptor.c
@@ -31,12 +31,11 @@
#include "tusb.h"
#include "mp_usbd.h"
-#include "mp_usbd_internal.h"
#define USBD_CDC_CMD_MAX_SIZE (8)
#define USBD_CDC_IN_OUT_MAX_SIZE ((CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED) ? 512 : 64)
-const tusb_desc_device_t mp_usbd_desc_device_static = {
+const tusb_desc_device_t mp_usbd_builtin_desc_dev = {
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
@@ -53,8 +52,8 @@ const tusb_desc_device_t mp_usbd_desc_device_static = {
.bNumConfigurations = 1,
};
-const uint8_t mp_usbd_desc_cfg_static[USBD_STATIC_DESC_LEN] = {
- TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_STATIC_MAX, USBD_STR_0, USBD_STATIC_DESC_LEN,
+const uint8_t mp_usbd_builtin_desc_cfg[MP_USBD_BUILTIN_DESC_CFG_LEN] = {
+ TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_BUILTIN_MAX, USBD_STR_0, MP_USBD_BUILTIN_DESC_CFG_LEN,
0, USBD_MAX_POWER_MA),
#if CFG_TUD_CDC
@@ -69,51 +68,68 @@ const uint8_t mp_usbd_desc_cfg_static[USBD_STATIC_DESC_LEN] = {
const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
char serial_buf[MICROPY_HW_USB_DESC_STR_MAX + 1]; // Includes terminating NUL byte
static uint16_t desc_wstr[MICROPY_HW_USB_DESC_STR_MAX + 1]; // Includes prefix uint16_t
- const char *desc_str;
+ const char *desc_str = NULL;
uint16_t desc_len;
- switch (index) {
- case 0:
- desc_wstr[1] = 0x0409; // supported language is English
- desc_len = 4;
- break;
- case USBD_STR_SERIAL:
- // TODO: make a port-specific serial number callback
- mp_usbd_port_get_serial_number(serial_buf);
- desc_str = serial_buf;
- break;
- case USBD_STR_MANUF:
- desc_str = MICROPY_HW_USB_MANUFACTURER_STRING;
- break;
- case USBD_STR_PRODUCT:
- desc_str = MICROPY_HW_USB_PRODUCT_FS_STRING;
- break;
- #if CFG_TUD_CDC
- case USBD_STR_CDC:
- desc_str = MICROPY_HW_USB_CDC_INTERFACE_STRING;
- break;
- #endif
- #if CFG_TUD_MSC
- case USBD_STR_MSC:
- desc_str = MICROPY_HW_USB_MSC_INTERFACE_STRING;
- break;
- #endif
- default:
- desc_str = NULL;
- }
+ #if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+ desc_str = mp_usbd_runtime_string_cb(index);
+ #endif
- if (index != 0) {
+ if (index == 0) {
+ // String descriptor 0 is special, see USB 2.0 section 9.6.7 String
+ //
+ // Expect any runtime value in desc_str to be a fully formed descriptor
if (desc_str == NULL) {
- return NULL; // Will STALL the endpoint
+ desc_str = "\x04\x03\x09\x04"; // Descriptor for "English"
}
+ if (desc_str[0] < sizeof(desc_wstr)) {
+ memcpy(desc_wstr, desc_str, desc_str[0]);
+ return desc_wstr;
+ }
+ return NULL; // Descriptor length too long (or malformed), stall endpoint
+ }
- // Convert from narrow string to wide string
- desc_len = 2;
- for (int i = 0; i < MICROPY_HW_USB_DESC_STR_MAX && desc_str[i] != 0; i++) {
- desc_wstr[1 + i] = desc_str[i];
- desc_len += 2;
+ // Otherwise, generate a "UNICODE" string descriptor from the C string
+
+ if (desc_str == NULL) {
+ // Fall back to the "static" string
+ switch (index) {
+ case USBD_STR_SERIAL:
+ mp_usbd_port_get_serial_number(serial_buf);
+ desc_str = serial_buf;
+ break;
+ case USBD_STR_MANUF:
+ desc_str = MICROPY_HW_USB_MANUFACTURER_STRING;
+ break;
+ case USBD_STR_PRODUCT:
+ desc_str = MICROPY_HW_USB_PRODUCT_FS_STRING;
+ break;
+ #if CFG_TUD_CDC
+ case USBD_STR_CDC:
+ desc_str = MICROPY_HW_USB_CDC_INTERFACE_STRING;
+ break;
+ #endif
+ #if CFG_TUD_MSC
+ case USBD_STR_MSC:
+ desc_str = MICROPY_HW_USB_MSC_INTERFACE_STRING;
+ break;
+ #endif
+ default:
+ break;
}
}
+
+ if (desc_str == NULL) {
+ return NULL; // No string, STALL the endpoint
+ }
+
+ // Convert from narrow string to wide string
+ desc_len = 2;
+ for (int i = 0; i < MICROPY_HW_USB_DESC_STR_MAX && desc_str[i] != 0; i++) {
+ desc_wstr[1 + i] = desc_str[i];
+ desc_len += 2;
+ }
+
// first byte is length (including header), second byte is string type
desc_wstr[0] = (TUSB_DESC_STRING << 8) | desc_len;
@@ -121,13 +137,21 @@ const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
}
+#if !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+
const uint8_t *tud_descriptor_device_cb(void) {
- return (const void *)&mp_usbd_desc_device_static;
+ return (const void *)&mp_usbd_builtin_desc_dev;
}
const uint8_t *tud_descriptor_configuration_cb(uint8_t index) {
(void)index;
- return mp_usbd_desc_cfg_static;
+ return mp_usbd_builtin_desc_cfg;
}
-#endif
+#else
+
+// If runtime device support is enabled, descriptor callbacks are implemented in usbd.c
+
+#endif // !MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+
+#endif // MICROPY_HW_ENABLE_USBDEV
diff --git a/shared/tinyusb/mp_usbd_internal.h b/shared/tinyusb/mp_usbd_internal.h
deleted file mode 100644
index 8b8f50bae..000000000
--- a/shared/tinyusb/mp_usbd_internal.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * This file is part of the MicroPython project, http://micropython.org/
- *
- * The MIT License (MIT)
- *
- * Copyright (c) 2022 Angus Gratton
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-#ifndef MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_INTERNAL_H
-#define MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_INTERNAL_H
-#include "tusb.h"
-
-// Static USB device descriptor values
-extern const tusb_desc_device_t mp_usbd_desc_device_static;
-extern const uint8_t mp_usbd_desc_cfg_static[USBD_STATIC_DESC_LEN];
-
-#endif // MICROPY_INCLUDED_SHARED_TINYUSB_MP_USBD_INTERNAL_H
diff --git a/shared/tinyusb/mp_usbd_runtime.c b/shared/tinyusb/mp_usbd_runtime.c
new file mode 100644
index 000000000..a46c0f85e
--- /dev/null
+++ b/shared/tinyusb/mp_usbd_runtime.c
@@ -0,0 +1,554 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2022 Blake W. Felt
+ * Copyright (c) 2022-2023 Angus Gratton
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdlib.h>
+
+#include "mp_usbd.h"
+#include "py/mpconfig.h"
+#include "py/mperrno.h"
+#include "py/mphal.h"
+#include "py/obj.h"
+#include "py/objarray.h"
+#include "py/objstr.h"
+#include "py/runtime.h"
+
+#if MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+
+#ifndef NO_QSTR
+#include "tusb.h" // TinyUSB is not available when running the string preprocessor
+#include "device/dcd.h"
+#include "device/usbd.h"
+#include "device/usbd_pvt.h"
+#endif
+
+static bool in_usbd_task; // Flags if mp_usbd_task() is processing already
+
+// Some top-level functions that manage global TinyUSB USBD state, not the
+// singleton object visible to Python
+static void mp_usbd_disconnect(mp_obj_usb_device_t *usbd);
+static void mp_usbd_task_inner(void);
+
+// Pend an exception raise in a USBD callback to print when safe.
+//
+// We can't raise any exceptions out of the TinyUSB task, as it may still need
+// to do some state cleanup.
+//
+// The requirement for this becomes very similar to
+// mp_call_function_x_protected() for interrupts, but it's more restrictive: if
+// the C-based USB-CDC serial port is in use, we can't print from inside a
+// TinyUSB callback as it might try to recursively call into TinyUSB to flush
+// the CDC port and make room. Therefore, we have to store the exception and
+// print it as we exit the TinyUSB task.
+//
+// (Worse, a single TinyUSB task can process multiple callbacks and therefore generate
+// multiple exceptions...)
+static void usbd_pend_exception(mp_obj_t exception) {
+ mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+ assert(usbd != NULL);
+ if (usbd->num_pend_excs < MP_USBD_MAX_PEND_EXCS) {
+ usbd->pend_excs[usbd->num_pend_excs] = exception;
+ }
+ usbd->num_pend_excs++;
+}
+
+// Call a Python function from inside a TinyUSB callback.
+//
+// Handles any exception using usbd_pend_exception()
+static mp_obj_t usbd_callback_function_n(mp_obj_t fun, size_t n_args, const mp_obj_t *args) {
+ nlr_buf_t nlr;
+ if (nlr_push(&nlr) == 0) {
+ mp_obj_t ret = mp_call_function_n_kw(fun, n_args, 0, args);
+ nlr_pop();
+ return ret;
+ } else {
+ usbd_pend_exception(MP_OBJ_FROM_PTR(nlr.ret_val));
+ return MP_OBJ_NULL;
+ }
+}
+
+// Return a pointer to the data inside a Python buffer provided in a callback
+static void *usbd_get_buffer_in_cb(mp_obj_t obj, mp_uint_t flags) {
+ mp_buffer_info_t buf_info;
+ if (obj == mp_const_none) {
+ // This is only if the user somehow
+ return NULL;
+ } else if (mp_get_buffer(obj, &buf_info, flags)) {
+ return buf_info.buf;
+ } else {
+ mp_obj_t exc = mp_obj_new_exception_msg(&mp_type_TypeError,
+ MP_ERROR_TEXT("object with buffer protocol required"));
+ usbd_pend_exception(exc);
+ return NULL;
+ }
+}
+
+const uint8_t *tud_descriptor_device_cb(void) {
+ mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+ const void *result = NULL;
+ if (usbd) {
+ result = usbd_get_buffer_in_cb(usbd->desc_dev, MP_BUFFER_READ);
+ }
+ return result ? result : &mp_usbd_builtin_desc_dev;
+}
+
+const uint8_t *tud_descriptor_configuration_cb(uint8_t index) {
+ (void)index;
+ mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+ const void *result = NULL;
+ if (usbd) {
+ result = usbd_get_buffer_in_cb(usbd->desc_cfg, MP_BUFFER_READ);
+ }
+ return result ? result : &mp_usbd_builtin_desc_cfg;
+}
+
+const char *mp_usbd_runtime_string_cb(uint8_t index) {
+ mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+ nlr_buf_t nlr;
+
+ if (usbd == NULL || usbd->desc_strs == mp_const_none) {
+ return NULL;
+ }
+
+ if (nlr_push(&nlr) == 0) {
+ mp_obj_t res = mp_obj_subscr(usbd->desc_strs, mp_obj_new_int(index), MP_OBJ_SENTINEL);
+ nlr_pop();
+ if (res != mp_const_none) {
+ return usbd_get_buffer_in_cb(res, MP_BUFFER_READ);
+ }
+ } else {
+ mp_obj_t exception = MP_OBJ_FROM_PTR(nlr.ret_val);
+ if (!(mp_obj_is_type(exception, &mp_type_KeyError) || mp_obj_is_type(exception, &mp_type_IndexError))) {
+ // Don't print KeyError or IndexError, allowing dicts or lists to have missing entries.
+ // but log any more exotic errors that pop up
+ usbd_pend_exception(exception);
+ }
+ }
+
+ return NULL;
+}
+
+bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) {
+ return false; // Currently no support for Vendor control transfers on the Python side
+}
+
+// Generic "runtime device" TinyUSB class driver, delegates everything to Python callbacks
+
+static void runtime_dev_init(void) {
+}
+
+static void runtime_dev_reset(uint8_t rhport) {
+ mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+ if (!usbd) {
+ return;
+ }
+
+ for (int epnum = 0; epnum < CFG_TUD_ENDPPOINT_MAX; epnum++) {
+ for (int dir = 0; dir < 2; dir++) {
+ usbd->xfer_data[epnum][dir] = mp_const_none;
+ }
+ }
+
+ if (mp_obj_is_callable(usbd->reset_cb)) {
+ usbd_callback_function_n(usbd->reset_cb, 0, NULL);
+ }
+}
+
+// Calculate how many interfaces TinyUSB expects us to claim from
+// driver open().
+//
+// Annoyingly, the calling function (process_set_config() in TinyUSB) knows
+// this but doesn't pass the information to us.
+//
+// The answer is:
+// - If an Interface Association Descriptor (IAD) is immediately before itf_desc
+// in the configuration descriptor, then claim all of the associated interfaces.
+// - Otherwise, claim exactly one interface
+//
+// Relying on the implementation detail that itf_desc is a pointer inside the
+// tud_descriptor_configuration_cb() result. Therefore, we can iterate through
+// from the beginning to check for an IAD immediately preceding it.
+//
+// Returns the number of associated interfaces to claim.
+static uint8_t _runtime_dev_count_itfs(tusb_desc_interface_t const *itf_desc) {
+ const tusb_desc_configuration_t *cfg_desc = (const void *)tud_descriptor_configuration_cb(0);
+ const uint8_t *p_desc = (const void *)cfg_desc;
+ const uint8_t *p_end = p_desc + cfg_desc->wTotalLength;
+ assert(p_desc <= itf_desc && itf_desc < p_end);
+ while (p_desc != (const void *)itf_desc && p_desc < p_end) {
+ const uint8_t *next = tu_desc_next(p_desc);
+
+ if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE_ASSOCIATION
+ && next == (const void *)itf_desc) {
+ const tusb_desc_interface_assoc_t *desc_iad = (const void *)p_desc;
+ return desc_iad->bInterfaceCount;
+ }
+ p_desc = next;
+ }
+ return 1; // No IAD found
+}
+
+// Scan the other descriptors after these interface(s) to find the total associated length to claim
+// from driver open().
+//
+// Total number of interfaces to scan for is assoc_itf_count.
+//
+// Opens any associated endpoints so they can have transfers submitted against them.
+//
+// Returns the total number of descriptor bytes to claim.
+static uint16_t _runtime_dev_claim_itfs(tusb_desc_interface_t const *itf_desc, uint8_t assoc_itf_count, uint16_t max_len) {
+ const uint8_t *p_desc = (const void *)itf_desc;
+ const uint8_t *p_end = p_desc + max_len;
+ while (p_desc < p_end) {
+ if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE) {
+ if (assoc_itf_count > 0) {
+ // Claim this interface descriptor
+ assoc_itf_count--;
+ } else {
+ // This is the end of the previous interface's associated descriptors
+ break;
+ }
+ } else if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) {
+ // Open any endpoints that we come across
+ if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) {
+ bool r = usbd_edpt_open(USBD_RHPORT, (const void *)p_desc);
+ if (!r) {
+ mp_obj_t exc = mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(MP_ENODEV));
+ usbd_pend_exception(exc);
+ break;
+ }
+ }
+ }
+ p_desc = tu_desc_next(p_desc);
+ }
+ return p_desc - (const uint8_t *)itf_desc;
+}
+
+// TinyUSB "Application driver" open callback. Called when the USB host sets
+// configuration. Returns number of bytes to claim from descriptors pointed to
+// by itf_desc.
+//
+// This is a little fiddly as it's called before any compiled-in "static"
+// TinyUSB drivers, but we don't want to override those.
+//
+// Also, TinyUSB expects us to know how many interfaces to claim for each time
+// this function is called, and will behave unexpectedly if we claim the wrong
+// number of interfaces. However, unlike a "normal" USB driver we don't know at
+// compile time how many interfaces we've implemented. Instead, we have to look
+// back through the configuration descriptor to figure this out.
+static uint16_t runtime_dev_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) {
+ mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+
+ // Runtime USB isn't initialised
+ if (!usbd) {
+ return 0;
+ }
+
+ // If TinyUSB built-in drivers are enabled, don't claim any interface in the static range
+ if (mp_usb_device_builtin_enabled(usbd) && itf_desc->bInterfaceNumber < USBD_ITF_BUILTIN_MAX) {
+ return 0;
+ }
+
+ // Determine the total descriptor length of the interface(s) we are going to claim
+ uint8_t assoc_itf_count = _runtime_dev_count_itfs(itf_desc);
+ uint16_t claim_len = _runtime_dev_claim_itfs(itf_desc, assoc_itf_count, max_len);
+
+ // Call the Python callback to allow the driver to start working with these interface(s)
+
+ if (mp_obj_is_callable(usbd->open_itf_cb)) {
+ // Repurpose the control_data memoryview to point into itf_desc for this one call
+ usbd->control_data->items = (void *)itf_desc;
+ usbd->control_data->len = claim_len;
+ mp_obj_t args[] = { MP_OBJ_FROM_PTR(usbd->control_data) };
+ usbd_callback_function_n(usbd->open_itf_cb, 1, args);
+ usbd->control_data->len = 0;
+ usbd->control_data->items = NULL;
+ }
+
+ return claim_len;
+}
+
+static bool runtime_dev_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) {
+ mp_obj_t cb_res = mp_const_false;
+ mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+ tusb_dir_t dir = request->bmRequestType_bit.direction;
+ mp_buffer_info_t buf_info;
+
+ if (!usbd) {
+ return false;
+ }
+
+ if (mp_obj_is_callable(usbd->control_xfer_cb)) {
+ usbd->control_data->items = (void *)request;
+ usbd->control_data->len = sizeof(tusb_control_request_t);
+ mp_obj_t args[] = {
+ mp_obj_new_int(stage),
+ MP_OBJ_FROM_PTR(usbd->control_data),
+ };
+ cb_res = usbd_callback_function_n(usbd->control_xfer_cb, MP_ARRAY_SIZE(args), args);
+ usbd->control_data->items = NULL;
+ usbd->control_data->len = 0;
+
+ if (cb_res == MP_OBJ_NULL) {
+ // Exception occurred in the callback handler, stall this transfer
+ cb_res = mp_const_false;
+ }
+ }
+
+ // Check if callback returned any data to submit
+ if (mp_get_buffer(cb_res, &buf_info, dir == TUSB_DIR_IN ? MP_BUFFER_READ : MP_BUFFER_RW)) {
+ bool result = tud_control_xfer(USBD_RHPORT,
+ request,
+ buf_info.buf,
+ buf_info.len);
+
+ if (result) {
+ // Keep buffer object alive until the transfer completes
+ usbd->xfer_data[0][dir] = cb_res;
+ }
+
+ return result;
+ } else {
+ // Expect True or False to stall or continue
+
+ if (stage == CONTROL_STAGE_ACK) {
+ // Allow data to be GCed once it's no longer in use
+ usbd->xfer_data[0][dir] = mp_const_none;
+ }
+ return mp_obj_is_true(cb_res);
+ }
+}
+
+static bool runtime_dev_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
+ mp_obj_t ep = mp_obj_new_int(ep_addr);
+ mp_obj_t cb_res = mp_const_false;
+ mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+ if (!usbd) {
+ return false;
+ }
+
+ if (mp_obj_is_callable(usbd->xfer_cb)) {
+ mp_obj_t args[] = {
+ ep,
+ MP_OBJ_NEW_SMALL_INT(result),
+ MP_OBJ_NEW_SMALL_INT(xferred_bytes),
+ };
+ cb_res = usbd_callback_function_n(usbd->xfer_cb, MP_ARRAY_SIZE(args), args);
+ }
+
+ // Clear any xfer_data for this endpoint
+ usbd->xfer_data[tu_edpt_number(ep_addr)][tu_edpt_dir(ep_addr)] = mp_const_none;
+
+ return cb_res != MP_OBJ_NULL && mp_obj_is_true(cb_res);
+}
+
+static usbd_class_driver_t const _runtime_dev_driver =
+{
+ #if CFG_TUSB_DEBUG >= 2
+ .name = "runtime_dev",
+ #endif
+ .init = runtime_dev_init,
+ .reset = runtime_dev_reset,
+ .open = runtime_dev_open,
+ .control_xfer_cb = runtime_dev_control_xfer_cb,
+ .xfer_cb = runtime_dev_xfer_cb,
+ .sof = NULL
+};
+
+usbd_class_driver_t const *usbd_app_driver_get_cb(uint8_t *driver_count) {
+ *driver_count = 1;
+ return &_runtime_dev_driver;
+}
+
+
+// Functions below here (named mp_usbd_xyz) apply to the whole TinyUSB C-based subsystem
+// and not necessarily the USBD singleton object (named usbd_xyz).
+
+// To support soft reset clearing USB runtime state, we manage three TinyUSB states:
+//
+// - "Not initialised" - tusb_inited() returns false, no USB at all). Only way
+// back to this state is hard reset.
+//
+// - "Activated" - tusb_inited() returns true, USB device "connected" at device
+// end and available to host.
+//
+// - "Deactivated" - tusb_inited() returns true, but USB device "disconnected"
+// at device end and host can't see it.
+
+
+// Top-level USB device subsystem init.
+//
+// Makes an on-demand call to mp_usbd_activate(), if USB is needed.
+//
+// This is called on any soft reset after boot.py runs, or on demand if the
+// user activates USB and it hasn't activated yet.
+void mp_usbd_init(void) {
+ mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+ bool need_usb;
+
+ if (usbd == NULL) {
+ // No runtime USB device
+ #if CFG_TUD_CDC || CFG_TUD_MSC
+ // Builtin drivers are available, so initialise as defaults
+ need_usb = true;
+ #else
+ // No static drivers, nothing to initialise
+ need_usb = false;
+ #endif
+ } else {
+ // Otherwise, initialise based on whether runtime USB is active
+ need_usb = usbd->active;
+ }
+
+ if (need_usb) {
+ tusb_init(); // Safe to call redundantly
+ tud_connect(); // Reconnect if mp_usbd_deinit() has disconnected
+ }
+}
+
+// Top-level USB device deinit.
+//
+// This variant is called from soft reset, NULLs out the USB device
+// singleton instance from MP_STATE_VM, and disconnects the port.
+void mp_usbd_deinit(void) {
+ mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+ MP_STATE_VM(usbd) = MP_OBJ_NULL;
+ if (usbd) {
+ // Disconnect if a runtime USB device was active
+ mp_usbd_disconnect(usbd);
+ }
+}
+
+// Thin wrapper around tud_disconnect() that tells TinyUSB all endpoints
+// have stalled, to prevent it getting confused if a transfer is in progress.
+static void mp_usbd_disconnect(mp_obj_usb_device_t *usbd) {
+ if (!tusb_inited()) {
+ return; // TinyUSB hasn't initialised
+ }
+
+ if (usbd) {
+ // There might be USB transfers in progress right now, so need to stall any live
+ // endpoints
+ //
+ // TODO: figure out if we really need this
+ for (int epnum = 0; epnum < CFG_TUD_ENDPPOINT_MAX; epnum++) {
+ for (int dir = 0; dir < 2; dir++) {
+ if (usbd->xfer_data[epnum][dir] != mp_const_none) {
+ usbd_edpt_stall(USBD_RHPORT, tu_edpt_addr(epnum, dir));
+ usbd->xfer_data[epnum][dir] = mp_const_none;
+ }
+ }
+ }
+ }
+
+ #if MICROPY_HW_USB_CDC
+ // Ensure no pending static CDC writes, as these can cause TinyUSB to crash
+ tud_cdc_write_clear();
+ #endif
+
+ bool was_connected = tud_connected();
+ tud_disconnect();
+ if (was_connected) {
+ mp_hal_delay_ms(50); // TODO: Always???
+ }
+}
+
+// Thjs callback is queued by mp_usbd_schedule_task() to process USB later.
+void mp_usbd_task_callback(mp_sched_node_t *node) {
+ if (tud_inited() && !in_usbd_task) {
+ mp_usbd_task_inner();
+ }
+ // If in_usbd_task is set, it means something else has already manually called
+ // mp_usbd_task() (most likely: C-based USB-CDC serial port). Now the MP
+ // scheduler is running inside there and triggering this callback. It's OK
+ // to skip, the already-running outer TinyUSB task will process all pending
+ // events before it returns.
+}
+
+// Task function can be called manually to force processing of USB events
+// (mostly from USB-CDC serial port when blocking.)
+void mp_usbd_task(void) {
+ if (in_usbd_task) {
+ // If this exception triggers, it means a USB callback tried to do
+ // something that itself became blocked on TinyUSB (most likely: read or
+ // write from a C-based USB-CDC serial port.)
+ mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("TinyUSB callback can't recurse"));
+ }
+
+ mp_usbd_task_inner();
+}
+
+static void mp_usbd_task_inner(void) {
+ in_usbd_task = true;
+
+ tud_task_ext(0, false);
+
+ mp_obj_usb_device_t *usbd = MP_OBJ_TO_PTR(MP_STATE_VM(usbd));
+
+ // Check for a triggered change to/from active state
+ if (usbd && usbd->trigger) {
+ if (usbd->active) {
+ if (tud_connected()) {
+ // If a SETUP packet has been received, first disconnect
+ // and wait for the host to recognise this and trigger a bus reset.
+ //
+ // Effectively this forces it to re-enumerate the device.
+ mp_usbd_disconnect(usbd);
+ }
+ tud_connect();
+ } else {
+ mp_usbd_disconnect(usbd);
+ }
+ usbd->trigger = false;
+ }
+
+ in_usbd_task = false;
+
+ if (usbd) {
+ // Print any exceptions that were raised by Python callbacks
+ // inside tud_task_ext(). See usbd_callback_function_n.
+
+ // As printing exceptions to USB-CDC may recursively call mp_usbd_task(),
+ // first copy out the pending data to the local stack
+ mp_uint_t num_pend_excs = usbd->num_pend_excs;
+ mp_obj_t pend_excs[MP_USBD_MAX_PEND_EXCS];
+ for (mp_uint_t i = 0; i < MIN(MP_USBD_MAX_PEND_EXCS, num_pend_excs); i++) {
+ pend_excs[i] = usbd->pend_excs[i];
+ usbd->pend_excs[i] = mp_const_none;
+ }
+ usbd->num_pend_excs = 0;
+
+ // Now print the exceptions stored from this mp_usbd_task() call
+ for (mp_uint_t i = 0; i < MIN(MP_USBD_MAX_PEND_EXCS, num_pend_excs); i++) {
+ mp_obj_print_exception(&mp_plat_print, pend_excs[i]);
+ }
+ if (num_pend_excs > MP_USBD_MAX_PEND_EXCS) {
+ mp_printf(&mp_plat_print, "%u additional exceptions in USB callbacks\n",
+ num_pend_excs - MP_USBD_MAX_PEND_EXCS);
+ }
+ }
+}
+
+#endif // MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
diff --git a/shared/tinyusb/tusb_config.h b/shared/tinyusb/tusb_config.h
index 266cb88cc..c5644e3d5 100644
--- a/shared/tinyusb/tusb_config.h
+++ b/shared/tinyusb/tusb_config.h
@@ -31,6 +31,10 @@
#if MICROPY_HW_ENABLE_USBDEV
+#ifndef MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE
+#define MICROPY_HW_ENABLE_USB_RUNTIME_DEVICE 0
+#endif
+
#ifndef MICROPY_HW_USB_MANUFACTURER_STRING
#define MICROPY_HW_USB_MANUFACTURER_STRING "MicroPython"
#endif
@@ -86,12 +90,9 @@
#define CFG_TUD_MSC_BUFSIZE (MICROPY_FATFS_MAX_SS)
#endif
-// Define static descriptor size and interface count based on the above config
+#define USBD_RHPORT (0) // Currently only one port is supported
-#define USBD_STATIC_DESC_LEN (TUD_CONFIG_DESC_LEN + \
- (CFG_TUD_CDC ? (TUD_CDC_DESC_LEN) : 0) + \
- (CFG_TUD_MSC ? (TUD_MSC_DESC_LEN) : 0) \
- )
+// Define built-in interface, string and endpoint numbering based on the above config
#define USBD_STR_0 (0x00)
#define USBD_STR_MANUF (0x01)
@@ -126,19 +127,19 @@
#endif // CFG_TUD_CDC
#endif // CFG_TUD_MSC
-/* Limits of statically defined USB interfaces, endpoints, strings */
+/* Limits of builtin USB interfaces, endpoints, strings */
#if CFG_TUD_MSC
-#define USBD_ITF_STATIC_MAX (USBD_ITF_MSC + 1)
-#define USBD_STR_STATIC_MAX (USBD_STR_MSC + 1)
-#define USBD_EP_STATIC_MAX (EPNUM_MSC_OUT + 1)
+#define USBD_ITF_BUILTIN_MAX (USBD_ITF_MSC + 1)
+#define USBD_STR_BUILTIN_MAX (USBD_STR_MSC + 1)
+#define USBD_EP_BUILTIN_MAX (EPNUM_MSC_OUT + 1)
#elif CFG_TUD_CDC
-#define USBD_ITF_STATIC_MAX (USBD_ITF_CDC + 2)
-#define USBD_STR_STATIC_MAX (USBD_STR_CDC + 1)
-#define USBD_EP_STATIC_MAX (((EPNUM_CDC_EP_IN)&~TUSB_DIR_IN_MASK) + 1)
+#define USBD_ITF_BUILTIN_MAX (USBD_ITF_CDC + 2)
+#define USBD_STR_BUILTIN_MAX (USBD_STR_CDC + 1)
+#define USBD_EP_BUILTIN_MAX (((USBD_CDC_EP_IN)&~TUSB_DIR_IN_MASK) + 1)
#else // !CFG_TUD_MSC && !CFG_TUD_CDC
-#define USBD_ITF_STATIC_MAX (0)
-#define USBD_STR_STATIC_MAX (0)
-#define USBD_EP_STATIC_MAX (0)
+#define USBD_ITF_BUILTIN_MAX (0)
+#define USBD_STR_BUILTIN_MAX (0)
+#define USBD_EP_BUILTIN_MAX (0)
#endif
#endif // MICROPY_HW_ENABLE_USBDEV