Fixed mouse driver and moved to seperate folder

This commit is contained in:
Thomas Hilscher
2026-01-15 22:04:34 +01:00
parent c42dff6384
commit 29b23b5964
9 changed files with 552 additions and 656 deletions

42
.gitignore vendored Normal file
View 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

View File

@@ -1,31 +0,0 @@
obj-m += usb_bootmouse.o
KVER := $(shell uname -r)
# Most distros expose headers at /lib/modules/$(KVER)/build, but Arch/Manjaro
# often uses /usr/lib/modules/$(KVER)/build.
KDIR ?= $(firstword \
$(wildcard /lib/modules/$(KVER)/build) \
$(wildcard /usr/lib/modules/$(KVER)/build))
PWD := $(shell pwd)
OUT := $(PWD)/out
all:
@if [ -z "$(KDIR)" ]; then \
echo "ERROR: kernel build dir not found for $(KVER). Install kernel headers (e.g. linux-headers)"; \
exit 2; \
fi
mkdir -p $(OUT)
$(MAKE) -C $(KDIR) M=$(PWD) modules
-mv -f -- *.ko *.mod.c *.o *.mod modules.order .*.cmd $(OUT) 2>/dev/null || true
@echo "Build outputs moved to $(OUT)"
clean:
@if [ -z "$(KDIR)" ]; then \
echo "ERROR: kernel build dir not found for $(KVER)."; \
exit 2; \
fi
$(MAKE) -C $(KDIR) M=$(PWD) clean
@rm -rf $(OUT)

134
README.md
View File

@@ -1,116 +1,54 @@
Device Driver Device Driver
============ ======================
This folder contains a minimal **out-of-tree** Linux kernel module that acts as a USB **boot-protocol mouse** driver. A collection of USB device drivers for Linux kernel, demonstrating how to interact with various USB HID devices.
It supports: ## Current Drivers
- Left and right buttons
- Scroll wheel
- Relative cursor motion (X/Y)
Important notes ### Mouse Driver (`mouse/`)
--------------- A USB HID mouse driver that supports 16-bit coordinate tracking for high-DPI gaming mice. Tested with Cooler Master MM710.
- This is an **educational** example. Real USB mice are normally handled by the kernel HID stack (e.g. `usbhid` / `hid-generic`). **Features:**
- This driver binds to HID **Boot Mouse** interfaces (class=HID, subclass=BOOT, protocol=MOUSE). Many mice work, but not all. - Left, right, middle, side, and extra button support
- To use it on a running system you typically must **unbind** the existing driver from that USB interface first. - 16-bit X/Y movement (high-speed tracking)
- Scroll wheel support
Files - Binds to HID Boot Protocol Mouse interfaces
-----
- `usb_bootmouse.c` kernel module (USB driver + input device)
- `Makefile` builds against your running kernel headers
Build
-----
Install kernel headers/build deps (examples):
- Debian/Ubuntu: `sudo apt-get install build-essential linux-headers-$(uname -r)`
- Fedora: `sudo dnf install @development-tools kernel-devel-$(uname -r)`
Then build:
**Building:**
```bash ```bash
cd Device-Driver cd mouse/
make make
``` ```
Load ## USB Driver Manager
----
The `usb_driver_manager.py` tool simplifies the process of binding USB devices to custom drivers.
**Usage:**
```bash ```bash
sudo insmod usb_bootmouse.ko # Search for .ko files in current directory
dmesg | tail -n 50 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
``` ```
If you want to restrict binding to a specific device: ## Future Drivers
```bash - **Keyboard**: USB HID keyboard driver
sudo insmod usb_bootmouse.ko match_vendor=0x046d match_product=0xc077 - **Racing Wheel**: USB racing wheel driver with force feedback
```
(Replace IDs with your mouse vendor/product from `lsusb`.) ## Requirements
Bind it to your mouse (unbind/bind) - Linux kernel headers
--------------------------------- - Python 3.6+
- Root/sudo access for driver loading and binding
1) Find the USB interface path.
You can use `dmesg` when plugging the mouse in, or inspect:
```bash
ls -l /sys/bus/usb/devices/
```
Typical interface names look like `1-2:1.0` (bus-port:config.interface).
2) Unbind the existing HID driver (commonly `usbhid`) from that interface:
```bash
DEVIF="1-2:1.0" # <- change this
echo -n "$DEVIF" | sudo tee /sys/bus/usb/drivers/usbhid/unbind
```
3) Bind this module to the interface:
```bash
echo -n "$DEVIF" | sudo tee /sys/bus/usb/drivers/usb_bootmouse/bind
```
At this point, the driver should create an input device (via `evdev`).
Test
----
List input devices and find the new one:
```bash
cat /proc/bus/input/devices
```
Or use `evtest`:
```bash
sudo apt-get install evtest # or your distro equivalent
sudo evtest
```
You should see events:
- `BTN_LEFT`, `BTN_RIGHT`
- `REL_X`, `REL_Y`
- `REL_WHEEL`
Unload
------
```bash
sudo rmmod usb_bootmouse
```
If you want the original HID driver back, bind it again:
```bash
echo -n "$DEVIF" | sudo tee /sys/bus/usb/drivers/usbhid/bind
```

View File

@@ -1,111 +0,0 @@
#!/bin/bash
# bind_usb_bootmouse.sh
# Usage: sudo ./bind_usb_bootmouse.sh [VID] [PID]
# Example: sudo ./bind_usb_bootmouse.sh 0x2516 0x012f
set -euo pipefail
if [ "$EUID" -ne 0 ]; then
echo "This script must be run as root (sudo)." >&2
exit 1
fi
VID_IN=${1:-0x2516}
PID_IN=${2:-0x012f}
# normalize (strip 0x, lowercase, pad to 4 hex digits)
norm_hex() {
local v=${1,,}
v=${v#0x}
printf "%04x" $((16#$v))
}
VID=$(norm_hex "$VID_IN")
PID=$(norm_hex "$PID_IN")
MAPFILE="/var/run/usb_bootmouse_bind_${VID}_${PID}.map"
echo "Looking for USB device VID=$VID PID=$PID..."
found=0
for d in /sys/bus/usb/devices/*; do
if [ -f "$d/idVendor" ] && [ -f "$d/idProduct" ]; then
v=$(cat "$d/idVendor")
p=$(cat "$d/idProduct")
if [ "$v" = "$VID" ] && [ "$p" = "$PID" ]; then
found=1
devdir="$d"
break
fi
fi
done
if [ $found -ne 1 ]; then
echo "Device not found in sysfs for VID=$VID PID=$PID" >&2
exit 2
fi
echo "Device found at $devdir"
# Ensure our module is loaded
if ! lsmod | grep -q '^usb_bootmouse'; then
if ! modprobe usb_bootmouse 2>/dev/null; then
echo "Attempting to insmod from module file..."
if [ -f "$(pwd)/usb_bootmouse.ko" ]; then
insmod "$(pwd)/usb_bootmouse.ko"
else
echo "usb_bootmouse module not found. Build the module first." >&2
exit 3
fi
fi
fi
echo "Module usb_bootmouse loaded"
# Prepare map file
rm -f "$MAPFILE"
mkdir -p "$(dirname "$MAPFILE")"
# Iterate over interfaces under the device (like 1-2:1.0)
for ifpath in "$devdir"/*:*; do
[ -e "$ifpath" ] || continue
IFNAME=$(basename "$ifpath")
# find the current driver for this interface
orig_driver=""
for drv in /sys/bus/usb/drivers/*; do
[ -e "$drv/$IFNAME" ] || continue
orig_driver=$(basename "$drv")
break
done
if [ -z "$orig_driver" ]; then
echo "Interface $IFNAME has no driver currently; will try to bind usb_bootmouse directly"
else
echo "Interface $IFNAME currently bound to driver '$orig_driver'"
# record original driver
echo "$IFNAME:$orig_driver" >> "$MAPFILE"
# unbind original driver
echo -n "$IFNAME" > "/sys/bus/usb/drivers/$orig_driver/unbind" || {
echo "Failed to unbind $IFNAME from $orig_driver" >&2
continue
}
echo "Unbound $IFNAME from $orig_driver"
fi
# bind our driver
echo -n "$IFNAME" > /sys/bus/usb/drivers/usb_bootmouse/bind || {
echo "Failed to bind $IFNAME to usb_bootmouse" >&2
continue
}
echo "Bound $IFNAME to usb_bootmouse"
done
if [ -f "$MAPFILE" ]; then
echo "Saved mapping to $MAPFILE"
else
echo "No original driver mappings were saved (device may have had no drivers)."
fi
exit 0

23
mouse/Makefile Normal file
View 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
View 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);

View File

@@ -1,64 +0,0 @@
#!/bin/bash
# restore_hid.sh
# Usage: sudo ./restore_hid.sh [VID] [PID]
# Restores original driver bindings saved by bind_usb_bootmouse.sh
set -euo pipefail
if [ "$EUID" -ne 0 ]; then
echo "This script must be run as root (sudo)." >&2
exit 1
fi
VID_IN=${1:-0x2516}
PID_IN=${2:-0x012f}
norm_hex() {
local v=${1,,}
v=${v#0x}
printf "%04x" $((16#$v))
}
VID=$(norm_hex "$VID_IN")
PID=$(norm_hex "$PID_IN")
MAPFILE="/var/run/usb_bootmouse_bind_${VID}_${PID}.map"
if [ ! -f "$MAPFILE" ]; then
echo "Mapping file $MAPFILE not found. Nothing to restore." >&2
exit 2
fi
echo "Restoring bindings from $MAPFILE"
while IFS=: read -r IFNAME ORIG; do
[ -n "$IFNAME" ] || continue
[ -n "$ORIG" ] || continue
# unbind our driver if it is bound
if [ -e "/sys/bus/usb/drivers/usb_bootmouse/$IFNAME" ]; then
echo -n "$IFNAME" > /sys/bus/usb/drivers/usb_bootmouse/unbind || {
echo "Failed to unbind $IFNAME from usb_bootmouse" >&2
}
echo "Unbound $IFNAME from usb_bootmouse"
else
echo "usb_bootmouse not bound to $IFNAME"
fi
# bind the original driver back
if [ -d "/sys/bus/usb/drivers/$ORIG" ]; then
echo -n "$IFNAME" > /sys/bus/usb/drivers/$ORIG/bind || {
echo "Failed to bind $IFNAME to $ORIG" >&2
continue
}
echo "Bound $IFNAME to $ORIG"
else
echo "Original driver $ORIG not present on this system; skipping bind for $IFNAME" >&2
fi
done < "$MAPFILE"
rm -f "$MAPFILE"
echo "Restoration complete."
exit 0

View File

@@ -1,324 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/usb/ch9.h>
#include <linux/usb/input.h>
#include <linux/input.h>
#include <linux/slab.h>
#define DRV_NAME "usb_bootmouse"
static ushort match_vendor;
module_param(match_vendor, ushort, 0444);
MODULE_PARM_DESC(match_vendor, "If non-zero, only bind to this USB vendor id (hex or dec)");
static ushort match_product;
module_param(match_product, ushort, 0444);
MODULE_PARM_DESC(match_product, "If non-zero, only bind to this USB product id (hex or dec)");
/* Runtime tuning parameters for devices with nonstandard report layouts */
static int force_offset = -1; /* -1 = auto, 0 = no report-id, 1 = report-id present */
module_param(force_offset, int, 0444);
MODULE_PARM_DESC(force_offset, "Force report-data offset (0 or 1). -1 = autodetect");
static bool swap_xy = false;
module_param(swap_xy, bool, 0444);
MODULE_PARM_DESC(swap_xy, "Swap X/Y axes (try true if horizontal/vertical are inverted)");
static int wheel_index = -1; /* -1 = autodetect (3+off) */
module_param(wheel_index, int, 0444);
MODULE_PARM_DESC(wheel_index, "Force wheel byte index in report (0-based). -1 = autodetect");
struct bootmouse {
struct usb_device *udev;
struct usb_interface *intf;
struct input_dev *input;
struct urb *irq_urb;
u8 *irq_data;
dma_addr_t irq_dma;
unsigned int irq_len;
unsigned int irq_interval;
unsigned int irq_ep;
char phys[64];
atomic_t opened;
};
static void bootmouse_irq(struct urb *urb)
{
struct bootmouse *m = urb->context;
int status = urb->status;
u8 *data;
s8 dx, dy, wheel;
if (!m)
return;
if (!atomic_read(&m->opened))
return;
if (status) {
/*
* -ENOENT/-ECONNRESET typically come from disconnect/kill/unlink.
* Avoid noisy logs; just stop resubmitting.
*/
if (status == -ENOENT || status == -ECONNRESET || status == -ESHUTDOWN)
return;
dev_dbg(&m->intf->dev, "irq urb status %d\n", status);
goto resubmit;
}
data = m->irq_data;
/*
* Many HID mice use the Boot protocol layout:
* [0]=buttons [1]=dx [2]=dy [3]=wheel
* Some devices prepend a report-id, shifting fields:
* [0]=rid [1]=buttons [2]=dx [3]=dy ...
* Use a small heuristic to pick the correct offset so axes don't get swapped.
*/
{
int off = 0;
int sA = 0, sB = 0;
/* score no-report-id layout: sum(|dx|+|dy|) at indexes 1,2 */
if (m->irq_len >= 3)
sA = abs((s8)data[1]) + abs((s8)data[2]);
/* score report-id layout: sum at indexes 2,3 */
if (m->irq_len >= 4)
sB = abs((s8)data[2]) + abs((s8)data[3]);
/* choose offset=1 if sB noticeably larger than sA */
if (sB > sA * 2)
off = 1;
/* buttons */
input_report_key(m->input, BTN_LEFT, !!(data[0 + off] & 0x01));
input_report_key(m->input, BTN_RIGHT, !!(data[0 + off] & 0x02));
/* motion */
dx = (s8)data[1 + off];
dy = (s8)data[2 + off];
if (swap_xy) {
s8 tmp = dx; dx = dy; dy = tmp;
}
input_report_rel(m->input, REL_X, dx);
input_report_rel(m->input, REL_Y, dy);
/* wheel if present -- allow override */
if (wheel_index >= 0) {
if ((unsigned)wheel_index < m->irq_len) {
wheel = (s8)data[wheel_index];
input_report_rel(m->input, REL_WHEEL, wheel);
}
} else if (m->irq_len >= 4 + off) {
wheel = (s8)data[3 + off];
input_report_rel(m->input, REL_WHEEL, wheel);
}
}
input_sync(m->input);
resubmit:
usb_submit_urb(m->irq_urb, GFP_ATOMIC);
}
static int bootmouse_open(struct input_dev *dev)
{
struct bootmouse *m = input_get_drvdata(dev);
int ret;
if (!m)
return -ENODEV;
atomic_set(&m->opened, 1);
ret = usb_submit_urb(m->irq_urb, GFP_KERNEL);
if (ret) {
atomic_set(&m->opened, 0);
return ret;
}
return 0;
}
static void bootmouse_close(struct input_dev *dev)
{
struct bootmouse *m = input_get_drvdata(dev);
if (!m)
return;
atomic_set(&m->opened, 0);
usb_kill_urb(m->irq_urb);
}
static int bootmouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
struct usb_host_interface *alts;
struct usb_endpoint_descriptor *epd = NULL;
struct bootmouse *m;
struct input_dev *input;
int i, error;
if (match_vendor && le16_to_cpu(udev->descriptor.idVendor) != match_vendor)
return -ENODEV;
if (match_product && le16_to_cpu(udev->descriptor.idProduct) != match_product)
return -ENODEV;
alts = intf->cur_altsetting;
for (i = 0; i < alts->desc.bNumEndpoints; i++) {
struct usb_endpoint_descriptor *d = &alts->endpoint[i].desc;
if (usb_endpoint_is_int_in(d)) {
epd = d;
break;
}
}
if (!epd)
return -ENODEV;
m = kzalloc(sizeof(*m), GFP_KERNEL);
if (!m)
return -ENOMEM;
m->udev = usb_get_dev(udev);
m->intf = intf;
atomic_set(&m->opened, 0);
m->irq_ep = usb_endpoint_num(epd);
m->irq_len = usb_endpoint_maxp(epd);
m->irq_interval = epd->bInterval;
m->irq_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!m->irq_urb) {
error = -ENOMEM;
goto err_free;
}
m->irq_data = usb_alloc_coherent(udev, m->irq_len, GFP_KERNEL, &m->irq_dma);
if (!m->irq_data) {
error = -ENOMEM;
goto err_free_urb;
}
input = input_allocate_device();
if (!input) {
error = -ENOMEM;
goto err_free_buf;
}
m->input = input;
usb_make_path(udev, m->phys, sizeof(m->phys));
strlcat(m->phys, "/input0", sizeof(m->phys));
input->name = "USB Boot Mouse (example driver)";
input->phys = m->phys;
usb_to_input_id(udev, &input->id);
input->dev.parent = &intf->dev;
input->open = bootmouse_open;
input->close = bootmouse_close;
__set_bit(EV_KEY, input->evbit);
__set_bit(EV_REL, input->evbit);
__set_bit(BTN_LEFT, input->keybit);
__set_bit(BTN_RIGHT, input->keybit);
__set_bit(REL_X, input->relbit);
__set_bit(REL_Y, input->relbit);
__set_bit(REL_WHEEL, input->relbit);
input_set_drvdata(input, m);
usb_fill_int_urb(
m->irq_urb,
udev,
usb_rcvintpipe(udev, epd->bEndpointAddress),
m->irq_data,
m->irq_len,
bootmouse_irq,
m,
m->irq_interval);
m->irq_urb->transfer_dma = m->irq_dma;
m->irq_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
usb_set_intfdata(intf, m);
error = input_register_device(input);
if (error)
goto err_clear_intfdata;
dev_info(&intf->dev,
"bound to %04x:%04x, int-in ep 0x%02x maxp %u interval %u\n",
le16_to_cpu(udev->descriptor.idVendor),
le16_to_cpu(udev->descriptor.idProduct),
epd->bEndpointAddress,
m->irq_len,
m->irq_interval);
return 0;
err_clear_intfdata:
usb_set_intfdata(intf, NULL);
input_free_device(input);
m->input = NULL;
err_free_buf:
usb_free_coherent(udev, m->irq_len, m->irq_data, m->irq_dma);
err_free_urb:
usb_free_urb(m->irq_urb);
err_free:
usb_put_dev(m->udev);
kfree(m);
return error;
}
static void bootmouse_disconnect(struct usb_interface *intf)
{
struct bootmouse *m = usb_get_intfdata(intf);
usb_set_intfdata(intf, NULL);
if (!m)
return;
if (m->input) {
/* Triggers ->close if opened */
input_unregister_device(m->input);
m->input = NULL;
}
usb_kill_urb(m->irq_urb);
usb_free_coherent(m->udev, m->irq_len, m->irq_data, m->irq_dma);
usb_free_urb(m->irq_urb);
usb_put_dev(m->udev);
kfree(m);
dev_info(&intf->dev, "disconnected\n");
}
/* Match HID Boot Mouse interfaces. */
static const struct usb_device_id bootmouse_id_table[] = {
/* HID Boot Mouse: bInterfaceClass = 3 (HID), bInterfaceSubClass = 1 (BOOT), bInterfaceProtocol = 2 (MOUSE) */
{ USB_INTERFACE_INFO(3, 1, 2) },
{ }
};
MODULE_DEVICE_TABLE(usb, bootmouse_id_table);
static struct usb_driver bootmouse_driver = {
.name = DRV_NAME,
.probe = bootmouse_probe,
.disconnect = bootmouse_disconnect,
.id_table = bootmouse_id_table,
};
module_usb_driver(bootmouse_driver);
MODULE_AUTHOR("Example / educational");
MODULE_DESCRIPTION("Minimal USB HID boot-protocol mouse driver (buttons, motion, wheel)");
MODULE_LICENSE("GPL");

View File

@@ -14,6 +14,7 @@ import subprocess
import glob import glob
import re import re
import time import time
import argparse
from pathlib import Path from pathlib import Path
@@ -208,15 +209,29 @@ def display_usb_devices(devices, filter_input=True):
return devices return devices
def get_kernel_modules(directory="."): def get_kernel_modules(directories=None):
"""Get list of available kernel modules (.ko files)""" """Get list of available kernel modules (.ko files)"""
modules = [] 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
# Search for .ko files in the specified directory
for ko_file in glob.glob(os.path.join(directory, "*.ko")): for ko_file in glob.glob(os.path.join(directory, "*.ko")):
module_name = os.path.basename(ko_file) module_name = os.path.basename(ko_file)
module_path = os.path.abspath(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 # Get module info if possible
try: try:
result = subprocess.run(['modinfo', module_path], result = subprocess.run(['modinfo', module_path],
@@ -485,8 +500,38 @@ def unload_module(module):
return False 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(): def main():
"""Main function""" """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(f"{Colors.BOLD}{Colors.CYAN}")
print("=" * 60) print("=" * 60)
print(" USB Driver Manager") print(" USB Driver Manager")
@@ -525,7 +570,9 @@ def main():
print(f" {iface['name']}: class={iface['class']} subclass={iface['subclass']} protocol={iface['protocol']} driver={driver_color}{iface['driver']}{Colors.END}") 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 # Step 3: List available kernel modules
modules = get_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) displayed_modules = display_kernel_modules(modules)
if not displayed_modules: if not displayed_modules:
@@ -540,12 +587,22 @@ def main():
selected_module = displayed_modules[module_idx] selected_module = displayed_modules[module_idx]
print(f"\n{Colors.CYAN}Selected module: {selected_module['name']}{Colors.END}") 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 # Step 5: Confirm operation
print(f"\n{Colors.YELLOW}This will:{Colors.END}") print(f"\n{Colors.YELLOW}This will:{Colors.END}")
if selected_device.get('interfaces'): if selected_device.get('interfaces'):
print(f" 1. Unbind interface(s) from current driver(s)") print(f" 1. Unbind interface(s) from current driver(s)")
else: else:
print(f" 1. Unbind {selected_device['name']} from {selected_device['driver']}") 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" 2. Load module {selected_module['name']}")
print(f" 3. Bind interface(s) to the new driver") print(f" 3. Bind interface(s) to the new driver")
@@ -562,6 +619,15 @@ def main():
print_error("Failed to unbind device. Aborting.") print_error("Failed to unbind device. Aborting.")
sys.exit(1) 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 # Load new module
if not load_module(selected_module): if not load_module(selected_module):
print_error("Failed to load module. Attempting to restore...") print_error("Failed to load module. Attempting to restore...")