-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
CircuitPython version and board name
Adafruit CircuitPython 10.0.0-beta.2 on 2025-07-30; Adafruit Fruit Jam with rp2350b
Code/REPL
import displayio
import gc
import time
import usb
from usb.core import USBError, USBTimeoutError
def test_descriptor_parsing_read_gamepad():
# Check USB device descriptors, then read from gamepad if found.
# This one is designed to trigger bugs where an in progress USB transaction
# fights with other CircuitPython tasks. For me, this reliably triggers a
# board reset with 10.0.0-beta.2, Fruit Jam rev D, and an 8BitDo SN30 Pro
# wired gamepad (XInput style) or the Adafruit generic SNES style gamepad.
# To trigger bug, attempt to write a file to CIRCUITPY once you see the
# "Reading gamepad input..." message.
gamepad_vid_pid_map = {
# ( vid, pid): (max_packet, sleep_before_read, description)
(0x081f, 0xe401): (8, True, "Adafruit generic SNES style (HID)"),
(0x045e, 0x028e): (32, False, "Xbox 360 compatible (XInput)"),
}
print("Finding USB devices...")
try:
for device in usb.core.find(find_all=True):
# 1. Print descriptor properties
vid = device.idVendor
pid = device.idProduct
print(f"idVendor {vid:04x}")
print(f"idProduct {pid:04x}")
print(f"serial_number {device.serial_number}")
print(f"product {device.product}")
print(f"manufacturer {device.manufacturer}")
print(f"speed {device.speed}")
# 2. Configure interface for known gamepads (skip other devices)
try:
if not ((vid, pid) in gamepad_vid_pid_map):
print(" skipping this one (not a known gamepad)")
continue
max_packet = gamepad_vid_pid_map[(vid,pid)][0]
sleep_before_read = gamepad_vid_pid_map[(vid,pid)][1]
buf = bytearray(max_packet)
interface = 0
if device.is_kernel_driver_active(interface):
device.detach_kernel_driver(interface)
device.set_configuration(interface)
except USBError as e:
print(f"conf USBError: .errno={e.errno} '{str(e)}'")
except USBTimeoutError as e:
print(f"conf USBError: .errno={e.errno} '{str(e)}'")
# 3. Rapidly poll gamepad for input. Goal of this is to trigger
# bugs where an in progress USB transaction fights with other
# CircuitPython tasks.
print("Reading gamepad input...")
print("Writing a file to CIRCUITPY now will probably reset board")
while True:
try:
if sleep_before_read:
# Adafruit generic SNES gamepad (low speed device) gets
# mad and raises USBError if you poll it too quickly
time.sleep(0.003)
n = device.read(0x81, buf, timeout=10)
except USBError as e:
print(f"read USBError: .errno={e.errno} '{str(e)}'")
except USBTimeoutError as e:
print(f"read USBError: .errno={e.errno} '{str(e)}'")
except USBError as e:
print(f"find USBError: .errno={e.errno} '{str(e)}'")
except USBTimeoutError as e:
print(f"find USBError: .errno={e.errno} '{str(e)}'")
displayio.release_displays()
gc.collect()
while True:
# pause with a prompt to avoid rapidly scrolling serial console output
input("press Enter to begin running test: ")
print()
test_descriptor_parsing_read_gamepad()
print()
Behavior
For me, the code above reliably triggers a bug where the board locks up when using an 8BitDo SN30 Pro wired gamepad (XInput style) or the Adafruit generic SNES style gamepad (HID style).
I've seen no indications that the bug depends on the model of gamepad, or even that it's specific to gamepads. I mention those two because they are easy to obtain. My impression is that the bug is related to the frequency of calls to usb.core.Device.read()
.
To trigger bug:
- Run the code and connect the gamepad
- Connect to the serial console and press Enter at the prompt to begin finding gamepad devices
- Once you see the "Reading gamepad input..." message, attempt to write a file to CIRCUIPY
When I do this, the cp code.py /Volumes/CIRCUIPY
shell command I'm using will get stuck (does not return to shell prompt). After a while, macOS gives me a "Disk Not Ejected Properly" notification and the serial console device disappears. The board LEDs do not indicate any error conditions. It seems like a pretty low level crash. To get things working again, I have to power cycle the board or press the reset button.
Description
No response
Additional information
It's possible this might be related to #10555 . That could be the case if the crash described here happens because input polling is starving other essential tasks for CPU time. (e.g. interrupt priority, blocking IO, or some other scheduling thing)