From 196bc310e541b92eddbaccbb47b5b8cf15fd0a34 Mon Sep 17 00:00:00 2001 From: "peter.marcisovsky" Date: Fri, 21 Mar 2025 09:07:53 +0100 Subject: [PATCH] feat(usb_host): Move DMA capable memory to external ram on P4 - DWC-OTG internal DMA can access psram on esp32p4 - Move DMA memory buffs to psram, to save internal ram - HCD tests and MSC example runs in CI with psram enabled --- components/usb/CMakeLists.txt | 5 ++++ components/usb/Kconfig | 10 +++++++ components/usb/hcd_dwc.c | 14 +++++++--- components/usb/test_apps/hcd/README.md | 5 ++-- .../usb/test_apps/hcd/main/test_hcd_common.c | 12 ++++++++- .../usb/test_apps/hcd/pytest_usb_hcd.py | 20 ++++++++------ components/usb/test_apps/hcd/sdkconfig.ci | 0 .../test_apps/hcd/sdkconfig.ci.esp32p4_psram | 12 +++++++++ components/usb/test_apps/usb_host/README.md | 5 ++-- .../usb/test_apps/usb_host/pytest_usb_host.py | 8 +++--- components/usb/usb_private.c | 11 +++++++- .../usb/host/msc/pytest_usb_host_msc.py | 27 ++++++++++++------- .../peripherals/usb/host/msc/sdkconfig.ci | 0 .../usb/host/msc/sdkconfig.ci.esp32p4_psram | 11 ++++++++ 14 files changed, 108 insertions(+), 32 deletions(-) create mode 100644 components/usb/test_apps/hcd/sdkconfig.ci create mode 100644 components/usb/test_apps/hcd/sdkconfig.ci.esp32p4_psram create mode 100644 examples/peripherals/usb/host/msc/sdkconfig.ci create mode 100644 examples/peripherals/usb/host/msc/sdkconfig.ci.esp32p4_psram diff --git a/components/usb/CMakeLists.txt b/components/usb/CMakeLists.txt index ba658c5f6a..75c1a4d4f8 100644 --- a/components/usb/CMakeLists.txt +++ b/components/usb/CMakeLists.txt @@ -12,6 +12,11 @@ set(priv_includes) # Thus, always add the (private) requirements, regardless of Kconfig set(priv_requires esp_driver_gpio esp_mm) # usb_phy driver relies on gpio driver API +# Explicitly add psram component for esp32p4, as the USB-DWC internal DMA can access PSRAM on esp32p4 +if(${target} STREQUAL "esp32p4") + list(APPEND priv_requires esp_psram) +endif() + if(CONFIG_SOC_USB_OTG_SUPPORTED) list(APPEND srcs "hcd_dwc.c" "enum.c" diff --git a/components/usb/Kconfig b/components/usb/Kconfig index ce8a032065..2c23821290 100644 --- a/components/usb/Kconfig +++ b/components/usb/Kconfig @@ -175,6 +175,16 @@ menu "USB-OTG" If enabled, the enumeration filter callback can be set via 'usb_host_config_t' when calling 'usb_host_install()'. + config USB_HOST_DWC_DMA_CAP_MEMORY_IN_PSRAM + depends on IDF_TARGET_ESP32P4 && SPIRAM + bool "Allocate USB_DWC DMA capable memory in PSRAM" + default n + help + In the ESP32P4, the USB-DWC internal DMA can access external RAM. Enabling this configuration can save + internal RAM by allocating the memory buffers used by the USB-DWC peripheral's DMA to external RAM. + However, it introduces minor performance degradation due to the overhead of accessing external RAM. + # Todo: IDF-11368 (aligned memory alloc size) + # Hidden or compatibility options config USB_OTG_SUPPORTED # Invisible config kept for compatibility diff --git a/components/usb/hcd_dwc.c b/components/usb/hcd_dwc.c index 1325469a46..bb81c1b509 100644 --- a/components/usb/hcd_dwc.c +++ b/components/usb/hcd_dwc.c @@ -7,6 +7,7 @@ #include #include #include +#include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" @@ -31,8 +32,6 @@ // --------------------- Constants ------------------------- -#define XFER_DESC_LIST_CAPS (MALLOC_CAP_DMA | MALLOC_CAP_CACHE_ALIGNED | MALLOC_CAP_INTERNAL) - #define INIT_DELAY_MS 30 // A delay of at least 25ms to enter Host mode. Make it 30ms to be safe #define DEBOUNCE_DELAY_MS CONFIG_USB_HOST_DEBOUNCE_DELAY_MS #define RESET_HOLD_MS CONFIG_USB_HOST_RESET_HOLD_MS @@ -47,6 +46,12 @@ // ----------------------- Configs ------------------------- +#ifdef CONFIG_USB_HOST_DWC_DMA_CAP_MEMORY_IN_PSRAM // In esp32p4, the USB-DWC internal DMA can access external RAM +#define XFER_DESC_LIST_CAPS (MALLOC_CAP_DMA | MALLOC_CAP_CACHE_ALIGNED | MALLOC_CAP_SPIRAM) +#else +#define XFER_DESC_LIST_CAPS (MALLOC_CAP_DMA | MALLOC_CAP_CACHE_ALIGNED | MALLOC_CAP_INTERNAL) +#endif + #define FRAME_LIST_LEN USB_HAL_FRAME_LIST_LEN_32 #define NUM_BUFFERS 2 @@ -1512,7 +1517,7 @@ static dma_buffer_block_t *buffer_block_alloc(usb_transfer_type_t type) break; } - // DMA buffer lock: Software structure for managing the transfer buffer + // DMA buffer block: Software structure for managing the transfer buffer dma_buffer_block_t *buffer = calloc(1, sizeof(dma_buffer_block_t)); if (buffer == NULL) { return NULL; @@ -1520,7 +1525,8 @@ static dma_buffer_block_t *buffer_block_alloc(usb_transfer_type_t type) // Transfer descriptor list: Must be 512 aligned and DMA capable (USB-DWC requirement) and its size must be cache aligned void *xfer_desc_list = heap_caps_aligned_calloc(USB_DWC_QTD_LIST_MEM_ALIGN, desc_list_len * sizeof(usb_dwc_ll_dma_qtd_t), 1, XFER_DESC_LIST_CAPS); - if (xfer_desc_list == NULL) { + + if ((xfer_desc_list == NULL) || ((uintptr_t)xfer_desc_list & (USB_DWC_QTD_LIST_MEM_ALIGN - 1))) { free(buffer); heap_caps_free(xfer_desc_list); return NULL; diff --git a/components/usb/test_apps/hcd/README.md b/components/usb/test_apps/hcd/README.md index 01e95ae973..51542cb6c8 100644 --- a/components/usb/test_apps/hcd/README.md +++ b/components/usb/test_apps/hcd/README.md @@ -3,8 +3,9 @@ # USB: Host test application -There are two sets of tests in this application: +There are three sets of tests in this application: 1. Low-speed: Expects low-speed USB mouse with interrupt endpoint to be connected 2. Full-speed: Expects full-speed USB flash disk with 2 bulk endpoints to be connected +3. High-speed: Expects high-speed USB flash disk with 2 bulk endpoints to be connected -For running these tests locally, you will have to update device definitions (VID, PID, ...) in [test_usb_mock_classes.h](../common/test_usb_mock_classes.h). +For running these tests locally, you will have to update device definitions (VID, PID, ...) in [dev_msc.c](../common/dev_msc.c), [dev_hid.c](../common/dev_hid.c) and [dev_isoc.c](../common/dev_isoc.c). diff --git a/components/usb/test_apps/hcd/main/test_hcd_common.c b/components/usb/test_apps/hcd/main/test_hcd_common.c index 93dd9404eb..9c78d10788 100644 --- a/components/usb/test_apps/hcd/main/test_hcd_common.c +++ b/components/usb/test_apps/hcd/main/test_hcd_common.c @@ -22,6 +22,10 @@ #include "mock_msc.h" #include "unity.h" +// ----------------------------------------------------- Macros -------------------------------------------------------- + +// --------------------- Constants ------------------------- + #define PORT_NUM 1 #define EVENT_QUEUE_LEN 5 #define ENUM_ADDR 1 // Device address to use for tests that enumerate the device @@ -279,9 +283,15 @@ void test_hcd_pipe_free(hcd_pipe_handle_t pipe_hdl) } #include "esp_private/esp_cache_private.h" -#define DATA_BUFFER_CAPS (MALLOC_CAP_DMA | MALLOC_CAP_CACHE_ALIGNED) + #define ALIGN_UP(num, align) ((align) == 0 ? (num) : (((num) + ((align) - 1)) & ~((align) - 1))) +#ifdef CONFIG_USB_HOST_DWC_DMA_CAP_MEMORY_IN_PSRAM // In esp32p4, the USB-DWC internal DMA can access external RAM +#define DATA_BUFFER_CAPS (MALLOC_CAP_DMA | MALLOC_CAP_CACHE_ALIGNED | MALLOC_CAP_SPIRAM) +#else +#define DATA_BUFFER_CAPS (MALLOC_CAP_DMA | MALLOC_CAP_CACHE_ALIGNED | MALLOC_CAP_INTERNAL) +#endif + urb_t *test_hcd_alloc_urb(int num_isoc_packets, size_t data_buffer_size) { // Allocate a URB and data buffer diff --git a/components/usb/test_apps/hcd/pytest_usb_hcd.py b/components/usb/test_apps/hcd/pytest_usb_hcd.py index 7df14e1779..4303fabd16 100644 --- a/components/usb/test_apps/hcd/pytest_usb_hcd.py +++ b/components/usb/test_apps/hcd/pytest_usb_hcd.py @@ -1,16 +1,20 @@ -# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut -@pytest.mark.esp32s2 -@pytest.mark.esp32s3 -@pytest.mark.esp32p4 -@pytest.mark.temp_skip_ci(targets=['esp32s2'], reason='lack of runners with usb_host_flash_disk tag') +CONFIGS = [ + pytest.param('default', marks=[pytest.mark.esp32s2, pytest.mark.esp32s3, pytest.mark.esp32p4]), + pytest.param('esp32p4_psram', marks=[pytest.mark.esp32p4]) +] + + @pytest.mark.usb_host_flash_disk +@pytest.mark.temp_skip_ci(targets=['esp32s2'], reason='lack of runners with usb_host_flash_disk tag') +@pytest.mark.parametrize('config', CONFIGS, indirect=True) def test_usb_hcd(dut: Dut) -> None: - if (dut.target == 'esp32s3'): - dut.run_all_single_board_cases(group='full_speed') + if dut.target == 'esp32p4': + dut.run_all_single_board_cases(group='high_speed', reset=True) else: - dut.run_all_single_board_cases(group='high_speed') + dut.run_all_single_board_cases(group='full_speed', reset=True) diff --git a/components/usb/test_apps/hcd/sdkconfig.ci b/components/usb/test_apps/hcd/sdkconfig.ci new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/usb/test_apps/hcd/sdkconfig.ci.esp32p4_psram b/components/usb/test_apps/hcd/sdkconfig.ci.esp32p4_psram new file mode 100644 index 0000000000..cd0a2aa9d0 --- /dev/null +++ b/components/usb/test_apps/hcd/sdkconfig.ci.esp32p4_psram @@ -0,0 +1,12 @@ +CONFIG_IDF_TARGET="esp32p4" + +# Enable experimental features, to set PSRAM frequency to 200 MHz +CONFIG_IDF_EXPERIMENTAL_FEATURES=y + +# Enable PSRAM and set PSRAM frequency +CONFIG_SPIRAM=y +CONFIG_SPIRAM_SPEED_200M=y +# CONFIG_SPIRAM_MEMTEST is not set + +# Allocate USB_DWC DMA capable memory in PSRAM +CONFIG_USB_HOST_DWC_DMA_CAP_MEMORY_IN_PSRAM=y diff --git a/components/usb/test_apps/usb_host/README.md b/components/usb/test_apps/usb_host/README.md index c76482cea3..2b702ed65a 100644 --- a/components/usb/test_apps/usb_host/README.md +++ b/components/usb/test_apps/usb_host/README.md @@ -3,9 +3,10 @@ # USB: Host test application -There are two sets of tests in this application: +There are three sets of tests in this application: 1. Low-speed: Expects low-speed USB mouse with interrupt endpoint to be connected 2. Full-speed: Expects full-speed USB flash disk with 2 bulk endpoints to be connected +3. High-speed: Expects high-speed USB flash disk with 2 bulk endpoints to be connected -For running these tests locally, you will have to update device definitions (VID, PID, ...) in [mock_msc.h](../common/mock_msc.h) and [mock_hid.h](../common/mock_hid.h). +For running these tests locally, you will have to update device definitions (VID, PID, ...) in [dev_msc.c](../common/dev_msc.c) and [dev_hid.c](../common/dev_hid.c). diff --git a/components/usb/test_apps/usb_host/pytest_usb_host.py b/components/usb/test_apps/usb_host/pytest_usb_host.py index 309ba6e510..0d6e260333 100644 --- a/components/usb/test_apps/usb_host/pytest_usb_host.py +++ b/components/usb/test_apps/usb_host/pytest_usb_host.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut @@ -10,7 +10,7 @@ from pytest_embedded import Dut @pytest.mark.temp_skip_ci(targets=['esp32s2'], reason='lack of runners with usb_host_flash_disk tag') @pytest.mark.usb_host_flash_disk def test_usb_host(dut: Dut) -> None: - if (dut.target == 'esp32s3'): - dut.run_all_single_board_cases(group='full_speed') + if dut.target == 'esp32p4': + dut.run_all_single_board_cases(group='high_speed', reset=True) else: - dut.run_all_single_board_cases(group='high_speed') + dut.run_all_single_board_cases(group='full_speed', reset=True) diff --git a/components/usb/usb_private.c b/components/usb/usb_private.c index 3891c03726..754f0940ca 100644 --- a/components/usb/usb_private.c +++ b/components/usb/usb_private.c @@ -9,11 +9,20 @@ #include "usb_private.h" #include "usb/usb_types_ch9.h" +// ----------------------------------------------------- Macros -------------------------------------------------------- + #if !CONFIG_IDF_TARGET_LINUX #include "esp_private/esp_cache_private.h" #define ALIGN_UP(num, align) ((align) == 0 ? (num) : (((num) + ((align) - 1)) & ~((align) - 1))) #endif -#define DATA_BUFFER_CAPS (MALLOC_CAP_DMA | MALLOC_CAP_CACHE_ALIGNED) + +// ----------------------- Configs ------------------------- + +#ifdef CONFIG_USB_HOST_DWC_DMA_CAP_MEMORY_IN_PSRAM // In esp32p4, the USB-DWC internal DMA can access external RAM +#define DATA_BUFFER_CAPS (MALLOC_CAP_DMA | MALLOC_CAP_CACHE_ALIGNED | MALLOC_CAP_SPIRAM) +#else +#define DATA_BUFFER_CAPS (MALLOC_CAP_DMA | MALLOC_CAP_CACHE_ALIGNED | MALLOC_CAP_INTERNAL) +#endif urb_t *urb_alloc(size_t data_buffer_size, int num_isoc_packets) { diff --git a/examples/peripherals/usb/host/msc/pytest_usb_host_msc.py b/examples/peripherals/usb/host/msc/pytest_usb_host_msc.py index cae9851d4c..cb9e8c78e5 100644 --- a/examples/peripherals/usb/host/msc/pytest_usb_host_msc.py +++ b/examples/peripherals/usb/host/msc/pytest_usb_host_msc.py @@ -1,15 +1,22 @@ -# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut -@pytest.mark.esp32s2 -@pytest.mark.esp32s3 -@pytest.mark.esp32p4 -@pytest.mark.temp_skip_ci(targets=['esp32s2'], reason='lack of runners with usb_host_flash_disk tag') +CONFIGS = [ + pytest.param('default', marks=[pytest.mark.esp32s2, pytest.mark.esp32s3, pytest.mark.esp32p4]), + pytest.param('esp32p4_psram', marks=[pytest.mark.esp32p4]) +] + + @pytest.mark.usb_host_flash_disk +@pytest.mark.temp_skip_ci(targets=['esp32s2'], reason='lack of runners with usb_host_flash_disk tag') +@pytest.mark.parametrize('config', CONFIGS, indirect=True) def test_usb_host_msc_example(dut: Dut) -> None: + # Check whether the USB-DWC DMA capable memory is allocated in PSRAM + usb_dwc_in_psram = bool(dut.app.sdkconfig.get('USB_HOST_DWC_DMA_CAP_MEMORY_IN_PSRAM')) + # Get wMaxPacketSize to get USB device speed max_packet_size = int(dut.expect(r'wMaxPacketSize (\d{2,3})')[1].decode()) @@ -21,14 +28,14 @@ def test_usb_host_msc_example(dut: Dut) -> None: read_throughput = float(dut.expect(r'example: Read speed ([0-9]*[.]?[0-9]+) MiB')[1].decode()) # Set write and read throughput limits - if (max_packet_size == 512): # wMaxPacketSize = 512 for HS - write_throughput_limit = 4.9 - read_throughput_limit = 11.5 - else: # wMaxPacketSize = 64 for FS + if max_packet_size == 512: # wMaxPacketSize = 512 for HS + write_throughput_limit = 3.5 if usb_dwc_in_psram else 4.9 + read_throughput_limit = 9.9 if usb_dwc_in_psram else 11.5 + else: # wMaxPacketSize = 64 for FS write_throughput_limit = 0.9 read_throughput_limit = 1.0 - # These values should be updated for HS targets + # Evaluate the speed measurements if write_throughput > write_throughput_limit: print('Write throughput put OK') else: diff --git a/examples/peripherals/usb/host/msc/sdkconfig.ci b/examples/peripherals/usb/host/msc/sdkconfig.ci new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/peripherals/usb/host/msc/sdkconfig.ci.esp32p4_psram b/examples/peripherals/usb/host/msc/sdkconfig.ci.esp32p4_psram new file mode 100644 index 0000000000..fa9e86eda7 --- /dev/null +++ b/examples/peripherals/usb/host/msc/sdkconfig.ci.esp32p4_psram @@ -0,0 +1,11 @@ +CONFIG_IDF_TARGET="esp32p4" + +# Enable experimental features, to set PSRAM frequency to 200 MHz +CONFIG_IDF_EXPERIMENTAL_FEATURES=y + +# Enable PSRAM and set PSRAM frequency +CONFIG_SPIRAM=y +CONFIG_SPIRAM_SPEED_200M=y + +# Allocate USB_DWC DMA capable memory in PSRAM +CONFIG_USB_HOST_DWC_DMA_CAP_MEMORY_IN_PSRAM=y