1
1
// This file is part of the CircuitPython project: https://circuitpython.org
2
2
//
3
3
// SPDX-FileCopyrightText: Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
4
+ // SPDX-FileCopyrightText: Copyright (c) 2025 Sam Blenny
4
5
//
5
6
// SPDX-License-Identifier: MIT
6
7
@@ -45,7 +46,7 @@ bool common_hal_usb_core_device_construct(usb_core_device_obj_t *self, uint8_t d
45
46
}
46
47
self -> device_address = device_address ;
47
48
self -> first_langid = 0 ;
48
- _xfer_result = 0xff ;
49
+ _xfer_result = XFER_RESULT_INVALID ;
49
50
return true;
50
51
}
51
52
@@ -70,14 +71,18 @@ void common_hal_usb_core_device_deinit(usb_core_device_obj_t *self) {
70
71
uint16_t common_hal_usb_core_device_get_idVendor (usb_core_device_obj_t * self ) {
71
72
uint16_t vid ;
72
73
uint16_t pid ;
73
- tuh_vid_pid_get (self -> device_address , & vid , & pid );
74
+ if (!tuh_vid_pid_get (self -> device_address , & vid , & pid )) {
75
+ mp_raise_usb_core_USBError (NULL );
76
+ }
74
77
return vid ;
75
78
}
76
79
77
80
uint16_t common_hal_usb_core_device_get_idProduct (usb_core_device_obj_t * self ) {
78
81
uint16_t vid ;
79
82
uint16_t pid ;
80
- tuh_vid_pid_get (self -> device_address , & vid , & pid );
83
+ if (!tuh_vid_pid_get (self -> device_address , & vid , & pid )) {
84
+ mp_raise_usb_core_USBError (NULL );
85
+ }
81
86
return pid ;
82
87
}
83
88
@@ -91,14 +96,86 @@ static void _transfer_done_cb(tuh_xfer_t *xfer) {
91
96
92
97
static bool _wait_for_callback (void ) {
93
98
while (!mp_hal_is_interrupted () &&
94
- _xfer_result == 0xff ) {
99
+ _xfer_result == XFER_RESULT_INVALID ) {
100
+ // The background tasks include TinyUSB which will call the function
101
+ // we provided above. In other words, the callback isn't in an interrupt.
102
+ RUN_BACKGROUND_TASKS ;
103
+ }
104
+ if (mp_hal_is_interrupted ()) {
105
+ // Handle case of VM being interrupted by Ctrl-C or autoreload
106
+ return false;
107
+ }
108
+ // Handle callback result code from TinyUSB
109
+ xfer_result_t result = _xfer_result ;
110
+ _xfer_result = XFER_RESULT_INVALID ;
111
+ switch (result ) {
112
+ case XFER_RESULT_SUCCESS :
113
+ return true;
114
+ case XFER_RESULT_FAILED :
115
+ mp_raise_usb_core_USBError (NULL );
116
+ break ;
117
+ case XFER_RESULT_STALLED :
118
+ mp_raise_usb_core_USBError (MP_ERROR_TEXT ("Pipe error" ));
119
+ break ;
120
+ case XFER_RESULT_TIMEOUT :
121
+ case XFER_RESULT_INVALID :
122
+ mp_raise_usb_core_USBTimeoutError ();
123
+ break ;
124
+ }
125
+ return false;
126
+ }
127
+
128
+ static void _prepare_for_transfer (void ) {
129
+ // Prepare for transfer. Unless there is a timeout, these static globals will
130
+ // get modified by the _transfer_done_cb() callback when tinyusb finishes the
131
+ // transfer or encounters an error condition.
132
+ _xfer_result = XFER_RESULT_INVALID ;
133
+ _actual_len = 0 ;
134
+ }
135
+
136
+ static size_t _handle_timed_transfer_callback (tuh_xfer_t * xfer , mp_int_t timeout ) {
137
+ if (xfer == NULL ) {
138
+ mp_raise_usb_core_USBError (NULL );
139
+ return 0 ;
140
+ }
141
+ uint32_t start_time = supervisor_ticks_ms32 ();
142
+ while ((timeout == 0 || supervisor_ticks_ms32 () - start_time < (uint32_t )timeout ) &&
143
+ !mp_hal_is_interrupted () &&
144
+ _xfer_result == XFER_RESULT_INVALID ) {
95
145
// The background tasks include TinyUSB which will call the function
96
146
// we provided above. In other words, the callback isn't in an interrupt.
97
147
RUN_BACKGROUND_TASKS ;
98
148
}
149
+ if (mp_hal_is_interrupted ()) {
150
+ // Handle case of VM being interrupted by Ctrl-C or autoreload
151
+ tuh_edpt_abort_xfer (xfer -> daddr , xfer -> ep_addr );
152
+ return 0 ;
153
+ }
154
+ // Handle transfer result code from TinyUSB
99
155
xfer_result_t result = _xfer_result ;
100
- _xfer_result = 0xff ;
101
- return result == XFER_RESULT_SUCCESS ;
156
+ _xfer_result = XFER_RESULT_INVALID ;
157
+ switch (result ) {
158
+ case XFER_RESULT_SUCCESS :
159
+ return _actual_len ;
160
+ case XFER_RESULT_FAILED :
161
+ mp_raise_usb_core_USBError (NULL );
162
+ break ;
163
+ case XFER_RESULT_STALLED :
164
+ mp_raise_usb_core_USBError (MP_ERROR_TEXT ("Pipe error" ));
165
+ break ;
166
+ case XFER_RESULT_TIMEOUT :
167
+ // This timeout comes from TinyUSB, so assume that it has stopped the
168
+ // transfer (note: timeout logic may be unimplemented on TinyUSB side)
169
+ mp_raise_usb_core_USBTimeoutError ();
170
+ break ;
171
+ case XFER_RESULT_INVALID :
172
+ // 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 );
175
+ mp_raise_usb_core_USBTimeoutError ();
176
+ break ;
177
+ }
178
+ return 0 ;
102
179
}
103
180
104
181
static mp_obj_t _get_string (const uint16_t * temp_buf ) {
@@ -115,41 +192,75 @@ static void _get_langid(usb_core_device_obj_t *self) {
115
192
}
116
193
// Two control bytes and one uint16_t language code.
117
194
uint16_t temp_buf [2 ];
118
- if (!tuh_descriptor_get_string (self -> device_address , 0 , 0 , temp_buf , sizeof (temp_buf ), _transfer_done_cb , 0 ) ||
119
- !_wait_for_callback ()) {
120
- return ;
195
+ _prepare_for_transfer ();
196
+ if (!tuh_descriptor_get_string (self -> device_address , 0 , 0 , temp_buf , sizeof (temp_buf ), _transfer_done_cb , 0 )) {
197
+ mp_raise_usb_core_USBError (NULL );
198
+ } else if (_wait_for_callback ()) {
199
+ self -> first_langid = temp_buf [1 ];
121
200
}
122
- self -> first_langid = temp_buf [1 ];
123
201
}
124
202
125
203
mp_obj_t common_hal_usb_core_device_get_serial_number (usb_core_device_obj_t * self ) {
126
204
uint16_t temp_buf [127 ];
127
- _get_langid ( self ) ;
128
- if (! tuh_descriptor_get_serial_string ( self -> device_address , self -> first_langid , temp_buf , sizeof ( temp_buf ), _transfer_done_cb , 0 ) ||
129
- ! _wait_for_callback ( )) {
205
+ tusb_desc_device_t descriptor ;
206
+ // First, be sure not to ask TinyUSB for a non-existent string (avoid error)
207
+ if (! tuh_descriptor_get_device_local ( self -> device_address , & descriptor )) {
130
208
return mp_const_none ;
131
209
}
132
- return _get_string (temp_buf );
210
+ if (descriptor .iSerialNumber == 0 ) {
211
+ return mp_const_none ;
212
+ }
213
+ // Device does provide this string, so continue
214
+ _get_langid (self );
215
+ _prepare_for_transfer ();
216
+ if (!tuh_descriptor_get_serial_string (self -> device_address , self -> first_langid , temp_buf , sizeof (temp_buf ), _transfer_done_cb , 0 )) {
217
+ mp_raise_usb_core_USBError (NULL );
218
+ } else if (_wait_for_callback ()) {
219
+ return _get_string (temp_buf );
220
+ }
221
+ return mp_const_none ;
133
222
}
134
223
135
224
mp_obj_t common_hal_usb_core_device_get_product (usb_core_device_obj_t * self ) {
136
225
uint16_t temp_buf [127 ];
137
- _get_langid (self );
138
- if (!tuh_descriptor_get_product_string (self -> device_address , self -> first_langid , temp_buf , sizeof (temp_buf ), _transfer_done_cb , 0 ) ||
139
- !_wait_for_callback ()) {
226
+ tusb_desc_device_t descriptor ;
227
+ // First, be sure not to ask TinyUSB for a non-existent string (avoid error)
228
+ if (!tuh_descriptor_get_device_local (self -> device_address , & descriptor )) {
229
+ return mp_const_none ;
230
+ }
231
+ if (descriptor .iProduct == 0 ) {
140
232
return mp_const_none ;
141
233
}
142
- return _get_string (temp_buf );
234
+ // Device does provide this string, so continue
235
+ _get_langid (self );
236
+ _prepare_for_transfer ();
237
+ if (!tuh_descriptor_get_product_string (self -> device_address , self -> first_langid , temp_buf , sizeof (temp_buf ), _transfer_done_cb , 0 )) {
238
+ mp_raise_usb_core_USBError (NULL );
239
+ } else if (_wait_for_callback ()) {
240
+ return _get_string (temp_buf );
241
+ }
242
+ return mp_const_none ;
143
243
}
144
244
145
245
mp_obj_t common_hal_usb_core_device_get_manufacturer (usb_core_device_obj_t * self ) {
146
246
uint16_t temp_buf [127 ];
147
- _get_langid (self );
148
- if (!tuh_descriptor_get_manufacturer_string (self -> device_address , self -> first_langid , temp_buf , sizeof (temp_buf ), _transfer_done_cb , 0 ) ||
149
- !_wait_for_callback ()) {
247
+ tusb_desc_device_t descriptor ;
248
+ // First, be sure not to ask TinyUSB for a non-existent string (avoid error)
249
+ if (!tuh_descriptor_get_device_local (self -> device_address , & descriptor )) {
250
+ return mp_const_none ;
251
+ }
252
+ if (descriptor .iManufacturer == 0 ) {
150
253
return mp_const_none ;
151
254
}
152
- return _get_string (temp_buf );
255
+ // Device does provide this string, so continue
256
+ _get_langid (self );
257
+ _prepare_for_transfer ();
258
+ if (!tuh_descriptor_get_manufacturer_string (self -> device_address , self -> first_langid , temp_buf , sizeof (temp_buf ), _transfer_done_cb , 0 )) {
259
+ mp_raise_usb_core_USBError (NULL );
260
+ } else if (_wait_for_callback ()) {
261
+ return _get_string (temp_buf );
262
+ }
263
+ return mp_const_none ;
153
264
}
154
265
155
266
@@ -225,38 +336,13 @@ void common_hal_usb_core_device_set_configuration(usb_core_device_obj_t *self, m
225
336
}
226
337
227
338
static size_t _xfer (tuh_xfer_t * xfer , mp_int_t timeout ) {
228
- _xfer_result = 0xff ;
339
+ _prepare_for_transfer () ;
229
340
xfer -> complete_cb = _transfer_done_cb ;
230
341
if (!tuh_edpt_xfer (xfer )) {
231
342
mp_raise_usb_core_USBError (NULL );
232
343
return 0 ;
233
344
}
234
- uint32_t start_time = supervisor_ticks_ms32 ();
235
- while ((timeout == 0 || supervisor_ticks_ms32 () - start_time < (uint32_t )timeout ) &&
236
- !mp_hal_is_interrupted () &&
237
- _xfer_result == 0xff ) {
238
- // The background tasks include TinyUSB which will call the function
239
- // we provided above. In other words, the callback isn't in an interrupt.
240
- RUN_BACKGROUND_TASKS ;
241
- }
242
- if (mp_hal_is_interrupted ()) {
243
- tuh_edpt_abort_xfer (xfer -> daddr , xfer -> ep_addr );
244
- return 0 ;
245
- }
246
- xfer_result_t result = _xfer_result ;
247
- _xfer_result = 0xff ;
248
- if (result == XFER_RESULT_STALLED ) {
249
- mp_raise_usb_core_USBError (MP_ERROR_TEXT ("Pipe error" ));
250
- }
251
- if (result == 0xff ) {
252
- tuh_edpt_abort_xfer (xfer -> daddr , xfer -> ep_addr );
253
- mp_raise_usb_core_USBTimeoutError ();
254
- }
255
- if (result == XFER_RESULT_SUCCESS ) {
256
- return _actual_len ;
257
- }
258
-
259
- return 0 ;
345
+ return _handle_timed_transfer_callback (xfer , timeout );
260
346
}
261
347
262
348
static bool _open_endpoint (usb_core_device_obj_t * self , mp_int_t endpoint ) {
@@ -355,38 +441,12 @@ mp_int_t common_hal_usb_core_device_ctrl_transfer(usb_core_device_obj_t *self,
355
441
.complete_cb = _transfer_done_cb ,
356
442
};
357
443
358
- _xfer_result = 0xff ;
359
-
444
+ _prepare_for_transfer ();
360
445
if (!tuh_control_xfer (& xfer )) {
361
446
mp_raise_usb_core_USBError (NULL );
362
447
return 0 ;
363
448
}
364
- uint32_t start_time = supervisor_ticks_ms32 ();
365
- while ((timeout == 0 || supervisor_ticks_ms32 () - start_time < (uint32_t )timeout ) &&
366
- !mp_hal_is_interrupted () &&
367
- _xfer_result == 0xff ) {
368
- // The background tasks include TinyUSB which will call the function
369
- // we provided above. In other words, the callback isn't in an interrupt.
370
- RUN_BACKGROUND_TASKS ;
371
- }
372
- if (mp_hal_is_interrupted ()) {
373
- tuh_edpt_abort_xfer (xfer .daddr , xfer .ep_addr );
374
- return 0 ;
375
- }
376
- xfer_result_t result = _xfer_result ;
377
- _xfer_result = 0xff ;
378
- if (result == XFER_RESULT_STALLED ) {
379
- mp_raise_usb_core_USBError (MP_ERROR_TEXT ("Pipe error" ));
380
- }
381
- if (result == 0xff ) {
382
- tuh_edpt_abort_xfer (xfer .daddr , xfer .ep_addr );
383
- mp_raise_usb_core_USBTimeoutError ();
384
- }
385
- if (result == XFER_RESULT_SUCCESS ) {
386
- return len ;
387
- }
388
-
389
- return 0 ;
449
+ return (mp_int_t )_handle_timed_transfer_callback (& xfer , timeout );
390
450
}
391
451
392
452
bool common_hal_usb_core_device_is_kernel_driver_active (usb_core_device_obj_t * self , mp_int_t interface ) {
0 commit comments