Skip to content

Commit c2ad10e

Browse files
authored
Merge pull request #10633 from tannewt/fix_usb_buffers
Ensure USB host buffers are allocated in internal RAM
2 parents 7b7d485 + 9f771b3 commit c2ad10e

File tree

6 files changed

+158
-12
lines changed

6 files changed

+158
-12
lines changed

locale/circuitpython.pot

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,10 @@ msgstr ""
844844
msgid "Coordinate arrays types have different sizes"
845845
msgstr ""
846846

847+
#: shared-module/usb/core/Device.c
848+
msgid "Could not allocate DMA capable buffer"
849+
msgstr ""
850+
847851
#: ports/espressif/common-hal/rclcpy/Publisher.c
848852
msgid "Could not publish to ROS topic"
849853
msgstr ""

ports/raspberrypi/mpconfigport.mk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ CIRCUITPY_CYW43_INIT_DELAY ?= 1000
5959
endif
6060

6161
ifeq ($(CHIP_VARIANT),RP2350)
62+
# RP2350 has PSRAM that is not DMA-capable
63+
CIRCUITPY_ALL_MEMORY_DMA_CAPABLE = 0
64+
6265
# This needs to be implemented.
6366
CIRCUITPY_ALARM = 0
6467
# Default PICODVI on because it doesn't require much code in RAM to talk to HSTX.

ports/raspberrypi/supervisor/port.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,14 @@ void *port_realloc(void *ptr, size_t size, bool dma_capable) {
302302
return new_ptr;
303303
}
304304

305+
#if !CIRCUITPY_ALL_MEMORY_DMA_CAPABLE
306+
bool port_buffer_is_dma_capable(const void *ptr) {
307+
// For RP2350, DMA can only access SRAM, not PSRAM
308+
// PSRAM addresses are below SRAM_BASE
309+
return ptr != NULL && ((size_t)ptr) >= SRAM_BASE;
310+
}
311+
#endif
312+
305313
static bool max_size_walker(void *ptr, size_t size, int used, void *user) {
306314
size_t *max_size = (size_t *)user;
307315
if (!used && *max_size < size) {

py/circuitpy_mpconfig.mk

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ CFLAGS += -DCIRCUITPY_ALARM=$(CIRCUITPY_ALARM)
9898
CIRCUITPY_ALARM_TOUCH ?= $(CIRCUITPY_ALARM)
9999
CFLAGS += -DCIRCUITPY_ALARM_TOUCH=$(CIRCUITPY_ALARM_TOUCH)
100100

101+
# Enable DMA buffer management for platforms where not all memory is DMA-capable
102+
# Platforms with PSRAM or other non-DMA memory should set this to 0
103+
CIRCUITPY_ALL_MEMORY_DMA_CAPABLE ?= 1
104+
CFLAGS += -DCIRCUITPY_ALL_MEMORY_DMA_CAPABLE=$(CIRCUITPY_ALL_MEMORY_DMA_CAPABLE)
105+
101106
CIRCUITPY_ANALOGBUFIO ?= 0
102107
CFLAGS += -DCIRCUITPY_ANALOGBUFIO=$(CIRCUITPY_ANALOGBUFIO)
103108

shared-module/usb/core/Device.c

Lines changed: 132 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include "shared-bindings/usb/core/Device.h"
99

1010
#include "tusb_config.h"
11+
#include "supervisor/port.h"
12+
#include "supervisor/port_heap.h"
1113

1214
#include "lib/tinyusb/src/host/hcd.h"
1315
#include "lib/tinyusb/src/host/usbh.h"
@@ -33,6 +35,28 @@ void tuh_umount_cb(uint8_t dev_addr) {
3335

3436
static xfer_result_t _xfer_result;
3537
static size_t _actual_len;
38+
39+
#if !CIRCUITPY_ALL_MEMORY_DMA_CAPABLE
40+
// Helper to ensure buffer is DMA-capable for transfer operations
41+
static uint8_t *_ensure_dma_buffer(usb_core_device_obj_t *self, const uint8_t *buffer, size_t len, bool for_write) {
42+
if (port_buffer_is_dma_capable(buffer)) {
43+
return (uint8_t *)buffer; // Already DMA-capable, use directly
44+
}
45+
46+
uint8_t *dma_buffer = port_malloc(len, true); // true = DMA capable
47+
if (dma_buffer == NULL) {
48+
return NULL; // Allocation failed
49+
}
50+
51+
// Copy data to DMA buffer if writing
52+
if (for_write && buffer != NULL) {
53+
memcpy(dma_buffer, buffer, len);
54+
}
55+
56+
return dma_buffer;
57+
}
58+
59+
#endif
3660
bool common_hal_usb_core_device_construct(usb_core_device_obj_t *self, uint8_t device_address) {
3761
if (!tuh_inited()) {
3862
mp_raise_RuntimeError(MP_ERROR_TEXT("No usb host port initialized"));
@@ -133,7 +157,24 @@ static void _prepare_for_transfer(void) {
133157
_actual_len = 0;
134158
}
135159

136-
static size_t _handle_timed_transfer_callback(tuh_xfer_t *xfer, mp_int_t timeout) {
160+
static void _abort_transfer(tuh_xfer_t *xfer) {
161+
bool aborted = tuh_edpt_abort_xfer(xfer->daddr, xfer->ep_addr);
162+
if (aborted) {
163+
// If the transfer was aborted, then we can continue.
164+
return;
165+
}
166+
uint32_t start_time = supervisor_ticks_ms32();
167+
// If not, we need to wait for it to finish, otherwise we may free memory out from under it.
168+
// Limit the wait time to 10 milliseconds to avoid blocking indefinitely.
169+
while (_xfer_result == XFER_RESULT_INVALID && (supervisor_ticks_ms32() - start_time < 10)) {
170+
// The background tasks include TinyUSB which will call the function
171+
// we provided above. In other words, the callback isn't in an interrupt.
172+
RUN_BACKGROUND_TASKS;
173+
}
174+
}
175+
176+
// Only frees the transfer buffer on error.
177+
static size_t _handle_timed_transfer_callback(tuh_xfer_t *xfer, mp_int_t timeout, bool our_buffer) {
137178
if (xfer == NULL) {
138179
mp_raise_usb_core_USBError(NULL);
139180
return 0;
@@ -148,12 +189,15 @@ static size_t _handle_timed_transfer_callback(tuh_xfer_t *xfer, mp_int_t timeout
148189
}
149190
if (mp_hal_is_interrupted()) {
150191
// Handle case of VM being interrupted by Ctrl-C or autoreload
151-
tuh_edpt_abort_xfer(xfer->daddr, xfer->ep_addr);
192+
_abort_transfer(xfer);
152193
return 0;
153194
}
154195
// Handle transfer result code from TinyUSB
155196
xfer_result_t result = _xfer_result;
156197
_xfer_result = XFER_RESULT_INVALID;
198+
if (our_buffer && result != XFER_RESULT_SUCCESS && result != XFER_RESULT_INVALID) {
199+
port_free(xfer->buffer);
200+
}
157201
switch (result) {
158202
case XFER_RESULT_SUCCESS:
159203
return _actual_len;
@@ -170,8 +214,11 @@ static size_t _handle_timed_transfer_callback(tuh_xfer_t *xfer, mp_int_t timeout
170214
break;
171215
case XFER_RESULT_INVALID:
172216
// This timeout comes from CircuitPython, not TinyUSB, so tell TinyUSB
173-
// to stop the transfer
174-
tuh_edpt_abort_xfer(xfer->daddr, xfer->ep_addr);
217+
// to stop the transfer and then wait to free the buffer.
218+
_abort_transfer(xfer);
219+
if (our_buffer) {
220+
port_free(xfer->buffer);
221+
}
175222
mp_raise_usb_core_USBTimeoutError();
176223
break;
177224
}
@@ -335,14 +382,18 @@ void common_hal_usb_core_device_set_configuration(usb_core_device_obj_t *self, m
335382
_wait_for_callback();
336383
}
337384

338-
static size_t _xfer(tuh_xfer_t *xfer, mp_int_t timeout) {
385+
// Raises an exception on failure. Returns the number of bytes transferred (maybe zero) on success.
386+
static size_t _xfer(tuh_xfer_t *xfer, mp_int_t timeout, bool our_buffer) {
339387
_prepare_for_transfer();
340388
xfer->complete_cb = _transfer_done_cb;
341389
if (!tuh_edpt_xfer(xfer)) {
390+
if (our_buffer) {
391+
port_free(xfer->buffer);
392+
}
342393
mp_raise_usb_core_USBError(NULL);
343394
return 0;
344395
}
345-
return _handle_timed_transfer_callback(xfer, timeout);
396+
return _handle_timed_transfer_callback(xfer, timeout, our_buffer);
346397
}
347398

348399
static bool _open_endpoint(usb_core_device_obj_t *self, mp_int_t endpoint) {
@@ -399,25 +450,65 @@ mp_int_t common_hal_usb_core_device_write(usb_core_device_obj_t *self, mp_int_t
399450
mp_raise_usb_core_USBError(NULL);
400451
return 0;
401452
}
453+
454+
#if !CIRCUITPY_ALL_MEMORY_DMA_CAPABLE
455+
// Ensure buffer is in DMA-capable memory
456+
uint8_t *dma_buffer = _ensure_dma_buffer(self, buffer, len, true); // true = for write
457+
if (dma_buffer == NULL) {
458+
mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("Could not allocate DMA capable buffer"));
459+
return 0;
460+
}
461+
#else
462+
uint8_t *dma_buffer = (uint8_t *)buffer; // All memory is DMA-capable
463+
#endif
464+
402465
tuh_xfer_t xfer;
403466
xfer.daddr = self->device_address;
404467
xfer.ep_addr = endpoint;
405-
xfer.buffer = (uint8_t *)buffer;
468+
xfer.buffer = dma_buffer;
406469
xfer.buflen = len;
407-
return _xfer(&xfer, timeout);
470+
size_t result = _xfer(&xfer, timeout, dma_buffer != buffer);
471+
#if !CIRCUITPY_ALL_MEMORY_DMA_CAPABLE
472+
if (dma_buffer != buffer) {
473+
port_free(dma_buffer);
474+
}
475+
#endif
476+
return result;
408477
}
409478

410479
mp_int_t common_hal_usb_core_device_read(usb_core_device_obj_t *self, mp_int_t endpoint, uint8_t *buffer, mp_int_t len, mp_int_t timeout) {
411480
if (!_open_endpoint(self, endpoint)) {
412481
mp_raise_usb_core_USBError(NULL);
413482
return 0;
414483
}
484+
485+
#if !CIRCUITPY_ALL_MEMORY_DMA_CAPABLE
486+
// Ensure buffer is in DMA-capable memory
487+
uint8_t *dma_buffer = _ensure_dma_buffer(self, buffer, len, false); // false = for read
488+
if (dma_buffer == NULL) {
489+
mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("Could not allocate DMA capable buffer"));
490+
return 0;
491+
}
492+
#else
493+
uint8_t *dma_buffer = buffer; // All memory is DMA-capable
494+
#endif
495+
415496
tuh_xfer_t xfer;
416497
xfer.daddr = self->device_address;
417498
xfer.ep_addr = endpoint;
418-
xfer.buffer = buffer;
499+
xfer.buffer = dma_buffer;
419500
xfer.buflen = len;
420-
return _xfer(&xfer, timeout);
501+
mp_int_t result = _xfer(&xfer, timeout, dma_buffer != buffer);
502+
503+
#if !CIRCUITPY_ALL_MEMORY_DMA_CAPABLE
504+
// Copy data back to original buffer if needed
505+
if (dma_buffer != buffer) {
506+
memcpy(buffer, dma_buffer, result);
507+
port_free(dma_buffer);
508+
}
509+
#endif
510+
511+
return result;
421512
}
422513

423514
mp_int_t common_hal_usb_core_device_ctrl_transfer(usb_core_device_obj_t *self,
@@ -426,6 +517,23 @@ mp_int_t common_hal_usb_core_device_ctrl_transfer(usb_core_device_obj_t *self,
426517
uint8_t *buffer, mp_int_t len, mp_int_t timeout) {
427518
// Timeout is in ms.
428519

520+
#if !CIRCUITPY_ALL_MEMORY_DMA_CAPABLE
521+
// Determine if this is a write (host-to-device) or read (device-to-host) transfer
522+
bool is_write = (bmRequestType & 0x80) == 0; // Bit 7: 0=host-to-device, 1=device-to-host
523+
524+
// Ensure buffer is in DMA-capable memory
525+
uint8_t *dma_buffer = NULL;
526+
if (len > 0 && buffer != NULL) {
527+
dma_buffer = _ensure_dma_buffer(self, buffer, len, is_write);
528+
if (dma_buffer == NULL) {
529+
mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("Could not allocate DMA capable buffer"));
530+
return 0;
531+
}
532+
}
533+
#else
534+
uint8_t *dma_buffer = buffer; // All memory is DMA-capable
535+
#endif
536+
429537
tusb_control_request_t request = {
430538
.bmRequestType = bmRequestType,
431539
.bRequest = bRequest,
@@ -437,7 +545,7 @@ mp_int_t common_hal_usb_core_device_ctrl_transfer(usb_core_device_obj_t *self,
437545
.daddr = self->device_address,
438546
.ep_addr = 0,
439547
.setup = &request,
440-
.buffer = buffer,
548+
.buffer = dma_buffer,
441549
.complete_cb = _transfer_done_cb,
442550
};
443551

@@ -446,7 +554,19 @@ mp_int_t common_hal_usb_core_device_ctrl_transfer(usb_core_device_obj_t *self,
446554
mp_raise_usb_core_USBError(NULL);
447555
return 0;
448556
}
449-
return (mp_int_t)_handle_timed_transfer_callback(&xfer, timeout);
557+
mp_int_t result = (mp_int_t)_handle_timed_transfer_callback(&xfer, timeout, dma_buffer != buffer);
558+
559+
#if !CIRCUITPY_ALL_MEMORY_DMA_CAPABLE
560+
if (dma_buffer != buffer) {
561+
// Copy data back to original buffer if this was a read transfer
562+
if (buffer != NULL && !is_write) {
563+
memcpy(buffer, dma_buffer, result);
564+
}
565+
port_free(dma_buffer);
566+
}
567+
#endif
568+
569+
return result;
450570
}
451571

452572
bool common_hal_usb_core_device_is_kernel_driver_active(usb_core_device_obj_t *self, mp_int_t interface) {

supervisor/port_heap.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,10 @@ void port_free(void *ptr);
2727

2828
void *port_realloc(void *ptr, size_t size, bool dma_capable);
2929

30+
#if !CIRCUITPY_ALL_MEMORY_DMA_CAPABLE
31+
// Check if a buffer pointer is in DMA-capable memory. DMA-capable memory is also accessible during
32+
// flash operations.
33+
bool port_buffer_is_dma_capable(const void *ptr);
34+
#endif
35+
3036
size_t port_heap_get_largest_free_size(void);

0 commit comments

Comments
 (0)