Skip to content

Commit 42b53b7

Browse files
committed
set errno for USBError & USBTimeoutError
This sets unique errno values for each place in the usb.core.Device implementation that raises USBError or USBTimeoutError. The idea is to provide more visibility so CircuitPython code can understand what's gone wrong when TinyUSB complains about something. CAUTION: This removes two error strings (stalled pipe and config not set). The new way relies on unique error codes as defined in shared-module/usb/core/Device.h. The "errno" values here are arbitrary with no relation to POSIX errno codes. I don't see this as a problem because the so-called errno codes returned by desktop PyUSB are undocumented and appear to be rather aribitrary.
1 parent fbf4e51 commit 42b53b7

File tree

4 files changed

+46
-25
lines changed

4 files changed

+46
-25
lines changed

shared-bindings/usb/core/__init__.c

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,9 @@
3030
//|
3131
//|
3232
MP_DEFINE_USB_CORE_EXCEPTION(USBError, OSError)
33-
NORETURN void mp_raise_usb_core_USBError(mp_rom_error_text_t fmt, ...) {
34-
mp_obj_t exception;
35-
if (fmt == NULL) {
36-
exception = mp_obj_new_exception(&mp_type_usb_core_USBError);
37-
} else {
38-
va_list argptr;
39-
va_start(argptr, fmt);
40-
exception = mp_obj_new_exception_msg_vlist(&mp_type_usb_core_USBError, fmt, argptr);
41-
va_end(argptr);
42-
}
43-
nlr_raise(exception);
33+
NORETURN MP_COLD void mp_raise_usb_core_USBError(int errno) {
34+
mp_obj_t args[1] = {MP_OBJ_NEW_SMALL_INT(errno)};
35+
nlr_raise(mp_obj_new_exception_args(&mp_type_usb_core_USBError, 1, args));
4436
}
4537

4638
//| class USBTimeoutError(USBError):
@@ -50,8 +42,9 @@ NORETURN void mp_raise_usb_core_USBError(mp_rom_error_text_t fmt, ...) {
5042
//|
5143
//|
5244
MP_DEFINE_USB_CORE_EXCEPTION(USBTimeoutError, usb_core_USBError)
53-
NORETURN void mp_raise_usb_core_USBTimeoutError(void) {
54-
mp_raise_type(&mp_type_usb_core_USBTimeoutError);
45+
NORETURN void mp_raise_usb_core_USBTimeoutError(int errno) {
46+
mp_obj_t args[1] = {MP_OBJ_NEW_SMALL_INT(errno)};
47+
nlr_raise(mp_obj_new_exception_args(&mp_type_usb_core_USBTimeoutError, 1, args));
5548
}
5649

5750

shared-bindings/usb/core/__init__.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ void usb_core_exception_print(const mp_print_t *print, mp_obj_t o_in, mp_print_k
2525
extern const mp_obj_type_t mp_type_usb_core_USBError;
2626
extern const mp_obj_type_t mp_type_usb_core_USBTimeoutError;
2727

28-
NORETURN void mp_raise_usb_core_USBError(mp_rom_error_text_t fmt, ...);
29-
NORETURN void mp_raise_usb_core_USBTimeoutError(void);
28+
NORETURN void mp_raise_usb_core_USBError(int errno);
29+
NORETURN void mp_raise_usb_core_USBTimeoutError(int errno);
3030

3131
// Find is all Python object oriented so we don't need a separate common-hal API
3232
// for it. It uses the device common-hal instead.

shared-module/usb/core/Device.c

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// This file is part of the CircuitPython project: https://circuitpython.org
22
//
33
// SPDX-FileCopyrightText: Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
4+
// SPDX-FileCopyrightText: Copyright (c) 2025 Sam Blenny
45
//
56
// SPDX-License-Identifier: MIT
67

@@ -111,7 +112,7 @@ static void _prepare_for_transfer(void) {
111112

112113
static size_t _handle_timed_transfer_callback(tuh_xfer_t *xfer, mp_int_t timeout) {
113114
if (xfer == NULL) {
114-
mp_raise_usb_core_USBError(NULL);
115+
mp_raise_usb_core_USBError(USB_CORE_NULL_PTR);
115116
return 0;
116117
}
117118
uint32_t start_time = supervisor_ticks_ms32();
@@ -134,21 +135,21 @@ static size_t _handle_timed_transfer_callback(tuh_xfer_t *xfer, mp_int_t timeout
134135
case XFER_RESULT_SUCCESS:
135136
return _actual_len;
136137
case XFER_RESULT_FAILED:
137-
mp_raise_usb_core_USBError(NULL);
138+
mp_raise_usb_core_USBError(USB_CORE_XFER_FAIL);
138139
break;
139140
case XFER_RESULT_STALLED:
140-
mp_raise_usb_core_USBError(MP_ERROR_TEXT("Pipe error"));
141+
mp_raise_usb_core_USBError(USB_CORE_STALLED);
141142
break;
142143
case XFER_RESULT_TIMEOUT:
143144
// This timeout comes from TinyUSB, so assume that it has stopped the
144145
// transfer (note: timeout logic may be unimplemented on TinyUSB side)
145-
mp_raise_usb_core_USBTimeoutError();
146+
mp_raise_usb_core_USBTimeoutError(USB_CORE_TIMEOUT);
146147
break;
147148
case XFER_RESULT_INVALID:
148149
// This timeout comes from CircuitPython, not TinyUSB, so tell TinyUSB
149150
// to stop the transfer
150151
tuh_edpt_abort_xfer(xfer->daddr, xfer->ep_addr);
151-
mp_raise_usb_core_USBTimeoutError();
152+
mp_raise_usb_core_USBTimeoutError(USB_CORE_INVALID);
152153
break;
153154
}
154155
return 0;
@@ -281,7 +282,7 @@ static size_t _xfer(tuh_xfer_t *xfer, mp_int_t timeout) {
281282
_prepare_for_transfer();
282283
xfer->complete_cb = _transfer_done_cb;
283284
if (!tuh_edpt_xfer(xfer)) {
284-
mp_raise_usb_core_USBError(NULL);
285+
mp_raise_usb_core_USBError(USB_CORE_EDPT_XFER);
285286
return 0;
286287
}
287288
return _handle_timed_transfer_callback(xfer, timeout);
@@ -303,7 +304,7 @@ static bool _open_endpoint(usb_core_device_obj_t *self, mp_int_t endpoint) {
303304
}
304305

305306
if (self->configuration_descriptor == NULL) {
306-
mp_raise_usb_core_USBError(MP_ERROR_TEXT("No configuration set"));
307+
mp_raise_usb_core_USBError(USB_CORE_NOCONFIG);
307308
return false;
308309
}
309310

@@ -338,7 +339,7 @@ static bool _open_endpoint(usb_core_device_obj_t *self, mp_int_t endpoint) {
338339

339340
mp_int_t common_hal_usb_core_device_write(usb_core_device_obj_t *self, mp_int_t endpoint, const uint8_t *buffer, mp_int_t len, mp_int_t timeout) {
340341
if (!_open_endpoint(self, endpoint)) {
341-
mp_raise_usb_core_USBError(NULL);
342+
mp_raise_usb_core_USBError(USB_CORE_OPEN_ENDPOINT);
342343
return 0;
343344
}
344345
tuh_xfer_t xfer;
@@ -351,7 +352,7 @@ mp_int_t common_hal_usb_core_device_write(usb_core_device_obj_t *self, mp_int_t
351352

352353
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) {
353354
if (!_open_endpoint(self, endpoint)) {
354-
mp_raise_usb_core_USBError(NULL);
355+
mp_raise_usb_core_USBError(USB_CORE_OPEN_ENDPOINT);
355356
return 0;
356357
}
357358
tuh_xfer_t xfer;
@@ -385,7 +386,7 @@ mp_int_t common_hal_usb_core_device_ctrl_transfer(usb_core_device_obj_t *self,
385386

386387
_prepare_for_transfer();
387388
if (!tuh_control_xfer(&xfer)) {
388-
mp_raise_usb_core_USBError(NULL);
389+
mp_raise_usb_core_USBError(USB_CORE_CONTROL_XFER);
389390
return 0;
390391
}
391392
return (mp_int_t)_handle_timed_transfer_callback(&xfer, timeout);

shared-module/usb/core/Device.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// This file is part of the CircuitPython project: https://circuitpython.org
22
//
33
// SPDX-FileCopyrightText: Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
4+
// SPDX-FileCopyrightText: Copyright (c) 2025 Sam Blenny
45
//
56
// SPDX-License-Identifier: MIT
67

@@ -16,3 +17,29 @@ typedef struct {
1617
uint8_t open_endpoints[8];
1718
uint16_t first_langid;
1819
} usb_core_device_obj_t;
20+
21+
22+
// These values get used to set USBError.errno and USBTimeoutError.errno.
23+
// It would be possible to define these to more closely mimic desktop PyUSB on
24+
// a given OS (maybe Debian?). But, for USB error handling in CircuitPython,
25+
// there's an argument to be made that it's more useful to set arbitrary codes
26+
// here that map directly to errors coming from TinyUSB. That way, CircuitPython
27+
// code can have more visibility into what went wrong. POSIX errno codes are
28+
// pretty far removed from the details of how TinyUSB can fail, so using them
29+
// here would be uninformative.
30+
31+
// Error due to attempting to open endpoint before setting configuration
32+
#define USB_CORE_NOCONFIG (1)
33+
34+
// Errors from transfer callback result
35+
#define USB_CORE_NULL_PTR (2)
36+
#define USB_CORE_XFER_FAIL (3)
37+
#define USB_CORE_STALLED (4)
38+
#define USB_CORE_TIMEOUT (5)
39+
#define USB_CORE_INVALID (6)
40+
41+
// Errors from a non-callback TinyUSB function returning false. Seeing one of
42+
// these probably means a TU_VERIFY(...) check failed in the TinyUSB code.
43+
#define USB_CORE_EDPT_XFER (7)
44+
#define USB_CORE_OPEN_ENDPOINT (8)
45+
#define USB_CORE_CONTROL_XFER (9)

0 commit comments

Comments
 (0)