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
This commit is contained in:
peter.marcisovsky
2025-03-21 09:07:53 +01:00
parent acea1fc047
commit 196bc310e5
14 changed files with 108 additions and 32 deletions

View File

@ -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"

View File

@ -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

View File

@ -7,6 +7,7 @@
#include <stdint.h>
#include <string.h>
#include <sys/queue.h>
#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;

View File

@ -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).

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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).

View File

@ -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)

View File

@ -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)
{

View File

@ -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:

View File

@ -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