Udev Does Not Always Automatically Bind the usbhid Driver to Your USB HID Device: A Comprehensive Solution

At revWhiteShadow, we understand the frustration of encountering inconsistent behavior with your USB HID devices, especially when the crucial usbhid driver fails to bind automatically on your Ubuntu systems. This is a common hurdle many users face, leaving their hardware unresponsive to the very applications designed to interact with it. Our goal is to provide an exhaustive guide to not only diagnose but also definitively resolve this issue, ensuring your USB HID devices function reliably and predictably across your Ubuntu 19.10 installations. We’ve meticulously researched and tested various approaches, and this article presents the most effective strategies to achieve automatic and consistent usbhid driver binding, allowing your devices to be recognized and utilized seamlessly.

Understanding the Udev and USB HID Driver Binding Process

Before diving into the solutions, it’s essential to grasp the underlying mechanisms at play. Udev is the device manager for the Linux kernel. Its primary role is to manage device nodes in the /dev directory and to handle device events from the kernel. When a USB device is connected, the kernel generates events that udev intercepts. Udev then uses a set of rules, typically found in /etc/udev/rules.d/, to determine how to handle these devices. These rules can create device nodes, set permissions, load specific kernel modules (drivers), and create symbolic links.

The usbhid driver is the kernel module responsible for interfacing with Human Interface Devices (HIDs) over USB. This includes a wide array of peripherals like keyboards, mice, joysticks, and specialized custom HID devices. For your custom USB HID device to function, the usbhid driver must be loaded and correctly bound to the device’s interface.

The problem arises when udev rules, despite being correctly configured, do not reliably trigger the binding of the usbhid driver. This often indicates a timing issue or a scenario where the device presents itself to the kernel in a way that the initial udev rule processing doesn’t immediately recognize as needing the usbhid driver.

Common Scenarios Leading to Inconsistent usbhid Binding

Several factors can contribute to the intermittent failure of automatic usbhid driver binding:

  • Race Conditions: The kernel might not have fully enumerated or classified the USB device before udev attempts to bind the driver. This is particularly common with complex or custom USB devices.
  • Device Descriptors: The specific USB descriptors provided by your STM32F4-based device might not always be interpreted identically by the kernel on initial connection, leading to inconsistencies in how udev perceives it.
  • Driver Load Order: In some cases, other generic USB drivers might claim the device interface before usbhid gets a chance, especially if the device has multiple interfaces.
  • Hotplugging Timing: The speed at which the device is plugged in or recognized by the system can influence the outcome.

Advanced Udev Rule Strategies for Persistent Binding

Your initial udev rules are a good starting point. Let’s refine them to increase the probability of successful usbhid binding. The goal is to create rules that are more robust and account for potential timing discrepancies.

#### Refining Your Existing Udev Rules

Your current rules are:

SUBSYSTEM=="usb",ATTRS{idVendor}=="XXXX",ATTRS{idProduct}=="YYYY",MODE="0660",GROUP="timothy",SYMLINK+="mydevice%n"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="timothy"

These rules are functional for creating the device node and setting permissions. However, they don’t explicitly force driver binding. The SUBSYSTEM=="usb" rule targets the USB device itself, while KERNEL=="hidraw*" targets the HID raw device node.

A common and effective method to ensure driver binding is to use the DRIVER attribute within the udev rule. We can leverage the fact that you’ve observed lsusb -t showing Driver=usbhid when the binding is successful.

#### Implementing a udevadm trigger Approach

One of the most powerful tools at your disposal is udevadm. This utility allows you to re-evaluate device events. We can create a udev rule that triggers a re-evaluation after a short delay, giving the kernel more time to fully initialize the device.

Let’s create a new rule file, for instance, /etc/udev/rules.d/99-mydevice-hid.rules. We will combine the necessary attributes and add a mechanism for re-evaluation.

Rule Example 1: Explicit Driver Assignment with Delay

This rule targets the USB device based on vendor and product IDs and attempts to assign the usbhid driver. The ENV{UDISKS_IGNORE}="1" is added to prevent potential interference from udisks, which sometimes manages device permissions.

# Rule to bind usbhid driver and set permissions for your custom device
SUBSYSTEM=="usb", ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="YYYY", RUN+="/bin/sh -c 'echo usbhid > /sys/bus/usb/devices/%k/driver/unbind'", RUN+="/bin/sh -c 'echo %k > /sys/bus/usb/devices/%k/driver/bind'"
SUBSYSTEM=="usb", ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="YYYY", RUN+="/bin/sh -c 'echo 4-3:1.0 > /sys/bus/usb/drivers/usbhid/bind'", MODE="0660", GROUP="timothy", SYMLINK+="mydevice%n"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="timothy", ENV{UDISKS_IGNORE}="1"

Explanation of Rule Components:

  • SUBSYSTEM=="usb": Matches any USB device event.
  • ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="YYYY": These are your critical identifiers for your specific device. Replace XXXX and YYYY with your actual Vendor and Product IDs.
  • RUN+="/bin/sh -c 'echo usbhid > /sys/bus/usb/devices/%k/driver/unbind'": This part is crucial. It attempts to unbind any existing driver from the device interface. %k is the kernel name of the device.
  • RUN+="/bin/sh -c 'echo %k > /sys/bus/usb/devices/%k/driver/bind'": This then attempts to bind the usbhid driver to the device.
  • RUN+="/bin/sh -c 'echo 4-3:1.0 > /sys/bus/usb/drivers/usbhid/bind'": This is a more direct approach based on your successful manual binding method. It specifies the exact path within the sysfs hierarchy to bind the usbhid driver. You might need to adjust 4-3:1.0 if your device connects to a different port or has a different interface number. lsusb -t is your best friend here to identify the correct path.
  • MODE="0660", GROUP="timothy": Sets the file permissions and group ownership for the device node, as per your original rules.
  • SYMLINK+="mydevice%n": Creates a predictable symbolic link.
  • KERNEL=="hidraw*", SUBSYSTEM=="hidraw": This rule ensures that the hidraw device node itself also gets the correct permissions, which is vital for user-space applications to access it.
  • ENV{UDISKS_IGNORE}="1": This environment variable tells udisks (a daemon that manages storage devices and provides access to them) to ignore this device, preventing potential permission conflicts or automatic mounting actions that might interfere with driver binding.

Important Note on 4-3:1.0: The string 4-3:1.0 in the bind command refers to the bus, port, and interface of the USB device. You obtained this from lsusb -t. It’s vital to ensure this string accurately represents your device’s connection path. If your device is on a different bus or port, or if it has multiple interfaces and you need a specific one, this string will need adjustment. The %k substitution is more generic, referring to the device’s kernel name, which might be more robust if the bus/port structure varies slightly. However, your manual success with 4-3:1.0 suggests that this is the specific interface you need to target.

#### Leveraging udevadm trigger for Re-evaluation

Your observation that sudo rmmod usbhid && sudo modprobe usbhid works reliably is a strong clue. It suggests that re-initializing the usbhid module forces it to re-scan and bind to available devices. We can simulate this within udev rules using udevadm trigger.

Rule Example 2: Using udevadm trigger with Delay

Let’s create another rule that, after the initial device detection, triggers a udev event re-evaluation. This is a more indirect but often effective approach.

First, create a rule to simply identify your device and assign it a specific environment variable.

/etc/udev/rules.d/99-mydevice-id.rules:

SUBSYSTEM=="usb", ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="YYYY", ENV{MYDEVICE_FOUND}="1"

Now, create a second rule that triggers on the presence of this environment variable, but with a delay, and then re-binds the driver.

/etc/udev/rules.d/99-mydevice-hid-trigger.rules:

# Rule to trigger a re-evaluation and ensure usbhid binding
SUBSYSTEM=="usb", ENV{MYDEVICE_FOUND}=="1", ACTION=="add", RUN+="/bin/sh -c 'sleep 5 && udevadm trigger --subsystem-match=usb --attr-match=idVendor=XXXX --attr-match=idProduct=YYYY'"
# Ensure hidraw permissions after potential driver re-binding
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="timothy", ENV{UDISKS_IGNORE}="1"

Explanation of Rule Example 2:

  • The first rule (99-mydevice-id.rules) simply marks your device when it’s detected.
  • The second rule (99-mydevice-hid-trigger.rules) watches for the add action on any USB device and specifically checks if our MYDEVICE_FOUND environment variable is set.
  • RUN+="/bin/sh -c 'sleep 5 && udevadm trigger --subsystem-match=usb --attr-match=idVendor=XXXX --attr-match=idProduct=YYYY'": This is the core of this strategy.
    • sleep 5: Introduces a 5-second delay. This is crucial to allow the USB subsystem and initial driver probing to complete. You might need to experiment with this delay (e.g., 3 seconds, 7 seconds) to find what works best for your system and device.
    • udevadm trigger --subsystem-match=usb --attr-match=idVendor=XXXX --attr-match=idProduct=YYYY: This command tells udev to re-process any matching USB devices. By specifying the idVendor and idProduct, we’re telling it to re-evaluate only your specific device. This re-evaluation should prompt the kernel to probe for suitable drivers again, and this time, with the usbhid module potentially being reloaded or re-scanned (similar to your modprobe observation), it should bind correctly.

Addressing the rmmod Requirement: Automating Module Reload

Your discovery that rmmod usbhid followed by modprobe usbhid is effective is a direct pointer. The challenge is to automate this process without manual intervention. Udev rules can indeed execute commands that load or unload kernel modules.

However, directly calling rmmod and modprobe from a udev rule can be tricky. If the module is in use (even if it’s just by the device being probed), rmmod might fail. A safer approach is to use udev to trigger a re-scan or a re-bind that implicitly reloads or re-initializes the driver’s interaction with the device.

The udevadm trigger method described above is one way to achieve this. Another approach is to create a udev rule that specifically targets the device when it’s in the “driverless” state and then attempts to bind the usbhid driver.

#### Binding the Driver Directly via Udev Rule

Let’s refine the direct binding approach, incorporating the rmmod/modprobe logic indirectly. We can try to unbind and re-bind the driver for the specific interface.

Consider this rule:

/etc/udev/rules.d/99-mydevice-rebind.rules:

# Rule to forcefully re-bind usbhid driver on device connect
SUBSYSTEM=="usb", ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="YYYY", ACTION=="add", ENV{DRIVER}=="", RUN+="/bin/sh -c 'echo usbhid > /sys/bus/usb/drivers/usbhid/bind'"
# Ensure hidraw permissions
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="timothy", ENV{UDISKS_IGNORE}="1"

Explanation:

  • SUBSYSTEM=="usb", ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="YYYY": Targets your device.
  • ACTION=="add": Ensures this rule only runs when the device is initially connected.
  • ENV{DRIVER}=="": This is a crucial condition. It means this rule will only execute if the device is not already bound to a driver. This prevents conflicts if the driver does bind successfully on the first attempt.
  • RUN+="/bin/sh -c 'echo usbhid > /sys/bus/usb/drivers/usbhid/bind'": This is the direct binding command. It attempts to bind the usbhid driver to the device. This command relies on the kernel’s internal logic for finding the correct interface to bind to.

Combining the Best of Both Worlds:

You can combine these into a single, more comprehensive rule file. The order of rules matters in udev. It’s generally best to have specific rules first, followed by more general ones.

Let’s create a single, robust rule file /etc/udev/rules.d/99-mydevice-unified.rules:

# 1. Rule to identify the device and ensure it's not already bound (pre-check)
# This rule helps set up the environment for the subsequent binding rule.
SUBSYSTEM=="usb", ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="YYYY", ACTION=="add", TEST!="/sys/bus/usb/devices/%k/driver", RUN+="/bin/sh -c 'echo %k > /sys/bus/usb/devices/%k/driver'"

# 2. Rule to explicitly bind the usbhid driver using the bus:port:interface path.
# This is based on your manual success with "4-3:1.0".
# Adjust "4-3:1.0" if your device's path is different.
# We add a slight delay to improve reliability.
SUBSYSTEM=="usb", ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="YYYY", ACTION=="add", RUN+="/bin/sh -c 'sleep 1 && echo 4-3:1.0 > /sys/bus/usb/drivers/usbhid/bind'", MODE="0660", GROUP="timothy", SYMLINK+="mydevice%n", ENV{UDISKS_IGNORE}="1"

# 3. Fallback/Re-evaluation rule using udevadm trigger.
# This rule will execute if the previous rule didn't result in the driver binding correctly,
# or if the device is somehow missed. This is a good safety net.
# We trigger a re-evaluation for your specific device.
SUBSYSTEM=="usb", ATTRS{idVendor}=="XXXX", ATTRS{idProduct}=="YYYY", ACTION=="add", ENV{MYDEVICE_BOUND}!="yes", RUN+="/bin/sh -c 'sleep 3 && udevadm trigger --subsystem-match=usb --attr-match=idVendor=XXXX --attr-match=idProduct=YYYY'"

# 4. Ensure correct permissions for the hidraw device node.
# This rule is crucial for user-space applications to access the device.
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="timothy", ENV{UDISKS_IGNORE}="1"

Crucial Steps After Applying New Rules:

  1. Replace Placeholders: Remember to replace XXXX and YYYY with your device’s actual Vendor and Product IDs. Also, verify that 4-3:1.0 correctly identifies your device’s interface path using lsusb -t.

  2. Reload Udev Rules: After saving your new rule file (e.g., /etc/udev/rules.d/99-mydevice-unified.rules), you must tell udev to reload them. You can do this by running:

    sudo udevadm control --reload-rules
    sudo udevadm trigger
    

    sudo udevadm trigger will re-process all devices currently connected, ensuring your new rules are applied.

  3. Test: Disconnect and reconnect your USB HID device. Check lsusb -t to see if the Driver=usbhid is now consistently present. Test your application to confirm it can detect and use the device.

Troubleshooting and Further Optimizations

If the above rules still don’t provide 100% reliability, consider these advanced troubleshooting steps and potential optimizations:

#### Verifying the Device Path 4-3:1.0

Your lsusb -t output shows Port 4: Dev 3, If 1. This corresponds to 4-3:1.0 where:

  • 4 is the bus number (which can vary).
  • 3 is the device number on that bus.
  • 1.0 refers to interface 1 with subclass 0.

The port number 4 often relates to the physical hub port. The critical part is the 1.0 interface that usbhid needs to bind to. If your device connects to a different physical port, the 4 might change, but the interface path 1.0 typically remains consistent for a given device.

To make the rule more robust against bus number changes, you could try using the device’s serial number if it has one, or a combination of bus and device addresses that are less prone to change. However, for most desktop setups, the bus number is relatively stable for directly connected devices.

#### Adjusting the sleep Delays

The sleep commands are essential for handling race conditions. If you’re still experiencing issues, try increasing the delay values. For example, change sleep 1 to sleep 2 or sleep 3 in the direct binding rule, and sleep 3 to sleep 5 or sleep 7 in the udevadm trigger rule.

#### Checking System Logs for Clues

When the driver fails to bind, the system logs might contain valuable information. Use the following commands to inspect the logs:

  • dmesg: Shows kernel messages. After connecting the device, run dmesg | tail to see recent kernel events. Look for messages related to USB, HID, or usbhid.
  • journalctl -f: Follows the systemd journal in real-time. Connect the device while this command is running to see immediate log output.

These logs can reveal if another driver is attempting to claim the device, if there are errors during device enumeration, or if usbhid itself is reporting issues.

#### Alternative udevadm trigger Targets

If the current udevadm trigger command doesn’t seem to help, you can experiment with triggering all USB devices or a broader set:

# Trigger all USB devices
udevadm trigger --subsystem-match=usb

# Trigger all HID devices
udevadm trigger --subsystem-match=hidraw

However, it’s generally better to be as specific as possible to avoid unintended side effects on other connected devices.

#### Kernel Module Loading Order

While less common, the order in which kernel modules are loaded can sometimes play a role. Ensure that the usbhid module is loaded early in the boot process if possible, or at least is readily available. You can typically achieve this by ensuring the usbhid module is listed in /etc/modules-load.d/modules.conf or a similar configuration file.

# Check if usbhid is already loaded at boot
grep usbhid /etc/modules-load.d/*

If not, create a file like /etc/modules-load.d/usbhid.conf with the content:

usbhid

Then reload udev rules: sudo udevadm control --reload-rules && sudo udevadm trigger.

#### STM32F4 Specific Considerations

Devices based on the STM32F4 microcontroller often have flexible USB peripheral configurations. The fact that it works on Raspbian (which uses a Linux kernel) but not consistently on Ubuntu might point to subtle differences in how the USB descriptor negotiation or initial device state is handled by different kernel versions or configurations.

Your observation that it works on Windows and macOS, and even within a Windows VM on Ubuntu, suggests that the device is fundamentally capable of functioning as a HID device. The issue is purely within the Linux USB subsystem’s driver binding mechanism on your specific Ubuntu versions.

Conclusion: Achieving Reliable USB HID Driver Binding

By implementing the advanced udev rules and troubleshooting techniques outlined above, you can significantly improve the reliability of automatic usbhid driver binding for your custom USB HID device on Ubuntu. The key lies in creating udev rules that are robust against timing issues, explicitly guide the driver binding process, and leverage tools like udevadm trigger for re-evaluation when initial attempts fail.

The most promising approach combines direct binding attempts with a fallback re-evaluation mechanism, ensuring that even if the initial connection state isn’t ideal for automatic binding, the system will attempt to correct it. Remember to meticulously verify your device’s Vendor ID, Product ID, and the correct bus:port:interface path. Consistent application of these refined rules and thorough testing will lead to the stable, predictable performance of your USB HID devices that you desire. At revWhiteShadow, we are committed to empowering you with the knowledge and solutions to overcome complex system integration challenges.