Skip to content

Periodic split scheduling option (Keyrah V3 support for BMC64) - #703

Open
aminch wants to merge 2 commits into
rsta2:developfrom
aminch:periodic-split-scheduling
Open

aminch wants to merge 2 commits into
rsta2:developfrom
aminch:periodic-split-scheduling

Conversation

@aminch

@aminch aminch commented Sep 16, 2026

Copy link
Copy Markdown

I am investigating USB transaction errors in BMC64 on Raspberry Pi 3 using Keyrah V3.

The affected device is an Individual Computers Keyrah V3: a full-speed composite HID device behind the Pi 3 onboard LAN9514 high-speed hub. It exposes six interrupt-IN HID endpoints, all normally with bInterval = 1.

I have been working with them based on the original Keyrah BMC64 issue created here (sorry it's a very long thread):
randyrossi/bmc64#235

They have a thread on their forum describing the problems as well:
https://forum.icomp.de/index.php?thread/4525-how-to-use-keyrah-v3-with-bmc64-and-other-circle-based-projects/

A separate independent joystick issue has also popped up in BMC64 that has the same recurring USBErrorTransaction pattern:
randyrossi/bmc64#371

What I discovered:

With high-speed USB and USE_USB_FIQ enabled (as BMC64 is configured), Circle reports recurring USBErrorTransaction failures in the situations listed above.

After a lot of research and testing it was looking like it was a problem with high-speed-to-full-speed split-transactions. I ran two tests that helped me narrow down the problem:

  • I set usbspeed=full and this eliminates the errors completely, which strongly implicates the high-speed-to-full-speed split-transaction path.
  • I then ran tests disabling USE_USB_FIQ while retaining USE_USB_SOF_INTR and high-speed USB, this also eliminates the errors.

This lead me to the code below in CDWHCIFrameSchedulerPeriodic::CompleteSplit(), where there is a difference for the FIQ path as it schedules the first Complete-Split one microframe offset, and two microframes when it's off, after a successful Start-Split: See here: https://github.com/rsta2/circle/blob/develop/lib/usb/dwhciframeschedper.cpp#L91

As a test I created a USE_USB_FIQ enabled build with 2 set as the m_usFrameOffset instead of 1, and then all the errors went away! :)

According to what I could understand, with the help of AI, changing this value to 2 in the FIQ path adds one extra microframe (125 microseconds) of scheduling delay, but otherwise should be ok.
I wanted to run this by you as it's deep in the USB code and I'm not an expert.

Fixes

Main fix

This PR added a new optional setting USE_USB_FIQ_PERIODIC_SPLIT_COMPATIBILITY, which by default is off, but when defined sets the microframe offset to 2. I was not sure if this should be a permant change so I created this optional define.

It is in the commit "Add periodic-split timing compatibility option"

Additional hardening

While I was investigating this problem I also found that a usb connection was dropped and wouldn't reconnect or retry if just a single error was reported. To try and make the system more tolerant to some errors, and retry and stay connected I added code to only drop the connection after multiple errors.

This enhancment is in the commit "Tolerate transient HID report errors". I think this makes sense to include in Circle but please let me know if you would like me to keep this as a patch in BMC64.

Testing

To reproduce the problem I creates a mock Keyrah V3 firmware that can be installed on a Raspberry Pi and connected to a Pi and driven over UART with telnet. It is available in the main repo if you want to use it for testing:
https://github.com/randyrossi/bmc64/tree/master/tools/keyrah_test

In addition I worked with the developer @mrdudz from Keyrah to test the device on an actual Keyrah unit. The fix has been confirmed working on actual hardware (see issue thread at the top).

I have also only tested on Pi0, Pi2 and Pi3 which BMC64 is targeted at.

Under USE_USB_FIQ, a periodic split transaction's first
Complete-Split is scheduled 1 microframe after its Start-Split
ACK instead of the 2 microframes used everywhere else, since the
FIQ's low latency normally makes that safe.

Some Full/Low-Speed hub/device combinations don't respond in
time for that tighter schedule and see persistent transaction
errors on their interrupt endpoint(s).

Add USE_USB_FIQ_PERIODIC_SPLIT_COMPATIBILITY to sysconfig.h,
which widens dwhciframeschedper.cpp's Complete-Split offset back
to 2 microframes to match the non-FIQ timing, leaving the rest
of USE_USB_FIQ's behaviour untouched.
Previously, CUSBHIDDevice::CompletionRoutine() gave up on a report
endpoint (bRestart = FALSE) after a single non-frame-overrun USB
error, so one transient glitch would permanently stop polling that
endpoint.

Add m_nErrorCount and m_nLastErrorTicksHZ to CUSBHIDDevice, and only
give up after MaxConsecutiveErrors (50) errors in a row. A run
resets to zero on any successful completion, and also decays if at
least ErrorDecayMs (2000ms) has passed since the last error, so an
idle endpoint's isolated errors don't accumulate toward the limit
just because it rarely completes successfully.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant