chiark / gitweb /
hid2hci: rewrite (and break) rules and device handling
authorKay Sievers <kay.sievers@vrfy.org>
Fri, 24 Jul 2009 16:06:22 +0000 (18:06 +0200)
committerKay Sievers <kay.sievers@vrfy.org>
Fri, 24 Jul 2009 16:06:22 +0000 (18:06 +0200)
We must never access random devices in /dev which do not belong to
the event we are handling. Hard-coding /dev/hidrawX, and looping over all
devices is absolutely not acceptable --> hook into hidraw events.

We can not relay on (rather random) properties merged into the parent
device by earlier rules --> use libudev to find the sibling device
with a matching interface.

Libusb does not fit into udev's use case. We never want want to scan
and open() all usb devices in the system, just to find the device
we are already handling the event for --> put all the stupid scanning
into a single function and prepare for a fixed libusb or drop it later.

TODO
extras/hid2hci/70-hid2hci.rules
extras/hid2hci/Makefile.am
extras/hid2hci/hid2hci.c

diff --git a/TODO b/TODO
index c6576fd..ac9b738 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,7 +1,8 @@
 
+  o get rid of braindead "scan all devices to find myself" libusb interface
+      if it can not be fixed, drop libusb entirely
   o enumerate: sort control* after pcm*
   o add tests for kernel provided DEVNAME logic
-  o drop modprobe floppy alias (SUSE), it will be in the module (2.6.30)
   o convert firmware.sh to C
 
   o symlink names to udevadm will no longer be resolved to old command names
index 47eec00..1af356c 100644 (file)
@@ -3,28 +3,27 @@
 ACTION!="add", GOTO="hid2hci_end"
 SUBSYSTEM!="usb", GOTO="hid2hci_end"
 
-# Variety of Dell Bluetooth devices - it looks like a bit of an odd rule,
-# because it is matching on a mouse device that is self powered, but that
-# is where a HID report needs to be sent to switch modes.
-#
+# Variety of Dell Bluetooth devices - match on a mouse device that is
+# self powered and where a HID report needs to be sent to switch modes
 # Known supported devices: 413c:8154, 413c:8158, 413c:8162
-ATTR{bInterfaceClass}=="03", ATTR{bInterfaceSubClass}=="01", ATTR{bInterfaceProtocol}=="02", ATTRS{bDeviceClass}=="00", ATTRS{idVendor}=="413c", ATTRS{bmAttributes}=="e0", \
-    RUN+="hid2hci --method dell -v $attr{idVendor} -p $attr{idProduct} --mode hci"
+ATTR{bInterfaceClass}=="03", ATTR{bInterfaceSubClass}=="01", ATTR{bInterfaceProtocol}=="02", \
+  ATTRS{bDeviceClass}=="00", ATTRS{idVendor}=="413c", ATTRS{bmAttributes}=="e0", \
+  RUN+="hid2hci --method=dell --devpath=%p"
 
-# When a Dell device recovers from S3, the mouse child needs to be repoked
-# Unfortunately the only event seen is the BT device disappearing, so the mouse
-# device needs to be chased down on the USB bus.
-ATTR{bDeviceClass}=="e0", ATTR{bDeviceSubClass}=="01", ATTR{bDeviceProtocol}=="01", ATTR{idVendor}=="413c", ATTR{bmAttributes}=="e0", IMPORT{parent}="ID_*", ENV{REMOVE_CMD}="hid2hci --method dell -v $env{ID_VENDOR_ID} -p $env{ID_MODEL_ID} --mode hci -s 02"
+# Logitech devices (hidraw)
+KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c70[345abce]|c71[34bc]", \
+  RUN+="hid2hci --method=logitech-hid --devpath=%p"
 
 ENV{DEVTYPE}!="usb_device", GOTO="hid2hci_end"
 
-# Logitech devices
-ATTR{idVendor}=="046d", ATTR{idProduct}=="c70[345abce]", RUN+="hid2hci --method logitech -v $attr{idVendor} -p $attr{idProduct} --mode hci"
-ATTR{idVendor}=="046d", ATTR{idProduct}=="c71[34bc]", RUN+="hid2hci --method logitech -v $attr{idVendor} -p $attr{idProduct} --mode hci"
+# When a Dell device recovers from S3, the mouse child needs to be repoked
+# Unfortunately the only event seen is the BT device disappearing, so the mouse
+# device needs to be chased down on the USB bus.
+ATTR{bDeviceClass}=="e0", ATTR{bDeviceSubClass}=="01", ATTR{bDeviceProtocol}=="01", \
+  ATTR{idVendor}=="413c", ATTR{bmAttributes}=="e0", \
+  ENV{REMOVE_CMD}="hid2hci --method=dell --devpath=%p --find-sibling-intf=03:01:02"
 
-# CSR devices (in HID mode)
-ATTR{idVendor}=="0a12", ATTR{idProduct}=="1000", RUN+="hid2hci --method csr -v $attr{idVendor} -p $attr{idProduct} --mode hci"
-ATTR{idVendor}=="0458", ATTR{idProduct}=="1000", RUN+="hid2hci --method csr -v $attr{idVendor} -p $attr{idProduct} --mode hci"
-ATTR{idVendor}=="05ac", ATTR{idProduct}=="1000", RUN+="hid2hci --method csr -v $attr{idVendor} -p $attr{idProduct} --mode hci"
+# CSR devices
+ATTR{idVendor}=="0a12|0458|05ac", ATTR{idProduct}=="1000", RUN+="hid2hci --method=csr --devpath=%p"
 
 LABEL="hid2hci_end"
index 62ba58d..5450c34 100644 (file)
@@ -7,7 +7,13 @@ dist_udevrules_DATA = \
        70-hid2hci.rules
 
 hid2hci_SOURCES = \
-       hid2hci.c
+       hid2hci.c \
+       ../../libudev/libudev.h \
+       ../../libudev/libudev.c \
+       ../../libudev/libudev-list.c \
+       ../../libudev/libudev-util.c \
+       ../../libudev/libudev-device.c \
+       ../../libudev/libudev-enumerate.c
 
 hid2hci_LDADD = \
        @LIBUSB_LIBS@
index 10beb61..298ac39 100644 (file)
@@ -1,25 +1,24 @@
 /*
+ * hid2hci : switch the radio on devices that support
+ *           it from HID to HCI and back
  *
- *  hid2hci : a tool for switching the radio on devices that support
- *                      it from HID to HCI and back
+ * Copyright (C) 2003-2009  Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2008-2009  Mario Limonciello <mario_limonciello@dell.com>
+ * Copyright (C) 2009 Kay Sievers <kay.sievers@vrfy.org>
  *
- *  Copyright (C) 2003-2009  Marcel Holtmann <marcel@holtmann.org>
- *  Copyright (C) 2008-2009  Mario Limonciello <mario_limonciello@dell.com>
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
  *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
  *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
 #include <stdio.h>
 #include <linux/hiddev.h>
 #include <usb.h>
 
-static char devpath[PATH_MAX + 1] = "/dev";
-
-#define HCI 0
-#define HID 1
+#include "libudev.h"
+#include "libudev-private.h"
 
-struct device_info {
-       struct usb_device *dev;
-       int mode;
-       uint16_t vendor;
-       uint16_t product;
+enum mode {
+       HCI = 0,
+       HID = 1,
 };
 
-static int switch_csr(struct device_info *devinfo)
+static int usb_switch_csr(struct usb_dev_handle *dev, enum mode mode)
 {
-       struct usb_dev_handle *udev;
        int err;
 
-       udev = usb_open(devinfo->dev);
-       if (!udev)
-               return -errno;
-
-       err = usb_control_msg(udev,
+       err = usb_control_msg(dev,
                              USB_ENDPOINT_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
-                             0, devinfo->mode, 0, NULL, 0, 10000);
-
+                             0, mode, 0, NULL, 0, 10000);
        if (err == 0) {
                err = -1;
                errno = EALREADY;
@@ -64,13 +53,10 @@ static int switch_csr(struct device_info *devinfo)
                if (errno == ETIMEDOUT)
                        err = 0;
        }
-
-       usb_close(udev);
-
        return err;
 }
 
-static int send_report(int fd, const char *buf, size_t size)
+static int hid_logitech_send_report(int fd, const char *buf, size_t size)
 {
        struct hiddev_report_info rinfo;
        struct hiddev_usage_ref uref;
@@ -99,72 +85,42 @@ static int send_report(int fd, const char *buf, size_t size)
        return err;
 }
 
-static int switch_logitech(struct device_info *devinfo)
+static int hid_switch_logitech(const char *filename)
 {
-       char devname[PATH_MAX + 1];
-       int i, fd, err = -1;
-
-       for (i = 0; i < 16; i++) {
-               struct hiddev_devinfo dinfo;
-               char rep1[] = { 0xff, 0x80, 0x80, 0x01, 0x00, 0x00 };
-               char rep2[] = { 0xff, 0x80, 0x00, 0x00, 0x30, 0x00 };
-               char rep3[] = { 0xff, 0x81, 0x80, 0x00, 0x00, 0x00 };
-
-               sprintf(devname, "%s/hiddev%d", devpath, i);
-               fd = open(devname, O_RDWR);
-               if (fd < 0) {
-                       sprintf(devname, "%s/usb/hiddev%d", devpath, i);
-                       fd = open(devname, O_RDWR);
-                       if (fd < 0) {
-                               sprintf(devname, "%s/usb/hid/hiddev%d", devpath, i);
-                               fd = open(devname, O_RDWR);
-                               if (fd < 0)
-                                       continue;
-                       }
-               }
-
-               memset(&dinfo, 0, sizeof(dinfo));
-               err = ioctl(fd, HIDIOCGDEVINFO, &dinfo);
-               if (err < 0 || (int) dinfo.busnum != atoi(devinfo->dev->bus->dirname) ||
-                               (int) dinfo.devnum != atoi(devinfo->dev->filename)) {
-                       close(fd);
-                       continue;
-               }
-
-               err = ioctl(fd, HIDIOCINITREPORT, 0);
-               if (err < 0) {
-                       close(fd);
-                       break;
-               }
-
-               err = send_report(fd, rep1, sizeof(rep1));
-               if (err < 0) {
-                       close(fd);
-                       break;
-               }
-
-               err = send_report(fd, rep2, sizeof(rep2));
-               if (err < 0) {
-                       close(fd);
-                       break;
-               }
-
-               err = send_report(fd, rep3, sizeof(rep3));
-               close(fd);
-               break;
-       }
-
+       char rep1[] = { 0xff, 0x80, 0x80, 0x01, 0x00, 0x00 };
+       char rep2[] = { 0xff, 0x80, 0x00, 0x00, 0x30, 0x00 };
+       char rep3[] = { 0xff, 0x81, 0x80, 0x00, 0x00, 0x00 };
+       int fd;
+       int err = -1;
+
+       fd = open(filename, O_RDWR);
+       if (fd < 0)
+               return err;
+
+       err = ioctl(fd, HIDIOCINITREPORT, 0);
+       if (err < 0)
+               goto out;
+
+       err = hid_logitech_send_report(fd, rep1, sizeof(rep1));
+       if (err < 0)
+               goto out;
+
+       err = hid_logitech_send_report(fd, rep2, sizeof(rep2));
+       if (err < 0)
+               goto out;
+
+       err = hid_logitech_send_report(fd, rep3, sizeof(rep3));
+out:
+       close(fd);
        return err;
 }
 
-static int switch_dell(struct device_info *devinfo)
+static int usb_switch_dell(struct usb_dev_handle *dev, enum mode mode)
 {
        char report[] = { 0x7f, 0x00, 0x00, 0x00 };
-
-       struct usb_dev_handle *handle;
        int err;
 
-       switch (devinfo->mode) {
+       switch (mode) {
        case HCI:
                report[1] = 0x13;
                break;
@@ -173,22 +129,16 @@ static int switch_dell(struct device_info *devinfo)
                break;
        }
 
-       handle = usb_open(devinfo->dev);
-       if (!handle)
-               return -EIO;
-
        /* Don't need to check return, as might not be in use */
-       usb_detach_kernel_driver_np(handle, 0);
+       usb_detach_kernel_driver_np(dev, 0);
 
-       if (usb_claim_interface(handle, 0) < 0) {
-               usb_close(handle);
+       if (usb_claim_interface(dev, 0) < 0)
                return -EIO;
-       }
 
-       err = usb_control_msg(handle,
-               USB_ENDPOINT_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+       err = usb_control_msg(dev,
+                       USB_ENDPOINT_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
                        USB_REQ_SET_CONFIGURATION, 0x7f | (0x03 << 8), 0,
-                                               report, sizeof(report), 5000);
+                       report, sizeof(report), 5000);
 
        if (err == 0) {
                err = -1;
@@ -197,133 +147,194 @@ static int switch_dell(struct device_info *devinfo)
                if (errno == ETIMEDOUT)
                        err = 0;
        }
-
-       usb_close(handle);
-
        return err;
 }
 
-static int find_device(struct device_info* devinfo)
+/*
+ * The braindead libusb needs to scan and open all devices, just to
+ * to find the device we already have. This needs to be fixed in libusb
+ * or it will be ripped out and we carry our own code.
+ */
+static struct usb_device *usb_device_open_from_udev(struct udev_device *usb_dev)
 {
        struct usb_bus *bus;
-       struct usb_device *dev;
+       const char *str;
+       int busnum;
+       int devnum;
+
+       str = udev_device_get_sysattr_value(usb_dev, "busnum");
+       if (str == NULL)
+               return NULL;
+       busnum = strtol(str, NULL, 0);
 
+       str = udev_device_get_sysattr_value(usb_dev, "devnum");
+       if (str == NULL)
+               return NULL;
+       devnum = strtol(str, NULL, 0);
+
+       usb_init();
        usb_find_busses();
        usb_find_devices();
 
-       for (bus = usb_get_busses(); bus; bus = bus->next)
+       for (bus = usb_get_busses(); bus; bus = bus->next) {
+               struct usb_device *dev;
+
+               if (strtol(bus->dirname, NULL, 10) != busnum)
+                       continue;
+
                for (dev = bus->devices; dev; dev = dev->next) {
-                       if (dev->descriptor.idVendor == devinfo->vendor &&
-                           dev->descriptor.idProduct == devinfo->product) {
-                               devinfo->dev=dev;
-                               return 1;
-                       }
+                       if (dev->devnum == devnum)
+                               return dev;
                }
-       return 0;
+       }
+
+       return NULL;
 }
 
-static int find_resuscitated_device(struct device_info* devinfo, uint8_t bInterfaceProtocol)
+static struct usb_dev_handle *find_device(struct udev_device *udev_dev, const char *sibling_intf)
 {
-       int i,j,k,l;
-       struct usb_device *dev, *child;
-       struct usb_config_descriptor config;
-       struct usb_interface interface;
-       struct usb_interface_descriptor altsetting;
-
-       /* Using the base device, attempt to find the child with the
-        * matching bInterfaceProtocol */
-       dev = devinfo->dev;
-       for (i = 0; i < dev->num_children; i++) {
-               child = dev->children[i];
-               for (j = 0; j < child->descriptor.bNumConfigurations; j++) {
-                       config = child->config[j];
-                       for (k = 0; k < config.bNumInterfaces; k++) {
-                               interface = config.interface[k];
-                               for (l = 0; l < interface.num_altsetting; l++) {
-                                       altsetting = interface.altsetting[l];
-                                       if (altsetting.bInterfaceProtocol == bInterfaceProtocol) {
-                                               devinfo->dev = child;
-                                               return 1;
-                                       }
-                               }
-                       }
-               }
+       struct udev *udev = udev_device_get_udev(udev_dev);
+       struct usb_device *dev;
+       struct udev_device *udev_parent;
+       char str[UTIL_NAME_SIZE];
+       struct udev_enumerate *enumerate;
+       struct udev_list_entry *entry;
+       struct usb_dev_handle *handle = NULL;
+
+       if (sibling_intf == NULL) {
+               dev = usb_device_open_from_udev(udev_dev);
+               if (dev == NULL)
+                       return NULL;
+               return usb_open(dev);
        }
-       return 0;
+
+       /* find matching sibling of the current usb_device, they share the same hub */
+       udev_parent = udev_device_get_parent_with_subsystem_devtype(udev_dev, "usb", "usb_device");
+       if (udev_parent == NULL)
+               return NULL;
+
+       enumerate = udev_enumerate_new(udev);
+       if (enumerate == NULL)
+               return NULL;
+
+       udev_enumerate_add_match_subsystem(enumerate, "usb");
+
+       /* match all childs of the parent */
+       util_strscpyl(str, sizeof(str), udev_device_get_sysname(udev_parent), "*", NULL);
+       udev_enumerate_add_match_sysname(enumerate, str);
+
+       /* match the specified interface */
+       util_strscpy(str, sizeof(str), sibling_intf);
+       str[2] = '\0';
+       str[5] = '\0';
+       str[8] = '\0';
+       if (strcmp(str, "-1") != 0)
+               udev_enumerate_add_match_sysattr(enumerate, "bInterfaceClass", str);
+       if (strcmp(&str[3], "-1") != 0)
+               udev_enumerate_add_match_sysattr(enumerate, "bInterfaceSubClass", &str[3]);
+       if (strcmp(&str[6], "-1") != 0)
+               udev_enumerate_add_match_sysattr(enumerate, "bInterfaceProtocol", &str[6]);
+
+       udev_enumerate_scan_devices(enumerate);
+       udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(enumerate)) {
+               struct udev_device *udev_device;
+
+               udev_device = udev_device_new_from_syspath(udev, udev_list_entry_get_name(entry));
+               if (udev_device != NULL) {
+                       /* get the usb_device of the usb_interface we matched */
+                       udev_parent = udev_device_get_parent_with_subsystem_devtype(udev_device, "usb", "usb_device");
+                       if (udev_parent == NULL)
+                               continue;
+                       /* only look at the first matching device */
+                       dev = usb_device_open_from_udev(udev_parent);
+                       if (dev != NULL)
+                               handle = usb_open(dev);
+                       udev_device_unref(udev_device);
+                       break;
+               }
+}
+       udev_enumerate_unref(enumerate);
+       return handle;
 }
 
-static void usage(char* error)
+static void usage(const char *error)
 {
        if (error)
                fprintf(stderr,"\n%s\n", error);
        else
                printf("hid2hci - Bluetooth HID to HCI mode switching utility\n\n");
 
-       printf("Usage:\n"
-               "\thid2hci [options]\n"
-               "\n");
-
-       printf("Options:\n"
-               "\t-h, --help           Display help\n"
-               "\t-q, --quiet          Don't display any messages\n"
-               "\t-r, --mode=          Mode to switch to [hid, hci]\n"
-               "\t-v, --vendor=        Vendor ID to act upon\n"
-               "\t-p, --product=       Product ID to act upon\n"
-               "\t-m, --method=        Method to use to switch [csr, logitech, dell]\n"
-               "\t-s, --resuscitate=   Find the child device with this bInterfaceProtocol to run on \n"
-               "\n");
-       if (error)
-               exit(1);
+       printf("Usage: hid2hci [options]\n"
+               "  --mode=               mode to switch to [hid|hci] (default hci)\n"
+               "  --devpath=            sys device path\n"
+               "  --method=             method to use to switch [csr|logitech-hid|dell]\n"
+               "  --find-sibling-intf=  find the sibling device with 00:00:00 (class:subclass:prot)\n"
+               "  --help\n\n");
 }
 
 int main(int argc, char *argv[])
 {
        static const struct option options[] = {
                { "help", no_argument, NULL, 'h' },
-               { "quiet", no_argument, NULL, 'q' },
-               { "mode", required_argument, NULL, 'r' },
-               { "vendor", required_argument, NULL, 'v' },
-               { "product", required_argument, NULL, 'p' },
-               { "method", required_argument, NULL, 'm' },
-               { "resuscitate", required_argument, NULL, 's' },
+               { "mode", required_argument, NULL, 'm' },
+               { "devpath", required_argument, NULL, 'p' },
+               { "method", required_argument, NULL, 'M' },
+               { "find-sibling-intf", required_argument, NULL, 'I' },
                { }
        };
-       struct device_info dev = { NULL, HCI, 0, 0 };
-       int opt, quiet = 0;
-       int (*method)(struct device_info *dev) = NULL;
-       uint8_t resuscitate = 0;
-
-       while ((opt = getopt_long(argc, argv, "+s:r:v:p:m:qh", options, NULL)) != -1) {
-               switch (opt) {
-               case 'r':
-                       if (optarg && !strcmp(optarg, "hid"))
-                               dev.mode = HID;
-                       else if (optarg && !strcmp(optarg, "hci"))
-                               dev.mode = HCI;
-                       else
-                               usage("ERROR: Undefined radio mode\n");
+       enum method {
+               METHOD_UNDEF,
+               METHOD_CSR,
+               METHOD_LOGITECH_HID,
+               METHOD_DELL,
+       } method = METHOD_UNDEF;
+       struct udev *udev;
+       struct udev_device *udev_dev = NULL;
+       char syspath[UTIL_PATH_SIZE];
+       int (*usb_switch)(struct usb_dev_handle *dev, enum mode mode) = NULL;
+       enum mode mode = HCI;
+       const char *devpath = NULL;
+       const char *sibling_intf = NULL;
+       int err = -1;
+       int rc = 1;
+
+       for (;;) {
+               int option;
+
+               option = getopt_long(argc, argv, "m:p:M:I:qh", options, NULL);
+               if (option == -1)
                        break;
-               case 'v':
-                       sscanf(optarg, "%4hx", &dev.vendor);
+
+               switch (option) {
+               case 'm':
+                       if (!strcmp(optarg, "hid")) {
+                               mode = HID;
+                       } else if (!strcmp(optarg, "hci")) {
+                               mode = HCI;
+                       } else {
+                               usage("error: undefined radio mode\n");
+                               exit(1);
+                       }
                        break;
                case 'p':
-                       sscanf(optarg, "%4hx", &dev.product);
-                       break;
-               case 'm':
-                       if (optarg && !strcmp(optarg, "csr"))
-                               method = switch_csr;
-                       else if (optarg && !strcmp(optarg, "logitech"))
-                               method = switch_logitech;
-                       else if (optarg && !strcmp(optarg, "dell"))
-                               method = switch_dell;
-                       else
-                               usage("ERROR: Undefined switching method\n");
+                       devpath = optarg;
                        break;
-               case 'q':
-                       quiet = 1;
+               case 'M':
+                       if (!strcmp(optarg, "csr")) {
+                               method = METHOD_CSR;
+                               usb_switch = usb_switch_csr;
+                       } else if (!strcmp(optarg, "logitech-hid")) {
+                               method = METHOD_LOGITECH_HID;
+                       } else if (!strcmp(optarg, "dell")) {
+                               method = METHOD_DELL;
+                               usb_switch = usb_switch_dell;
+                       } else {
+                               usage("error: undefined switching method\n");
+                               exit(1);
+                       }
                        break;
-               case 's':
-                       sscanf(optarg, "%2hx", (short unsigned int*) &resuscitate);
+               case 'I':
+                       sibling_intf = optarg;
                        break;
                case 'h':
                        usage(NULL);
@@ -332,38 +343,69 @@ int main(int argc, char *argv[])
                }
        }
 
-       if (!quiet && (!dev.vendor || !dev.product || !method))
-               usage("ERROR: Vendor ID, Product ID, and Switching Method must all be defined.\n");
-
-       argc -= optind;
-       argv += optind;
-       optind = 0;
-
-       usb_init();
-
-       if (!find_device(&dev)) {
-               if (!quiet)
-                       fprintf(stderr, "Device %04x:%04x not found on USB bus.\n",
-                               dev.vendor, dev.product);
+       if (!devpath || method == METHOD_UNDEF) {
+               usage("error: --devpath= and --method= must be defined\n");
                exit(1);
        }
 
-       if (resuscitate && !find_resuscitated_device(&dev, resuscitate)) {
-               if (!quiet)
-                       fprintf(stderr, "Device %04x:%04x was unable to resucitate any child devices.\n",
-                               dev.vendor,dev.product);
-               exit(1);
+       udev = udev_new();
+       if (udev == NULL)
+               goto exit;
+
+       util_strscpyl(syspath, sizeof(syspath), udev_get_sys_path(udev), devpath, NULL);
+       udev_dev = udev_device_new_from_syspath(udev, syspath);
+       if (udev_dev == NULL) {
+               fprintf(stderr, "error: could not find '%s'\n", devpath);
+               goto exit;
        }
 
-       if (!quiet)
-               printf("Attempting to switch device %04x:%04x to %s mode ",
-                       dev.vendor, dev.product, dev.mode ? "HID" : "HCI");
-       fflush(stdout);
+       switch (method) {
+       case METHOD_CSR:
+       case METHOD_DELL: {
+               struct udev_device *dev;
+               struct usb_dev_handle *handle;
+               const char *type;
+
+               /* get the parent usb_device if needed */
+               dev = udev_dev;
+               type = udev_device_get_devtype(dev);
+               if (type == NULL || strcmp(type, "usb_device") != 0) {
+                       dev = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device");
+                       if (dev == NULL) {
+                               fprintf(stderr, "error: could not find usb_device for '%s'\n", devpath);
+                               goto exit;
+                       }
+               }
 
-       if (method(&dev) < 0 && !quiet)
-               printf("failed (%s)\n", strerror(errno));
-       else if (!quiet)
-               printf("was successful\n");
+               handle = find_device(dev, sibling_intf);
+               if (handle == NULL) {
+                       fprintf(stderr, "error: unable to handle '%s' (intf=%s)\n",
+                               udev_device_get_syspath(dev), sibling_intf);
+                       goto exit;
+               }
+               err = usb_switch(handle, mode);
+               break;
+       }
+       case METHOD_LOGITECH_HID: {
+               const char *device;
+
+               device = udev_device_get_devnode(udev_dev);
+               if (device == NULL) {
+                       fprintf(stderr, "error: could not find hiddev device node\n");
+                       goto exit;
+               }
+               err = hid_switch_logitech(device);
+               break;
+       }
+       default:
+               break;
+       }
 
-       return errno;
+       if (err < 0)
+               fprintf(stderr, "error: switching device '%s' (intf=%s) failed.\n",
+                       udev_device_get_syspath(udev_dev), sibling_intf);
+exit:
+       udev_device_unref(udev_dev);
+       udev_unref(udev);
+       return rc;
 }