diff --git a/.gitignore b/.gitignore index 216fa65..ba88a87 100644 --- a/.gitignore +++ b/.gitignore @@ -1,42 +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 +# 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/README.md b/README.md index 0eb09c7..82a6802 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,62 @@ +# Device Drivers +---------------------------------- -Device Driver -====================== +This repository contains Linux **kernel modules** (``.ko``) that implement low-level USB input drivers and expose device events through the Linux **input subsystem** (evdev). -A collection of USB device drivers for Linux kernel, demonstrating how to interact with various USB HID devices. +A helper tool (``usb_driver_manager.py``) is included to make it easier to: +- list USB devices and their **interfaces** +- load/unload (reload) a chosen ``.ko`` module +- unbind/bind a selected interface to a chosen driver during development -## Current Drivers +## Drivers +---------- -### Mouse Driver (`mouse/`) -A USB HID mouse driver that supports 16-bit coordinate tracking for high-DPI gaming mice. Tested with Cooler Master MM710. +### Mouse driver (``mouse/``) +USB HID boot-protocol mouse driver. -**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:** +**Build:** ```bash -cd mouse/ +cd mouse make ``` -## USB Driver Manager +### Logitech G29 media driver (``g29_media_usb/``) +Logitech G29 driver that maps selected wheel inputs to media key events. -The `usb_driver_manager.py` tool simplifies the process of binding USB devices to custom drivers. +**Current mapping (Mode 0):** +- Red rotary clockwise / counter-clockwise -> Volume Up / Volume Down +- Return ("Enter") -> Play/Pause +- Plus / Minus -> Next track / Previous track -**Usage:** +**Build:** ```bash -# Search for .ko files in current directory -sudo python3 usb_driver_manager.py - -# Search in specific directories -sudo python3 usb_driver_manager.py ./mouse ./keyboard - -# The tool will: -# 1. List available USB HID devices -# 2. Show available kernel modules (.ko files) -# 3. Unbind the device from its current driver -# 4. Unload existing module (if already loaded) -# 5. Load the new module -# 6. Bind the device to the new driver +cd g29_media_usb +make ``` -## Future Drivers +After building, the module (``*.ko``) is typically placed under ``build/`` by the provided Makefiles. -- **Keyboard**: USB HID keyboard driver -- **Racing Wheel**: USB racing wheel driver with force feedback +## USB Driver Manager +--------------------- -## Requirements +**Usage examples:** +```bash +# Search for modules in these driver directories (the tool searches recursively) +sudo python3 usb_driver_manager.py ./mouse ./g29_media_usb -- Linux kernel headers -- Python 3.6+ -- Root/sudo access for driver loading and binding +# Or point directly at build/ directories +sudo python3 usb_driver_manager.py ./mouse/build ./g29_media_usb/build +``` + +**Workflow:** +1. Select the USB device. +2. (Optional but recommended for non-mouse devices) select the USB **interface** to bind. +3. Select the kernel module. +4. Confirm; the tool unbinds the current driver, reloads the module if needed, and binds the chosen interface. + +## Testing +---------- +After binding the driver, use ``evtest`` to confirm key events: +```bash +sudo evtest +``` diff --git a/g29_media_usb/Makefile b/g29_media_usb/Makefile new file mode 100644 index 0000000..8cf0250 --- /dev/null +++ b/g29_media_usb/Makefile @@ -0,0 +1,28 @@ +obj-m += g29_media_usb.o + +PWD := $(CURDIR) + +.PHONY: all clean install uninstall + +all: + $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + mkdir -p build + mv -f *.ko build/ 2>/dev/null || 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: + @modpath=$$(modinfo -n g29_media_usb 2>/dev/null); \ + if [ -z "$$modpath" ]; then \ + echo "Module g29_media_usb not found via modinfo (not installed?)"; \ + exit 1; \ + fi; \ + echo "Removing $$modpath"; \ + rm -f "$$modpath"; \ + depmod -a diff --git a/g29_media_usb/g29_media_usb.c b/g29_media_usb/g29_media_usb.c new file mode 100644 index 0000000..9775eaa --- /dev/null +++ b/g29_media_usb/g29_media_usb.c @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Logitech G29 -> Media Keys (USB interface driver) + * + * Proof-of-concept Linux kernel module for low-level programming course. + * + * This driver: + * - Binds to a Logitech G29 USB interface (VID/PID match) + * - Receives 12-byte input reports via an interrupt-IN URB + * - Parses the report into a normalized state (Stage A) + * - Translates selected signals into media key events (Stage B) + * + * Stage A is designed to remain stable across different mapping policies. + * Stage B is designed to be replaced/extended by swapping mapping tables + * or adding per-signal handler functions. + */ + +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("LLP group 32"); +MODULE_DESCRIPTION("Logitech G29 -> Media keys (USB driver)"); +MODULE_LICENSE("GPL"); + +#define USB_VENDOR_ID_LOGITECH 0x046d +#define USB_DEVICE_ID_LOGITECH_G29 0xc24f +#define USB_DEVICE_ID_LOGITECH_G29_ALT 0xc260 + +#define G29_REPORT_LEN 12 + +/* + * Button masks + */ +#define G29_BTN_PLUS 0x00800000u +#define G29_BTN_MINUS 0x01000000u +#define G29_BTN_RED_CW 0x02000000u +#define G29_BTN_RED_CCW 0x04000000u +#define G29_BTN_RETURN 0x08000000u +#define G29_BTN_R1 0x00000100u +#define G29_BTN_L1 0x00000200u + +enum g29_mode { + G29_MODE_MEDIA = 0, +}; + +static int mode = G29_MODE_MEDIA; +module_param(mode, int, 0444); +MODULE_PARM_DESC(mode, "Mapping mode (0=MEDIA)"); + +struct g29_state { + u32 buttons; + u16 rot; + u8 gas; + u8 brk; + u8 clt; + u8 grx; + u8 gry; + u8 grz; +}; + +/* + * Mapping table entry + */ +struct g29_keymap_edge { + u32 mask; + unsigned short keycode; +}; + +static const struct g29_keymap_edge g29_media_edge_map[] = { + /* Red rotary = volume */ + { G29_BTN_RED_CW, KEY_VOLUMEUP }, + { G29_BTN_RED_CCW, KEY_VOLUMEDOWN }, + + /* Return = play/pause */ + { G29_BTN_RETURN, KEY_PLAYPAUSE }, + + /* Plus/Minus = next/prev */ + { G29_BTN_R1, KEY_NEXTSONG }, + { G29_BTN_L1, KEY_PREVIOUSSONG }, +}; + +struct g29_dev { + char name[128]; + char phys[64]; + + struct usb_device *udev; + struct input_dev *input; + + struct urb *urb; + u8 *buf; + dma_addr_t buf_dma; + + struct g29_state last; +}; + +/* Parsing */ + +static bool g29_parse_report(struct g29_state *out, const u8 *data, int len) +{ + if (len < G29_REPORT_LEN) + return false; + + /* bytes 0..3 buttons bitfield */ + out->buttons = get_unaligned_le32(&data[0]); + + /* bytes 4..5 rotation */ + out->rot = get_unaligned_le16(&data[4]); + + out->gas = data[6]; + out->brk = data[7]; + out->clt = data[8]; + out->grx = data[9]; + out->gry = data[10]; + out->grz = data[11]; + + return true; +} + +/* Mapping policy */ + +static void g29_pulse_key(struct input_dev *input, unsigned short keycode) +{ + /* A pulse is a press+release within one report frame. */ + input_report_key(input, keycode, 1); + input_report_key(input, keycode, 0); +} + +static void g29_apply_media_mode(struct g29_dev *g29, + const struct g29_state *prev, + const struct g29_state *cur) +{ + u32 pressed = cur->buttons & ~prev->buttons; + size_t i; + + for (i = 0; i < ARRAY_SIZE(g29_media_edge_map); i++) { + const struct g29_keymap_edge *e = &g29_media_edge_map[i]; + if (pressed & e->mask) + g29_pulse_key(g29->input, e->keycode); + } + + input_sync(g29->input); +} + +static void g29_process_report(struct g29_dev *g29, const u8 *data, int len) +{ + struct g29_state cur; + + if (!g29_parse_report(&cur, data, len)) + return; + + switch (mode) { + case G29_MODE_MEDIA: + default: + g29_apply_media_mode(g29, &g29->last, &cur); + break; + } + + g29->last = cur; +} + +/* URB plumbing */ + +static void g29_urb_complete(struct urb *urb) +{ + struct g29_dev *g29 = urb->context; + int ret; + + switch (urb->status) { + case 0: + break; /* success */ + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + return; /* cancelled/disconnected */ + default: + goto resubmit; /* transient error */ + } + + g29_process_report(g29, g29->buf, urb->actual_length); + +resubmit: + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) + dev_err(&g29->udev->dev, "usb_submit_urb failed: %d\n", ret); +} + +static int g29_input_open(struct input_dev *input) +{ + struct g29_dev *g29 = input_get_drvdata(input); + + g29->urb->dev = g29->udev; + if (usb_submit_urb(g29->urb, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static void g29_input_close(struct input_dev *input) +{ + struct g29_dev *g29 = input_get_drvdata(input); + + usb_kill_urb(g29->urb); +} + +/* USB driver binding */ + +static int g29_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_host_interface *alts = intf->cur_altsetting; + struct usb_endpoint_descriptor *ep = NULL; + struct g29_dev *g29; + struct input_dev *input; + int i, pipe, maxp, error; + + /* Find an interrupt IN endpoint capable of carrying the 12-byte report. */ + for (i = 0; i < alts->desc.bNumEndpoints; i++) { + struct usb_endpoint_descriptor *cand = &alts->endpoint[i].desc; + if (!usb_endpoint_is_int_in(cand)) + continue; + pipe = usb_rcvintpipe(udev, cand->bEndpointAddress); + maxp = usb_maxpacket(udev, pipe); + if (maxp >= G29_REPORT_LEN) { + ep = cand; + break; + } + } + + if (!ep) + return -ENODEV; + + g29 = kzalloc(sizeof(*g29), GFP_KERNEL); + if (!g29) + return -ENOMEM; + + input = input_allocate_device(); + if (!input) { + error = -ENOMEM; + goto err_free_g29; + } + + g29->udev = udev; + g29->input = input; + memset(&g29->last, 0, sizeof(g29->last)); + + /* Allocate a fixed-size report buffer (12 bytes). */ + g29->buf = usb_alloc_coherent(udev, G29_REPORT_LEN, GFP_KERNEL, &g29->buf_dma); + if (!g29->buf) { + error = -ENOMEM; + goto err_free_input; + } + + g29->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!g29->urb) { + error = -ENOMEM; + goto err_free_buf; + } + + /* Build a friendly input device name. */ + if (udev->manufacturer) + strscpy(g29->name, udev->manufacturer, sizeof(g29->name)); + if (udev->product) { + if (udev->manufacturer) + strlcat(g29->name, " ", sizeof(g29->name)); + strlcat(g29->name, udev->product, sizeof(g29->name)); + } + if (!strlen(g29->name)) + snprintf(g29->name, sizeof(g29->name), + "Logitech G29 Media %04x:%04x", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + + usb_make_path(udev, g29->phys, sizeof(g29->phys)); + strlcat(g29->phys, "/input0", sizeof(g29->phys)); + + input->name = g29->name; + input->phys = g29->phys; + usb_to_input_id(udev, &input->id); + input->dev.parent = &intf->dev; + + __set_bit(EV_KEY, input->evbit); + + /* Advertise only the keys we emit in media mode. */ + input_set_capability(input, EV_KEY, KEY_VOLUMEUP); + input_set_capability(input, EV_KEY, KEY_VOLUMEDOWN); + input_set_capability(input, EV_KEY, KEY_PLAYPAUSE); + input_set_capability(input, EV_KEY, KEY_NEXTSONG); + input_set_capability(input, EV_KEY, KEY_PREVIOUSSONG); + + input_set_drvdata(input, g29); + input->open = g29_input_open; + input->close = g29_input_close; + + pipe = usb_rcvintpipe(udev, ep->bEndpointAddress); + usb_fill_int_urb(g29->urb, udev, pipe, + g29->buf, G29_REPORT_LEN, + g29_urb_complete, g29, ep->bInterval); + g29->urb->transfer_dma = g29->buf_dma; + g29->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + error = input_register_device(input); + if (error) + goto err_free_urb; + + usb_set_intfdata(intf, g29); + + dev_info(&intf->dev, + "G29 media driver bound (ep=%02x interval=%u)\n", + ep->bEndpointAddress, ep->bInterval); + + return 0; + +err_free_urb: + usb_free_urb(g29->urb); +err_free_buf: + usb_free_coherent(udev, G29_REPORT_LEN, g29->buf, g29->buf_dma); +err_free_input: + input_free_device(input); +err_free_g29: + kfree(g29); + return error; +} + +static void g29_disconnect(struct usb_interface *intf) +{ + struct g29_dev *g29 = usb_get_intfdata(intf); + + usb_set_intfdata(intf, NULL); + if (!g29) + return; + + usb_kill_urb(g29->urb); + input_unregister_device(g29->input); + usb_free_urb(g29->urb); + usb_free_coherent(interface_to_usbdev(intf), G29_REPORT_LEN, + g29->buf, g29->buf_dma); + kfree(g29); + + dev_info(&intf->dev, "G29 media driver disconnected\n"); +} + +static const struct usb_device_id g29_id_table[] = { + { USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G29) }, + { USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G29_ALT) }, + { } +}; +MODULE_DEVICE_TABLE(usb, g29_id_table); + +static struct usb_driver g29_driver = { + .name = "g29_media_usb", + .probe = g29_probe, + .disconnect = g29_disconnect, + .id_table = g29_id_table, +}; + +module_usb_driver(g29_driver); diff --git a/mouse/simple_usb_mouse.c b/mouse/simple_usb_mouse.c index ff95f80..15f0249 100644 --- a/mouse/simple_usb_mouse.c +++ b/mouse/simple_usb_mouse.c @@ -1,357 +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); +// 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/usb_driver_manager.py b/usb_driver_manager.py index 8e44dda..54da4d8 100755 --- a/usb_driver_manager.py +++ b/usb_driver_manager.py @@ -6,6 +6,11 @@ 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. + +Wheel-friendly improvements: + - Finds kernel modules (*.ko) recursively under provided directories (e.g. ./build). + - Lets the user select which USB interface(s) to unbind/bind. + Default selection: all HID interfaces (bInterfaceClass==03). """ import os @@ -74,7 +79,7 @@ def get_usb_devices(): 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) + match = re.match(r'Bus (\d+) Device (\d+): ID ([0-9a-f]{4}):([0-9a-f]{4})\s+(.*)', line, re.IGNORECASE) if match: bus, dev, vendor, product, name = match.groups() key = f"{int(bus)}-{int(dev)}" @@ -93,7 +98,7 @@ def get_usb_devices(): # 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): + if not re.match(r'\d+-[\d.]+$', device_name): continue # Skip root hubs @@ -116,21 +121,9 @@ def get_usb_devices(): 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 = [] + is_input = False for interface_path in device_path.glob("*:*.*"): if not interface_path.is_dir(): continue @@ -160,6 +153,12 @@ def get_usb_devices(): except Exception: continue + # Improve product string from lsusb when possible (safe conversion) + if busnum.isdigit() and devnum.isdigit(): + lsusb_key = f"{int(busnum)}-{int(devnum)}" + if lsusb_key in lsusb_output: + product = lsusb_output[lsusb_key]['name'] + devices.append({ 'path': str(device_path), 'name': device_name, @@ -172,7 +171,7 @@ def get_usb_devices(): 'driver': driver, 'is_input': is_input, 'interfaces': interfaces, - 'display_name': f"{manufacturer} {product}" + 'display_name': f"{manufacturer} {product}".strip() }) except Exception as e: @@ -217,42 +216,45 @@ def get_kernel_modules(directories=None): modules = [] seen_modules = set() # Track module names to avoid duplicates - # Search for .ko files in each specified directory + # Search for .ko files recursively in each specified directory for directory in directories: if not os.path.exists(directory): print_warning(f"Directory not found: {directory}") continue - for ko_file in glob.glob(os.path.join(directory, "*.ko")): - module_name = os.path.basename(ko_file) - module_path = os.path.abspath(ko_file) + for root, dirs, files in os.walk(directory): + for filename in files: + if not filename.endswith('.ko'): + continue - # Skip duplicates (same module name already found) - if module_name in seen_modules: - continue - seen_modules.add(module_name) + module_name = filename + module_path = os.path.abspath(os.path.join(root, filename)) - # 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 + # Skip duplicates (same module name already found) + if module_name in seen_modules: + continue + seen_modules.add(module_name) - modules.append({ - 'name': module_name, - 'path': module_path, - 'description': description - }) - except Exception: - modules.append({ - 'name': module_name, - 'path': module_path, - 'description': "No description available" - }) + # 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 @@ -295,6 +297,55 @@ def get_user_choice(prompt, max_choice): return None +def get_interface_selection(device): + """Select which interface(s) to unbind/bind. + + Default (Enter): all HID interfaces (class 03). + 'a' selects all interfaces. + Comma-separated list selects specific interfaces. + """ + if not device.get('interfaces'): + return [] + + print_header("Interfaces") + for idx, iface in enumerate(device['interfaces'], 1): + driver_color = Colors.GREEN if iface['driver'] != "none" else Colors.YELLOW + print(f" {idx:>2}. {iface['name']}: class={iface['class']} subclass={iface['subclass']} protocol={iface['protocol']} driver={driver_color}{iface['driver']}{Colors.END}") + + print("\nSelect interfaces to bind:") + print(" [Enter] AUTO: all HID interfaces (class 03)") + print(" a ALL interfaces") + print(" 1,3 Comma-separated list") + + raw = input("Selection: ").strip().lower() + if raw == "": + picked = [i for i in device['interfaces'] if i['class'] == "03"] + if not picked: + print_warning("No HID interfaces found; selecting ALL interfaces.") + picked = list(device['interfaces']) + return picked + + if raw == "a": + return list(device['interfaces']) + + picked = [] + parts = [p.strip() for p in raw.split(',') if p.strip()] + for p in parts: + try: + i = int(p) + if 1 <= i <= len(device['interfaces']): + picked.append(device['interfaces'][i - 1]) + except ValueError: + continue + + if not picked: + print_warning("No valid interfaces selected; using AUTO (all HID interfaces).") + picked = [i for i in device['interfaces'] if i['class'] == "03"] + if not picked: + picked = list(device['interfaces']) + return picked + + def unbind_device(device, interface=None): """Unbind device interface from current driver""" # If specific interface provided, unbind that interface @@ -355,8 +406,7 @@ 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) + result = subprocess.run(['insmod', module['path']], capture_output=True, text=True) if result.returncode == 0: print_success(f"Successfully loaded {module['name']}") return True @@ -435,7 +485,7 @@ def bind_device(device, module, interface=None): print_error(f"Failed to bind interface: {e}") return False - # Otherwise try to bind all HID mouse interfaces (class 03, subclass 01, protocol 02) + # Default behavior (kept for backwards compatibility): try boot mouse, then device. target_interfaces = [] for iface in device.get('interfaces', []): # Look for HID Boot Mouse interfaces @@ -482,13 +532,35 @@ def bind_device(device, module, interface=None): return success +def bind_interface_to_driver(interface_name, driver_name): + """Bind a specific USB interface back to a given driver name (sysfs).""" + driver_path = Path(f"/sys/bus/usb/drivers/{driver_name}") + bind_path = driver_path / "bind" + + if not driver_path.exists(): + print_warning(f"Cannot restore: driver path not found: {driver_path}") + return False + + if not bind_path.exists(): + print_warning(f"Cannot restore: bind path not found: {bind_path}") + return False + + try: + print(f"Restoring interface {interface_name} to driver {driver_name}...") + bind_path.write_text(interface_name) + print_success(f"Restored {interface_name} to {driver_name}") + return True + except Exception as e: + print_warning(f"Failed to restore interface {interface_name} to {driver_name}: {e}") + return False + + 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) + result = subprocess.run(['rmmod', driver_name], capture_output=True, text=True) if result.returncode == 0: print_success(f"Successfully unloaded {driver_name}") return True @@ -562,12 +634,16 @@ def main(): 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 2b: Select interfaces + selected_interfaces = get_interface_selection(selected_device) + if not selected_interfaces: + print_warning("No interfaces available/selected; cannot bind.") + sys.exit(1) + + # Remember original per-interface drivers for restore attempts + original_interface_drivers = {} + for iface in selected_interfaces: + original_interface_drivers[iface['name']] = iface.get('driver', 'none') # Step 3: List available kernel modules if len(args.directories) > 1 or args.directories[0] != '.': @@ -594,17 +670,14 @@ def main(): # 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" 1. Unbind {len(selected_interfaces)} interface(s) from current driver(s)") 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") + print(f" 4. Bind selected 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") + print(f" 3. Bind selected 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']: @@ -614,10 +687,11 @@ def main(): # 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) + # Unbind selected interfaces from current drivers + for iface in selected_interfaces: + if not unbind_device(selected_device, iface): + print_error("Failed to unbind interface. Aborting.") + sys.exit(1) # Unload module if already loaded if module_already_loaded: @@ -631,24 +705,36 @@ def main(): # 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'}) + # Try to rebind each selected interface to its original driver + for iface in selected_interfaces: + orig = original_interface_drivers.get(iface['name'], 'none') + if orig != 'none': + bind_interface_to_driver(iface['name'], orig) 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.") + # Bind selected interfaces to new driver + bound_any = False + for iface in selected_interfaces: + if bind_device(selected_device, selected_module, iface): + bound_any = True + else: + # Restore interfaces that failed to bind to the new driver + orig = original_interface_drivers.get(iface['name'], 'none') + if orig != 'none': + bind_interface_to_driver(iface['name'], orig) + + if not bound_any: + print_error("Failed to bind any interface to the new driver.") + print_warning("All selected interfaces were restored where possible. You might need to reconnect the device.") 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}") + print(f"{Colors.GREEN}Device {selected_device['name']} interface(s) are now using the new driver{Colors.END}") # Offer to show new device status print("\nVerifying device status...")