From 65a55f04cb440bd63a92674dec1146f9ee0c4d79 Mon Sep 17 00:00:00 2001 From: Eagle Claw <301410538+ladyada-eagleclaw@users.noreply.github.com> Date: Fri, 2 Oct 2026 21:53:22 -0400 Subject: [PATCH 1/4] espressif: support the ESP32-P4 high-speed USB host controller Run host events on the VM task and poll delayed enumeration work. Dedicated USB PHY pads are exposed separately from GPIO pins. Board configuration is submitted separately. Co-authored-by: Limor Fried --- ports/espressif/Makefile | 4 ++ ports/espressif/background.c | 10 ++++ .../common-hal/microcontroller/Pin.c | 2 +- .../common-hal/microcontroller/__init__.c | 4 ++ ports/espressif/common-hal/usb_host/Port.c | 59 +++++++++++++++++++ ports/espressif/common-hal/usb_host/Port.h | 15 +++++ .../espressif/common-hal/usb_host/__init__.c | 7 +++ ports/espressif/peripherals/esp32p4/pins.c | 4 ++ ports/espressif/peripherals/esp32p4/pins.h | 3 + supervisor/shared/usb/tusb_config.h | 2 +- supervisor/shared/usb/usb.c | 5 ++ 11 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 ports/espressif/common-hal/usb_host/Port.c create mode 100644 ports/espressif/common-hal/usb_host/Port.h create mode 100644 ports/espressif/common-hal/usb_host/__init__.c diff --git a/ports/espressif/Makefile b/ports/espressif/Makefile index 881bfbf45e6..6b2d26e0457 100644 --- a/ports/espressif/Makefile +++ b/ports/espressif/Makefile @@ -660,6 +660,10 @@ SRC_C += \ lib/tinyusb/src/portable/synopsys/dwc2/dwc2_common.c endif +ifeq ($(CIRCUITPY_USB_HOST),1) +SRC_C += lib/tinyusb/src/portable/synopsys/dwc2/hcd_dwc2.c +endif + ifneq ($(CIRCUITPY_AUDIOBUSIO),0) CHIP_COMPONENTS += esp_driver_i2s endif diff --git a/ports/espressif/background.c b/ports/espressif/background.c index b1b81ac2c83..d28b1bb3765 100644 --- a/ports/espressif/background.c +++ b/ports/espressif/background.c @@ -8,11 +8,21 @@ #include "supervisor/filesystem.h" #include "supervisor/port.h" #include "supervisor/shared/stack.h" +#if CIRCUITPY_USB_HOST +#include "supervisor/usb.h" +#include "tusb.h" +#endif #include "freertos/FreeRTOS.h" #include "freertos/task.h" void port_background_tick(void) { + #if CIRCUITPY_USB_HOST + // Enumeration delays need polling even when no new USB IRQ arrives. + if (tuh_task_event_ready()) { + usb_background_schedule(); + } + #endif // Yield with zero delay in case FreeRTOS wants to switch to something else. port_task_yield(); } diff --git a/ports/espressif/common-hal/microcontroller/Pin.c b/ports/espressif/common-hal/microcontroller/Pin.c index 0c49318f4bc..36a83c7d900 100644 --- a/ports/espressif/common-hal/microcontroller/Pin.c +++ b/ports/espressif/common-hal/microcontroller/Pin.c @@ -471,7 +471,7 @@ void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin) { } bool pin_number_is_free(gpio_num_t pin_number) { - return !(_in_use_pin_mask & PIN_BIT(pin_number)); + return GPIO_IS_VALID_GPIO(pin_number) && !(_in_use_pin_mask & PIN_BIT(pin_number)); } bool common_hal_mcu_pin_is_free(const mcu_pin_obj_t *pin) { diff --git a/ports/espressif/common-hal/microcontroller/__init__.c b/ports/espressif/common-hal/microcontroller/__init__.c index 7e0d8b7233d..04eb54f8c2c 100644 --- a/ports/espressif/common-hal/microcontroller/__init__.c +++ b/ports/espressif/common-hal/microcontroller/__init__.c @@ -347,5 +347,9 @@ static const mp_rom_map_elem_t mcu_pin_global_dict_table[] = { #ifdef GPIO54_EXISTS { MP_ROM_QSTR(MP_QSTR_GPIO54), MP_ROM_PTR(&pin_GPIO54) }, #endif + #if defined(CONFIG_IDF_TARGET_ESP32P4) + { MP_ROM_QSTR(MP_QSTR_USB_HS_DP), MP_ROM_PTR(&pin_USB_HS_DP) }, + { MP_ROM_QSTR(MP_QSTR_USB_HS_DM), MP_ROM_PTR(&pin_USB_HS_DM) }, + #endif }; MP_DEFINE_CONST_DICT(mcu_pin_globals, mcu_pin_global_dict_table); diff --git a/ports/espressif/common-hal/usb_host/Port.c b/ports/espressif/common-hal/usb_host/Port.c new file mode 100644 index 00000000000..510906289a1 --- /dev/null +++ b/ports/espressif/common-hal/usb_host/Port.c @@ -0,0 +1,59 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Limor Fried for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "bindings/espidf/__init__.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/usb_host/Port.h" +#include "supervisor/usb.h" +#include "supervisor/shared/tick.h" +#include "esp_private/usb_phy.h" +#include "tusb.h" + +#if !defined(CONFIG_IDF_TARGET_ESP32P4) || CIRCUITPY_USB_HOST_INSTANCE != 1 +#error "USB host requires the ESP32-P4 high-speed controller" +#endif +#if CIRCUITPY_USB_DEVICE && CIRCUITPY_USB_DEVICE_INSTANCE == CIRCUITPY_USB_HOST_INSTANCE +#error "USB host and device must use different controllers" +#endif + +static usb_host_port_obj_t usb_host_instance; +static usb_phy_handle_t host_phy; + +usb_host_port_obj_t *common_hal_usb_host_port_construct(const mcu_pin_obj_t *dp, const mcu_pin_obj_t *dm) { + // The high-speed PHY has dedicated pads, not GPIOs. + if (dp != &pin_USB_HS_DP || dm != &pin_USB_HS_DM) { + raise_ValueError_invalid_pins(); + } + usb_host_port_obj_t *self = &usb_host_instance; + if (self->dp != NULL) { + return self; + } + + const usb_phy_config_t config = { + .controller = USB_PHY_CTRL_OTG, + .target = USB_PHY_TARGET_UTMI, + .otg_mode = USB_OTG_MODE_HOST, + .otg_speed = USB_PHY_SPEED_HIGH, + }; + CHECK_ESP_RESULT(usb_new_phy(&config, &host_phy)); + if (!tuh_init(TUH_OPT_RHPORT)) { + usb_del_phy(host_phy); + host_phy = NULL; + CHECK_ESP_RESULT(ESP_FAIL); + } + self->base.type = &usb_host_port_type; + self->dp = dp; + self->dm = dm; + // Keep polling enumeration timers even without an active display. + supervisor_enable_tick(); + return self; +} + +// TinyUSB installs the controller IRQ. Process its events on the VM task, +// where the Python USB device and keyboard callbacks are safe to run. +void tuh_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr) { + usb_background_schedule(); +} diff --git a/ports/espressif/common-hal/usb_host/Port.h b/ports/espressif/common-hal/usb_host/Port.h new file mode 100644 index 00000000000..e7b44c9524c --- /dev/null +++ b/ports/espressif/common-hal/usb_host/Port.h @@ -0,0 +1,15 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2022 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + const mcu_pin_obj_t *dp; + const mcu_pin_obj_t *dm; +} usb_host_port_obj_t; diff --git a/ports/espressif/common-hal/usb_host/__init__.c b/ports/espressif/common-hal/usb_host/__init__.c new file mode 100644 index 00000000000..c3cdbae38c4 --- /dev/null +++ b/ports/espressif/common-hal/usb_host/__init__.c @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2022 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +// Nothing diff --git a/ports/espressif/peripherals/esp32p4/pins.c b/ports/espressif/peripherals/esp32p4/pins.c index c981d83abe0..05a6bc33bfa 100644 --- a/ports/espressif/peripherals/esp32p4/pins.c +++ b/ports/espressif/peripherals/esp32p4/pins.c @@ -61,3 +61,7 @@ const mcu_pin_obj_t pin_GPIO51 = PIN(51, ADC_UNIT_2, ADC_CHANNEL_2, NO_TOUCH_CHA const mcu_pin_obj_t pin_GPIO52 = PIN(52, ADC_UNIT_2, ADC_CHANNEL_3, NO_TOUCH_CHANNEL); const mcu_pin_obj_t pin_GPIO53 = PIN(53, ADC_UNIT_2, ADC_CHANNEL_4, NO_TOUCH_CHANNEL); const mcu_pin_obj_t pin_GPIO54 = PIN(54, ADC_UNIT_2, ADC_CHANNEL_5, NO_TOUCH_CHANNEL); + +// The USB high-speed PHY pads cannot be used as GPIOs. +const mcu_pin_obj_t pin_USB_HS_DP = PIN(NO_PIN, NO_ADC, NO_ADC_CHANNEL, NO_TOUCH_CHANNEL); +const mcu_pin_obj_t pin_USB_HS_DM = PIN(NO_PIN, NO_ADC, NO_ADC_CHANNEL, NO_TOUCH_CHANNEL); diff --git a/ports/espressif/peripherals/esp32p4/pins.h b/ports/espressif/peripherals/esp32p4/pins.h index 59cf4258a40..7f9f7717c88 100644 --- a/ports/espressif/peripherals/esp32p4/pins.h +++ b/ports/espressif/peripherals/esp32p4/pins.h @@ -120,3 +120,6 @@ extern const mcu_pin_obj_t pin_GPIO52; extern const mcu_pin_obj_t pin_GPIO53; #define GPIO54_EXISTS 1 extern const mcu_pin_obj_t pin_GPIO54; + +extern const mcu_pin_obj_t pin_USB_HS_DP; +extern const mcu_pin_obj_t pin_USB_HS_DM; diff --git a/supervisor/shared/usb/tusb_config.h b/supervisor/shared/usb/tusb_config.h index 8bd920c996f..e66b5f71e90 100644 --- a/supervisor/shared/usb/tusb_config.h +++ b/supervisor/shared/usb/tusb_config.h @@ -180,7 +180,7 @@ extern "C" { #define CFG_TUH_RPI_PIO_USB 1 #endif -#if CIRCUITPY_USB_DEVICE_HIGH_SPEED == 1 +#if CIRCUITPY_USB_DEVICE_HIGH_SPEED == 1 || (defined(CONFIG_IDF_TARGET_ESP32P4) && CIRCUITPY_USB_HOST_INSTANCE == 1) #define _HOST_SPEED OPT_MODE_HIGH_SPEED #else #define _HOST_SPEED 0 diff --git a/supervisor/shared/usb/usb.c b/supervisor/shared/usb/usb.c index 51222be0e59..fbb00541cbc 100644 --- a/supervisor/shared/usb/usb.c +++ b/supervisor/shared/usb/usb.c @@ -167,6 +167,11 @@ void usb_background(void) { #elif CFG_TUSB_OS == OPT_OS_FREERTOS // TinyUSB may run in a separate task, at the same priority as CircuitPython. port_task_yield(); + #if CIRCUITPY_USB_HOST + if (tuh_inited()) { + tuh_task_ext(0, false); + } + #endif #endif // No need to flush if there's no REPL. #if CIRCUITPY_USB_DEVICE && CIRCUITPY_USB_CDC From d4981b0d8d0d10b6a4058c46d0b9e35f19af1e7a Mon Sep 17 00:00:00 2001 From: Eagle Claw <301410538+ladyada-eagleclaw@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:34:42 -0400 Subject: [PATCH 2/4] espressif: fix P4 host-only linkage and USB interrupt scheduling Include the shared DWC2 implementation when either USB role is enabled. Leave interrupt-originated host events to the background tick so they do not invoke the task-only FreeRTOS wake path. Co-authored-by: Limor Fried --- ports/espressif/Makefile | 8 +++++--- ports/espressif/common-hal/usb_host/Port.c | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ports/espressif/Makefile b/ports/espressif/Makefile index 6b2d26e0457..658508c71f2 100644 --- a/ports/espressif/Makefile +++ b/ports/espressif/Makefile @@ -655,15 +655,17 @@ SRC_C += peripherals/touch.c endif ifneq ($(CIRCUITPY_USB_DEVICE),0) -SRC_C += \ - lib/tinyusb/src/portable/synopsys/dwc2/dcd_dwc2.c \ - lib/tinyusb/src/portable/synopsys/dwc2/dwc2_common.c +SRC_C += lib/tinyusb/src/portable/synopsys/dwc2/dcd_dwc2.c endif ifeq ($(CIRCUITPY_USB_HOST),1) SRC_C += lib/tinyusb/src/portable/synopsys/dwc2/hcd_dwc2.c endif +ifneq ($(filter 1,$(CIRCUITPY_USB_DEVICE) $(CIRCUITPY_USB_HOST)),) +SRC_C += lib/tinyusb/src/portable/synopsys/dwc2/dwc2_common.c +endif + ifneq ($(CIRCUITPY_AUDIOBUSIO),0) CHIP_COMPONENTS += esp_driver_i2s endif diff --git a/ports/espressif/common-hal/usb_host/Port.c b/ports/espressif/common-hal/usb_host/Port.c index 510906289a1..3411006d375 100644 --- a/ports/espressif/common-hal/usb_host/Port.c +++ b/ports/espressif/common-hal/usb_host/Port.c @@ -55,5 +55,8 @@ usb_host_port_obj_t *common_hal_usb_host_port_construct(const mcu_pin_obj_t *dp, // TinyUSB installs the controller IRQ. Process its events on the VM task, // where the Python USB device and keyboard callbacks are safe to run. void tuh_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr) { - usb_background_schedule(); + // The background tick polls IRQ events; scheduling here wakes a FreeRTOS task. + if (!in_isr) { + usb_background_schedule(); + } } From 295977f266305552e01a08ea3c3b793cc6fd58d5 Mon Sep 17 00:00:00 2001 From: Eagle Claw <301410538+ladyada-eagleclaw@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:55:16 -0400 Subject: [PATCH 3/4] espressif: use cache-safe buffers for P4 USB host transfers Use private cache-line-aligned DMA buffers for P4 USB host transfers. Route internal descriptor reads through the same buffer handling, and enable host DMA independently of device support. Co-authored-by: Limor Fried --- ports/espressif/mpconfigport.mk | 1 + ports/espressif/supervisor/port.c | 13 +++++++ shared-module/usb/core/Device.c | 55 +++++++++++------------------ supervisor/shared/usb/tusb_config.h | 5 ++- 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/ports/espressif/mpconfigport.mk b/ports/espressif/mpconfigport.mk index 48ab8d04580..9bb6793a940 100644 --- a/ports/espressif/mpconfigport.mk +++ b/ports/espressif/mpconfigport.mk @@ -359,6 +359,7 @@ UF2_BOOTLOADER = 0 CIRCUITPY_USB_HID = 0 CIRCUITPY_USB_MIDI = 0 CIRCUITPY_TUSB_MEM_ALIGN = 64 +CIRCUITPY_ALL_MEMORY_DMA_CAPABLE = 0 CIRCUITPY_MAX3421E = 0 diff --git a/ports/espressif/supervisor/port.c b/ports/espressif/supervisor/port.c index 68d97c20dcf..1dc80c04955 100644 --- a/ports/espressif/supervisor/port.c +++ b/ports/espressif/supervisor/port.c @@ -332,7 +332,13 @@ void port_heap_init(void) { void *port_malloc(size_t size, bool dma_capable) { if (dma_capable) { // SPIRAM is not DMA-capable, so don't bother to ask for it. + #if defined(CONFIG_IDF_TARGET_ESP32P4) + // Cache maintenance must not touch memory outside this allocation. + size = (size + CONFIG_CACHE_L1_CACHE_LINE_SIZE - 1) & ~(CONFIG_CACHE_L1_CACHE_LINE_SIZE - 1); + return heap_caps_aligned_alloc(CONFIG_CACHE_L1_CACHE_LINE_SIZE, size, MALLOC_CAP_8BIT | MALLOC_CAP_DMA); + #else return heap_caps_malloc(size, MALLOC_CAP_8BIT | MALLOC_CAP_DMA); + #endif } void *ptr = NULL; @@ -346,6 +352,13 @@ void *port_malloc(size_t size, bool dma_capable) { return ptr; } +#if !CIRCUITPY_ALL_MEMORY_DMA_CAPABLE +bool port_buffer_is_dma_capable(const void *ptr) { + // Python buffers may share cache lines with other objects, even when aligned. + return false; +} +#endif + void port_free(void *ptr) { heap_caps_free(ptr); } diff --git a/shared-module/usb/core/Device.c b/shared-module/usb/core/Device.c index a3bb449e87e..b5eaf086bb7 100644 --- a/shared-module/usb/core/Device.c +++ b/shared-module/usb/core/Device.c @@ -248,18 +248,20 @@ static mp_obj_t _get_string(const uint16_t *temp_buf) { return utf16le_to_string(temp_buf + 1, utf16_len); } +static void _get_descriptor(usb_core_device_obj_t *self, uint8_t type, uint8_t index, + uint16_t language, void *buffer, size_t len) { + common_hal_usb_core_device_ctrl_transfer(self, 0x80, TUSB_REQ_GET_DESCRIPTOR, + (type << 8) | index, language, buffer, len, 1000); +} + static void _get_langid(usb_core_device_obj_t *self) { if (self->first_langid != 0) { return; } // Two control bytes and one uint16_t language code. uint16_t temp_buf[2]; - _prepare_for_transfer(); - if (!tuh_descriptor_get_string(self->device_address, 0, 0, temp_buf, sizeof(temp_buf), _transfer_done_cb, 0)) { - mp_raise_usb_core_USBError(NULL); - } else if (_wait_for_callback()) { - self->first_langid = temp_buf[1]; - } + _get_descriptor(self, TUSB_DESC_STRING, 0, 0, temp_buf, sizeof(temp_buf)); + self->first_langid = temp_buf[1]; } mp_obj_t common_hal_usb_core_device_get_serial_number(usb_core_device_obj_t *self) { @@ -274,13 +276,8 @@ mp_obj_t common_hal_usb_core_device_get_serial_number(usb_core_device_obj_t *sel } // Device does provide this string, so continue _get_langid(self); - _prepare_for_transfer(); - if (!tuh_descriptor_get_serial_string(self->device_address, self->first_langid, temp_buf, sizeof(temp_buf), _transfer_done_cb, 0)) { - mp_raise_usb_core_USBError(NULL); - } else if (_wait_for_callback()) { - return _get_string(temp_buf); - } - return mp_const_none; + _get_descriptor(self, TUSB_DESC_STRING, descriptor.iSerialNumber, self->first_langid, temp_buf, sizeof(temp_buf)); + return _get_string(temp_buf); } mp_obj_t common_hal_usb_core_device_get_product(usb_core_device_obj_t *self) { @@ -295,13 +292,8 @@ mp_obj_t common_hal_usb_core_device_get_product(usb_core_device_obj_t *self) { } // Device does provide this string, so continue _get_langid(self); - _prepare_for_transfer(); - if (!tuh_descriptor_get_product_string(self->device_address, self->first_langid, temp_buf, sizeof(temp_buf), _transfer_done_cb, 0)) { - mp_raise_usb_core_USBError(NULL); - } else if (_wait_for_callback()) { - return _get_string(temp_buf); - } - return mp_const_none; + _get_descriptor(self, TUSB_DESC_STRING, descriptor.iProduct, self->first_langid, temp_buf, sizeof(temp_buf)); + return _get_string(temp_buf); } mp_obj_t common_hal_usb_core_device_get_manufacturer(usb_core_device_obj_t *self) { @@ -316,13 +308,8 @@ mp_obj_t common_hal_usb_core_device_get_manufacturer(usb_core_device_obj_t *self } // Device does provide this string, so continue _get_langid(self); - _prepare_for_transfer(); - if (!tuh_descriptor_get_manufacturer_string(self->device_address, self->first_langid, temp_buf, sizeof(temp_buf), _transfer_done_cb, 0)) { - mp_raise_usb_core_USBError(NULL); - } else if (_wait_for_callback()) { - return _get_string(temp_buf); - } - return mp_const_none; + _get_descriptor(self, TUSB_DESC_STRING, descriptor.iManufacturer, self->first_langid, temp_buf, sizeof(temp_buf)); + return _get_string(temp_buf); } @@ -382,17 +369,12 @@ void common_hal_usb_core_device_set_configuration(usb_core_device_obj_t *self, m // Get only the config descriptor first. tusb_desc_configuration_t desc; - if (!tuh_descriptor_get_configuration(self->device_address, config_index, &desc, sizeof(desc), _transfer_done_cb, 0) || - !_wait_for_callback()) { - return; - } + _get_descriptor(self, TUSB_DESC_CONFIGURATION, config_index, 0, &desc, sizeof(desc)); // Get the config descriptor plus interfaces and endpoints. self->configuration_descriptor = m_realloc(self->configuration_descriptor, desc.wTotalLength); - if (!tuh_descriptor_get_configuration(self->device_address, config_index, self->configuration_descriptor, desc.wTotalLength, _transfer_done_cb, 0) || - !_wait_for_callback()) { - return; - } + _get_descriptor(self, TUSB_DESC_CONFIGURATION, config_index, 0, self->configuration_descriptor, desc.wTotalLength); + _prepare_for_transfer(); tuh_configuration_set(self->device_address, configuration, _transfer_done_cb, 0); _wait_for_callback(); } @@ -566,6 +548,9 @@ mp_int_t common_hal_usb_core_device_ctrl_transfer(usb_core_device_obj_t *self, _prepare_for_transfer(); if (!tuh_control_xfer(&xfer)) { + if (dma_buffer != buffer) { + port_free(dma_buffer); + } mp_raise_usb_core_USBError(NULL); return 0; } diff --git a/supervisor/shared/usb/tusb_config.h b/supervisor/shared/usb/tusb_config.h index e66b5f71e90..eeb97c7787f 100644 --- a/supervisor/shared/usb/tusb_config.h +++ b/supervisor/shared/usb/tusb_config.h @@ -94,7 +94,6 @@ extern "C" { // Use DMA with the USB peripheral. #if defined(CONFIG_IDF_TARGET_ESP32P4) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) #define CFG_TUD_DWC2_DMA_ENABLE (1) -#define CFG_TUH_DWC2_DMA_ENABLE (1) #endif // Vendor name included in Inquiry response, max 8 bytes @@ -173,6 +172,10 @@ extern "C" { #if CIRCUITPY_USB_HOST || CIRCUITPY_MAX3421E #define CFG_TUH_ENABLED 1 +#if defined(CONFIG_IDF_TARGET_ESP32P4) +#define CFG_TUH_DWC2_DMA_ENABLE (1) +#endif + // Always use PIO to do host on RP2. #if !CIRCUITPY_MAX3421E #define CFG_TUH_RPI_PIO_USB 1 From 8f36b42a262ba2e2e3934ff2aada01f8bf8e5e86 Mon Sep 17 00:00:00 2001 From: Eagle Claw <301410538+ladyada-eagleclaw@users.noreply.github.com> Date: Mon, 5 Oct 2026 15:34:53 -0400 Subject: [PATCH 4/4] usb: preserve descriptor errors and reuse cache-safe DMA buffers Use the L1 cache capability for aligned allocations. Check both buffer extent and cache-line alignment before avoiding a bounce buffer. Stop descriptor parsing after interrupted transfers and retain USBError propagation. Co-authored-by: Limor Fried --- ports/espressif/supervisor/port.c | 18 ++++++++--- ports/raspberrypi/supervisor/port.c | 2 +- shared-module/usb/core/Device.c | 50 +++++++++++++++++++++-------- supervisor/port_heap.h | 6 ++-- 4 files changed, 54 insertions(+), 22 deletions(-) diff --git a/ports/espressif/supervisor/port.c b/ports/espressif/supervisor/port.c index 1dc80c04955..2add704fc74 100644 --- a/ports/espressif/supervisor/port.c +++ b/ports/espressif/supervisor/port.c @@ -20,6 +20,7 @@ #endif #include "esp_mac.h" +#include "esp_memory_utils.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -332,7 +333,7 @@ void port_heap_init(void) { void *port_malloc(size_t size, bool dma_capable) { if (dma_capable) { // SPIRAM is not DMA-capable, so don't bother to ask for it. - #if defined(CONFIG_IDF_TARGET_ESP32P4) + #if defined(SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE) && SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE // Cache maintenance must not touch memory outside this allocation. size = (size + CONFIG_CACHE_L1_CACHE_LINE_SIZE - 1) & ~(CONFIG_CACHE_L1_CACHE_LINE_SIZE - 1); return heap_caps_aligned_alloc(CONFIG_CACHE_L1_CACHE_LINE_SIZE, size, MALLOC_CAP_8BIT | MALLOC_CAP_DMA); @@ -353,9 +354,18 @@ void *port_malloc(size_t size, bool dma_capable) { } #if !CIRCUITPY_ALL_MEMORY_DMA_CAPABLE -bool port_buffer_is_dma_capable(const void *ptr) { - // Python buffers may share cache lines with other objects, even when aligned. - return false; +bool port_buffer_is_dma_capable(const void *ptr, size_t len) { + if (len == 0 || !esp_ptr_dma_capable(ptr) || + !esp_ptr_dma_capable((const uint8_t *)ptr + len - 1)) { + return false; + } + #if defined(SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE) && SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE + // Cache maintenance must stay inside the caller's buffer at both ends. + return ((uintptr_t)ptr % CONFIG_CACHE_L1_CACHE_LINE_SIZE) == 0 && + (len % CONFIG_CACHE_L1_CACHE_LINE_SIZE) == 0; + #else + return true; + #endif } #endif diff --git a/ports/raspberrypi/supervisor/port.c b/ports/raspberrypi/supervisor/port.c index 34e9fc159fd..3ba4e7c806a 100644 --- a/ports/raspberrypi/supervisor/port.c +++ b/ports/raspberrypi/supervisor/port.c @@ -348,7 +348,7 @@ void *port_realloc(void *ptr, size_t size, bool dma_capable) { } #if !CIRCUITPY_ALL_MEMORY_DMA_CAPABLE -bool port_buffer_is_dma_capable(const void *ptr) { +bool port_buffer_is_dma_capable(const void *ptr, size_t len) { // For RP2350, DMA can only access SRAM, not PSRAM // PSRAM addresses are below SRAM_BASE return ptr != NULL && ((size_t)ptr) >= SRAM_BASE; diff --git a/shared-module/usb/core/Device.c b/shared-module/usb/core/Device.c index b5eaf086bb7..16cf68b8a46 100644 --- a/shared-module/usb/core/Device.c +++ b/shared-module/usb/core/Device.c @@ -39,7 +39,7 @@ static size_t _actual_len; #if !CIRCUITPY_ALL_MEMORY_DMA_CAPABLE // Helper to ensure buffer is DMA-capable for transfer operations static uint8_t *_ensure_dma_buffer(usb_core_device_obj_t *self, const uint8_t *buffer, size_t len, bool for_write) { - if (port_buffer_is_dma_capable(buffer)) { + if (port_buffer_is_dma_capable(buffer, len)) { return (uint8_t *)buffer; // Already DMA-capable, use directly } @@ -248,20 +248,32 @@ static mp_obj_t _get_string(const uint16_t *temp_buf) { return utf16le_to_string(temp_buf + 1, utf16_len); } -static void _get_descriptor(usb_core_device_obj_t *self, uint8_t type, uint8_t index, +static bool _get_descriptor(usb_core_device_obj_t *self, uint8_t type, uint8_t index, uint16_t language, void *buffer, size_t len) { - common_hal_usb_core_device_ctrl_transfer(self, 0x80, TUSB_REQ_GET_DESCRIPTOR, + // TinyUSB's descriptor helpers pass buffers directly to DMA. Use the same + // cache-safe buffer handling and USBError propagation as control transfers. + mp_int_t count = common_hal_usb_core_device_ctrl_transfer(self, 0x80, TUSB_REQ_GET_DESCRIPTOR, (type << 8) | index, language, buffer, len, 1000); + if (mp_hal_is_interrupted()) { + return false; + } + if (count == 0) { + mp_raise_usb_core_USBError(NULL); + } + return true; } -static void _get_langid(usb_core_device_obj_t *self) { +static bool _get_langid(usb_core_device_obj_t *self) { if (self->first_langid != 0) { - return; + return true; } // Two control bytes and one uint16_t language code. uint16_t temp_buf[2]; - _get_descriptor(self, TUSB_DESC_STRING, 0, 0, temp_buf, sizeof(temp_buf)); + if (!_get_descriptor(self, TUSB_DESC_STRING, 0, 0, temp_buf, sizeof(temp_buf))) { + return false; + } self->first_langid = temp_buf[1]; + return true; } mp_obj_t common_hal_usb_core_device_get_serial_number(usb_core_device_obj_t *self) { @@ -275,8 +287,10 @@ mp_obj_t common_hal_usb_core_device_get_serial_number(usb_core_device_obj_t *sel return mp_const_none; } // Device does provide this string, so continue - _get_langid(self); - _get_descriptor(self, TUSB_DESC_STRING, descriptor.iSerialNumber, self->first_langid, temp_buf, sizeof(temp_buf)); + if (!_get_langid(self) || + !_get_descriptor(self, TUSB_DESC_STRING, descriptor.iSerialNumber, self->first_langid, temp_buf, sizeof(temp_buf))) { + return mp_const_none; + } return _get_string(temp_buf); } @@ -291,8 +305,10 @@ mp_obj_t common_hal_usb_core_device_get_product(usb_core_device_obj_t *self) { return mp_const_none; } // Device does provide this string, so continue - _get_langid(self); - _get_descriptor(self, TUSB_DESC_STRING, descriptor.iProduct, self->first_langid, temp_buf, sizeof(temp_buf)); + if (!_get_langid(self) || + !_get_descriptor(self, TUSB_DESC_STRING, descriptor.iProduct, self->first_langid, temp_buf, sizeof(temp_buf))) { + return mp_const_none; + } return _get_string(temp_buf); } @@ -307,8 +323,10 @@ mp_obj_t common_hal_usb_core_device_get_manufacturer(usb_core_device_obj_t *self return mp_const_none; } // Device does provide this string, so continue - _get_langid(self); - _get_descriptor(self, TUSB_DESC_STRING, descriptor.iManufacturer, self->first_langid, temp_buf, sizeof(temp_buf)); + if (!_get_langid(self) || + !_get_descriptor(self, TUSB_DESC_STRING, descriptor.iManufacturer, self->first_langid, temp_buf, sizeof(temp_buf))) { + return mp_const_none; + } return _get_string(temp_buf); } @@ -369,11 +387,15 @@ void common_hal_usb_core_device_set_configuration(usb_core_device_obj_t *self, m // Get only the config descriptor first. tusb_desc_configuration_t desc; - _get_descriptor(self, TUSB_DESC_CONFIGURATION, config_index, 0, &desc, sizeof(desc)); + if (!_get_descriptor(self, TUSB_DESC_CONFIGURATION, config_index, 0, &desc, sizeof(desc))) { + return; + } // Get the config descriptor plus interfaces and endpoints. self->configuration_descriptor = m_realloc(self->configuration_descriptor, desc.wTotalLength); - _get_descriptor(self, TUSB_DESC_CONFIGURATION, config_index, 0, self->configuration_descriptor, desc.wTotalLength); + if (!_get_descriptor(self, TUSB_DESC_CONFIGURATION, config_index, 0, self->configuration_descriptor, desc.wTotalLength)) { + return; + } _prepare_for_transfer(); tuh_configuration_set(self->device_address, configuration, _transfer_done_cb, 0); _wait_for_callback(); diff --git a/supervisor/port_heap.h b/supervisor/port_heap.h index 3e6b5a660fa..ff37d02700d 100644 --- a/supervisor/port_heap.h +++ b/supervisor/port_heap.h @@ -28,9 +28,9 @@ void port_free(void *ptr); void *port_realloc(void *ptr, size_t size, bool dma_capable); #if !CIRCUITPY_ALL_MEMORY_DMA_CAPABLE -// Check if a buffer pointer is in DMA-capable memory. DMA-capable memory is also accessible during -// flash operations. -bool port_buffer_is_dma_capable(const void *ptr); +// Check if the full buffer is in DMA-capable memory and meets cache alignment +// requirements. DMA-capable memory is also accessible during flash operations. +bool port_buffer_is_dma_capable(const void *ptr, size_t len); #endif size_t port_heap_get_largest_free_size(void);