Udev does not always automatically bind the usbhid driver to my device
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. ReplaceXXXX
andYYYY
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 theusbhid
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 theusbhid
driver. You might need to adjust4-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 thehidraw
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 tellsudisks
(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 theadd
action on any USB device and specifically checks if ourMYDEVICE_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 theidVendor
andidProduct
, 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 theusbhid
module potentially being reloaded or re-scanned (similar to yourmodprobe
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 theusbhid
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:
Replace Placeholders: Remember to replace
XXXX
andYYYY
with your device’s actual Vendor and Product IDs. Also, verify that4-3:1.0
correctly identifies your device’s interface path usinglsusb -t
.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.Test: Disconnect and reconnect your USB HID device. Check
lsusb -t
to see if theDriver=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 interface1
with subclass0
.
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, rundmesg | tail
to see recent kernel events. Look for messages related to USB, HID, orusbhid
.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.