Uh oh!
There was an error while loading. Please reload this page.
fix(hid): Store client instance per-device instead of per-class - #262
Merged
fdesbiens merged 2 commits intoJun 4, 2026
Merged
Conversation
kajteklau
commented
May 7, 2026
ContributorAuthor
Hi, sorry I made a mistake when signing the ECA. It should be fixed now. Please let me know what can I do to contribute. -- Kajtek |
fdesbiens
commented
May 28, 2026
Contributor
Hi @kajteklau. The ECA passes. Thank you. @rahmanih: Can you please review. I would like to ship this in the Q2 2026 release if possible. |
Three issues fixed, discovered during maintainer review: 1. Memory leak in standalone activation error path (entry.c): _ux_host_class_hid_client_activate_wait() set hid_client to NULL without freeing the per-instance copy allocated in client_search. The HID_ENUM_ERROR handler destroys the hid struct without freeing hid_client, so the copy was leaked on every standalone activation failure. Fixed by freeing hid_client before clearing it. 2. Variable declared inside if-block (client_search.c): hid_client_instance was declared inside the if (status == UX_SUCCESS) block, which is a C99 feature. USBX targets C89/C90 embedded toolchains. Moved to the top of the function with other locals. 3. Trailing whitespace throughout both changed files: The PR introduced trailing spaces on most comment-block lines. Reverted all affected lines to their original whitespace. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fdesbiens
commented
Jun 4, 2026
Contributor
Thank you for this fix, @kajteklau. I addressed a few small issues I discovered while reviewing the PR. I am merging into dev now, and this will ship with our Q2 2026 release next week. |
Uh oh!
There was an error while loading. Please reload this page.
kajteklau
commented
Jun 4, 2026
ContributorAuthor
Pleasure working with you guys. Thank you. |
fdesbiens added a commit
that referenced
this pull request
Jun 4, 2026
…control client lifecycle (#265) PR #262 introduced per-instance UX_HOST_CLASS_HID_CLIENT allocation in client_search.c. However, keyboard/mouse/remote_control activate handlers already embed a UX_HOST_CLASS_HID_CLIENT inside their own combined allocation (e.g. UX_HOST_CLASS_HID_CLIENT_KEYBOARD), override hid->hid_client with the embedded copy, and then free the entire combined struct (as 'keyboard_instance', the first field) during deactivation. This created two bugs: 1. Memory leak: the per-instance copy from client_search was abandoned when activate handlers replaced hid->hid_client with their embedded copy. 2. Double-free / UX_MEMORY_CORRUPTED: deactivate.c freed hid->hid_client after calling the handler, but for keyboard/mouse/remote_control the deactivate handler had already freed the entire combined allocation (which contains the embedded hid_client), causing a second free of a pointer into the middle of a now-freed block. Fix: - keyboard/mouse/remote_control activate: free the per-instance copy from client_search before overriding hid->hid_client with the embedded one. - keyboard/mouse/remote_control deactivate: null hid->hid_client after freeing the combined struct, signalling that cleanup is done. - deactivate.c: re-check hid_client != NULL after calling the handler before freeing; keyboard/mouse/remote_control will have nulled it, simple clients will not. - keyboard/mouse ACTIVATE_WAIT error paths (standalone): null hid->hid_client after freeing the combined struct so that the generic cleanup in entry.c skips the already-freed pointer. - entry.c standalone ACTIVATE_WAIT error: guard the free with a NULL check to safely handle both cases. Discovered while investigating test failures introduced by PR #262. All 430 tests pass after this fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
USBX HID Shared Client Instance Pointer Bug
Target Repo
eclipse-threadx/usbx(MIT license, Category A Simple Contribution)Symptom
This bug surfaces when a user plugs in two or more HID devices of the same class to the hub tree. Removing any one HID device of a given type (keyboard or mouse) causes all remaining devices of that type to stop reporting input. Callbacks write into freed memory.
Background: The Global Client Table
At startup, the application registers HID client types (keyboard, mouse) by calling
ux_host_class_hid_client_register(). This function allocates a single flat array ofUX_HOST_CLASS_HID_CLIENTstructs, stored atclass->ux_host_class_clienton theUX_HOST_CLASScontainer for HID. In this application there are two entries:Each entry holds a handler function pointer (e.g.
ux_host_class_hid_keyboard_entry) and aVOID *ux_host_class_hid_client_local_instancefield intended to point to the per-device instance (e.g.UX_HOST_CLASS_HID_KEYBOARD).Original Code: Device Activation Sequence
When a USB HID device enumerates, the middleware creates a per-interface
UX_HOST_CLASS_HIDstruct (hid) and calls_ux_host_class_hid_client_search(hid). This function:_ux_host_class_hid_keyboard_activate).The activate handler then:
hid_client = hid->ux_host_class_hid_clientUX_HOST_CLASS_HID_KEYBOARDstruct (keyboard_instance)The problem:
hid_clientpoints to the shared table entry. Every keyboard shares the same entry. Thelocal_instancefield is a single pointer — the last keyboard to activate overwrites it.Original Code: Device Deactivation Sequence
When a device is unplugged,
_ux_host_class_hid_keyboard_deactivate()runs:hid_client = hid->ux_host_class_hid_client(shared entry)keyboard_instance = hid_client->local_instanceThe deactivation has no other way to find the keyboard instance. The address was only
ever stored in one place:
hid_client->local_instanceon the shared table entry.Failure Sequence (Two Keyboards, A and B)
Keyboard B's interrupt reports continue to arrive, but the callback writes into freed memory. The result is silent data loss and memory leak.
Fix
Instead of storing a pointer to the shared table entry, allocate a per-instance copy of the client struct so each HID device gets its own
local_instancepointer.In
_ux_host_class_hid_client_search()(lines 119–155):The copy inherits the handler function pointer and name from the shared entry but has its own
local_instancefield.local_instanceis explicitly set to NULL so the copy doesn't carry a stale pointer from a previous activation of the same device type. The activate handler then writes the freshly allocated keyboard instance to this privatecopy's
local_instance.In
_ux_host_class_hid_deactivate(), the per-instance copy is freed after the client deactivate handler runs:Fixed Sequence (Two Keyboards, A and B)
Files Changed
common/usbx_host_classes/src/ux_host_class_hid_client_search.c(lines 119–155)common/usbx_host_classes/src/ux_host_class_hid_deactivate.cMemory Cost
One additional
sizeof(UX_HOST_CLASS_HID_CLIENT)allocation (~40 bytes) per connectedHID device. Freed on device removal.