blob: e121cfb1746a4c7c4bf6d4e501d119fbee4bc60e [file] [log] [blame]
/*
* Copyright (C) 2011 matt mooney <mfm@muteddisk.com>
* 2005-2007 Takahiro Hirofuchi
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include <libudev.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include "usbip_common.h"
#include "utils.h"
#include "usbip.h"
#include "sysfs_utils.h"
enum unbind_status {
UNBIND_ST_OK,
UNBIND_ST_USBIP_HOST,
UNBIND_ST_FAILED
};
static const char usbip_bind_usage_string[] =
"usbip bind <args>\n"
" -b, --busid=<busid> Bind " USBIP_HOST_DRV_NAME ".ko to device "
"on <busid>\n";
void usbip_bind_usage(void)
{
printf("usage: %s", usbip_bind_usage_string);
}
/* call at unbound state */
static int bind_usbip(char *busid)
{
char attr_name[] = "bind";
char bind_attr_path[SYSFS_PATH_MAX];
int rc = -1;
snprintf(bind_attr_path, sizeof(bind_attr_path), "%s/%s/%s/%s/%s/%s",
SYSFS_MNT_PATH, SYSFS_BUS_NAME, SYSFS_BUS_TYPE,
SYSFS_DRIVERS_NAME, USBIP_HOST_DRV_NAME, attr_name);
rc = write_sysfs_attribute(bind_attr_path, busid, strlen(busid));
if (rc < 0) {
err("error binding device %s to driver: %s", busid,
strerror(errno));
return -1;
}
return 0;
}
/* buggy driver may cause dead lock */
static int unbind_other(char *busid)
{
enum unbind_status status = UNBIND_ST_OK;
char attr_name[] = "unbind";
char unbind_attr_path[SYSFS_PATH_MAX];
int rc = -1;
struct udev *udev;
struct udev_device *dev;
const char *driver;
const char *bDevClass;
/* Create libudev context. */
udev = udev_new();
/* Get the device. */
dev = udev_device_new_from_subsystem_sysname(udev, "usb", busid);
if (!dev) {
dbg("unable to find device with bus ID %s", busid);
goto err_close_busid_dev;
}
/* Check what kind of device it is. */
bDevClass = udev_device_get_sysattr_value(dev, "bDeviceClass");
if (!bDevClass) {
dbg("unable to get bDevClass device attribute");
goto err_close_busid_dev;
}
if (!strncmp(bDevClass, "09", strlen(bDevClass))) {
dbg("skip unbinding of hub");
goto err_close_busid_dev;
}
/* Get the device driver. */
driver = udev_device_get_driver(dev);
if (!driver) {
/* No driver bound to this device. */
goto out;
}
if (!strncmp(USBIP_HOST_DRV_NAME, driver,
strlen(USBIP_HOST_DRV_NAME))) {
/* Already bound to usbip-host. */
status = UNBIND_ST_USBIP_HOST;
goto out;
}
/* Unbind device from driver. */
snprintf(unbind_attr_path, sizeof(unbind_attr_path), "%s/%s/%s/%s/%s/%s",
SYSFS_MNT_PATH, SYSFS_BUS_NAME, SYSFS_BUS_TYPE,
SYSFS_DRIVERS_NAME, driver, attr_name);
rc = write_sysfs_attribute(unbind_attr_path, busid, strlen(busid));
if (rc < 0) {
err("error unbinding device %s from driver", busid);
goto err_close_busid_dev;
}
goto out;
err_close_busid_dev:
status = UNBIND_ST_FAILED;
out:
udev_device_unref(dev);
udev_unref(udev);
return status;
}
static int bind_device(char *busid)
{
int rc;
struct udev *udev;
struct udev_device *dev;
const char *devpath;
/* Check whether the device with this bus ID exists. */
udev = udev_new();
dev = udev_device_new_from_subsystem_sysname(udev, "usb", busid);
if (!dev) {
err("device with the specified bus ID does not exist");
return -1;
}
devpath = udev_device_get_devpath(dev);
udev_unref(udev);
/* If the device is already attached to vhci_hcd - bail out */
if (strstr(devpath, USBIP_VHCI_DRV_NAME)) {
err("bind loop detected: device: %s is attached to %s\n",
devpath, USBIP_VHCI_DRV_NAME);
return -1;
}
rc = unbind_other(busid);
if (rc == UNBIND_ST_FAILED) {
err("could not unbind driver from device on busid %s", busid);
return -1;
} else if (rc == UNBIND_ST_USBIP_HOST) {
err("device on busid %s is already bound to %s", busid,
USBIP_HOST_DRV_NAME);
return -1;
}
rc = modify_match_busid(busid, 1);
if (rc < 0) {
err("unable to bind device on %s", busid);
return -1;
}
rc = bind_usbip(busid);
if (rc < 0) {
err("could not bind device to %s", USBIP_HOST_DRV_NAME);
modify_match_busid(busid, 0);
return -1;
}
info("bind device on busid %s: complete", busid);
return 0;
}
int usbip_bind(int argc, char *argv[])
{
static const struct option opts[] = {
{ "busid", required_argument, NULL, 'b' },
{ NULL, 0, NULL, 0 }
};
int opt;
int ret = -1;
for (;;) {
opt = getopt_long(argc, argv, "b:", opts, NULL);
if (opt == -1)
break;
switch (opt) {
case 'b':
ret = bind_device(optarg);
goto out;
default:
goto err_out;
}
}
err_out:
usbip_bind_usage();
out:
return ret;
}