Added first tries
This commit is contained in:
31
Makefile
Normal file
31
Makefile
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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)
|
||||||
113
README.md
113
README.md
@@ -1,3 +1,116 @@
|
|||||||
|
|
||||||
Device Driver
|
Device Driver
|
||||||
============
|
============
|
||||||
|
|
||||||
|
This folder contains a minimal **out-of-tree** Linux kernel module that acts as a USB **boot-protocol mouse** driver.
|
||||||
|
|
||||||
|
It supports:
|
||||||
|
- Left and right buttons
|
||||||
|
- Scroll wheel
|
||||||
|
- Relative cursor motion (X/Y)
|
||||||
|
|
||||||
|
Important notes
|
||||||
|
---------------
|
||||||
|
|
||||||
|
- 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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd Device-Driver
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
Load
|
||||||
|
----
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo insmod usb_bootmouse.ko
|
||||||
|
dmesg | tail -n 50
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to restrict binding to a specific device:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo insmod usb_bootmouse.ko match_vendor=0x046d match_product=0xc077
|
||||||
|
```
|
||||||
|
|
||||||
|
(Replace IDs with your mouse vendor/product from `lsusb`.)
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|||||||
111
bind_usb_bootmouse.sh
Executable file
111
bind_usb_bootmouse.sh
Executable file
@@ -0,0 +1,111 @@
|
|||||||
|
#!/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
|
||||||
64
restore_hid.sh
Executable file
64
restore_hid.sh
Executable file
@@ -0,0 +1,64 @@
|
|||||||
|
#!/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
|
||||||
324
usb_bootmouse.c
Normal file
324
usb_bootmouse.c
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
// 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");
|
||||||
610
usb_driver_manager.py
Executable file
610
usb_driver_manager.py
Executable file
@@ -0,0 +1,610 @@
|
|||||||
|
#!/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
|
||||||
|
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(directory="."):
|
||||||
|
"""Get list of available kernel modules (.ko files)"""
|
||||||
|
modules = []
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# 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 main():
|
||||||
|
"""Main function"""
|
||||||
|
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
|
||||||
|
modules = get_kernel_modules()
|
||||||
|
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}")
|
||||||
|
|
||||||
|
# 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")
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 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