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
3436static xfer_result_t _xfer_result ;
3537static 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
3660bool 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
348399static 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
410479mp_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
423514mp_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
452572bool common_hal_usb_core_device_is_kernel_driver_active (usb_core_device_obj_t * self , mp_int_t interface ) {
0 commit comments