From cd5b3877bf0baa3814796d01bee97794f11a95e4 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/common/hcd_common.c | 12 +++++++++++- components/usb/test_apps/hcd/README.md | 5 +++-- components/usb/test_apps/hcd/pytest_usb_hcd.py | 12 ++++++++---- .../usb/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 | 6 +++--- components/usb/usb_private.c | 11 ++++++++++- .../usb/host/msc/pytest_usb_host_msc.py | 15 +++++++++++---- examples/peripherals/usb/host/msc/sdkconfig.ci | 0 .../usb/host/msc/sdkconfig.ci.esp32p4_psram | 11 +++++++++++ 13 files changed, 97 insertions(+), 21 deletions(-) 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 fc4c06026e..1d8057c8d1 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 36d8ab84de..e9ed4899d4 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" @@ -32,8 +33,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 @@ -48,6 +47,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 @@ -1514,7 +1519,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; @@ -1522,7 +1527,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/common/hcd_common.c b/components/usb/test_apps/common/hcd_common.c index 74c8512dcd..9cef709762 100644 --- a/components/usb/test_apps/common/hcd_common.c +++ b/components/usb/test_apps/common/hcd_common.c @@ -23,6 +23,10 @@ #include "unity.h" #include "sdkconfig.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 @@ -261,9 +265,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/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/pytest_usb_hcd.py b/components/usb/test_apps/hcd/pytest_usb_hcd.py index 9b0f3048f1..66c2c416bb 100644 --- a/components/usb/test_apps/hcd/pytest_usb_hcd.py +++ b/components/usb/test_apps/hcd/pytest_usb_hcd.py @@ -7,9 +7,13 @@ from pytest_embedded_idf.utils import idf_parametrize @pytest.mark.temp_skip_ci(targets=['esp32s2'], reason='lack of runners with usb_host_flash_disk tag') @pytest.mark.usb_host_flash_disk -@idf_parametrize('target', ['esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']) +@idf_parametrize( + 'config,target', + [('default', 'esp32s2'), ('default', 'esp32s3'), ('default', 'esp32p4'), ('esp32p4_psram', 'esp32p4')], + indirect=['config', 'target'], +) def test_usb_hcd(dut: Dut) -> None: - if dut.target == 'esp32s3': - dut.run_all_single_board_cases(group='full_speed', reset=True) - else: + if dut.target == 'esp32p4': dut.run_all_single_board_cases(group='high_speed', reset=True) + else: + dut.run_all_single_board_cases(group='full_speed', reset=True) 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 8d712c673f..a797423bb3 100644 --- a/components/usb/test_apps/usb_host/pytest_usb_host.py +++ b/components/usb/test_apps/usb_host/pytest_usb_host.py @@ -9,7 +9,7 @@ from pytest_embedded_idf.utils import idf_parametrize @pytest.mark.usb_host_flash_disk @idf_parametrize('target', ['esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']) def test_usb_host(dut: Dut) -> None: - if dut.target == 'esp32s3': - dut.run_all_single_board_cases(group='full_speed', reset=True) - else: + if dut.target == 'esp32p4': dut.run_all_single_board_cases(group='high_speed', reset=True) + else: + 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 a2c73d8362..5a4fda394a 100644 --- a/examples/peripherals/usb/host/msc/pytest_usb_host_msc.py +++ b/examples/peripherals/usb/host/msc/pytest_usb_host_msc.py @@ -7,8 +7,15 @@ from pytest_embedded_idf.utils import idf_parametrize @pytest.mark.temp_skip_ci(targets=['esp32s2'], reason='lack of runners with usb_host_flash_disk tag') @pytest.mark.usb_host_flash_disk -@idf_parametrize('target', ['esp32s2', 'esp32s3', 'esp32p4'], indirect=['target']) +@idf_parametrize( + 'config,target', + [('default', 'esp32s2'), ('default', 'esp32s3'), ('default', 'esp32p4'), ('esp32p4_psram', 'esp32p4')], + indirect=['config', 'target'], +) 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,13 +28,13 @@ def test_usb_host_msc_example(dut: Dut) -> None: # 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 + 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