From 29b23b596429dfdddc34d7a984c62edc7cba019f Mon Sep 17 00:00:00 2001 From: Thomas Hilscher Date: Thu, 15 Jan 2026 22:04:34 +0100 Subject: [PATCH] Fixed mouse driver and moved to seperate folder --- .gitignore | 42 +++++ Makefile | 31 ---- README.md | 134 ++++----------- bind_usb_bootmouse.sh | 111 ------------ mouse/Makefile | 23 +++ mouse/simple_usb_mouse.c | 357 +++++++++++++++++++++++++++++++++++++++ restore_hid.sh | 64 ------- usb_bootmouse.c | 324 ----------------------------------- usb_driver_manager.py | 122 ++++++++++--- 9 files changed, 552 insertions(+), 656 deletions(-) create mode 100644 .gitignore delete mode 100644 Makefile delete mode 100755 bind_usb_bootmouse.sh create mode 100644 mouse/Makefile create mode 100644 mouse/simple_usb_mouse.c delete mode 100755 restore_hid.sh delete mode 100644 usb_bootmouse.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..216fa65 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/Makefile b/Makefile deleted file mode 100644 index 3096596..0000000 --- a/Makefile +++ /dev/null @@ -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) diff --git a/README.md b/README.md index d5e8064..0eb09c7 100644 --- a/README.md +++ b/README.md @@ -1,116 +1,54 @@ 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: -- Left and right buttons -- Scroll wheel -- Relative cursor motion (X/Y) +## Current Drivers -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`). -- This driver binds to HID **Boot Mouse** interfaces (class=HID, subclass=BOOT, protocol=MOUSE). Many mice work, but not all. -- To use it on a running system you typically must **unbind** the existing driver from that USB interface first. - -Files ------ - -- `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: +**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 Device-Driver +cd mouse/ make ``` -Load ----- +## USB Driver Manager +The `usb_driver_manager.py` tool simplifies the process of binding USB devices to custom drivers. + +**Usage:** ```bash -sudo insmod usb_bootmouse.ko -dmesg | tail -n 50 +# 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 ``` -If you want to restrict binding to a specific device: +## Future Drivers -```bash -sudo insmod usb_bootmouse.ko match_vendor=0x046d match_product=0xc077 -``` +- **Keyboard**: USB HID keyboard driver +- **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) ---------------------------------- - -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 -``` +- Linux kernel headers +- Python 3.6+ +- Root/sudo access for driver loading and binding diff --git a/bind_usb_bootmouse.sh b/bind_usb_bootmouse.sh deleted file mode 100755 index 2a15e8a..0000000 --- a/bind_usb_bootmouse.sh +++ /dev/null @@ -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 diff --git a/mouse/Makefile b/mouse/Makefile new file mode 100644 index 0000000..5afec8f --- /dev/null +++ b/mouse/Makefile @@ -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 diff --git a/mouse/simple_usb_mouse.c b/mouse/simple_usb_mouse.c new file mode 100644 index 0000000..ff95f80 --- /dev/null +++ b/mouse/simple_usb_mouse.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#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); diff --git a/restore_hid.sh b/restore_hid.sh deleted file mode 100755 index fbb73a8..0000000 --- a/restore_hid.sh +++ /dev/null @@ -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 diff --git a/usb_bootmouse.c b/usb_bootmouse.c deleted file mode 100644 index 9fcc3d9..0000000 --- a/usb_bootmouse.c +++ /dev/null @@ -1,324 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -#include -#include -#include -#include -#include -#include -#include - -#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"); diff --git a/usb_driver_manager.py b/usb_driver_manager.py index a1911f6..8e44dda 100755 --- a/usb_driver_manager.py +++ b/usb_driver_manager.py @@ -14,6 +14,7 @@ import subprocess import glob import re import time +import argparse from pathlib import Path @@ -208,36 +209,50 @@ def display_usb_devices(devices, filter_input=True): return devices -def get_kernel_modules(directory="."): +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 the specified directory - 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) + # 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 - # 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 + 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) - modules.append({ - 'name': module_name, - 'path': module_path, - 'description': description - }) - except Exception: - modules.append({ - 'name': module_name, - 'path': module_path, - 'description': "No description available" - }) + # 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 @@ -485,8 +500,38 @@ def unload_module(module): 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") @@ -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}") # 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) if not displayed_modules: @@ -540,14 +587,24 @@ def main(): 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']}") - print(f" 2. Load module {selected_module['name']}") - print(f" 3. Bind interface(s) to the new 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']: @@ -562,6 +619,15 @@ def main(): 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...")