Skip to content

usb: Preserve HCI event and ACL packet ordering in UsbPacketSource - #1007

Open
uier wants to merge 1 commit into
mainfrom
usb-packet-source-poll-sync
Open

uier wants to merge 1 commit into
mainfrom
usb-packet-source-poll-sync

Conversation

@uier

@uier uier commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

As mentioned in #935 (comment), we have a patch in transport/usb.py implementing the mechanism in Intel's btusb driver, and it works for me:

Defer incoming USB Bulk IN ACL packets by one USB interrupt polling interval (default 4ms, matching Linux btusb.c force_poll_sync) and flush deferred ACL packets immediately after queueing incoming USB Interrupt IN HCI events. This prevents Bulk IN ACL packets (such as SMP Identity Information) from racing ahead of concurrent Interrupt IN HCI events (such as HCI_Encryption_Change) on controllers like Intel BE200.

Special handling:

  • For HCI_Disconnection_Complete (0x05), flush deferred ACL packets before queueing the disconnection event so ACL payloads transmitted by the peer prior to disconnect are delivered while the connection state is still active.
  • Cancel any pending ACL flush timer and clear deferred ACL packets in close() and on_transport_lost().

Additional verification:

  • Google Navi smoke tests, all passed with Pixel + Intel BE200, excluding the SCO/ISO packets related test cases.

@uier
uier requested a review from zxzxwu October 16, 2026 08:10

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a mechanism in UsbPacketSource to defer ACL packets by a polling interval, mirroring the behavior of Linux's btusb.c driver to preserve correct packet ordering. It also adds special handling for the disconnection complete event to flush pending ACL packets first, and includes unit tests to verify these behaviors. There are no review comments, so I have no feedback to provide.

…tSource

Defer incoming USB Bulk IN ACL packets by one USB interrupt polling interval
(default 4ms, matching Linux btusb.c force_poll_sync) and flush deferred ACL
packets immediately after queueing incoming USB Interrupt IN HCI events.
This prevents Bulk IN ACL packets (such as SMP Identity Information) from
racing ahead of concurrent Interrupt IN HCI events (such as
HCI_Encryption_Change) on controllers like Intel BE200.

Special handling:
- For HCI_Disconnection_Complete (0x05), flush deferred ACL packets before
  queueing the disconnection event so ACL payloads transmitted by the peer
  prior to disconnect are delivered while the connection state is still active.
- Cancel any pending ACL flush timer and clear deferred ACL packets in close()
  and on_transport_lost().
@uier
uier force-pushed the usb-packet-source-poll-sync branch from 8b8b4e8 to 5481049 Compare October 16, 2026 08:35
@zxzxwu

zxzxwu commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

Thanks for tackling this, the event/ACL race on Intel BE200 is real and deferring Bulk IN ACL until the next Interrupt IN poll is a sensible approach. A few concerns before merging:

  1. Opt-in / scope. This adds up to intr_interval (≥4 ms) latency to every inbound ACL packet on all USB controllers. In Linux this behavior is gated behind poll_sync, not enabled unconditionally. Could we make it opt-in (e.g. a transport spec option like usb:...[poll_sync], or keyed on VID/PID), and plumb intr_interval through open_usb_transport()? Right now callers have no way to tune or disable it.
  2. bInterval units. getInterval() returns raw bInterval, which is only in ms for low/full-speed endpoints. For high-speed it's 2^(bInterval-1) * 125 µs. Please take device speed into account (getDeviceSpeed()). Also, max(interval, 4ms) means the endpoint value can only make the delay longer. Is that intended?
  3. Semantics vs. btusb. btusb's rx_work drains the whole acl_q once the first deadline expires, and it has no special case for Disconnection Complete. Per-packet deadlines and the pre-disconnect flush may well be fine, but please update the "Mirrors Linux btusb.c" comments so they describe what this code actually does.
  4. Tests.
    • In case 3, assert source.queue.empty() is always true because feed() schedules via call_soon_threadsafe, so it doesn't check the deferral. Consider await asyncio.sleep(0) and then assert the queue is still empty, then assert it's delivered after intr_interval.
    • Case 2 also passes without this change.
    • Please add coverage for multiple ACL packets with staggered deadlines, close() cancelling the pending timer, intr_interval=0 bypass, and interval derivation from the endpoint.
  5. Nit: factor the duplicated timer-cancel/clear logic in close() / on_transport_lost() into a helper.

This branch has not been deployed

No deployments
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.

2 participants