-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Improve usb.core.Device error handling REV 2 #10616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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.
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)
Previously this returned the requested transfer length argument, ignoring the actual length of transferred bytes. This changes to returning the actual length.
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.
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.
Previously these weren't checking to see if TinyUSB reported a failure. Now they check.
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.
|
Just added a commit to fix missing error handling for product, manufacturer, and serial_number string getter properties. Details of missing error checks at:
I'm reasonably confident these changes should be enough to resolve issue #10553 |
|
With this version of Device.c I get this error starting Fruit Jam OS launcher and an unlabeled generic usb mouse, the "absolute newest" 10.x version doesn't have a problem starting up with this mouse (although I think it may not always see the mouse initially but once you start moving it everything seems to start working): Note that you have to add the "use_mouse" parameter to the launcher.conf.json file to test mouse use in the launcher. The Fruit Jam OS OS launcher starts fine with the firmware from this PR using the "3D Optical Mouse" I believe I purchased from the Adafruit Shop. Removing any mouse while the launcher is running causes: This exact same message occurs using the "absolute newest 10.x" firmware from Circuitpython.org as well though. I tried hot swapping in the editor and PyPaint apps and both the "absolute newest 10.x" and the new Device.c version seem to work fine unplugging and re-plugging a different mouse (even the generic one) in while the app was running I think both these crashes could be better handled in the applications and are not necessarily core issues, as the core is now properly returning USBErrors. From the application stand point the errors aren't necessarily fatal and should be caught and handled. The reason the launcher wasn't crashing with my generic mouse before this PR is that the core wasn't raising an error when it couldn't retrieve the PID or VID, it just returned None as the values. When this happened the applications would either retry or simply ignore the issue. |
If I understand your description correctly, that sounds like what I would expect from CircuitPython code that is using the
|
|
Also, I suppose it might be a good idea to include exception information in the documentation at: The current docs mention that |
|
There may be a race condition in Currently, [edit: actually, it's more complicated. See next comment.] |
|
Actually, it's way more complicated than I thought. In TinyUSB, there's a device state progression from
|
|
By my reading of the code, it seems CircuitPython's
|
|
When I used ripgrep to check the |
|
An alternate way of looking at the automatic class driver binding thing is that it's similar to kernel device drivers with desktop PyUSB. From that perspective, |
|
To bring this back into sharper focus on error handling... I'm concerned about the automatic TinyUSB class driver binding stuff because it's redundant (drivers are active but CircuitPython doesn't use them), it's asynchronous (relies on The current To summarize, the changes in this PR make |
|
Thinking about the TinyUSB enumeration state machine some more, and considering what RetiredWizard mentioned on Discord over the weekend about additional control transfer in progress mutex logic... It seems plausible the TinyUSB enumeration state machine might not be adequately keeping track of when a control transfer is in progress. The enumeration process makes a bunch of async control transfers, and it also has some provisions for debouncing as a solid physical connection is established at the USB jack. It's possible there's a bug in that stuff somewhere that doesn't cancel a control transfer when it should. |
|
Turns out it's even more complicated... I was wrong about CircuitPython not using TinyUSB class drivers. Reading more carefully, So, that eliminates my idea of modifying TinyUSB to entirely disable class drivers. Seems like we might need to build a way to detach the HID driver in cases where we don't want the supervisor to manage the device. For example, how does the HID class driver handle HID gamepads? |
|
Looks like TinyUSB actually has a relatively elaborate HID class driver that covers keyboards, mice, and HID gamepads: Maybe it would be better to implement a way of accessing the HID class driver rather than detaching it? |
|
To make use of the HID class driver for gamepad input, seems like a possibility would be to modify Might be a terrible idea. Dunno. Seems plausibly doable though. In any case, my current revised opinion is that So, with the idea of leaving that path open for a possible future PR, that considerably narrows down what changes might be suitable for |
tannewt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like an improvement to me. Thanks!
I think the related issue is a better place for discussion about other aspects of the USB debugging you are doing (like the mystery delay.)
This is a second attempt at
with a smaller set of changes to usb.core.Device error handling:
0xff(not a valid enum value) was assigned to thestatic xfer_result_t _xfer_result;variable (an enum type) are converted to usingXFER_RESULT_INVALID(a valid enum value). Both values were used to indicate the condition of waiting for a callback to finish. This change makes usage in usb.core.Device consistent with usage in the TinyUSB implementation.ifstatements to aswitchstatement that fully covers all the possible enum values. In particular, this new code will raise aUSBErrorexception in the case ofresult == XFER_RESULT_FAILED(old behavior was to return the buffer's previous contents -- all zeros for a freshly allocated array or undefined garbage for a previously used array -- without indicating any error condition)._xfer()andcommon_hal_usb_core_device_ctrl_transfer().usb.core.Device.idVendorandusb.core.Device.idProductusb.core.Device.product,usb.core.Device.manufacturer, andusb.core.Device.serial_numberChecks:
Testing
Test Code:
Test Hardware:
Test Results Before Changes (10.0.0-beta.3):
Note how usb.core.Device.ctrl_transfer() begins returning all zero result buffers. This continues for as long as I let it run.
Test Results After Changes:
In this case, I plugged in an SN30 pro which has a weird startup sequence where it disconnects itself and swaps device descriptors a couple times. The first 3 descriptor info prints are from plugging in the SN30 pro. After that, I unplugged it, producing a series of 3 USBErrors. After that, I plugged in an Adafruit generic SNES style gamepad.
Note how there isn't any bad data, and the code can let TinyUSB auto-recover from unplugged (or self-disconnected) devices by sleeping for 20ms after it gets a USBError.
[edit 9/6: added
serial_numberto test code and test results]