From 05db6eefb2f08e579d5c03d462e507a1ba5c8a57 Mon Sep 17 00:00:00 2001 From: sam blenny <68084116+samblenny@users.noreply.github.com> Date: Tue, 2 Sep 2025 22:16:35 +0000 Subject: [PATCH 1/8] fix bad xfer_result_t enum values shared-module/usb/core/Device.c was using its own 0xff value for the xfer_result_t enum defined by tinyusb/src/common/tusb_types.h. The 0xff value served the same purpose as the already exisiting XFER_RESULT_INVALID enum value (a placeholder to mark in-progress transactions). This commit standardizes on XFER_RESULT_INVALID in usb.core.Device consistent with the usage in tinyusb. Making this change allows implementing `switch(result){...}` style result code checks without compiler errors about 0xff not being a valid value for the enum. --- shared-module/usb/core/Device.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/shared-module/usb/core/Device.c b/shared-module/usb/core/Device.c index c9df0cf73805f..f6cd03ebcd40a 100644 --- a/shared-module/usb/core/Device.c +++ b/shared-module/usb/core/Device.c @@ -45,7 +45,7 @@ bool common_hal_usb_core_device_construct(usb_core_device_obj_t *self, uint8_t d } self->device_address = device_address; self->first_langid = 0; - _xfer_result = 0xff; + _xfer_result = XFER_RESULT_INVALID; return true; } @@ -91,13 +91,13 @@ static void _transfer_done_cb(tuh_xfer_t *xfer) { static bool _wait_for_callback(void) { while (!mp_hal_is_interrupted() && - _xfer_result == 0xff) { + _xfer_result == XFER_RESULT_INVALID) { // The background tasks include TinyUSB which will call the function // we provided above. In other words, the callback isn't in an interrupt. RUN_BACKGROUND_TASKS; } xfer_result_t result = _xfer_result; - _xfer_result = 0xff; + _xfer_result = XFER_RESULT_INVALID; return result == XFER_RESULT_SUCCESS; } @@ -225,7 +225,7 @@ void common_hal_usb_core_device_set_configuration(usb_core_device_obj_t *self, m } static size_t _xfer(tuh_xfer_t *xfer, mp_int_t timeout) { - _xfer_result = 0xff; + _xfer_result = XFER_RESULT_INVALID; xfer->complete_cb = _transfer_done_cb; if (!tuh_edpt_xfer(xfer)) { mp_raise_usb_core_USBError(NULL); @@ -234,7 +234,7 @@ static size_t _xfer(tuh_xfer_t *xfer, mp_int_t timeout) { uint32_t start_time = supervisor_ticks_ms32(); while ((timeout == 0 || supervisor_ticks_ms32() - start_time < (uint32_t)timeout) && !mp_hal_is_interrupted() && - _xfer_result == 0xff) { + _xfer_result == XFER_RESULT_INVALID) { // The background tasks include TinyUSB which will call the function // we provided above. In other words, the callback isn't in an interrupt. RUN_BACKGROUND_TASKS; @@ -244,11 +244,11 @@ static size_t _xfer(tuh_xfer_t *xfer, mp_int_t timeout) { return 0; } xfer_result_t result = _xfer_result; - _xfer_result = 0xff; + _xfer_result = XFER_RESULT_INVALID; if (result == XFER_RESULT_STALLED) { mp_raise_usb_core_USBError(MP_ERROR_TEXT("Pipe error")); } - if (result == 0xff) { + if (result == XFER_RESULT_INVALID) { tuh_edpt_abort_xfer(xfer->daddr, xfer->ep_addr); mp_raise_usb_core_USBTimeoutError(); } @@ -355,7 +355,7 @@ mp_int_t common_hal_usb_core_device_ctrl_transfer(usb_core_device_obj_t *self, .complete_cb = _transfer_done_cb, }; - _xfer_result = 0xff; + _xfer_result = XFER_RESULT_INVALID; if (!tuh_control_xfer(&xfer)) { mp_raise_usb_core_USBError(NULL); @@ -364,7 +364,7 @@ mp_int_t common_hal_usb_core_device_ctrl_transfer(usb_core_device_obj_t *self, uint32_t start_time = supervisor_ticks_ms32(); while ((timeout == 0 || supervisor_ticks_ms32() - start_time < (uint32_t)timeout) && !mp_hal_is_interrupted() && - _xfer_result == 0xff) { + _xfer_result == XFER_RESULT_INVALID) { // The background tasks include TinyUSB which will call the function // we provided above. In other words, the callback isn't in an interrupt. RUN_BACKGROUND_TASKS; @@ -374,11 +374,11 @@ mp_int_t common_hal_usb_core_device_ctrl_transfer(usb_core_device_obj_t *self, return 0; } xfer_result_t result = _xfer_result; - _xfer_result = 0xff; + _xfer_result = XFER_RESULT_INVALID; if (result == XFER_RESULT_STALLED) { mp_raise_usb_core_USBError(MP_ERROR_TEXT("Pipe error")); } - if (result == 0xff) { + if (result == XFER_RESULT_INVALID) { tuh_edpt_abort_xfer(xfer.daddr, xfer.ep_addr); mp_raise_usb_core_USBTimeoutError(); } From b0760773077aaab556394061fde17168cbf52df3 Mon Sep 17 00:00:00 2001 From: sam blenny <68084116+samblenny@users.noreply.github.com> Date: Tue, 2 Sep 2025 22:40:46 +0000 Subject: [PATCH 2/8] use switch for ctrl_transfer result check This directly translates the Device.ctrl_transfer() result check logic from its old if-statements to an equivalent switch-statement. The point is to make it clear how each possible result code is handled. Note that XFER_RESULT_FAILED and XFER_RESULT_TIMEOUT both return 0 without generating any exception. (but also, tinyusb may not actually use XFER_RESULT_TIMOUT if its comments are still accurate) --- shared-module/usb/core/Device.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/shared-module/usb/core/Device.c b/shared-module/usb/core/Device.c index f6cd03ebcd40a..87d1141bfde39 100644 --- a/shared-module/usb/core/Device.c +++ b/shared-module/usb/core/Device.c @@ -375,17 +375,21 @@ mp_int_t common_hal_usb_core_device_ctrl_transfer(usb_core_device_obj_t *self, } xfer_result_t result = _xfer_result; _xfer_result = XFER_RESULT_INVALID; - if (result == XFER_RESULT_STALLED) { - mp_raise_usb_core_USBError(MP_ERROR_TEXT("Pipe error")); - } - if (result == XFER_RESULT_INVALID) { - tuh_edpt_abort_xfer(xfer.daddr, xfer.ep_addr); - mp_raise_usb_core_USBTimeoutError(); - } - if (result == XFER_RESULT_SUCCESS) { - return len; + switch (result) { + case XFER_RESULT_SUCCESS: + return len; + case XFER_RESULT_FAILED: + break; + case XFER_RESULT_STALLED: + mp_raise_usb_core_USBError(MP_ERROR_TEXT("Pipe error")); + break; + case XFER_RESULT_TIMEOUT: + break; + case XFER_RESULT_INVALID: + tuh_edpt_abort_xfer(xfer.daddr, xfer.ep_addr); + mp_raise_usb_core_USBTimeoutError(); + break; } - return 0; } From e9da6bce6dd037f4befe9c5966cd81b4960f9a7d Mon Sep 17 00:00:00 2001 From: sam blenny <68084116+samblenny@users.noreply.github.com> Date: Tue, 2 Sep 2025 23:02:39 +0000 Subject: [PATCH 3/8] return actual length from ctrl_transfer Previously this returned the requested transfer length argument, ignoring the actual length of transferred bytes. This changes to returning the actual length. --- shared-module/usb/core/Device.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shared-module/usb/core/Device.c b/shared-module/usb/core/Device.c index 87d1141bfde39..517eb5f357a07 100644 --- a/shared-module/usb/core/Device.c +++ b/shared-module/usb/core/Device.c @@ -355,7 +355,11 @@ mp_int_t common_hal_usb_core_device_ctrl_transfer(usb_core_device_obj_t *self, .complete_cb = _transfer_done_cb, }; + // Prepare for transfer. Unless there is a timeout, these static globals will + // get modified by the _transfer_done_cb() callback when tinyusb finishes the + // transfer or encounters an error condition. _xfer_result = XFER_RESULT_INVALID; + _actual_len = 0; if (!tuh_control_xfer(&xfer)) { mp_raise_usb_core_USBError(NULL); @@ -377,7 +381,7 @@ mp_int_t common_hal_usb_core_device_ctrl_transfer(usb_core_device_obj_t *self, _xfer_result = XFER_RESULT_INVALID; switch (result) { case XFER_RESULT_SUCCESS: - return len; + return _actual_len; case XFER_RESULT_FAILED: break; case XFER_RESULT_STALLED: From f3560f98b5527e469977b5d450b3dd813df06d1e Mon Sep 17 00:00:00 2001 From: sam blenny <68084116+samblenny@users.noreply.github.com> Date: Wed, 3 Sep 2025 00:41:44 +0000 Subject: [PATCH 4/8] handle XFER_RESULT_FAILED with exception Previously, usb.core.Device.ctrl_transfer() did not raise an exception when TinyUSB returned an XFER_RESULT_FAILED result code. This change raises an exception which prevents a failed transfer from returning an all zero result buffer. --- shared-module/usb/core/Device.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/shared-module/usb/core/Device.c b/shared-module/usb/core/Device.c index 517eb5f357a07..79b6775dd0c1a 100644 --- a/shared-module/usb/core/Device.c +++ b/shared-module/usb/core/Device.c @@ -374,22 +374,30 @@ mp_int_t common_hal_usb_core_device_ctrl_transfer(usb_core_device_obj_t *self, RUN_BACKGROUND_TASKS; } if (mp_hal_is_interrupted()) { + // Handle case of VM being interrupted by Ctrl-C or autoreload tuh_edpt_abort_xfer(xfer.daddr, xfer.ep_addr); return 0; } + // Handle control transfer result code from TinyUSB xfer_result_t result = _xfer_result; _xfer_result = XFER_RESULT_INVALID; switch (result) { case XFER_RESULT_SUCCESS: return _actual_len; case XFER_RESULT_FAILED: + mp_raise_usb_core_USBError(NULL); break; case XFER_RESULT_STALLED: mp_raise_usb_core_USBError(MP_ERROR_TEXT("Pipe error")); break; case XFER_RESULT_TIMEOUT: + // This timeout comes from TinyUSB, so assume that it has stopped the + // transfer (note: timeout logic may be unimplemented on TinyUSB side) + mp_raise_usb_core_USBTimeoutError(); break; case XFER_RESULT_INVALID: + // This timeout comes from CircuitPython, not TinyUSB, so tell TinyUSB + // to stop the transfer tuh_edpt_abort_xfer(xfer.daddr, xfer.ep_addr); mp_raise_usb_core_USBTimeoutError(); break; From 7d26fe986600718ac687f7bc2230c679ef3d6f2a Mon Sep 17 00:00:00 2001 From: sam blenny <68084116+samblenny@users.noreply.github.com> Date: Wed, 3 Sep 2025 01:50:48 +0000 Subject: [PATCH 5/8] factor out timed transfer setup & err handling The code for endpoint transfers and control transfers previously had a bunch of duplicated logic for setup, timeout checking, and result code error handling. This change factors that stuff out into functions, using my new transfer result error handling code from the last commit. Now the endpoint and control transfers can share the setup, timeout, and error check logic. For me, this refactor reduced the firmware image size by 176 bytes. --- shared-module/usb/core/Device.c | 128 ++++++++++++++------------------ 1 file changed, 57 insertions(+), 71 deletions(-) diff --git a/shared-module/usb/core/Device.c b/shared-module/usb/core/Device.c index 79b6775dd0c1a..f71ac8844bb73 100644 --- a/shared-module/usb/core/Device.c +++ b/shared-module/usb/core/Device.c @@ -101,6 +101,59 @@ static bool _wait_for_callback(void) { return result == XFER_RESULT_SUCCESS; } +static void _prepare_for_transfer(void) { + // Prepare for transfer. Unless there is a timeout, these static globals will + // get modified by the _transfer_done_cb() callback when tinyusb finishes the + // transfer or encounters an error condition. + _xfer_result = XFER_RESULT_INVALID; + _actual_len = 0; +} + +static size_t _handle_timed_transfer_callback(tuh_xfer_t *xfer, mp_int_t timeout) { + if (xfer == NULL) { + mp_raise_usb_core_USBError(NULL); + return 0; + } + uint32_t start_time = supervisor_ticks_ms32(); + while ((timeout == 0 || supervisor_ticks_ms32() - start_time < (uint32_t)timeout) && + !mp_hal_is_interrupted() && + _xfer_result == XFER_RESULT_INVALID) { + // The background tasks include TinyUSB which will call the function + // we provided above. In other words, the callback isn't in an interrupt. + RUN_BACKGROUND_TASKS; + } + if (mp_hal_is_interrupted()) { + // Handle case of VM being interrupted by Ctrl-C or autoreload + tuh_edpt_abort_xfer(xfer->daddr, xfer->ep_addr); + return 0; + } + // Handle control transfer result code from TinyUSB + xfer_result_t result = _xfer_result; + _xfer_result = XFER_RESULT_INVALID; + switch (result) { + case XFER_RESULT_SUCCESS: + return _actual_len; + case XFER_RESULT_FAILED: + mp_raise_usb_core_USBError(NULL); + break; + case XFER_RESULT_STALLED: + mp_raise_usb_core_USBError(MP_ERROR_TEXT("Pipe error")); + break; + case XFER_RESULT_TIMEOUT: + // This timeout comes from TinyUSB, so assume that it has stopped the + // transfer (note: timeout logic may be unimplemented on TinyUSB side) + mp_raise_usb_core_USBTimeoutError(); + break; + case XFER_RESULT_INVALID: + // This timeout comes from CircuitPython, not TinyUSB, so tell TinyUSB + // to stop the transfer + tuh_edpt_abort_xfer(xfer->daddr, xfer->ep_addr); + mp_raise_usb_core_USBTimeoutError(); + break; + } + return 0; +} + static mp_obj_t _get_string(const uint16_t *temp_buf) { size_t utf16_len = ((temp_buf[0] & 0xff) - 2) / sizeof(uint16_t); if (utf16_len == 0) { @@ -225,38 +278,13 @@ void common_hal_usb_core_device_set_configuration(usb_core_device_obj_t *self, m } static size_t _xfer(tuh_xfer_t *xfer, mp_int_t timeout) { - _xfer_result = XFER_RESULT_INVALID; + _prepare_for_transfer(); xfer->complete_cb = _transfer_done_cb; if (!tuh_edpt_xfer(xfer)) { mp_raise_usb_core_USBError(NULL); return 0; } - uint32_t start_time = supervisor_ticks_ms32(); - while ((timeout == 0 || supervisor_ticks_ms32() - start_time < (uint32_t)timeout) && - !mp_hal_is_interrupted() && - _xfer_result == XFER_RESULT_INVALID) { - // The background tasks include TinyUSB which will call the function - // we provided above. In other words, the callback isn't in an interrupt. - RUN_BACKGROUND_TASKS; - } - if (mp_hal_is_interrupted()) { - tuh_edpt_abort_xfer(xfer->daddr, xfer->ep_addr); - return 0; - } - xfer_result_t result = _xfer_result; - _xfer_result = XFER_RESULT_INVALID; - if (result == XFER_RESULT_STALLED) { - mp_raise_usb_core_USBError(MP_ERROR_TEXT("Pipe error")); - } - if (result == XFER_RESULT_INVALID) { - tuh_edpt_abort_xfer(xfer->daddr, xfer->ep_addr); - mp_raise_usb_core_USBTimeoutError(); - } - if (result == XFER_RESULT_SUCCESS) { - return _actual_len; - } - - return 0; + return _handle_timed_transfer_callback(xfer, timeout); } static bool _open_endpoint(usb_core_device_obj_t *self, mp_int_t endpoint) { @@ -355,54 +383,12 @@ mp_int_t common_hal_usb_core_device_ctrl_transfer(usb_core_device_obj_t *self, .complete_cb = _transfer_done_cb, }; - // Prepare for transfer. Unless there is a timeout, these static globals will - // get modified by the _transfer_done_cb() callback when tinyusb finishes the - // transfer or encounters an error condition. - _xfer_result = XFER_RESULT_INVALID; - _actual_len = 0; - + _prepare_for_transfer(); if (!tuh_control_xfer(&xfer)) { mp_raise_usb_core_USBError(NULL); return 0; } - uint32_t start_time = supervisor_ticks_ms32(); - while ((timeout == 0 || supervisor_ticks_ms32() - start_time < (uint32_t)timeout) && - !mp_hal_is_interrupted() && - _xfer_result == XFER_RESULT_INVALID) { - // The background tasks include TinyUSB which will call the function - // we provided above. In other words, the callback isn't in an interrupt. - RUN_BACKGROUND_TASKS; - } - if (mp_hal_is_interrupted()) { - // Handle case of VM being interrupted by Ctrl-C or autoreload - tuh_edpt_abort_xfer(xfer.daddr, xfer.ep_addr); - return 0; - } - // Handle control transfer result code from TinyUSB - xfer_result_t result = _xfer_result; - _xfer_result = XFER_RESULT_INVALID; - switch (result) { - case XFER_RESULT_SUCCESS: - return _actual_len; - case XFER_RESULT_FAILED: - mp_raise_usb_core_USBError(NULL); - break; - case XFER_RESULT_STALLED: - mp_raise_usb_core_USBError(MP_ERROR_TEXT("Pipe error")); - break; - case XFER_RESULT_TIMEOUT: - // This timeout comes from TinyUSB, so assume that it has stopped the - // transfer (note: timeout logic may be unimplemented on TinyUSB side) - mp_raise_usb_core_USBTimeoutError(); - break; - case XFER_RESULT_INVALID: - // This timeout comes from CircuitPython, not TinyUSB, so tell TinyUSB - // to stop the transfer - tuh_edpt_abort_xfer(xfer.daddr, xfer.ep_addr); - mp_raise_usb_core_USBTimeoutError(); - break; - } - return 0; + return (mp_int_t)_handle_timed_transfer_callback(&xfer, timeout); } bool common_hal_usb_core_device_is_kernel_driver_active(usb_core_device_obj_t *self, mp_int_t interface) { From e8a6d38ec4396a4ed0987829e01db9deabbfc84f Mon Sep 17 00:00:00 2001 From: sam blenny <68084116+samblenny@users.noreply.github.com> Date: Sat, 6 Sep 2025 00:52:51 +0000 Subject: [PATCH 6/8] fix idVendor and idProduct error check Previously these weren't checking to see if TinyUSB reported a failure. Now they check. --- shared-module/usb/core/Device.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/shared-module/usb/core/Device.c b/shared-module/usb/core/Device.c index f71ac8844bb73..df108776fd446 100644 --- a/shared-module/usb/core/Device.c +++ b/shared-module/usb/core/Device.c @@ -1,6 +1,7 @@ // This file is part of the CircuitPython project: https://circuitpython.org // // SPDX-FileCopyrightText: Copyright (c) 2022 Scott Shawcroft for Adafruit Industries +// SPDX-FileCopyrightText: Copyright (c) 2025 Sam Blenny // // SPDX-License-Identifier: MIT @@ -70,14 +71,18 @@ void common_hal_usb_core_device_deinit(usb_core_device_obj_t *self) { uint16_t common_hal_usb_core_device_get_idVendor(usb_core_device_obj_t *self) { uint16_t vid; uint16_t pid; - tuh_vid_pid_get(self->device_address, &vid, &pid); + if (!tuh_vid_pid_get(self->device_address, &vid, &pid)) { + mp_raise_usb_core_USBError(NULL); + } return vid; } uint16_t common_hal_usb_core_device_get_idProduct(usb_core_device_obj_t *self) { uint16_t vid; uint16_t pid; - tuh_vid_pid_get(self->device_address, &vid, &pid); + if (!tuh_vid_pid_get(self->device_address, &vid, &pid)) { + mp_raise_usb_core_USBError(NULL); + } return pid; } From 99f244e033d08c7ae862a52e8eccc831661608ed Mon Sep 17 00:00:00 2001 From: sam blenny <68084116+samblenny@users.noreply.github.com> Date: Sat, 6 Sep 2025 07:46:34 +0000 Subject: [PATCH 7/8] fix comment --- shared-module/usb/core/Device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-module/usb/core/Device.c b/shared-module/usb/core/Device.c index df108776fd446..604e945f1db96 100644 --- a/shared-module/usb/core/Device.c +++ b/shared-module/usb/core/Device.c @@ -132,7 +132,7 @@ static size_t _handle_timed_transfer_callback(tuh_xfer_t *xfer, mp_int_t timeout tuh_edpt_abort_xfer(xfer->daddr, xfer->ep_addr); return 0; } - // Handle control transfer result code from TinyUSB + // Handle transfer result code from TinyUSB xfer_result_t result = _xfer_result; _xfer_result = XFER_RESULT_INVALID; switch (result) { From ad745c2c33a9e91fcbda22b9b58a08a56eb25fc4 Mon Sep 17 00:00:00 2001 From: sam blenny <68084116+samblenny@users.noreply.github.com> Date: Sun, 7 Sep 2025 01:00:31 +0000 Subject: [PATCH 8/8] error checks for usb device string properties Improve the error handling for usb.core.Device string properties: .serial_number, .product, .manufacturer Previously, the property getters didn't check the device descriptor to see if the device actually had the requested string. Instead, they relied on TinyUSB to return a failure result if the string was not available. That made it impossible to distinguish missing strings from other more serious USB errors (e.g. unplugged device). These changes make it possible to return None for a missing string or raise a USBError exception in case of a more serious problem. --- shared-module/usb/core/Device.c | 87 ++++++++++++++++++++++++++------- 1 file changed, 70 insertions(+), 17 deletions(-) diff --git a/shared-module/usb/core/Device.c b/shared-module/usb/core/Device.c index 604e945f1db96..c07ffda1149c9 100644 --- a/shared-module/usb/core/Device.c +++ b/shared-module/usb/core/Device.c @@ -101,9 +101,28 @@ static bool _wait_for_callback(void) { // we provided above. In other words, the callback isn't in an interrupt. RUN_BACKGROUND_TASKS; } + if (mp_hal_is_interrupted()) { + // Handle case of VM being interrupted by Ctrl-C or autoreload + return false; + } + // Handle callback result code from TinyUSB xfer_result_t result = _xfer_result; _xfer_result = XFER_RESULT_INVALID; - return result == XFER_RESULT_SUCCESS; + switch (result) { + case XFER_RESULT_SUCCESS: + return true; + case XFER_RESULT_FAILED: + mp_raise_usb_core_USBError(NULL); + break; + case XFER_RESULT_STALLED: + mp_raise_usb_core_USBError(MP_ERROR_TEXT("Pipe error")); + break; + case XFER_RESULT_TIMEOUT: + case XFER_RESULT_INVALID: + mp_raise_usb_core_USBTimeoutError(); + break; + } + return false; } static void _prepare_for_transfer(void) { @@ -173,41 +192,75 @@ static void _get_langid(usb_core_device_obj_t *self) { } // Two control bytes and one uint16_t language code. uint16_t temp_buf[2]; - if (!tuh_descriptor_get_string(self->device_address, 0, 0, temp_buf, sizeof(temp_buf), _transfer_done_cb, 0) || - !_wait_for_callback()) { - return; + _prepare_for_transfer(); + if (!tuh_descriptor_get_string(self->device_address, 0, 0, temp_buf, sizeof(temp_buf), _transfer_done_cb, 0)) { + mp_raise_usb_core_USBError(NULL); + } else if (_wait_for_callback()) { + self->first_langid = temp_buf[1]; } - self->first_langid = temp_buf[1]; } mp_obj_t common_hal_usb_core_device_get_serial_number(usb_core_device_obj_t *self) { uint16_t temp_buf[127]; - _get_langid(self); - if (!tuh_descriptor_get_serial_string(self->device_address, self->first_langid, temp_buf, sizeof(temp_buf), _transfer_done_cb, 0) || - !_wait_for_callback()) { + tusb_desc_device_t descriptor; + // First, be sure not to ask TinyUSB for a non-existent string (avoid error) + if (!tuh_descriptor_get_device_local(self->device_address, &descriptor)) { + return mp_const_none; + } + if (descriptor.iSerialNumber == 0) { return mp_const_none; } - return _get_string(temp_buf); + // Device does provide this string, so continue + _get_langid(self); + _prepare_for_transfer(); + if (!tuh_descriptor_get_serial_string(self->device_address, self->first_langid, temp_buf, sizeof(temp_buf), _transfer_done_cb, 0)) { + mp_raise_usb_core_USBError(NULL); + } else if (_wait_for_callback()) { + return _get_string(temp_buf); + } + return mp_const_none; } mp_obj_t common_hal_usb_core_device_get_product(usb_core_device_obj_t *self) { uint16_t temp_buf[127]; - _get_langid(self); - if (!tuh_descriptor_get_product_string(self->device_address, self->first_langid, temp_buf, sizeof(temp_buf), _transfer_done_cb, 0) || - !_wait_for_callback()) { + tusb_desc_device_t descriptor; + // First, be sure not to ask TinyUSB for a non-existent string (avoid error) + if (!tuh_descriptor_get_device_local(self->device_address, &descriptor)) { return mp_const_none; } - return _get_string(temp_buf); + if (descriptor.iProduct == 0) { + return mp_const_none; + } + // Device does provide this string, so continue + _get_langid(self); + _prepare_for_transfer(); + if (!tuh_descriptor_get_product_string(self->device_address, self->first_langid, temp_buf, sizeof(temp_buf), _transfer_done_cb, 0)) { + mp_raise_usb_core_USBError(NULL); + } else if (_wait_for_callback()) { + return _get_string(temp_buf); + } + return mp_const_none; } mp_obj_t common_hal_usb_core_device_get_manufacturer(usb_core_device_obj_t *self) { uint16_t temp_buf[127]; - _get_langid(self); - if (!tuh_descriptor_get_manufacturer_string(self->device_address, self->first_langid, temp_buf, sizeof(temp_buf), _transfer_done_cb, 0) || - !_wait_for_callback()) { + tusb_desc_device_t descriptor; + // First, be sure not to ask TinyUSB for a non-existent string (avoid error) + if (!tuh_descriptor_get_device_local(self->device_address, &descriptor)) { return mp_const_none; } - return _get_string(temp_buf); + if (descriptor.iManufacturer == 0) { + return mp_const_none; + } + // Device does provide this string, so continue + _get_langid(self); + _prepare_for_transfer(); + if (!tuh_descriptor_get_manufacturer_string(self->device_address, self->first_langid, temp_buf, sizeof(temp_buf), _transfer_done_cb, 0)) { + mp_raise_usb_core_USBError(NULL); + } else if (_wait_for_callback()) { + return _get_string(temp_buf); + } + return mp_const_none; }