Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29b23b5964 | ||
|
|
c42dff6384 |
42
.gitignore
vendored
Normal file
42
.gitignore
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
# IDE and Editor
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# Dependencies
|
||||
node_modules/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.egg-info/
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
build/
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.ko
|
||||
*.mod
|
||||
*.mod.c
|
||||
*.cmd
|
||||
.*.cmd
|
||||
Module.symvers
|
||||
modules.order
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# OS files
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
53
README.md
53
README.md
@@ -1,3 +1,54 @@
|
||||
|
||||
Device Driver
|
||||
============
|
||||
======================
|
||||
|
||||
A collection of USB device drivers for Linux kernel, demonstrating how to interact with various USB HID devices.
|
||||
|
||||
## Current Drivers
|
||||
|
||||
### Mouse Driver (`mouse/`)
|
||||
A USB HID mouse driver that supports 16-bit coordinate tracking for high-DPI gaming mice. Tested with Cooler Master MM710.
|
||||
|
||||
**Features:**
|
||||
- Left, right, middle, side, and extra button support
|
||||
- 16-bit X/Y movement (high-speed tracking)
|
||||
- Scroll wheel support
|
||||
- Binds to HID Boot Protocol Mouse interfaces
|
||||
|
||||
**Building:**
|
||||
```bash
|
||||
cd mouse/
|
||||
make
|
||||
```
|
||||
|
||||
## USB Driver Manager
|
||||
|
||||
The `usb_driver_manager.py` tool simplifies the process of binding USB devices to custom drivers.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
# Search for .ko files in current directory
|
||||
sudo python3 usb_driver_manager.py
|
||||
|
||||
# Search in specific directories
|
||||
sudo python3 usb_driver_manager.py ./mouse ./keyboard
|
||||
|
||||
# The tool will:
|
||||
# 1. List available USB HID devices
|
||||
# 2. Show available kernel modules (.ko files)
|
||||
# 3. Unbind the device from its current driver
|
||||
# 4. Unload existing module (if already loaded)
|
||||
# 5. Load the new module
|
||||
# 6. Bind the device to the new driver
|
||||
```
|
||||
|
||||
## Future Drivers
|
||||
|
||||
- **Keyboard**: USB HID keyboard driver
|
||||
- **Racing Wheel**: USB racing wheel driver with force feedback
|
||||
|
||||
## Requirements
|
||||
|
||||
- Linux kernel headers
|
||||
- Python 3.6+
|
||||
- Root/sudo access for driver loading and binding
|
||||
|
||||
23
mouse/Makefile
Normal file
23
mouse/Makefile
Normal file
@@ -0,0 +1,23 @@
|
||||
obj-m += simple_usb_mouse.o
|
||||
|
||||
PWD := $(CURDIR)
|
||||
|
||||
all:
|
||||
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
|
||||
mkdir -p build
|
||||
mv -f *.o *.ko *.mod *.mod.c Module.symvers modules.order build/ 2>/dev/null || true
|
||||
find . -maxdepth 1 -name '.*.cmd' -exec mv {} build/ \; 2>/dev/null || true
|
||||
find . -maxdepth 1 -name '.*.o' -exec mv {} build/ \; 2>/dev/null || true
|
||||
[ -d .tmp_versions ] && mv .tmp_versions build/ || true
|
||||
|
||||
clean:
|
||||
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
|
||||
rm -rf build
|
||||
|
||||
install:
|
||||
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules_install
|
||||
depmod -a
|
||||
|
||||
uninstall:
|
||||
rm -f /lib/modules/$(shell uname -r)/kernel/drivers/usb/input/simple_usb_mouse.ko
|
||||
depmod -a
|
||||
357
mouse/simple_usb_mouse.c
Normal file
357
mouse/simple_usb_mouse.c
Normal file
@@ -0,0 +1,357 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Simple USB Mouse Driver
|
||||
*
|
||||
* A minimal USB HID Boot Protocol mouse driver for learning purposes.
|
||||
* This driver can bind to any standard USB mouse that supports the
|
||||
* HID Boot Protocol.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb/input.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
#define DRIVER_AUTHOR "Testor"
|
||||
#define DRIVER_DESC "Simple USB Mouse Driver"
|
||||
|
||||
MODULE_AUTHOR(DRIVER_AUTHOR);
|
||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/*
|
||||
* Driver context structure
|
||||
* This holds all the data we need for each connected mouse
|
||||
*/
|
||||
struct simple_usb_mouse {
|
||||
char name[128]; /* Device name */
|
||||
char phys[64]; /* Physical path */
|
||||
struct usb_device *usbdev; /* USB device */
|
||||
struct input_dev *input_dev; /* Input device for reporting events */
|
||||
struct urb *irq; /* URB for interrupt transfers */
|
||||
unsigned char *data; /* Data buffer (8 bytes for mouse data) */
|
||||
dma_addr_t data_dma; /* DMA address for data buffer */
|
||||
};
|
||||
|
||||
/*
|
||||
* IRQ handler - called when mouse sends data
|
||||
*
|
||||
* Cooler Master MM710 format (8 bytes):
|
||||
* Byte 0: Button states
|
||||
* Bit 0: Left button
|
||||
* Bit 1: Right button
|
||||
* Bit 2: Middle button
|
||||
* Bit 3: Side button
|
||||
* Bit 4: Extra button
|
||||
* Bytes 1: (unused)
|
||||
* Bytes 2-3: X movement (16-bit signed, little-endian)
|
||||
* Bytes 4-5: Y movement (16-bit signed, little-endian)
|
||||
* Byte 6: Wheel movement (8-bit signed)
|
||||
* Byte 7: (unused)
|
||||
*/
|
||||
static void simple_mouse_irq(struct urb *urb)
|
||||
{
|
||||
struct simple_usb_mouse *mouse = urb->context;
|
||||
unsigned char *data = mouse->data;
|
||||
struct input_dev *dev = mouse->input_dev;
|
||||
int status;
|
||||
int16_t x_movement, y_movement;
|
||||
|
||||
/* Check URB status */
|
||||
switch (urb->status) {
|
||||
case 0:
|
||||
/* Success - process the data */
|
||||
break;
|
||||
case -ECONNRESET:
|
||||
case -ENOENT:
|
||||
case -ESHUTDOWN:
|
||||
/* Device disconnected or URB killed - don't resubmit */
|
||||
pr_debug("simple_mouse: URB stopped (status %d)\n", urb->status);
|
||||
return;
|
||||
default:
|
||||
/* Transient error - we'll resubmit and try again */
|
||||
pr_debug("simple_mouse: URB error (status %d)\n", urb->status);
|
||||
goto resubmit;
|
||||
}
|
||||
|
||||
/* Debug: Print raw data bytes */
|
||||
/* pr_info("simple_mouse: RAW DATA: [0]=%02x [1]=%02x [2]=%02x [3]=%02x [4]=%02x [5]=%02x [6]=%02x [7]=%02x\n",
|
||||
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); */
|
||||
|
||||
/* Report button states */
|
||||
input_report_key(dev, BTN_LEFT, data[0] & 0x01);
|
||||
input_report_key(dev, BTN_RIGHT, data[0] & 0x02);
|
||||
input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);
|
||||
input_report_key(dev, BTN_SIDE, data[0] & 0x08);
|
||||
input_report_key(dev, BTN_EXTRA, data[0] & 0x10);
|
||||
|
||||
/* Combine bytes for 16-bit movement (little-endian) */
|
||||
x_movement = (int16_t)(data[2] | (data[3] << 8));
|
||||
y_movement = (int16_t)(data[4] | (data[5] << 8));
|
||||
|
||||
/* Report movement (relative coordinates) */
|
||||
input_report_rel(dev, REL_X, x_movement);
|
||||
input_report_rel(dev, REL_Y, y_movement);
|
||||
input_report_rel(dev, REL_WHEEL, (signed char) data[6]);
|
||||
|
||||
/* Sync - tell input subsystem we're done with this event */
|
||||
input_sync(dev);
|
||||
|
||||
resubmit:
|
||||
/* Resubmit URB to continue receiving data */
|
||||
status = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
if (status) {
|
||||
dev_err(&mouse->usbdev->dev,
|
||||
"Failed to resubmit URB: %d\n", status);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when device is opened (e.g., when an application reads from it)
|
||||
* We start the URB here to save resources when mouse isn't being used
|
||||
*/
|
||||
static int simple_mouse_open(struct input_dev *dev)
|
||||
{
|
||||
struct simple_usb_mouse *mouse = input_get_drvdata(dev);
|
||||
|
||||
pr_info("simple_mouse: Device opened\n");
|
||||
|
||||
mouse->irq->dev = mouse->usbdev;
|
||||
if (usb_submit_urb(mouse->irq, GFP_KERNEL)) {
|
||||
pr_err("simple_mouse: Failed to submit URB on open\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when device is closed
|
||||
* Stop the URB to save resources
|
||||
*/
|
||||
static void simple_mouse_close(struct input_dev *dev)
|
||||
{
|
||||
struct simple_usb_mouse *mouse = input_get_drvdata(dev);
|
||||
|
||||
pr_info("simple_mouse: Device closed\n");
|
||||
usb_kill_urb(mouse->irq);
|
||||
}
|
||||
|
||||
/*
|
||||
* Probe function - called when a matching USB device is connected
|
||||
*/
|
||||
static int simple_mouse_probe(struct usb_interface *intf,
|
||||
const struct usb_device_id *id)
|
||||
{
|
||||
struct usb_device *usbdev = interface_to_usbdev(intf);
|
||||
struct usb_host_interface *interface;
|
||||
struct usb_endpoint_descriptor *endpoint;
|
||||
struct simple_usb_mouse *mouse;
|
||||
struct input_dev *input_dev;
|
||||
int pipe, maxp;
|
||||
int error = -ENOMEM;
|
||||
|
||||
pr_info("simple_mouse: Probing device %04x:%04x\n",
|
||||
le16_to_cpu(usbdev->descriptor.idVendor),
|
||||
le16_to_cpu(usbdev->descriptor. idProduct));
|
||||
|
||||
interface = intf->cur_altsetting;
|
||||
|
||||
/* Validate interface has exactly 1 endpoint */
|
||||
if (interface->desc.bNumEndpoints != 1) {
|
||||
pr_err("simple_mouse: Interface has %d endpoints (expected 1)\n",
|
||||
interface->desc.bNumEndpoints);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
endpoint = &interface->endpoint[0]. desc;
|
||||
|
||||
/* Ensure it's an interrupt IN endpoint */
|
||||
if (! usb_endpoint_is_int_in(endpoint)) {
|
||||
pr_err("simple_mouse: Endpoint is not interrupt IN\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Calculate pipe and max packet size */
|
||||
pipe = usb_rcvintpipe(usbdev, endpoint->bEndpointAddress);
|
||||
maxp = usb_maxpacket(usbdev, pipe);
|
||||
|
||||
/* Allocate our context structure */
|
||||
mouse = kzalloc(sizeof(struct simple_usb_mouse), GFP_KERNEL);
|
||||
if (!mouse)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Allocate input device */
|
||||
input_dev = input_allocate_device();
|
||||
if (!input_dev) {
|
||||
pr_err("simple_mouse: Failed to allocate input device\n");
|
||||
goto fail_input_alloc;
|
||||
}
|
||||
|
||||
/* Allocate DMA-coherent buffer for USB data */
|
||||
mouse->data = usb_alloc_coherent(usbdev, 8, GFP_KERNEL,
|
||||
&mouse->data_dma);
|
||||
if (!mouse->data) {
|
||||
pr_err("simple_mouse: Failed to allocate DMA buffer\n");
|
||||
goto fail_dma_alloc;
|
||||
}
|
||||
|
||||
/* Allocate URB */
|
||||
mouse->irq = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (!mouse->irq) {
|
||||
pr_err("simple_mouse: Failed to allocate URB\n");
|
||||
goto fail_urb_alloc;
|
||||
}
|
||||
|
||||
/* Store references */
|
||||
mouse->usbdev = usbdev;
|
||||
mouse->input_dev = input_dev;
|
||||
|
||||
/* Build device name from USB descriptors */
|
||||
if (usbdev->manufacturer)
|
||||
strscpy(mouse->name, usbdev->manufacturer, sizeof(mouse->name));
|
||||
|
||||
if (usbdev->product) {
|
||||
if (usbdev->manufacturer)
|
||||
strlcat(mouse->name, " ", sizeof(mouse->name));
|
||||
strlcat(mouse->name, usbdev->product, sizeof(mouse->name));
|
||||
}
|
||||
|
||||
/* Fallback name if no descriptors available */
|
||||
if (! strlen(mouse->name)) {
|
||||
snprintf(mouse->name, sizeof(mouse->name),
|
||||
"Simple USB Mouse %04x:%04x",
|
||||
le16_to_cpu(usbdev->descriptor. idVendor),
|
||||
le16_to_cpu(usbdev->descriptor.idProduct));
|
||||
}
|
||||
|
||||
/* Build physical path */
|
||||
usb_make_path(usbdev, mouse->phys, sizeof(mouse->phys));
|
||||
strlcat(mouse->phys, "/input0", sizeof(mouse->phys));
|
||||
|
||||
pr_info("simple_mouse: Device name: %s\n", mouse->name);
|
||||
pr_info("simple_mouse: Physical path: %s\n", mouse->phys);
|
||||
|
||||
/* Configure input device */
|
||||
input_dev->name = mouse->name;
|
||||
input_dev->phys = mouse->phys;
|
||||
usb_to_input_id(usbdev, &input_dev->id);
|
||||
input_dev->dev.parent = &intf->dev;
|
||||
|
||||
/* Set event types we can generate */
|
||||
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
|
||||
|
||||
/* Set button capabilities */
|
||||
input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
|
||||
BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);
|
||||
input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |
|
||||
BIT_MASK(BTN_EXTRA);
|
||||
|
||||
/* Set relative axis capabilities */
|
||||
input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y) |
|
||||
BIT_MASK(REL_WHEEL);
|
||||
|
||||
/* Set driver data and callbacks */
|
||||
input_set_drvdata(input_dev, mouse);
|
||||
input_dev->open = simple_mouse_open;
|
||||
input_dev->close = simple_mouse_close;
|
||||
|
||||
/* Initialize URB */
|
||||
usb_fill_int_urb(mouse->irq, usbdev, pipe, mouse->data,
|
||||
(maxp > 8 ? 8 : maxp),
|
||||
simple_mouse_irq, mouse, endpoint->bInterval);
|
||||
mouse->irq->transfer_dma = mouse->data_dma;
|
||||
mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
||||
|
||||
/* Register input device with the kernel */
|
||||
error = input_register_device(mouse->input_dev);
|
||||
if (error) {
|
||||
pr_err("simple_mouse: Failed to register input device: %d\n",
|
||||
error);
|
||||
goto fail_register;
|
||||
}
|
||||
|
||||
/* Save our context in interface data */
|
||||
usb_set_intfdata(intf, mouse);
|
||||
|
||||
pr_info("simple_mouse: Probe successful!\n");
|
||||
return 0;
|
||||
|
||||
/* Error handling - cleanup in reverse order */
|
||||
fail_register:
|
||||
usb_free_urb(mouse->irq);
|
||||
fail_urb_alloc:
|
||||
usb_free_coherent(usbdev, 8, mouse->data, mouse->data_dma);
|
||||
fail_dma_alloc:
|
||||
input_free_device(input_dev);
|
||||
fail_input_alloc:
|
||||
kfree(mouse);
|
||||
return error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disconnect function - called when device is unplugged
|
||||
*/
|
||||
static void simple_mouse_disconnect(struct usb_interface *intf)
|
||||
{
|
||||
struct simple_usb_mouse *mouse = usb_get_intfdata(intf);
|
||||
|
||||
pr_info("simple_mouse: Device disconnected\n");
|
||||
|
||||
/* Clear interface data */
|
||||
usb_set_intfdata(intf, NULL);
|
||||
|
||||
if (mouse) {
|
||||
/* Stop URB */
|
||||
usb_kill_urb(mouse->irq);
|
||||
|
||||
/* Unregister from input subsystem */
|
||||
input_unregister_device(mouse->input_dev);
|
||||
|
||||
/* Free URB */
|
||||
usb_free_urb(mouse->irq);
|
||||
|
||||
/* Free DMA buffer */
|
||||
usb_free_coherent(interface_to_usbdev(intf), 8,
|
||||
mouse->data, mouse->data_dma);
|
||||
|
||||
/* Free context structure */
|
||||
kfree(mouse);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Device ID table - matches ANY USB HID Boot Protocol mouse
|
||||
* This is the key to binding to any mouse!
|
||||
*/
|
||||
static const struct usb_device_id simple_mouse_id_table[] = {
|
||||
{
|
||||
USB_INTERFACE_INFO(
|
||||
USB_INTERFACE_CLASS_HID, /* Class: HID */
|
||||
USB_INTERFACE_SUBCLASS_BOOT, /* Subclass: Boot */
|
||||
USB_INTERFACE_PROTOCOL_MOUSE /* Protocol: Mouse */
|
||||
)
|
||||
},
|
||||
{ } /* Terminating entry */
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(usb, simple_mouse_id_table);
|
||||
|
||||
/*
|
||||
* USB Driver structure
|
||||
*/
|
||||
static struct usb_driver simple_mouse_driver = {
|
||||
.name = "simple_usb_mouse",
|
||||
.probe = simple_mouse_probe,
|
||||
.disconnect = simple_mouse_disconnect,
|
||||
.id_table = simple_mouse_id_table,
|
||||
};
|
||||
|
||||
/*
|
||||
* Module init/exit
|
||||
*/
|
||||
module_usb_driver(simple_mouse_driver);
|
||||
676
usb_driver_manager.py
Executable file
676
usb_driver_manager.py
Executable file
@@ -0,0 +1,676 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
USB Driver Manager - CLI tool for managing USB device driver bindings
|
||||
Helps with finding USB devices, unloading current drivers, loading new drivers,
|
||||
and binding USB devices to new drivers.
|
||||
|
||||
Note: USB drivers typically bind to interfaces, not devices. This script
|
||||
handles both device-level and interface-level driver binding.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import glob
|
||||
import re
|
||||
import time
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class Colors:
|
||||
"""ANSI color codes for terminal output"""
|
||||
HEADER = '\033[95m'
|
||||
BLUE = '\033[94m'
|
||||
CYAN = '\033[96m'
|
||||
GREEN = '\033[92m'
|
||||
YELLOW = '\033[93m'
|
||||
RED = '\033[91m'
|
||||
END = '\033[0m'
|
||||
BOLD = '\033[1m'
|
||||
|
||||
|
||||
def print_header(text):
|
||||
"""Print colored header"""
|
||||
print(f"\n{Colors.BOLD}{Colors.HEADER}{text}{Colors.END}")
|
||||
|
||||
|
||||
def print_success(text):
|
||||
"""Print success message"""
|
||||
print(f"{Colors.GREEN}✓ {text}{Colors.END}")
|
||||
|
||||
|
||||
def print_error(text):
|
||||
"""Print error message"""
|
||||
print(f"{Colors.RED}✗ {text}{Colors.END}")
|
||||
|
||||
|
||||
def print_warning(text):
|
||||
"""Print warning message"""
|
||||
print(f"{Colors.YELLOW}⚠ {text}{Colors.END}")
|
||||
|
||||
|
||||
def check_root():
|
||||
"""Check if script is running with root privileges"""
|
||||
if os.geteuid() != 0:
|
||||
print_error("This tool requires root privileges.")
|
||||
print("Please run with sudo:")
|
||||
print(f" sudo {' '.join(sys.argv)}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_usb_devices():
|
||||
"""Get list of USB devices with their information"""
|
||||
devices = []
|
||||
usb_devices_path = Path("/sys/bus/usb/devices")
|
||||
|
||||
if not usb_devices_path.exists():
|
||||
print_error("USB devices path not found. Is USB subsystem available?")
|
||||
return devices
|
||||
|
||||
# Get lsusb output for human-readable names
|
||||
lsusb_output = {}
|
||||
try:
|
||||
result = subprocess.run(['lsusb'], capture_output=True, text=True)
|
||||
for line in result.stdout.splitlines():
|
||||
# Format: Bus 001 Device 005: ID 046d:c52b Logitech, Inc. Unifying Receiver
|
||||
match = re.match(r'Bus (\d+) Device (\d+): ID ([0-9a-f]{4}):([0-9a-f]{4})\s+(.*)', line)
|
||||
if match:
|
||||
bus, dev, vendor, product, name = match.groups()
|
||||
key = f"{int(bus)}-{int(dev)}"
|
||||
lsusb_output[key] = {
|
||||
'vendor_id': vendor,
|
||||
'product_id': product,
|
||||
'name': name.strip()
|
||||
}
|
||||
except Exception as e:
|
||||
print_warning(f"Could not run lsusb: {e}")
|
||||
|
||||
# Iterate through USB devices
|
||||
for device_path in usb_devices_path.iterdir():
|
||||
if not device_path.is_dir():
|
||||
continue
|
||||
|
||||
# Only process actual device entries (format: busnum-devnum or busnum-port.port...)
|
||||
device_name = device_path.name
|
||||
if not re.match(r'\d+-[\d.]+', device_name):
|
||||
continue
|
||||
|
||||
# Skip root hubs
|
||||
devpath_file = device_path / "devpath"
|
||||
if not devpath_file.exists():
|
||||
continue
|
||||
|
||||
try:
|
||||
# Read device information
|
||||
vendor_id = (device_path / "idVendor").read_text().strip() if (device_path / "idVendor").exists() else "unknown"
|
||||
product_id = (device_path / "idProduct").read_text().strip() if (device_path / "idProduct").exists() else "unknown"
|
||||
manufacturer = (device_path / "manufacturer").read_text().strip() if (device_path / "manufacturer").exists() else "Unknown"
|
||||
product = (device_path / "product").read_text().strip() if (device_path / "product").exists() else "Unknown"
|
||||
busnum = (device_path / "busnum").read_text().strip() if (device_path / "busnum").exists() else "?"
|
||||
devnum = (device_path / "devnum").read_text().strip() if (device_path / "devnum").exists() else "?"
|
||||
|
||||
# Get current driver
|
||||
driver = "none"
|
||||
driver_link = device_path / "driver"
|
||||
if driver_link.exists() and driver_link.is_symlink():
|
||||
driver = driver_link.resolve().name
|
||||
|
||||
# Try to get better name from lsusb
|
||||
lsusb_key = f"{int(busnum)}-{int(devnum)}"
|
||||
if lsusb_key in lsusb_output:
|
||||
product = lsusb_output[lsusb_key]['name']
|
||||
|
||||
# Check if it's an input device by looking at device class or interfaces
|
||||
is_input = False
|
||||
device_class = (device_path / "bDeviceClass").read_text().strip() if (device_path / "bDeviceClass").exists() else "00"
|
||||
|
||||
# Class 03 is HID (Human Interface Device)
|
||||
if device_class == "03":
|
||||
is_input = True
|
||||
|
||||
# Check interfaces for HID class and collect interface information
|
||||
interfaces = []
|
||||
for interface_path in device_path.glob("*:*.*"):
|
||||
if not interface_path.is_dir():
|
||||
continue
|
||||
try:
|
||||
iface_class = (interface_path / "bInterfaceClass").read_text().strip() if (interface_path / "bInterfaceClass").exists() else "00"
|
||||
iface_subclass = (interface_path / "bInterfaceSubClass").read_text().strip() if (interface_path / "bInterfaceSubClass").exists() else "00"
|
||||
iface_protocol = (interface_path / "bInterfaceProtocol").read_text().strip() if (interface_path / "bInterfaceProtocol").exists() else "00"
|
||||
|
||||
# Get interface driver
|
||||
iface_driver = "none"
|
||||
iface_driver_link = interface_path / "driver"
|
||||
if iface_driver_link.exists() and iface_driver_link.is_symlink():
|
||||
iface_driver = iface_driver_link.resolve().name
|
||||
|
||||
interfaces.append({
|
||||
'name': interface_path.name,
|
||||
'path': str(interface_path),
|
||||
'class': iface_class,
|
||||
'subclass': iface_subclass,
|
||||
'protocol': iface_protocol,
|
||||
'driver': iface_driver
|
||||
})
|
||||
|
||||
# Class 03 is HID (Human Interface Device)
|
||||
if iface_class == "03":
|
||||
is_input = True
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
devices.append({
|
||||
'path': str(device_path),
|
||||
'name': device_name,
|
||||
'vendor_id': vendor_id,
|
||||
'product_id': product_id,
|
||||
'manufacturer': manufacturer,
|
||||
'product': product,
|
||||
'bus': busnum,
|
||||
'device': devnum,
|
||||
'driver': driver,
|
||||
'is_input': is_input,
|
||||
'interfaces': interfaces,
|
||||
'display_name': f"{manufacturer} {product}"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
# Skip devices that can't be read
|
||||
continue
|
||||
|
||||
return devices
|
||||
|
||||
|
||||
def display_usb_devices(devices, filter_input=True):
|
||||
"""Display USB devices in a formatted list"""
|
||||
if filter_input:
|
||||
devices = [d for d in devices if d['is_input']]
|
||||
|
||||
if not devices:
|
||||
print_warning("No USB devices found.")
|
||||
return None
|
||||
|
||||
print_header("Available USB Devices:")
|
||||
print(f"\n{'#':<4} {'Device':<15} {'Vendor:Product':<15} {'Name':<40} {'Interfaces':<15}")
|
||||
print("-" * 100)
|
||||
|
||||
for idx, device in enumerate(devices, 1):
|
||||
vendor_product = f"{device['vendor_id']}:{device['product_id']}"
|
||||
|
||||
display_name = device['display_name']
|
||||
if len(display_name) > 40:
|
||||
display_name = display_name[:37] + "..."
|
||||
|
||||
iface_info = f"{len(device.get('interfaces', []))} interface(s)"
|
||||
|
||||
print(f"{idx:<4} {device['name']:<15} {vendor_product:<15} {display_name:<40} {iface_info}")
|
||||
|
||||
return devices
|
||||
|
||||
|
||||
def get_kernel_modules(directories=None):
|
||||
"""Get list of available kernel modules (.ko files)"""
|
||||
if directories is None:
|
||||
directories = ["."]
|
||||
|
||||
modules = []
|
||||
seen_modules = set() # Track module names to avoid duplicates
|
||||
|
||||
# Search for .ko files in each specified directory
|
||||
for directory in directories:
|
||||
if not os.path.exists(directory):
|
||||
print_warning(f"Directory not found: {directory}")
|
||||
continue
|
||||
|
||||
for ko_file in glob.glob(os.path.join(directory, "*.ko")):
|
||||
module_name = os.path.basename(ko_file)
|
||||
module_path = os.path.abspath(ko_file)
|
||||
|
||||
# Skip duplicates (same module name already found)
|
||||
if module_name in seen_modules:
|
||||
continue
|
||||
seen_modules.add(module_name)
|
||||
|
||||
# Get module info if possible
|
||||
try:
|
||||
result = subprocess.run(['modinfo', module_path],
|
||||
capture_output=True, text=True)
|
||||
description = "No description"
|
||||
for line in result.stdout.splitlines():
|
||||
if line.startswith("description:"):
|
||||
description = line.split(":", 1)[1].strip()
|
||||
break
|
||||
|
||||
modules.append({
|
||||
'name': module_name,
|
||||
'path': module_path,
|
||||
'description': description
|
||||
})
|
||||
except Exception:
|
||||
modules.append({
|
||||
'name': module_name,
|
||||
'path': module_path,
|
||||
'description': "No description available"
|
||||
})
|
||||
|
||||
return modules
|
||||
|
||||
|
||||
def display_kernel_modules(modules):
|
||||
"""Display available kernel modules"""
|
||||
if not modules:
|
||||
print_warning("No kernel modules (.ko files) found in current directory.")
|
||||
return None
|
||||
|
||||
print_header("Available Kernel Modules:")
|
||||
print(f"\n{'#':<4} {'Module Name':<30} {'Description':<50}")
|
||||
print("-" * 90)
|
||||
|
||||
for idx, module in enumerate(modules, 1):
|
||||
desc = module['description']
|
||||
if len(desc) > 50:
|
||||
desc = desc[:47] + "..."
|
||||
print(f"{idx:<4} {module['name']:<30} {desc}")
|
||||
|
||||
return modules
|
||||
|
||||
|
||||
def get_user_choice(prompt, max_choice):
|
||||
"""Get user input for selection"""
|
||||
while True:
|
||||
try:
|
||||
choice = input(f"\n{prompt} (1-{max_choice}, or 'q' to quit): ").strip()
|
||||
if choice.lower() == 'q':
|
||||
return None
|
||||
choice = int(choice)
|
||||
if 1 <= choice <= max_choice:
|
||||
return choice - 1 # Return 0-indexed
|
||||
else:
|
||||
print_error(f"Please enter a number between 1 and {max_choice}")
|
||||
except ValueError:
|
||||
print_error("Invalid input. Please enter a number.")
|
||||
except KeyboardInterrupt:
|
||||
print("\n")
|
||||
return None
|
||||
|
||||
|
||||
def unbind_device(device, interface=None):
|
||||
"""Unbind device interface from current driver"""
|
||||
# If specific interface provided, unbind that interface
|
||||
if interface:
|
||||
if interface['driver'] == "none":
|
||||
print_warning(f"Interface {interface['name']} is not bound to any driver.")
|
||||
return True
|
||||
|
||||
driver_path = Path(f"/sys/bus/usb/drivers/{interface['driver']}")
|
||||
unbind_path = driver_path / "unbind"
|
||||
|
||||
if not unbind_path.exists():
|
||||
print_error(f"Cannot unbind: {unbind_path} not found")
|
||||
return False
|
||||
|
||||
try:
|
||||
print(f"Unbinding interface {interface['name']} from driver {interface['driver']}...")
|
||||
unbind_path.write_text(interface['name'])
|
||||
print_success(f"Successfully unbound from {interface['driver']}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print_error(f"Failed to unbind interface: {e}")
|
||||
return False
|
||||
|
||||
# Otherwise unbind all interfaces
|
||||
success = True
|
||||
for iface in device.get('interfaces', []):
|
||||
if iface['driver'] != "none":
|
||||
if not unbind_device(device, iface):
|
||||
success = False
|
||||
|
||||
if not device.get('interfaces'):
|
||||
# Fallback to old behavior for device-level driver
|
||||
if device['driver'] == "none":
|
||||
print_warning("Device is not bound to any driver.")
|
||||
return True
|
||||
|
||||
driver_path = Path(f"/sys/bus/usb/drivers/{device['driver']}")
|
||||
unbind_path = driver_path / "unbind"
|
||||
|
||||
if not unbind_path.exists():
|
||||
print_error(f"Cannot unbind: {unbind_path} not found")
|
||||
return False
|
||||
|
||||
try:
|
||||
print(f"Unbinding device {device['name']} from driver {device['driver']}...")
|
||||
unbind_path.write_text(device['name'])
|
||||
print_success(f"Successfully unbound from {device['driver']}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print_error(f"Failed to unbind device: {e}")
|
||||
return False
|
||||
|
||||
return success
|
||||
|
||||
|
||||
def load_module(module):
|
||||
"""Load kernel module"""
|
||||
try:
|
||||
print(f"Loading module {module['name']}...")
|
||||
result = subprocess.run(['insmod', module['path']],
|
||||
capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
print_success(f"Successfully loaded {module['name']}")
|
||||
return True
|
||||
else:
|
||||
# Module might already be loaded
|
||||
if "File exists" in result.stderr or "already" in result.stderr.lower():
|
||||
print_warning(f"Module {module['name']} is already loaded")
|
||||
return True
|
||||
print_error(f"Failed to load module: {result.stderr}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print_error(f"Failed to load module: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def get_module_driver_name(module):
|
||||
"""Get the driver name from module"""
|
||||
# Extract driver name from module (remove .ko extension)
|
||||
driver_name = os.path.splitext(module['name'])[0]
|
||||
|
||||
# Check if driver exists in /sys/bus/usb/drivers/
|
||||
driver_path = Path(f"/sys/bus/usb/drivers/{driver_name}")
|
||||
if driver_path.exists():
|
||||
return driver_name
|
||||
|
||||
# Try to get it from loaded modules
|
||||
try:
|
||||
result = subprocess.run(['lsmod'], capture_output=True, text=True)
|
||||
for line in result.stdout.splitlines():
|
||||
if line.startswith(driver_name):
|
||||
return driver_name
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return driver_name
|
||||
|
||||
|
||||
def bind_device(device, module, interface=None):
|
||||
"""Bind device interface to new driver"""
|
||||
driver_name = get_module_driver_name(module)
|
||||
driver_path = Path(f"/sys/bus/usb/drivers/{driver_name}")
|
||||
bind_path = driver_path / "bind"
|
||||
|
||||
if not driver_path.exists():
|
||||
print_error(f"Driver path not found: {driver_path}")
|
||||
print_warning("The driver might not be loaded or might use a different name.")
|
||||
return False
|
||||
|
||||
if not bind_path.exists():
|
||||
print_error(f"Bind interface not found: {bind_path}")
|
||||
return False
|
||||
|
||||
# If specific interface provided, bind that interface
|
||||
if interface:
|
||||
# Check if already bound to target driver
|
||||
iface_path = Path(interface['path'])
|
||||
driver_link = iface_path / "driver"
|
||||
if driver_link.exists() and driver_link.is_symlink():
|
||||
current_driver = driver_link.resolve().name
|
||||
if current_driver == driver_name:
|
||||
print_success(f"Interface {interface['name']} already bound to {driver_name}")
|
||||
return True
|
||||
|
||||
try:
|
||||
print(f"Binding interface {interface['name']} to driver {driver_name}...")
|
||||
bind_path.write_text(interface['name'])
|
||||
print_success(f"Successfully bound to {driver_name}")
|
||||
return True
|
||||
except Exception as e:
|
||||
# Check again if it got bound (might be EBUSY because it auto-bound)
|
||||
if driver_link.exists() and driver_link.is_symlink():
|
||||
current_driver = driver_link.resolve().name
|
||||
if current_driver == driver_name:
|
||||
print_success(f"Interface {interface['name']} bound to {driver_name} (auto-probed)")
|
||||
return True
|
||||
print_error(f"Failed to bind interface: {e}")
|
||||
return False
|
||||
|
||||
# Otherwise try to bind all HID mouse interfaces (class 03, subclass 01, protocol 02)
|
||||
target_interfaces = []
|
||||
for iface in device.get('interfaces', []):
|
||||
# Look for HID Boot Mouse interfaces
|
||||
if iface['class'] == "03" and iface['subclass'] == "01" and iface['protocol'] == "02":
|
||||
target_interfaces.append(iface)
|
||||
|
||||
if not target_interfaces:
|
||||
# Fallback: try to bind the device itself
|
||||
try:
|
||||
print(f"Binding device {device['name']} to driver {driver_name}...")
|
||||
bind_path.write_text(device['name'])
|
||||
print_success(f"Successfully bound to {driver_name}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print_error(f"Failed to bind device: {e}")
|
||||
return False
|
||||
|
||||
# Bind each target interface
|
||||
success = True
|
||||
for iface in target_interfaces:
|
||||
# Check if already bound to target driver
|
||||
iface_path = Path(iface['path'])
|
||||
driver_link = iface_path / "driver"
|
||||
if driver_link.exists() and driver_link.is_symlink():
|
||||
current_driver = driver_link.resolve().name
|
||||
if current_driver == driver_name:
|
||||
print_success(f"Interface {iface['name']} already bound to {driver_name}")
|
||||
continue
|
||||
|
||||
try:
|
||||
print(f"Binding interface {iface['name']} to driver {driver_name}...")
|
||||
bind_path.write_text(iface['name'])
|
||||
print_success(f"Successfully bound interface {iface['name']} to {driver_name}")
|
||||
except Exception as e:
|
||||
# Check again if it got bound (might be EBUSY because it auto-bound)
|
||||
if driver_link.exists() and driver_link.is_symlink():
|
||||
current_driver = driver_link.resolve().name
|
||||
if current_driver == driver_name:
|
||||
print_success(f"Interface {iface['name']} bound to {driver_name} (auto-probed)")
|
||||
continue
|
||||
print_error(f"Failed to bind interface {iface['name']}: {e}")
|
||||
success = False
|
||||
|
||||
return success
|
||||
|
||||
|
||||
def unload_module(module):
|
||||
"""Unload kernel module"""
|
||||
driver_name = get_module_driver_name(module)
|
||||
try:
|
||||
print(f"Unloading module {driver_name}...")
|
||||
result = subprocess.run(['rmmod', driver_name],
|
||||
capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
print_success(f"Successfully unloaded {driver_name}")
|
||||
return True
|
||||
else:
|
||||
print_warning(f"Could not unload module: {result.stderr}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print_warning(f"Could not unload module: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def is_module_loaded(module):
|
||||
"""Check if a kernel module is currently loaded"""
|
||||
driver_name = get_module_driver_name(module)
|
||||
try:
|
||||
result = subprocess.run(['lsmod'], capture_output=True, text=True)
|
||||
for line in result.stdout.splitlines():
|
||||
if line.split()[0] == driver_name:
|
||||
return True
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function"""
|
||||
# Parse command line arguments
|
||||
parser = argparse.ArgumentParser(
|
||||
description='USB Driver Manager - CLI tool for managing USB device driver bindings',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog='Examples:\n'
|
||||
' sudo %(prog)s # Search for .ko files in current directory\n'
|
||||
' sudo %(prog)s /path/to/modules # Search in specific directory\n'
|
||||
' sudo %(prog)s . /path/to/dir2 # Search in multiple directories\n'
|
||||
)
|
||||
parser.add_argument(
|
||||
'directories',
|
||||
nargs='*',
|
||||
default=['.'],
|
||||
help='Directories to search for kernel modules (.ko files). Defaults to current directory.'
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
print(f"{Colors.BOLD}{Colors.CYAN}")
|
||||
print("=" * 60)
|
||||
print(" USB Driver Manager")
|
||||
print("=" * 60)
|
||||
print(Colors.END)
|
||||
|
||||
# Check root privileges
|
||||
check_root()
|
||||
|
||||
# Step 1: List USB devices
|
||||
devices = get_usb_devices()
|
||||
displayed_devices = display_usb_devices(devices, filter_input=True)
|
||||
|
||||
if not displayed_devices:
|
||||
print_error("No input devices found. Showing all USB devices...")
|
||||
displayed_devices = display_usb_devices(devices, filter_input=False)
|
||||
if not displayed_devices:
|
||||
sys.exit(1)
|
||||
|
||||
# Step 2: Select device
|
||||
device_idx = get_user_choice("Select USB device", len(displayed_devices))
|
||||
if device_idx is None:
|
||||
print("\nOperation cancelled.")
|
||||
sys.exit(0)
|
||||
|
||||
selected_device = displayed_devices[device_idx]
|
||||
print(f"\n{Colors.CYAN}Selected device: {selected_device['display_name']}{Colors.END}")
|
||||
print(f" Device: {selected_device['name']}")
|
||||
print(f" Current driver: {selected_device['driver']}")
|
||||
|
||||
# Show interfaces
|
||||
if selected_device.get('interfaces'):
|
||||
print(f"\n Interfaces:")
|
||||
for iface in selected_device['interfaces']:
|
||||
driver_color = Colors.GREEN if iface['driver'] != "none" else Colors.YELLOW
|
||||
print(f" {iface['name']}: class={iface['class']} subclass={iface['subclass']} protocol={iface['protocol']} driver={driver_color}{iface['driver']}{Colors.END}")
|
||||
|
||||
# Step 3: List available kernel modules
|
||||
if len(args.directories) > 1 or args.directories[0] != '.':
|
||||
print(f"\nSearching for kernel modules in: {', '.join(args.directories)}")
|
||||
modules = get_kernel_modules(args.directories)
|
||||
displayed_modules = display_kernel_modules(modules)
|
||||
|
||||
if not displayed_modules:
|
||||
sys.exit(1)
|
||||
|
||||
# Step 4: Select module
|
||||
module_idx = get_user_choice("Select kernel module", len(displayed_modules))
|
||||
if module_idx is None:
|
||||
print("\nOperation cancelled.")
|
||||
sys.exit(0)
|
||||
|
||||
selected_module = displayed_modules[module_idx]
|
||||
print(f"\n{Colors.CYAN}Selected module: {selected_module['name']}{Colors.END}")
|
||||
|
||||
# Check if module is already loaded
|
||||
module_already_loaded = is_module_loaded(selected_module)
|
||||
if module_already_loaded:
|
||||
print_warning(f"Module {selected_module['name']} is already loaded and will be reloaded.")
|
||||
|
||||
# Step 5: Confirm operation
|
||||
print(f"\n{Colors.YELLOW}This will:{Colors.END}")
|
||||
if selected_device.get('interfaces'):
|
||||
print(f" 1. Unbind interface(s) from current driver(s)")
|
||||
else:
|
||||
print(f" 1. Unbind {selected_device['name']} from {selected_device['driver']}")
|
||||
if module_already_loaded:
|
||||
print(f" 2. Unload existing module {selected_module['name']}")
|
||||
print(f" 3. Load module {selected_module['name']} (fresh version)")
|
||||
print(f" 4. Bind interface(s) to the new driver")
|
||||
else:
|
||||
print(f" 2. Load module {selected_module['name']}")
|
||||
print(f" 3. Bind interface(s) to the new driver")
|
||||
|
||||
confirm = input(f"\n{Colors.BOLD}Proceed? (yes/no): {Colors.END}").strip().lower()
|
||||
if confirm not in ['yes', 'y']:
|
||||
print("\nOperation cancelled.")
|
||||
sys.exit(0)
|
||||
|
||||
# Step 6: Perform operations
|
||||
print_header("\nExecuting operations...")
|
||||
|
||||
# Unbind from current driver
|
||||
if not unbind_device(selected_device):
|
||||
print_error("Failed to unbind device. Aborting.")
|
||||
sys.exit(1)
|
||||
|
||||
# Unload module if already loaded
|
||||
if module_already_loaded:
|
||||
if not unload_module(selected_module):
|
||||
print_error("Failed to unload existing module.")
|
||||
print_warning("You may need to manually unbind all devices using this driver first.")
|
||||
sys.exit(1)
|
||||
# Give kernel a moment after unloading
|
||||
time.sleep(0.3)
|
||||
|
||||
# Load new module
|
||||
if not load_module(selected_module):
|
||||
print_error("Failed to load module. Attempting to restore...")
|
||||
# Try to rebind to original driver if available
|
||||
if selected_device['driver'] != "none":
|
||||
bind_device(selected_device, {'name': selected_device['driver'] + '.ko'})
|
||||
sys.exit(1)
|
||||
|
||||
# Give kernel time to auto-probe and bind
|
||||
print("Waiting for kernel to probe interfaces...")
|
||||
time.sleep(0.5)
|
||||
|
||||
# Bind to new driver
|
||||
if not bind_device(selected_device, selected_module):
|
||||
print_error("Failed to bind device to new driver.")
|
||||
print_warning("Device may be unbound. You might need to reconnect it or bind manually.")
|
||||
sys.exit(1)
|
||||
|
||||
# Success
|
||||
print_header("\nOperation completed successfully!")
|
||||
print(f"{Colors.GREEN}Device {selected_device['name']} is now using the new driver{Colors.END}")
|
||||
|
||||
# Offer to show new device status
|
||||
print("\nVerifying device status...")
|
||||
new_devices = get_usb_devices()
|
||||
for dev in new_devices:
|
||||
if dev['name'] == selected_device['name']:
|
||||
for iface in dev.get('interfaces', []):
|
||||
driver_color = Colors.GREEN if iface['driver'] != "none" else Colors.YELLOW
|
||||
print(f" Interface {iface['name']}: {driver_color}{iface['driver']}{Colors.END}")
|
||||
if not dev.get('interfaces') and dev['driver'] != 'none':
|
||||
print(f" Current driver: {Colors.GREEN}{dev['driver']}{Colors.END}")
|
||||
break
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n\n{Colors.YELLOW}Operation cancelled by user.{Colors.END}")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print_error(f"Unexpected error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user