diff options
Diffstat (limited to 'drivers/staging/usbip')
-rw-r--r-- | drivers/staging/usbip/Kconfig | 36 | ||||
-rw-r--r-- | drivers/staging/usbip/Makefile | 12 | ||||
-rw-r--r-- | drivers/staging/usbip/README | 6 | ||||
-rw-r--r-- | drivers/staging/usbip/stub.h | 95 | ||||
-rw-r--r-- | drivers/staging/usbip/stub_dev.c | 483 | ||||
-rw-r--r-- | drivers/staging/usbip/stub_main.c | 300 | ||||
-rw-r--r-- | drivers/staging/usbip/stub_rx.c | 615 | ||||
-rw-r--r-- | drivers/staging/usbip/stub_tx.c | 371 | ||||
-rw-r--r-- | drivers/staging/usbip/usbip_common.c | 997 | ||||
-rw-r--r-- | drivers/staging/usbip/usbip_common.h | 406 | ||||
-rw-r--r-- | drivers/staging/usbip/usbip_event.c | 141 | ||||
-rw-r--r-- | drivers/staging/usbip/vhci.h | 142 | ||||
-rw-r--r-- | drivers/staging/usbip/vhci_hcd.c | 1275 | ||||
-rw-r--r-- | drivers/staging/usbip/vhci_rx.c | 251 | ||||
-rw-r--r-- | drivers/staging/usbip/vhci_sysfs.c | 250 | ||||
-rw-r--r-- | drivers/staging/usbip/vhci_tx.c | 239 |
16 files changed, 5619 insertions, 0 deletions
diff --git a/drivers/staging/usbip/Kconfig b/drivers/staging/usbip/Kconfig new file mode 100644 index 00000000000..7426235ccc4 --- /dev/null +++ b/drivers/staging/usbip/Kconfig @@ -0,0 +1,36 @@ +config USB_IP_COMMON + tristate "USB IP support (EXPERIMENTAL)" + depends on USB && EXPERIMENTAL + default N + ---help--- + This enables pushing USB packets over IP to allow remote + machines access to USB devices directly. For more details, + and links to the userspace utility programs to let this work + properly, see http://usbip.naist.jp/ + + To compile this driver as a module, choose M here: the + module will be called usbip_common_mod. + + If unsure, say N. + +config USB_IP_VHCI_HCD + tristate "USB IP client driver" + depends on USB_IP_COMMON + default N + ---help--- + This enables the USB IP host controller driver which will + run on the client machine. + + To compile this driver as a module, choose M here: the + module will be called vhci_hcd. + +config USB_IP_HOST + tristate "USB IP host driver" + depends on USB_IP_COMMON + default N + ---help--- + This enables the USB IP device driver which will run on the + host machine. + + To compile this driver as a module, choose M here: the + module will be called usbip. diff --git a/drivers/staging/usbip/Makefile b/drivers/staging/usbip/Makefile new file mode 100644 index 00000000000..179f4211f96 --- /dev/null +++ b/drivers/staging/usbip/Makefile @@ -0,0 +1,12 @@ +obj-$(CONFIG_USB_IP_COMMON) += usbip_common_mod.o +usbip_common_mod-objs := usbip_common.o usbip_event.o + +obj-$(CONFIG_USB_IP_VHCI_HCD) += vhci-hcd.o +vhci-hcd-objs := vhci_sysfs.o vhci_tx.o vhci_rx.o vhci_hcd.o + +obj-$(CONFIG_USB_IP_HOST) += usbip.o +usbip-objs := stub_dev.o stub_main.o stub_rx.o stub_tx.o + +ifeq ($(CONFIG_USB_DEBUG),y) + EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/staging/usbip/README b/drivers/staging/usbip/README new file mode 100644 index 00000000000..c11be573548 --- /dev/null +++ b/drivers/staging/usbip/README @@ -0,0 +1,6 @@ +TODO: + - more discussion about the protocol + - testing + - review of the userspace interface + +Please send patches for this code to Greg Kroah-Hartman <greg@kroah.com> diff --git a/drivers/staging/usbip/stub.h b/drivers/staging/usbip/stub.h new file mode 100644 index 00000000000..f541a3a83bd --- /dev/null +++ b/drivers/staging/usbip/stub.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * + * This 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/module.h> +#include <linux/net.h> + +struct stub_device { + struct usb_interface *interface; + struct list_head list; + + struct usbip_device ud; + __u32 devid; + + /* + * stub_priv preserves private data of each urb. + * It is allocated as stub_priv_cache and assigned to urb->context. + * + * stub_priv is always linked to any one of 3 lists; + * priv_init: linked to this until the comletion of a urb. + * priv_tx : linked to this after the completion of a urb. + * priv_free: linked to this after the sending of the result. + * + * Any of these list operations should be locked by priv_lock. + */ + spinlock_t priv_lock; + struct list_head priv_init; + struct list_head priv_tx; + struct list_head priv_free; + + /* see comments for unlinking in stub_rx.c */ + struct list_head unlink_tx; + struct list_head unlink_free; + + + wait_queue_head_t tx_waitq; +}; + +/* private data into urb->priv */ +struct stub_priv { + unsigned long seqnum; + struct list_head list; + struct stub_device *sdev; + struct urb *urb; + + int unlinking; +}; + +struct stub_unlink { + unsigned long seqnum; + struct list_head list; + __u32 status; +}; + + +extern struct kmem_cache *stub_priv_cache; + + +/*-------------------------------------------------------------------------*/ +/* prototype declarations */ + +/* stub_tx.c */ +void stub_complete(struct urb *); +void stub_tx_loop(struct usbip_task *); + +/* stub_dev.c */ +extern struct usb_driver stub_driver; + +/* stub_rx.c */ +void stub_rx_loop(struct usbip_task *); +void stub_enqueue_ret_unlink(struct stub_device *, __u32, __u32); + +/* stub_main.c */ +int match_busid(char *busid); +void stub_device_cleanup_urbs(struct stub_device *sdev); diff --git a/drivers/staging/usbip/stub_dev.c b/drivers/staging/usbip/stub_dev.c new file mode 100644 index 00000000000..ee455a087ea --- /dev/null +++ b/drivers/staging/usbip/stub_dev.c @@ -0,0 +1,483 @@ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * + * This 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include "usbip_common.h" +#include "stub.h" + + + +static int stub_probe(struct usb_interface *interface, + const struct usb_device_id *id); +static void stub_disconnect(struct usb_interface *interface); + + +/* + * Define device IDs here if you want to explicitly limit exportable devices. + * In the most cases, wild card matching will be ok because driver binding can + * be changed dynamically by a userland program. + */ +static struct usb_device_id stub_table[] = { +#if 0 + /* just an example */ + { USB_DEVICE(0x05ac, 0x0301) }, /* Mac 1 button mouse */ + { USB_DEVICE(0x0430, 0x0009) }, /* Plat Home Keyboard */ + { USB_DEVICE(0x059b, 0x0001) }, /* Iomega USB Zip 100 */ + { USB_DEVICE(0x04b3, 0x4427) }, /* IBM USB CD-ROM */ + { USB_DEVICE(0x05a9, 0xa511) }, /* LifeView USB cam */ + { USB_DEVICE(0x55aa, 0x0201) }, /* Imation card reader */ + { USB_DEVICE(0x046d, 0x0870) }, /* Qcam Express(QV-30) */ + { USB_DEVICE(0x04bb, 0x0101) }, /* IO-DATA HD 120GB */ + { USB_DEVICE(0x04bb, 0x0904) }, /* IO-DATA USB-ET/TX */ + { USB_DEVICE(0x04bb, 0x0201) }, /* IO-DATA USB-ET/TX */ + { USB_DEVICE(0x08bb, 0x2702) }, /* ONKYO USB Speaker */ + { USB_DEVICE(0x046d, 0x08b2) }, /* Logicool Qcam 4000 Pro */ +#endif + /* magic for wild card */ + { .driver_info = 1 }, + { 0, } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, stub_table); + +struct usb_driver stub_driver = { + .name = "usbip", + .probe = stub_probe, + .disconnect = stub_disconnect, + .id_table = stub_table, +}; + + +/*-------------------------------------------------------------------------*/ + +/* Define sysfs entries for a usbip-bound device */ + + +/* + * usbip_status shows status of usbip as long as this driver is bound to the + * target device. + */ +static ssize_t show_status(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct stub_device *sdev = dev_get_drvdata(dev); + int status; + + if (!sdev) { + dev_err(dev, "sdev is null\n"); + return -ENODEV; + } + + spin_lock(&sdev->ud.lock); + status = sdev->ud.status; + spin_unlock(&sdev->ud.lock); + + return snprintf(buf, PAGE_SIZE, "%d\n", status); +} +static DEVICE_ATTR(usbip_status, S_IRUGO, show_status, NULL); + +/* + * usbip_sockfd gets a socket descriptor of an established TCP connection that + * is used to transfer usbip requests by kernel threads. -1 is a magic number + * by which usbip connection is finished. + */ +static ssize_t store_sockfd(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct stub_device *sdev = dev_get_drvdata(dev); + int sockfd = 0; + struct socket *socket; + + if (!sdev) { + dev_err(dev, "sdev is null\n"); + return -ENODEV; + } + + sscanf(buf, "%d", &sockfd); + + if (sockfd != -1) { + dev_info(dev, "stub up\n"); + + spin_lock(&sdev->ud.lock); + + if (sdev->ud.status != SDEV_ST_AVAILABLE) { + dev_err(dev, "not ready\n"); + spin_unlock(&sdev->ud.lock); + return -EINVAL; + } + + socket = sockfd_to_socket(sockfd); + if (!socket) { + spin_unlock(&sdev->ud.lock); + return -EINVAL; + } + +#if 0 + setnodelay(socket); + setkeepalive(socket); + setreuse(socket); +#endif + + sdev->ud.tcp_socket = socket; + + spin_unlock(&sdev->ud.lock); + + usbip_start_threads(&sdev->ud); + + spin_lock(&sdev->ud.lock); + sdev->ud.status = SDEV_ST_USED; + spin_unlock(&sdev->ud.lock); + + } else { + dev_info(dev, "stub down\n"); + + spin_lock(&sdev->ud.lock); + if (sdev->ud.status != SDEV_ST_USED) { + spin_unlock(&sdev->ud.lock); + return -EINVAL; + } + spin_unlock(&sdev->ud.lock); + + usbip_event_add(&sdev->ud, SDEV_EVENT_DOWN); + } + + return count; +} +static DEVICE_ATTR(usbip_sockfd, S_IWUSR, NULL, store_sockfd); + +static int stub_add_files(struct device *dev) +{ + int err = 0; + + err = device_create_file(dev, &dev_attr_usbip_status); + if (err) + goto err_status; + + err = device_create_file(dev, &dev_attr_usbip_sockfd); + if (err) + goto err_sockfd; + + err = device_create_file(dev, &dev_attr_usbip_debug); + if (err) + goto err_debug; + + return 0; + +err_debug: + device_remove_file(dev, &dev_attr_usbip_sockfd); + +err_sockfd: + device_remove_file(dev, &dev_attr_usbip_status); + +err_status: + return err; +} + +static void stub_remove_files(struct device *dev) +{ + device_remove_file(dev, &dev_attr_usbip_status); + device_remove_file(dev, &dev_attr_usbip_sockfd); + device_remove_file(dev, &dev_attr_usbip_debug); +} + + + +/*-------------------------------------------------------------------------*/ + +/* Event handler functions called by an event handler thread */ + +static void stub_shutdown_connection(struct usbip_device *ud) +{ + struct stub_device *sdev = container_of(ud, struct stub_device, ud); + + /* + * When removing an exported device, kernel panic sometimes occurred + * and then EIP was sk_wait_data of stub_rx thread. Is this because + * sk_wait_data returned though stub_rx thread was already finished by + * step 1? + */ + if (ud->tcp_socket) { + udbg("shutdown tcp_socket %p\n", ud->tcp_socket); + kernel_sock_shutdown(ud->tcp_socket, SHUT_RDWR); + } + + /* 1. stop threads */ + usbip_stop_threads(ud); + + /* 2. close the socket */ + /* + * tcp_socket is freed after threads are killed. + * So usbip_xmit do not touch NULL socket. + */ + if (ud->tcp_socket) { + sock_release(ud->tcp_socket); + ud->tcp_socket = NULL; + } + + /* 3. free used data */ + stub_device_cleanup_urbs(sdev); + + /* 4. free stub_unlink */ + { + unsigned long flags; + struct stub_unlink *unlink, *tmp; + + spin_lock_irqsave(&sdev->priv_lock, flags); + + list_for_each_entry_safe(unlink, tmp, &sdev->unlink_tx, list) { + list_del(&unlink->list); + kfree(unlink); + } + + list_for_each_entry_safe(unlink, tmp, + &sdev->unlink_free, list) { + list_del(&unlink->list); + kfree(unlink); + } + + spin_unlock_irqrestore(&sdev->priv_lock, flags); + } +} + +static void stub_device_reset(struct usbip_device *ud) +{ + struct stub_device *sdev = container_of(ud, struct stub_device, ud); + struct usb_device *udev = interface_to_usbdev(sdev->interface); + int ret; + + udbg("device reset"); + ret = usb_lock_device_for_reset(udev, sdev->interface); + if (ret < 0) { + dev_err(&udev->dev, "lock for reset\n"); + + spin_lock(&ud->lock); + ud->status = SDEV_ST_ERROR; + spin_unlock(&ud->lock); + + return; + } + + /* try to reset the device */ + ret = usb_reset_device(udev); + + usb_unlock_device(udev); + + spin_lock(&ud->lock); + if (ret) { + dev_err(&udev->dev, "device reset\n"); + ud->status = SDEV_ST_ERROR; + + } else { + dev_info(&udev->dev, "device reset\n"); + ud->status = SDEV_ST_AVAILABLE; + + } + spin_unlock(&ud->lock); + + return; +} + +static void stub_device_unusable(struct usbip_device *ud) +{ + spin_lock(&ud->lock); + ud->status = SDEV_ST_ERROR; + spin_unlock(&ud->lock); +} + + +/*-------------------------------------------------------------------------*/ + +/** + * stub_device_alloc - allocate a new stub_device struct + * @interface: usb_interface of a new device + * + * Allocates and initializes a new stub_device struct. + */ +static struct stub_device *stub_device_alloc(struct usb_interface *interface) +{ + struct stub_device *sdev; + int busnum = interface_to_busnum(interface); + int devnum = interface_to_devnum(interface); + + dev_dbg(&interface->dev, "allocating stub device"); + + /* yes, it's a new device */ + sdev = kzalloc(sizeof(struct stub_device), GFP_KERNEL); + if (!sdev) { + dev_err(&interface->dev, "no memory for stub_device\n"); + return NULL; + } + + sdev->interface = interface; + + /* + * devid is defined with devnum when this driver is first allocated. + * devnum may change later if a device is reset. However, devid never + * changes during a usbip connection. + */ + sdev->devid = (busnum << 16) | devnum; + + usbip_task_init(&sdev->ud.tcp_rx, "stub_rx", stub_rx_loop); + usbip_task_init(&sdev->ud.tcp_tx, "stub_tx", stub_tx_loop); + + sdev->ud.side = USBIP_STUB; + sdev->ud.status = SDEV_ST_AVAILABLE; + /* sdev->ud.lock = SPIN_LOCK_UNLOCKED; */ + spin_lock_init(&sdev->ud.lock); + sdev->ud.tcp_socket = NULL; + + INIT_LIST_HEAD(&sdev->priv_init); + INIT_LIST_HEAD(&sdev->priv_tx); + INIT_LIST_HEAD(&sdev->priv_free); + INIT_LIST_HEAD(&sdev->unlink_free); + INIT_LIST_HEAD(&sdev->unlink_tx); + /* sdev->priv_lock = SPIN_LOCK_UNLOCKED; */ + spin_lock_init(&sdev->priv_lock); + + init_waitqueue_head(&sdev->tx_waitq); + + sdev->ud.eh_ops.shutdown = stub_shutdown_connection; + sdev->ud.eh_ops.reset = stub_device_reset; + sdev->ud.eh_ops.unusable = stub_device_unusable; + + usbip_start_eh(&sdev->ud); + + udbg("register new interface\n"); + return sdev; +} + +static int stub_device_free(struct stub_device *sdev) +{ + if (!sdev) + return -EINVAL; + + kfree(sdev); + udbg("kfree udev ok\n"); + + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +/* + * If a usb device has multiple active interfaces, this driver is bound to all + * the active interfaces. However, usbip exports *a* usb device (i.e., not *an* + * active interface). Currently, a userland program must ensure that it + * looks at the usbip's sysfs entries of only the first active interface. + * + * TODO: use "struct usb_device_driver" to bind a usb device. + * However, it seems it is not fully supported in mainline kernel yet + * (2.6.19.2). + */ +static int stub_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct stub_device *sdev = NULL; + char *udev_busid = interface->dev.parent->bus_id; + int err = 0; + + dev_dbg(&interface->dev, "Enter\n"); + + /* check we should claim or not by busid_table */ + if (match_busid(udev_busid)) { + dev_info(&interface->dev, + "this device %s is not in match_busid table. skip!\n", + udev_busid); + + /* + * Return value should be ENODEV or ENOXIO to continue trying + * other matched drivers by the driver core. + * See driver_probe_device() in driver/base/dd.c + */ + return -ENODEV; + } + + if (udev->descriptor.bDeviceClass == USB_CLASS_HUB) { + udbg("this device %s is a usb hub device. skip!\n", + udev_busid); + return -ENODEV; + } + + if (!strcmp(udev->bus->bus_name, "vhci_hcd")) { + udbg("this device %s is attached on vhci_hcd. skip!\n", + udev_busid); + return -ENODEV; + } + + /* ok. this is my device. */ + sdev = stub_device_alloc(interface); + if (!sdev) + return -ENOMEM; + + dev_info(&interface->dev, "USB/IP Stub: register a new interface " + "(bus %u dev %u ifn %u)\n", udev->bus->busnum, udev->devnum, + interface->cur_altsetting->desc.bInterfaceNumber); + + /* set private data to usb_interface */ + usb_set_intfdata(interface, sdev); + + err = stub_add_files(&interface->dev); + if (err) { + dev_err(&interface->dev, "create sysfs files for %s\n", + udev_busid); + return err; + } + + return 0; +} + + +/* + * called in usb_disconnect() or usb_deregister() + * but only if actconfig(active configuration) exists + */ +static void stub_disconnect(struct usb_interface *interface) +{ + struct stub_device *sdev = usb_get_intfdata(interface); + + udbg("Enter\n"); + + /* get stub_device */ + if (!sdev) { + err(" could not get device from inteface data"); + /* BUG(); */ + return; + } + + usb_set_intfdata(interface, NULL); + + + /* + * NOTE: + * rx/tx threads are invoked for each usb_device. + */ + stub_remove_files(&interface->dev); + + /* 1. shutdown the current connection */ + usbip_event_add(&sdev->ud, SDEV_EVENT_REMOVED); + + /* 2. wait for the stop of the event handler */ + usbip_stop_eh(&sdev->ud); + + /* 3. free sdev */ + stub_device_free(sdev); + + + udbg("bye\n"); +} diff --git a/drivers/staging/usbip/stub_main.c b/drivers/staging/usbip/stub_main.c new file mode 100644 index 00000000000..c665d7f1ca9 --- /dev/null +++ b/drivers/staging/usbip/stub_main.c @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * + * This 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + + +#include "usbip_common.h" +#include "stub.h" + +/* Version Information */ +#define DRIVER_VERSION "1.0" +#define DRIVER_AUTHOR "Takahiro Hirofuchi" +#define DRIVER_DESC "Stub Driver for USB/IP" + +/* stub_priv is allocated from stub_priv_cache */ +struct kmem_cache *stub_priv_cache; + +/*-------------------------------------------------------------------------*/ + +/* Define sysfs entries for the usbip driver */ + + +/* + * busid_tables defines matching busids that usbip can grab. A user can change + * dynamically what device is locally used and what device is exported to a + * remote host. + */ +#define MAX_BUSID 16 +static char busid_table[MAX_BUSID][BUS_ID_SIZE]; +static spinlock_t busid_table_lock; + + +int match_busid(char *busid) +{ + int i; + + spin_lock(&busid_table_lock); + + for (i = 0; i < MAX_BUSID; i++) + if (busid_table[i][0]) + if (!strncmp(busid_table[i], busid, BUS_ID_SIZE)) { + /* already registerd */ + spin_unlock(&busid_table_lock); + return 0; + } + + spin_unlock(&busid_table_lock); + + return 1; +} + +static ssize_t show_match_busid(struct device_driver *drv, char *buf) +{ + int i; + char *out = buf; + + spin_lock(&busid_table_lock); + + for (i = 0; i < MAX_BUSID; i++) + if (busid_table[i][0]) + out += sprintf(out, "%s ", busid_table[i]); + + spin_unlock(&busid_table_lock); + + out += sprintf(out, "\n"); + + return out - buf; +} + +static int add_match_busid(char *busid) +{ + int i; + + if (!match_busid(busid)) + return 0; + + spin_lock(&busid_table_lock); + + for (i = 0; i < MAX_BUSID; i++) + if (!busid_table[i][0]) { + strncpy(busid_table[i], busid, BUS_ID_SIZE); + spin_unlock(&busid_table_lock); + return 0; + } + + spin_unlock(&busid_table_lock); + + return -1; +} + +static int del_match_busid(char *busid) +{ + int i; + + spin_lock(&busid_table_lock); + + for (i = 0; i < MAX_BUSID; i++) + if (!strncmp(busid_table[i], busid, BUS_ID_SIZE)) { + /* found */ + memset(busid_table[i], 0, BUS_ID_SIZE); + spin_unlock(&busid_table_lock); + return 0; + } + + spin_unlock(&busid_table_lock); + + return -1; +} + +static ssize_t store_match_busid(struct device_driver *dev, const char *buf, + size_t count) +{ + int len; + char busid[BUS_ID_SIZE]; + + if (count < 5) + return -EINVAL; + + /* strnlen() does not include \0 */ + len = strnlen(buf + 4, BUS_ID_SIZE); + + /* busid needs to include \0 termination */ + if (!(len < BUS_ID_SIZE)) + return -EINVAL; + + strncpy(busid, buf + 4, BUS_ID_SIZE); + + + if (!strncmp(buf, "add ", 4)) { + if (add_match_busid(busid) < 0) + return -ENOMEM; + else { + udbg("add busid %s\n", busid); + return count; + } + } else if (!strncmp(buf, "del ", 4)) { + if (del_match_busid(busid) < 0) + return -ENODEV; + else { + udbg("del busid %s\n", busid); + return count; + } + } else + return -EINVAL; +} + +static DRIVER_ATTR(match_busid, S_IRUSR|S_IWUSR, show_match_busid, + store_match_busid); + + + +/*-------------------------------------------------------------------------*/ + +/* Cleanup functions used to free private data */ + +static struct stub_priv *stub_priv_pop_from_listhead(struct list_head *listhead) +{ + struct stub_priv *priv, *tmp; + + list_for_each_entry_safe(priv, tmp, listhead, list) { + list_del(&priv->list); + return priv; + } + + return NULL; +} + +static struct stub_priv *stub_priv_pop(struct stub_device *sdev) +{ + unsigned long flags; + struct stub_priv *priv; + + spin_lock_irqsave(&sdev->priv_lock, flags); + + priv = stub_priv_pop_from_listhead(&sdev->priv_init); + if (priv) { + spin_unlock_irqrestore(&sdev->priv_lock, flags); + return priv; + } + + priv = stub_priv_pop_from_listhead(&sdev->priv_tx); + if (priv) { + spin_unlock_irqrestore(&sdev->priv_lock, flags); + return priv; + } + + priv = stub_priv_pop_from_listhead(&sdev->priv_free); + if (priv) { + spin_unlock_irqrestore(&sdev->priv_lock, flags); + return priv; + } + + spin_unlock_irqrestore(&sdev->priv_lock, flags); + return NULL; +} + +void stub_device_cleanup_urbs(struct stub_device *sdev) +{ + struct stub_priv *priv; + + udbg("free sdev %p\n", sdev); + + while ((priv = stub_priv_pop(sdev))) { + struct urb *urb = priv->urb; + + udbg(" free urb %p\n", urb); + usb_kill_urb(urb); + + kmem_cache_free(stub_priv_cache, priv); + + if (urb->transfer_buffer != NULL) + kfree(urb->transfer_buffer); + + if (urb->setup_packet != NULL) + kfree(urb->setup_packet); + + usb_free_urb(urb); + } +} + + +/*-------------------------------------------------------------------------*/ + +static int __init usb_stub_init(void) +{ + int ret; + + stub_priv_cache = kmem_cache_create("stub_priv", + sizeof(struct stub_priv), 0, + SLAB_HWCACHE_ALIGN, NULL); + + if (!stub_priv_cache) { + printk(KERN_ERR KBUILD_MODNAME + ": create stub_priv_cache error\n"); + return -ENOMEM; + } + + ret = usb_register(&stub_driver); + if (ret) { + printk(KERN_ERR KBUILD_MODNAME ": usb_register failed %d\n", + ret); + goto error_usb_register; + } + + printk(KERN_INFO KBUILD_MODNAME ":" + DRIVER_DESC ":" DRIVER_VERSION "\n"); + + memset(busid_table, 0, sizeof(busid_table)); + spin_lock_init(&busid_table_lock); + + ret = driver_create_file(&stub_driver.drvwrap.driver, + &driver_attr_match_busid); + + if (ret) { + printk(KERN_ERR KBUILD_MODNAME ": create driver sysfs\n"); + goto error_create_file; + } + + return ret; +error_create_file: + usb_deregister(&stub_driver); +error_usb_register: + kmem_cache_destroy(stub_priv_cache); + return ret; +} + +static void __exit usb_stub_exit(void) +{ + driver_remove_file(&stub_driver.drvwrap.driver, + &driver_attr_match_busid); + + /* + * deregister() calls stub_disconnect() for all devices. Device + * specific data is cleared in stub_disconnect(). + */ + usb_deregister(&stub_driver); + + kmem_cache_destroy(stub_priv_cache); +} + +module_init(usb_stub_init); +module_exit(usb_stub_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/usbip/stub_rx.c b/drivers/staging/usbip/stub_rx.c new file mode 100644 index 00000000000..36ce898fced --- /dev/null +++ b/drivers/staging/usbip/stub_rx.c @@ -0,0 +1,615 @@ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * + * This 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include "usbip_common.h" +#include "stub.h" +#include "../../usb/core/hcd.h" + + +static int is_clear_halt_cmd(struct urb *urb) +{ + struct usb_ctrlrequest *req; + + req = (struct usb_ctrlrequest *) urb->setup_packet; + + return (req->bRequest == USB_REQ_CLEAR_FEATURE) && + (req->bRequestType == USB_RECIP_ENDPOINT) && + (req->wValue == USB_ENDPOINT_HALT); +} + +static int is_set_interface_cmd(struct urb *urb) +{ + struct usb_ctrlrequest *req; + + req = (struct usb_ctrlrequest *) urb->setup_packet; + + return (req->bRequest == USB_REQ_SET_INTERFACE) && + (req->bRequestType == USB_RECIP_INTERFACE); +} + +static int is_set_configuration_cmd(struct urb *urb) +{ + struct usb_ctrlrequest *req; + + req = (struct usb_ctrlrequest *) urb->setup_packet; + + return (req->bRequest == USB_REQ_SET_CONFIGURATION) && + (req->bRequestType == USB_RECIP_DEVICE); +} + +static int is_reset_device_cmd(struct urb *urb) +{ + struct usb_ctrlrequest *req; + __u16 value; + __u16 index; + + req = (struct usb_ctrlrequest *) urb->setup_packet; + value = le16_to_cpu(req->wValue); + index = le16_to_cpu(req->wIndex); + + if ((req->bRequest == USB_REQ_SET_FEATURE) && + (req->bRequestType == USB_RT_PORT) && + (value = USB_PORT_FEAT_RESET)) { + dbg_stub_rx("reset_device_cmd, port %u\n", index); + return 1; + } else + return 0; +} + +static int tweak_clear_halt_cmd(struct urb *urb) +{ + struct usb_ctrlrequest *req; + int target_endp; + int target_dir; + int target_pipe; + int ret; + + req = (struct usb_ctrlrequest *) urb->setup_packet; + + /* + * The stalled endpoint is specified in the wIndex value. The endpoint + * of the urb is the target of this clear_halt request (i.e., control + * endpoint). + */ + target_endp = le16_to_cpu(req->wIndex) & 0x000f; + + /* the stalled endpoint direction is IN or OUT?. USB_DIR_IN is 0x80. */ + target_dir = le16_to_cpu(req->wIndex) & 0x0080; + + if (target_dir) + target_pipe = usb_rcvctrlpipe(urb->dev, target_endp); + else + target_pipe = usb_sndctrlpipe(urb->dev, target_endp); + + ret = usb_clear_halt(urb->dev, target_pipe); + if (ret < 0) + uinfo("clear_halt error: devnum %d endp %d, %d\n", + urb->dev->devnum, target_endp, ret); + else + uinfo("clear_halt done: devnum %d endp %d\n", + urb->dev->devnum, target_endp); + + return ret; +} + +static int tweak_set_interface_cmd(struct urb *urb) +{ + struct usb_ctrlrequest *req; + __u16 alternate; + __u16 interface; + int ret; + + req = (struct usb_ctrlrequest *) urb->setup_packet; + alternate = le16_to_cpu(req->wValue); + interface = le16_to_cpu(req->wIndex); + + dbg_stub_rx("set_interface: inf %u alt %u\n", interface, alternate); + + ret = usb_set_interface(urb->dev, interface, alternate); + if (ret < 0) + uinfo("set_interface error: inf %u alt %u, %d\n", + interface, alternate, ret); + else + uinfo("set_interface done: inf %u alt %u\n", + interface, + alternate); + + return ret; +} + +static int tweak_set_configuration_cmd(struct urb *urb) +{ + struct usb_ctrlrequest *req; + __u16 config; + + req = (struct usb_ctrlrequest *) urb->setup_packet; + config = le16_to_cpu(req->wValue); + + /* + * I have never seen a multi-config device. Very rare. + * For most devices, this will be called to choose a default + * configuration only once in an initialization phase. + * + * set_configuration may change a device configuration and its device + * drivers will be unbound and assigned for a new device configuration. + * This means this usbip driver will be also unbound when called, then + * eventually reassigned to the device as far as driver matching + * condition is kept. + * + * Unfortunatelly, an existing usbip connection will be dropped + * due to this driver unbinding. So, skip here. + * A user may need to set a special configuration value before + * exporting the device. + */ + uinfo("set_configuration (%d) to %s\n", config, urb->dev->dev.bus_id); + uinfo("but, skip!\n"); + + return 0; + /* return usb_driver_set_configuration(urb->dev, config); */ +} + +static int tweak_reset_device_cmd(struct urb *urb) +{ + struct usb_ctrlrequest *req; + __u16 value; + __u16 index; + int ret; + + req = (struct usb_ctrlrequest *) urb->setup_packet; + value = le16_to_cpu(req->wValue); + index = le16_to_cpu(req->wIndex); + + uinfo("reset_device (port %d) to %s\n", index, urb->dev->dev.bus_id); + + /* all interfaces should be owned by usbip driver, so just reset it. */ + ret = usb_lock_device_for_reset(urb->dev, NULL); + if (ret < 0) { + dev_err(&urb->dev->dev, "lock for reset\n"); + return ret; + } + + /* try to reset the device */ + ret = usb_reset_device(urb->dev); + if (ret < 0) + dev_err(&urb->dev->dev, "device reset\n"); + + usb_unlock_device(urb->dev); + + return ret; +} + +/* + * clear_halt, set_interface, and set_configuration require special tricks. + */ +static void tweak_special_requests(struct urb *urb) +{ + if (!urb || !urb->setup_packet) + return; + + if (usb_pipetype(urb->pipe) != PIPE_CONTROL) + return; + + if (is_clear_halt_cmd(urb)) + /* tweak clear_halt */ + tweak_clear_halt_cmd(urb); + + else if (is_set_interface_cmd(urb)) + /* tweak set_interface */ + tweak_set_interface_cmd(urb); + + else if (is_set_configuration_cmd(urb)) + /* tweak set_configuration */ + tweak_set_configuration_cmd(urb); + + else if (is_reset_device_cmd(urb)) + tweak_reset_device_cmd(urb); + else + dbg_stub_rx("no need to tweak\n"); +} + +/* + * stub_recv_unlink() unlinks the URB by a call to usb_unlink_urb(). + * By unlinking the urb asynchronously, stub_rx can continuously + * process coming urbs. Even if the urb is unlinked, its completion + * handler will be called and stub_tx will send a return pdu. + * + * See also comments about unlinking strategy in vhci_hcd.c. + */ +static int stub_recv_cmd_unlink(struct stub_device *sdev, + struct usbip_header *pdu) +{ + struct list_head *listhead = &sdev->priv_init; + struct list_head *ptr; + unsigned long flags; + + struct stub_priv *priv; + + + spin_lock_irqsave(&sdev->priv_lock, flags); + + for (ptr = listhead->next; ptr != listhead; ptr = ptr->next) { + priv = list_entry(ptr, struct stub_priv, list); + if (priv->seqnum == pdu->u.cmd_unlink.seqnum) { + int ret; + + dev_info(&priv->urb->dev->dev, "unlink urb %p\n", + priv->urb); + + /* + * This matched urb is not completed yet (i.e., be in + * flight in usb hcd hardware/driver). Now we are + * cancelling it. The unlinking flag means that we are + * now not going to return the normal result pdu of a + * submission request, but going to return a result pdu + * of the unlink request. + */ + priv->unlinking = 1; + + /* + * In the case that unlinking flag is on, prev->seqnum + * is changed from the seqnum of the cancelling urb to + * the seqnum of the unlink request. This will be used + * to make the result pdu of the unlink request. + */ + priv->seqnum = pdu->base.seqnum; + + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + /* + * usb_unlink_urb() is now out of spinlocking to avoid + * spinlock recursion since stub_complete() is + * sometimes called in this context but not in the + * interrupt context. If stub_complete() is executed + * before we call usb_unlink_urb(), usb_unlink_urb() + * will return an error value. In this case, stub_tx + * will return the result pdu of this unlink request + * though submission is completed and actual unlinking + * is not executed. OK? + */ + /* In the above case, urb->status is not -ECONNRESET, + * so a driver in a client host will know the failure + * of the unlink request ? + */ + ret = usb_unlink_urb(priv->urb); + if (ret != -EINPROGRESS) + dev_err(&priv->urb->dev->dev, + "failed to unlink a urb %p, ret %d\n", + priv->urb, ret); + return 0; + } + } + + dbg_stub_rx("seqnum %d is not pending\n", pdu->u.cmd_unlink.seqnum); + + /* + * The urb of the unlink target is not found in priv_init queue. It was + * already completed and its results is/was going to be sent by a + * CMD_RET pdu. In this case, usb_unlink_urb() is not needed. We only + * return the completeness of this unlink request to vhci_hcd. + */ + stub_enqueue_ret_unlink(sdev, pdu->base.seqnum, 0); + + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + + return 0; +} + +static int valid_request(struct stub_device *sdev, struct usbip_header *pdu) +{ + struct usbip_device *ud = &sdev->ud; + + if (pdu->base.devid == sdev->devid) { + spin_lock(&ud->lock); + if (ud->status == SDEV_ST_USED) { + /* A request is valid. */ + spin_unlock(&ud->lock); + return 1; + } + spin_unlock(&ud->lock); + } + + return 0; +} + +static struct stub_priv *stub_priv_alloc(struct stub_device *sdev, + struct usbip_header *pdu) +{ + struct stub_priv *priv; + struct usbip_device *ud = &sdev->ud; + unsigned long flags; + + spin_lock_irqsave(&sdev->priv_lock, flags); + + priv = kmem_cache_alloc(stub_priv_cache, GFP_ATOMIC); + if (!priv) { + dev_err(&sdev->interface->dev, "alloc stub_priv\n"); + spin_unlock_irqrestore(&sdev->priv_lock, flags); + usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC); + return NULL; + } + + memset(priv, 0, sizeof(struct stub_priv)); + + priv->seqnum = pdu->base.seqnum; + priv->sdev = sdev; + + /* + * After a stub_priv is linked to a list_head, + * our error handler can free allocated data. + */ + list_add_tail(&priv->list, &sdev->priv_init); + + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + return priv; +} + + +static struct usb_host_endpoint *get_ep_from_epnum(struct usb_device *udev, + int epnum0) +{ + struct usb_host_config *config; + int i = 0, j = 0; + struct usb_host_endpoint *ep = NULL; + int epnum; + int found = 0; + + if (epnum0 == 0) + return &udev->ep0; + + config = udev->actconfig; + if (!config) + return NULL; + + for (i = 0; i < config->desc.bNumInterfaces; i++) { + struct usb_host_interface *setting; + + setting = config->interface[i]->cur_altsetting; + + for (j = 0; j < setting->desc.bNumEndpoints; j++) { + ep = &setting->endpoint[j]; + epnum = (ep->desc.bEndpointAddress & 0x7f); + + if (epnum == epnum0) { + /* uinfo("found epnum %d\n", epnum0); */ + found = 1; + break; + } + } + } + + if (found) + return ep; + else + return NULL; +} + + +static int get_pipe(struct stub_device *sdev, int epnum, int dir) +{ + struct usb_device *udev = interface_to_usbdev(sdev->interface); + struct usb_host_endpoint *ep; + struct usb_endpoint_descriptor *epd = NULL; + + ep = get_ep_from_epnum(udev, epnum); + if (!ep) { + dev_err(&sdev->interface->dev, "no such endpoint?, %d\n", + epnum); + BUG(); + } + + epd = &ep->desc; + + +#if 0 + /* epnum 0 is always control */ + if (epnum == 0) { + if (dir == USBIP_DIR_OUT) + return usb_sndctrlpipe(udev, 0); + else + return usb_rcvctrlpipe(udev, 0); + } +#endif + + if (usb_endpoint_xfer_control(epd)) { + if (dir == USBIP_DIR_OUT) + return usb_sndctrlpipe(udev, epnum); + else + return usb_rcvctrlpipe(udev, epnum); + } + + if (usb_endpoint_xfer_bulk(epd)) { + if (dir == USBIP_DIR_OUT) + return usb_sndbulkpipe(udev, epnum); + else + return usb_rcvbulkpipe(udev, epnum); + } + + if (usb_endpoint_xfer_int(epd)) { + if (dir == USBIP_DIR_OUT) + return usb_sndintpipe(udev, epnum); + else + return usb_rcvintpipe(udev, epnum); + } + + if (usb_endpoint_xfer_isoc(epd)) { + if (dir == USBIP_DIR_OUT) + return usb_sndisocpipe(udev, epnum); + else + return usb_rcvisocpipe(udev, epnum); + } + + /* NOT REACHED */ + dev_err(&sdev->interface->dev, "get pipe, epnum %d\n", epnum); + return 0; +} + +static void stub_recv_cmd_submit(struct stub_device *sdev, + struct usbip_header *pdu) +{ + int ret; + struct stub_priv *priv; + struct usbip_device *ud = &sdev->ud; + struct usb_device *udev = interface_to_usbdev(sdev->interface); + int pipe = get_pipe(sdev, pdu->base.ep, pdu->base.direction); + + + priv = stub_priv_alloc(sdev, pdu); + if (!priv) + return; + + /* setup a urb */ + if (usb_pipeisoc(pipe)) + priv->urb = usb_alloc_urb(pdu->u.cmd_submit.number_of_packets, + GFP_KERNEL); + else + priv->urb = usb_alloc_urb(0, GFP_KERNEL); + + if (!priv->urb) { + dev_err(&sdev->interface->dev, "malloc urb\n"); + usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC); + return; + } + + /* set priv->urb->transfer_buffer */ + if (pdu->u.cmd_submit.transfer_buffer_length > 0) { + priv->urb->transfer_buffer = + kzalloc(pdu->u.cmd_submit.transfer_buffer_length, + GFP_KERNEL); + if (!priv->urb->transfer_buffer) { + dev_err(&sdev->interface->dev, "malloc x_buff\n"); + usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC); + return; + } + } + + /* set priv->urb->setup_packet */ + priv->urb->setup_packet = kzalloc(8, GFP_KERNEL); + if (!priv->urb->setup_packet) { + dev_err(&sdev->interface->dev, "allocate setup_packet\n"); + usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC); + return; + } + memcpy(priv->urb->setup_packet, &pdu->u.cmd_submit.setup, 8); + + /* set other members from the base header of pdu */ + priv->urb->context = (void *) priv; + priv->urb->dev = udev; + priv->urb->pipe = pipe; + priv->urb->complete = stub_complete; + + usbip_pack_pdu(pdu, priv->urb, USBIP_CMD_SUBMIT, 0); + + + if (usbip_recv_xbuff(ud, priv->urb) < 0) + return; + + if (usbip_recv_iso(ud, priv->urb) < 0) + return; + + /* no need to submit an intercepted request, but harmless? */ + tweak_special_requests(priv->urb); + + /* urb is now ready to submit */ + ret = usb_submit_urb(priv->urb, GFP_KERNEL); + + if (ret == 0) + dbg_stub_rx("submit urb ok, seqnum %u\n", pdu->base.seqnum); + else { + dev_err(&sdev->interface->dev, "submit_urb error, %d\n", ret); + usbip_dump_header(pdu); + usbip_dump_urb(priv->urb); + + /* + * Pessimistic. + * This connection will be discarded. + */ + usbip_event_add(ud, SDEV_EVENT_ERROR_SUBMIT); + } + + dbg_stub_rx("Leave\n"); + return; +} + +/* recv a pdu */ +static void stub_rx_pdu(struct usbip_device *ud) +{ + int ret; + struct usbip_header pdu; + struct stub_device *sdev = container_of(ud, struct stub_device, ud); + struct device *dev = &sdev->interface->dev; + + dbg_stub_rx("Enter\n"); + + memset(&pdu, 0, sizeof(pdu)); + + /* 1. receive a pdu header */ + ret = usbip_xmit(0, ud->tcp_socket, (char *) &pdu, sizeof(pdu), 0); + if (ret != sizeof(pdu)) { + dev_err(dev, "recv a header, %d\n", ret); + usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); + return; + } + + usbip_header_correct_endian(&pdu, 0); + + if (dbg_flag_stub_rx) + usbip_dump_header(&pdu); + + if (!valid_request(sdev, &pdu)) { + dev_err(dev, "recv invalid request\n"); + usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); + return; + } + + switch (pdu.base.command) { + case USBIP_CMD_UNLINK: + stub_recv_cmd_unlink(sdev, &pdu); + break; + + case USBIP_CMD_SUBMIT: + stub_recv_cmd_submit(sdev, &pdu); + break; + + default: + /* NOTREACHED */ + dev_err(dev, "unknown pdu\n"); + usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); + return; + } + +} + +void stub_rx_loop(struct usbip_task *ut) +{ + struct usbip_device *ud = container_of(ut, struct usbip_device, tcp_rx); + + while (1) { + if (signal_pending(current)) { + dbg_stub_rx("signal caught!\n"); + break; + } + + if (usbip_event_happend(ud)) + break; + + stub_rx_pdu(ud); + } +} diff --git a/drivers/staging/usbip/stub_tx.c b/drivers/staging/usbip/stub_tx.c new file mode 100644 index 00000000000..d5563cd980b --- /dev/null +++ b/drivers/staging/usbip/stub_tx.c @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * + * This 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include "usbip_common.h" +#include "stub.h" + + +static void stub_free_priv_and_urb(struct stub_priv *priv) +{ + struct urb *urb = priv->urb; + + kfree(urb->setup_packet); + kfree(urb->transfer_buffer); + list_del(&priv->list); + kmem_cache_free(stub_priv_cache, priv); + usb_free_urb(urb); +} + +/* be in spin_lock_irqsave(&sdev->priv_lock, flags) */ +void stub_enqueue_ret_unlink(struct stub_device *sdev, __u32 seqnum, + __u32 status) +{ + struct stub_unlink *unlink; + + unlink = kzalloc(sizeof(struct stub_unlink), GFP_ATOMIC); + if (!unlink) { + dev_err(&sdev->interface->dev, "alloc stub_unlink\n"); + usbip_event_add(&sdev->ud, VDEV_EVENT_ERROR_MALLOC); + return; + } + + unlink->seqnum = seqnum; + unlink->status = status; + + list_add_tail(&unlink->list, &sdev->unlink_tx); +} + +/** + * stub_complete - completion handler of a usbip urb + * @urb: pointer to the urb completed + * @regs: + * + * When a urb has completed, the USB core driver calls this function mostly in + * the interrupt context. To return the result of a urb, the completed urb is + * linked to the pending list of returning. + * + */ +void stub_complete(struct urb *urb) +{ + struct stub_priv *priv = (struct stub_priv *) urb->context; + struct stub_device *sdev = priv->sdev; + unsigned long flags; + + dbg_stub_tx("complete! status %d\n", urb->status); + + + switch (urb->status) { + case 0: + /* OK */ + break; + case -ENOENT: + uinfo("stopped by a call of usb_kill_urb() because of" + "cleaning up a virtual connection\n"); + return; + case -ECONNRESET: + uinfo("unlinked by a call of usb_unlink_urb()\n"); + break; + case -EPIPE: + uinfo("endpoint %d is stalled\n", usb_pipeendpoint(urb->pipe)); + break; + case -ESHUTDOWN: + uinfo("device removed?\n"); + break; + default: + uinfo("urb completion with non-zero status %d\n", urb->status); + } + + /* link a urb to the queue of tx. */ + spin_lock_irqsave(&sdev->priv_lock, flags); + + if (priv->unlinking) { + stub_enqueue_ret_unlink(sdev, priv->seqnum, urb->status); + stub_free_priv_and_urb(priv); + } else + list_move_tail(&priv->list, &sdev->priv_tx); + + + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + /* wake up tx_thread */ + wake_up(&sdev->tx_waitq); +} + + +/*-------------------------------------------------------------------------*/ +/* fill PDU */ + +static inline void setup_base_pdu(struct usbip_header_basic *base, + __u32 command, __u32 seqnum) +{ + base->command = command; + base->seqnum = seqnum; + base->devid = 0; + base->ep = 0; + base->direction = 0; +} + +static void setup_ret_submit_pdu(struct usbip_header *rpdu, struct urb *urb) +{ + struct stub_priv *priv = (struct stub_priv *) urb->context; + + setup_base_pdu(&rpdu->base, USBIP_RET_SUBMIT, priv->seqnum); + + usbip_pack_pdu(rpdu, urb, USBIP_RET_SUBMIT, 1); +} + +static void setup_ret_unlink_pdu(struct usbip_header *rpdu, + struct stub_unlink *unlink) +{ + setup_base_pdu(&rpdu->base, USBIP_RET_UNLINK, unlink->seqnum); + + rpdu->u.ret_unlink.status = unlink->status; +} + + +/*-------------------------------------------------------------------------*/ +/* send RET_SUBMIT */ + +static struct stub_priv *dequeue_from_priv_tx(struct stub_device *sdev) +{ + unsigned long flags; + struct stub_priv *priv, *tmp; + + spin_lock_irqsave(&sdev->priv_lock, flags); + + list_for_each_entry_safe(priv, tmp, &sdev->priv_tx, list) { + list_move_tail(&priv->list, &sdev->priv_free); + spin_unlock_irqrestore(&sdev->priv_lock, flags); + return priv; + } + + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + return NULL; +} + +static int stub_send_ret_submit(struct stub_device *sdev) +{ + unsigned long flags; + struct stub_priv *priv, *tmp; + + struct msghdr msg; + struct kvec iov[3]; + size_t txsize; + + size_t total_size = 0; + + while ((priv = dequeue_from_priv_tx(sdev)) != NULL) { + int ret; + struct urb *urb = priv->urb; + struct usbip_header pdu_header; + void *iso_buffer = NULL; + + txsize = 0; + memset(&pdu_header, 0, sizeof(pdu_header)); + memset(&msg, 0, sizeof(msg)); + memset(&iov, 0, sizeof(iov)); + + dbg_stub_tx("setup txdata urb %p\n", urb); + + + /* 1. setup usbip_header */ + setup_ret_submit_pdu(&pdu_header, urb); + usbip_header_correct_endian(&pdu_header, 1); + + iov[0].iov_base = &pdu_header; + iov[0].iov_len = sizeof(pdu_header); + txsize += sizeof(pdu_header); + + /* 2. setup transfer buffer */ + if (usb_pipein(urb->pipe) && urb->actual_length > 0) { + iov[1].iov_base = urb->transfer_buffer; + iov[1].iov_len = urb->actual_length; + txsize += urb->actual_length; + } + + /* 3. setup iso_packet_descriptor */ + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + ssize_t len = 0; + + iso_buffer = usbip_alloc_iso_desc_pdu(urb, &len); + if (!iso_buffer) { + usbip_event_add(&sdev->ud, + SDEV_EVENT_ERROR_MALLOC); + return -1; + } + + iov[2].iov_base = iso_buffer; + iov[2].iov_len = len; + txsize += len; + } + + ret = kernel_sendmsg(sdev->ud.tcp_socket, &msg, iov, + 3, txsize); + if (ret != txsize) { + dev_err(&sdev->interface->dev, + "sendmsg failed!, retval %d for %zd\n", + ret, txsize); + kfree(iso_buffer); + usbip_event_add(&sdev->ud, SDEV_EVENT_ERROR_TCP); + return -1; + } + + kfree(iso_buffer); + dbg_stub_tx("send txdata\n"); + + total_size += txsize; + } + + + spin_lock_irqsave(&sdev->priv_lock, flags); + + list_for_each_entry_safe(priv, tmp, &sdev->priv_free, list) { + stub_free_priv_and_urb(priv); + } + + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + return total_size; +} + + +/*-------------------------------------------------------------------------*/ +/* send RET_UNLINK */ + +static struct stub_unlink *dequeue_from_unlink_tx(struct stub_device *sdev) +{ + unsigned long flags; + struct stub_unlink *unlink, *tmp; + + spin_lock_irqsave(&sdev->priv_lock, flags); + + list_for_each_entry_safe(unlink, tmp, &sdev->unlink_tx, list) { + list_move_tail(&unlink->list, &sdev->unlink_free); + spin_unlock_irqrestore(&sdev->priv_lock, flags); + return unlink; + } + + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + return NULL; +} + + +static int stub_send_ret_unlink(struct stub_device *sdev) +{ + unsigned long flags; + struct stub_unlink *unlink, *tmp; + + struct msghdr msg; + struct kvec iov[1]; + size_t txsize; + + size_t total_size = 0; + + while ((unlink = dequeue_from_unlink_tx(sdev)) != NULL) { + int ret; + struct usbip_header pdu_header; + + txsize = 0; + memset(&pdu_header, 0, sizeof(pdu_header)); + memset(&msg, 0, sizeof(msg)); + memset(&iov, 0, sizeof(iov)); + + dbg_stub_tx("setup ret unlink %lu\n", unlink->seqnum); + + /* 1. setup usbip_header */ + setup_ret_unlink_pdu(&pdu_header, unlink); + usbip_header_correct_endian(&pdu_header, 1); + + iov[0].iov_base = &pdu_header; + iov[0].iov_len = sizeof(pdu_header); + txsize += sizeof(pdu_header); + + ret = kernel_sendmsg(sdev->ud.tcp_socket, &msg, iov, + 1, txsize); + if (ret != txsize) { + dev_err(&sdev->interface->dev, + "sendmsg failed!, retval %d for %zd\n", + ret, txsize); + usbip_event_add(&sdev->ud, SDEV_EVENT_ERROR_TCP); + return -1; + } + + + dbg_stub_tx("send txdata\n"); + + total_size += txsize; + } + + + spin_lock_irqsave(&sdev->priv_lock, flags); + + list_for_each_entry_safe(unlink, tmp, &sdev->unlink_free, list) { + list_del(&unlink->list); + kfree(unlink); + } + + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + return total_size; +} + + +/*-------------------------------------------------------------------------*/ + +void stub_tx_loop(struct usbip_task *ut) +{ + struct usbip_device *ud = container_of(ut, struct usbip_device, tcp_tx); + struct stub_device *sdev = container_of(ud, struct stub_device, ud); + + while (1) { + if (signal_pending(current)) { + dbg_stub_tx("signal catched\n"); + break; + } + + if (usbip_event_happend(ud)) + break; + + /* + * send_ret_submit comes earlier than send_ret_unlink. stub_rx + * looks at only priv_init queue. If the completion of a URB is + * earlier than the receive of CMD_UNLINK, priv is moved to + * priv_tx queue and stub_rx does not find the target priv. In + * this case, vhci_rx receives the result of the submit request + * and then receives the result of the unlink request. The + * result of the submit is given back to the usbcore as the + * completion of the unlink request. The request of the + * unlink is ignored. This is ok because a driver who calls + * usb_unlink_urb() understands the unlink was too late by + * getting the status of the given-backed URB which has the + * status of usb_submit_urb(). + */ + if (stub_send_ret_submit(sdev) < 0) + break; + + if (stub_send_ret_unlink(sdev) < 0) + break; + + wait_event_interruptible(sdev->tx_waitq, + (!list_empty(&sdev->priv_tx) || + !list_empty(&sdev->unlink_tx))); + } +} diff --git a/drivers/staging/usbip/usbip_common.c b/drivers/staging/usbip/usbip_common.c new file mode 100644 index 00000000000..e64918f42ff --- /dev/null +++ b/drivers/staging/usbip/usbip_common.c @@ -0,0 +1,997 @@ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * + * This 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include <linux/kernel.h> +#include <linux/file.h> +#include <linux/tcp.h> +#include <linux/in.h> +#include "usbip_common.h" + +/* version information */ +#define DRIVER_VERSION "1.0" +#define DRIVER_AUTHOR "Takahiro Hirofuchi <hirofuchi _at_ users.sourceforge.net>" +#define DRIVER_DESC "usbip common driver" + +/*-------------------------------------------------------------------------*/ +/* debug routines */ + +#ifdef CONFIG_USB_DEBUG +unsigned long usbip_debug_flag = 0xffffffff; +#else +unsigned long usbip_debug_flag; +#endif +EXPORT_SYMBOL_GPL(usbip_debug_flag); + + +/* FIXME */ +struct device_attribute dev_attr_usbip_debug; +EXPORT_SYMBOL_GPL(dev_attr_usbip_debug); + + +static ssize_t show_flag(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%lx\n", usbip_debug_flag); +} + +static ssize_t store_flag(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long flag; + + sscanf(buf, "%lx", &flag); + usbip_debug_flag = flag; + + return count; +} +DEVICE_ATTR(usbip_debug, (S_IRUGO | S_IWUSR), show_flag, store_flag); + +static void usbip_dump_buffer(char *buff, int bufflen) +{ + int i; + + if (bufflen > 128) { + for (i = 0; i < 128; i++) { + if (i%24 == 0) + printk(" "); + printk("%02x ", (unsigned char) buff[i]); + if (i%4 == 3) + printk("| "); + if (i%24 == 23) + printk("\n"); + } + printk("... (%d byte)\n", bufflen); + return; + } + + for (i = 0; i < bufflen; i++) { + if (i%24 == 0) + printk(" "); + printk("%02x ", (unsigned char) buff[i]); + if (i%4 == 3) + printk("| "); + if (i%24 == 23) + printk("\n"); + } + printk("\n"); + +} + +static void usbip_dump_pipe(unsigned int p) +{ + unsigned char type = usb_pipetype(p); + unsigned char ep = usb_pipeendpoint(p); + unsigned char dev = usb_pipedevice(p); + unsigned char dir = usb_pipein(p); + + printk("dev(%d) ", dev); + printk("ep(%d) ", ep); + printk("%s ", dir ? "IN" : "OUT"); + + switch (type) { + case PIPE_ISOCHRONOUS: + printk("%s ", "ISO"); + break; + case PIPE_INTERRUPT: + printk("%s ", "INT"); + break; + case PIPE_CONTROL: + printk("%s ", "CTL"); + break; + case PIPE_BULK: + printk("%s ", "BLK"); + break; + default: + printk("ERR"); + } + + printk("\n"); + +} + +static void usbip_dump_usb_device(struct usb_device *udev) +{ + struct device *dev = &udev->dev; + int i; + + dev_dbg(dev, " devnum(%d) devpath(%s)", + udev->devnum, udev->devpath); + + switch (udev->speed) { + case USB_SPEED_HIGH: + printk(" SPD_HIGH"); + break; + case USB_SPEED_FULL: + printk(" SPD_FULL"); + break; + case USB_SPEED_LOW: + printk(" SPD_LOW"); + break; + case USB_SPEED_UNKNOWN: + printk(" SPD_UNKNOWN"); + break; + default: + printk(" SPD_ERROR"); + } + + printk(" tt %p, ttport %d", udev->tt, udev->ttport); + printk("\n"); + + dev_dbg(dev, " "); + for (i = 0; i < 16; i++) + printk(" %2u", i); + printk("\n"); + + dev_dbg(dev, " toggle0(IN) :"); + for (i = 0; i < 16; i++) + printk(" %2u", (udev->toggle[0] & (1 << i)) ? 1 : 0); + printk("\n"); + + dev_dbg(dev, " toggle1(OUT):"); + for (i = 0; i < 16; i++) + printk(" %2u", (udev->toggle[1] & (1 << i)) ? 1 : 0); + printk("\n"); + + + dev_dbg(dev, " epmaxp_in :"); + for (i = 0; i < 16; i++) { + if (udev->ep_in[i]) + printk(" %2u", + le16_to_cpu(udev->ep_in[i]->desc.wMaxPacketSize)); + } + printk("\n"); + + dev_dbg(dev, " epmaxp_out :"); + for (i = 0; i < 16; i++) { + if (udev->ep_out[i]) + printk(" %2u", + le16_to_cpu(udev->ep_out[i]->desc.wMaxPacketSize)); + } + printk("\n"); + + dev_dbg(dev, "parent %p, bus %p\n", udev->parent, udev->bus); + + dev_dbg(dev, "descriptor %p, config %p, actconfig %p, " + "rawdescriptors %p\n", &udev->descriptor, udev->config, + udev->actconfig, udev->rawdescriptors); + + dev_dbg(dev, "have_langid %d, string_langid %d\n", + udev->have_langid, udev->string_langid); + + dev_dbg(dev, "maxchild %d, children %p\n", + udev->maxchild, udev->children); +} + +static void usbip_dump_request_type(__u8 rt) +{ + switch (rt & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + printk("DEVICE"); + break; + case USB_RECIP_INTERFACE: + printk("INTERF"); + break; + case USB_RECIP_ENDPOINT: + printk("ENDPOI"); + break; + case USB_RECIP_OTHER: + printk("OTHER "); + break; + default: + printk("------"); + } +} + +static void usbip_dump_usb_ctrlrequest(struct usb_ctrlrequest *cmd) +{ + if (!cmd) { + printk(" %s : null pointer\n", __FUNCTION__); + return; + } + + printk(" "); + printk("bRequestType(%02X) ", cmd->bRequestType); + printk("bRequest(%02X) " , cmd->bRequest); + printk("wValue(%04X) ", cmd->wValue); + printk("wIndex(%04X) ", cmd->wIndex); + printk("wLength(%04X) ", cmd->wLength); + + printk("\n "); + + if ((cmd->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + printk("STANDARD "); + switch (cmd->bRequest) { + case USB_REQ_GET_STATUS: + printk("GET_STATUS"); + break; + case USB_REQ_CLEAR_FEATURE: + printk("CLEAR_FEAT"); + break; + case USB_REQ_SET_FEATURE: + printk("SET_FEAT "); + break; + case USB_REQ_SET_ADDRESS: + printk("SET_ADDRRS"); + break; + case USB_REQ_GET_DESCRIPTOR: + printk("GET_DESCRI"); + break; + case USB_REQ_SET_DESCRIPTOR: + printk("SET_DESCRI"); + break; + case USB_REQ_GET_CONFIGURATION: + printk("GET_CONFIG"); + break; + case USB_REQ_SET_CONFIGURATION: + printk("SET_CONFIG"); + break; + case USB_REQ_GET_INTERFACE: + printk("GET_INTERF"); + break; + case USB_REQ_SET_INTERFACE: + printk("SET_INTERF"); + break; + case USB_REQ_SYNCH_FRAME: + printk("SYNC_FRAME"); + break; + default: + printk("REQ(%02X) ", cmd->bRequest); + } + + printk(" "); + usbip_dump_request_type(cmd->bRequestType); + + } else if ((cmd->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) + printk("CLASS "); + + else if ((cmd->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) + printk("VENDOR "); + + else if ((cmd->bRequestType & USB_TYPE_MASK) == USB_TYPE_RESERVED) + printk("RESERVED"); + + printk("\n"); +} + +void usbip_dump_urb(struct urb *urb) +{ + struct device *dev; + + if (!urb) { + printk(KERN_DEBUG KBUILD_MODNAME + ":%s: urb: null pointer!!\n", __func__); + return; + } + + if (!urb->dev) { + printk(KERN_DEBUG KBUILD_MODNAME + ":%s: urb->dev: null pointer!!\n", __func__); + return; + } + dev = &urb->dev->dev; + + dev_dbg(dev, " urb :%p\n", urb); + dev_dbg(dev, " dev :%p\n", urb->dev); + + usbip_dump_usb_device(urb->dev); + + dev_dbg(dev, " pipe :%08x ", urb->pipe); + + usbip_dump_pipe(urb->pipe); + + dev_dbg(dev, " status :%d\n", urb->status); + dev_dbg(dev, " transfer_flags :%08X\n", urb->transfer_flags); + dev_dbg(dev, " transfer_buffer :%p\n", urb->transfer_buffer); + dev_dbg(dev, " transfer_buffer_length:%d\n", urb->transfer_buffer_length); + dev_dbg(dev, " actual_length :%d\n", urb->actual_length); + dev_dbg(dev, " setup_packet :%p\n", urb->setup_packet); + + if (urb->setup_packet && usb_pipetype(urb->pipe) == PIPE_CONTROL) + usbip_dump_usb_ctrlrequest( + (struct usb_ctrlrequest *)urb->setup_packet); + + dev_dbg(dev, " start_frame :%d\n", urb->start_frame); + dev_dbg(dev, " number_of_packets :%d\n", urb->number_of_packets); + dev_dbg(dev, " interval :%d\n", urb->interval); + dev_dbg(dev, " error_count :%d\n", urb->error_count); + dev_dbg(dev, " context :%p\n", urb->context); + dev_dbg(dev, " complete :%p\n", urb->complete); +} +EXPORT_SYMBOL_GPL(usbip_dump_urb); + +void usbip_dump_header(struct usbip_header *pdu) +{ + udbg("BASE: cmd %u seq %u devid %u dir %u ep %u\n", + pdu->base.command, + pdu->base.seqnum, + pdu->base.devid, + pdu->base.direction, + pdu->base.ep); + + switch (pdu->base.command) { + case USBIP_CMD_SUBMIT: + udbg("CMD_SUBMIT: x_flags %u x_len %u sf %u #p %u iv %u\n", + pdu->u.cmd_submit.transfer_flags, + pdu->u.cmd_submit.transfer_buffer_length, + pdu->u.cmd_submit.start_frame, + pdu->u.cmd_submit.number_of_packets, + pdu->u.cmd_submit.interval); + break; + case USBIP_CMD_UNLINK: + udbg("CMD_UNLINK: seq %u\n", pdu->u.cmd_unlink.seqnum); + break; + case USBIP_RET_SUBMIT: + udbg("RET_SUBMIT: st %d al %u sf %d ec %d\n", + pdu->u.ret_submit.status, + pdu->u.ret_submit.actual_length, + pdu->u.ret_submit.start_frame, + pdu->u.ret_submit.error_count); + case USBIP_RET_UNLINK: + udbg("RET_UNLINK: status %d\n", pdu->u.ret_unlink.status); + break; + default: + /* NOT REACHED */ + udbg("UNKNOWN\n"); + } +} +EXPORT_SYMBOL_GPL(usbip_dump_header); + + +/*-------------------------------------------------------------------------*/ +/* thread routines */ + +int usbip_thread(void *param) +{ + struct usbip_task *ut = param; + + if (!ut) + return -EINVAL; + + lock_kernel(); + daemonize(ut->name); + allow_signal(SIGKILL); + ut->thread = current; + unlock_kernel(); + + /* srv.rb must wait for rx_thread starting */ + complete(&ut->thread_done); + + /* start of while loop */ + ut->loop_ops(ut); + + /* end of loop */ + ut->thread = NULL; + + complete_and_exit(&ut->thread_done, 0); +} + +void usbip_start_threads(struct usbip_device *ud) +{ + /* + * threads are invoked per one device (per one connection). + */ + kernel_thread(usbip_thread, (void *)&ud->tcp_rx, 0); + kernel_thread(usbip_thread, (void *)&ud->tcp_tx, 0); + + /* confirm threads are starting */ + wait_for_completion(&ud->tcp_rx.thread_done); + wait_for_completion(&ud->tcp_tx.thread_done); +} +EXPORT_SYMBOL_GPL(usbip_start_threads); + +void usbip_stop_threads(struct usbip_device *ud) +{ + /* kill threads related to this sdev, if v.c. exists */ + if (ud->tcp_rx.thread != NULL) { + send_sig(SIGKILL, ud->tcp_rx.thread, 1); + wait_for_completion(&ud->tcp_rx.thread_done); + udbg("rx_thread for ud %p has finished\n", ud); + } + + if (ud->tcp_tx.thread != NULL) { + send_sig(SIGKILL, ud->tcp_tx.thread, 1); + wait_for_completion(&ud->tcp_tx.thread_done); + udbg("tx_thread for ud %p has finished\n", ud); + } +} +EXPORT_SYMBOL_GPL(usbip_stop_threads); + +void usbip_task_init(struct usbip_task *ut, char *name, + void (*loop_ops)(struct usbip_task *)) +{ + ut->thread = NULL; + init_completion(&ut->thread_done); + ut->name = name; + ut->loop_ops = loop_ops; +} +EXPORT_SYMBOL_GPL(usbip_task_init); + + +/*-------------------------------------------------------------------------*/ +/* socket routines */ + + /* Send/receive messages over TCP/IP. I refer drivers/block/nbd.c */ +int usbip_xmit(int send, struct socket *sock, char *buf, + int size, int msg_flags) +{ + int result; + struct msghdr msg; + struct kvec iov; + int total = 0; + + /* for blocks of if (dbg_flag_xmit) */ + char *bp = buf; + int osize = size; + + dbg_xmit("enter\n"); + + if (!sock || !buf || !size) { + printk(KERN_ERR "%s: invalid arg, sock %p buff %p size %d\n", + __func__, sock, buf, size); + return -EINVAL; + } + + + if (dbg_flag_xmit) { + if (send) { + if (!in_interrupt()) + printk(KERN_DEBUG "%-10s:", current->comm); + else + printk(KERN_DEBUG "interupt :"); + + printk("%s: sending... , sock %p, buf %p, " + "size %d, msg_flags %d\n", __func__, + sock, buf, size, msg_flags); + usbip_dump_buffer(buf, size); + } + } + + + do { + sock->sk->sk_allocation = GFP_NOIO; + iov.iov_base = buf; + iov.iov_len = size; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_namelen = 0; + msg.msg_flags = msg_flags | MSG_NOSIGNAL; + + if (send) + result = kernel_sendmsg(sock, &msg, &iov, 1, size); + else + result = kernel_recvmsg(sock, &msg, &iov, 1, size, + MSG_WAITALL); + + if (result <= 0) { + udbg("usbip_xmit: %s sock %p buf %p size %u ret %d" + " total %d\n", + send ? "send" : "receive", sock, buf, + size, result, total); + goto err; + } + + size -= result; + buf += result; + total += result; + + } while (size > 0); + + + if (dbg_flag_xmit) { + if (!send) { + if (!in_interrupt()) + printk(KERN_DEBUG "%-10s:", current->comm); + else + printk(KERN_DEBUG "interupt :"); + + printk("usbip_xmit: receiving....\n"); + usbip_dump_buffer(bp, osize); + printk("usbip_xmit: received, osize %d ret %d size %d " + "total %d\n", osize, result, size, + total); + } + + if (send) + printk("usbip_xmit: send, total %d\n", total); + } + + return total; + +err: + return result; +} +EXPORT_SYMBOL_GPL(usbip_xmit); + + +/* now a usrland utility should set options. */ +#if 0 +int setquickack(struct socket *socket) +{ + mm_segment_t oldfs; + int val = 1; + int ret; + + oldfs = get_fs(); + set_fs(get_ds()); + ret = socket->ops->setsockopt(socket, SOL_TCP, TCP_QUICKACK, + (char __user *) &val, sizeof(ret)); + set_fs(oldfs); + + return ret; +} + +int setnodelay(struct socket *socket) +{ + mm_segment_t oldfs; + int val = 1; + int ret; + + oldfs = get_fs(); + set_fs(get_ds()); + ret = socket->ops->setsockopt(socket, SOL_TCP, TCP_NODELAY, + (char __user *) &val, sizeof(ret)); + set_fs(oldfs); + + return ret; +} + +int setkeepalive(struct socket *socket) +{ + mm_segment_t oldfs; + int val = 1; + int ret; + + oldfs = get_fs(); + set_fs(get_ds()); + ret = socket->ops->setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, + (char __user *) &val, sizeof(ret)); + set_fs(oldfs); + + return ret; +} + +void setreuse(struct socket *socket) +{ + socket->sk->sk_reuse = 1; +} +#endif + +struct socket *sockfd_to_socket(unsigned int sockfd) +{ + struct socket *socket; + struct file *file; + struct inode *inode; + + file = fget(sockfd); + if (!file) { + printk(KERN_ERR "%s: invalid sockfd\n", __func__); + return NULL; + } + + inode = file->f_dentry->d_inode; + + if (!inode || !S_ISSOCK(inode->i_mode)) + return NULL; + + socket = SOCKET_I(inode); + + return socket; +} +EXPORT_SYMBOL_GPL(sockfd_to_socket); + + + +/*-------------------------------------------------------------------------*/ +/* pdu routines */ + +/* there may be more cases to tweak the flags. */ +static unsigned int tweak_transfer_flags(unsigned int flags) +{ + + if (flags & URB_NO_TRANSFER_DMA_MAP) + /* + * vhci_hcd does not provide DMA-mapped I/O. The upper + * driver does not need to set this flag. The remote + * usbip.ko does not still perform DMA-mapped I/O for + * DMA-caplable host controllers. So, clear this flag. + */ + flags &= ~URB_NO_TRANSFER_DMA_MAP; + + if (flags & URB_NO_SETUP_DMA_MAP) + flags &= ~URB_NO_SETUP_DMA_MAP; + + return flags; +} + +static void usbip_pack_cmd_submit(struct usbip_header *pdu, struct urb *urb, + int pack) +{ + struct usbip_header_cmd_submit *spdu = &pdu->u.cmd_submit; + + /* + * Some members are not still implemented in usbip. I hope this issue + * will be discussed when usbip is ported to other operating systems. + */ + if (pack) { + /* vhci_tx.c */ + spdu->transfer_flags = + tweak_transfer_flags(urb->transfer_flags); + spdu->transfer_buffer_length = urb->transfer_buffer_length; + spdu->start_frame = urb->start_frame; + spdu->number_of_packets = urb->number_of_packets; + spdu->interval = urb->interval; + } else { + /* stub_rx.c */ + urb->transfer_flags = spdu->transfer_flags; + + urb->transfer_buffer_length = spdu->transfer_buffer_length; + urb->start_frame = spdu->start_frame; + urb->number_of_packets = spdu->number_of_packets; + urb->interval = spdu->interval; + } +} + +static void usbip_pack_ret_submit(struct usbip_header *pdu, struct urb *urb, + int pack) +{ + struct usbip_header_ret_submit *rpdu = &pdu->u.ret_submit; + + if (pack) { + /* stub_tx.c */ + + rpdu->status = urb->status; + rpdu->actual_length = urb->actual_length; + rpdu->start_frame = urb->start_frame; + rpdu->error_count = urb->error_count; + } else { + /* vhci_rx.c */ + + urb->status = rpdu->status; + urb->actual_length = rpdu->actual_length; + urb->start_frame = rpdu->start_frame; + urb->error_count = rpdu->error_count; + } +} + + +void usbip_pack_pdu(struct usbip_header *pdu, struct urb *urb, int cmd, + int pack) +{ + switch (cmd) { + case USBIP_CMD_SUBMIT: + usbip_pack_cmd_submit(pdu, urb, pack); + break; + case USBIP_RET_SUBMIT: + usbip_pack_ret_submit(pdu, urb, pack); + break; + default: + err("unknown command"); + /* NOTREACHED */ + /* BUG(); */ + } +} +EXPORT_SYMBOL_GPL(usbip_pack_pdu); + + +static void correct_endian_basic(struct usbip_header_basic *base, int send) +{ + if (send) { + base->command = cpu_to_be32(base->command); + base->seqnum = cpu_to_be32(base->seqnum); + base->devid = cpu_to_be32(base->devid); + base->direction = cpu_to_be32(base->direction); + base->ep = cpu_to_be32(base->ep); + } else { + base->command = be32_to_cpu(base->command); + base->seqnum = be32_to_cpu(base->seqnum); + base->devid = be32_to_cpu(base->devid); + base->direction = be32_to_cpu(base->direction); + base->ep = be32_to_cpu(base->ep); + } +} + +static void correct_endian_cmd_submit(struct usbip_header_cmd_submit *pdu, + int send) +{ + if (send) { + pdu->transfer_flags = cpu_to_be32(pdu->transfer_flags); + + cpu_to_be32s(&pdu->transfer_buffer_length); + cpu_to_be32s(&pdu->start_frame); + cpu_to_be32s(&pdu->number_of_packets); + cpu_to_be32s(&pdu->interval); + } else { + pdu->transfer_flags = be32_to_cpu(pdu->transfer_flags); + + be32_to_cpus(&pdu->transfer_buffer_length); + be32_to_cpus(&pdu->start_frame); + be32_to_cpus(&pdu->number_of_packets); + be32_to_cpus(&pdu->interval); + } +} + +static void correct_endian_ret_submit(struct usbip_header_ret_submit *pdu, + int send) +{ + if (send) { + cpu_to_be32s(&pdu->status); + cpu_to_be32s(&pdu->actual_length); + cpu_to_be32s(&pdu->start_frame); + cpu_to_be32s(&pdu->error_count); + } else { + be32_to_cpus(&pdu->status); + be32_to_cpus(&pdu->actual_length); + be32_to_cpus(&pdu->start_frame); + be32_to_cpus(&pdu->error_count); + } +} + +static void correct_endian_cmd_unlink(struct usbip_header_cmd_unlink *pdu, + int send) +{ + if (send) + pdu->seqnum = cpu_to_be32(pdu->seqnum); + else + pdu->seqnum = be32_to_cpu(pdu->seqnum); +} + +static void correct_endian_ret_unlink(struct usbip_header_ret_unlink *pdu, + int send) +{ + if (send) + cpu_to_be32s(&pdu->status); + else + be32_to_cpus(&pdu->status); +} + +void usbip_header_correct_endian(struct usbip_header *pdu, int send) +{ + __u32 cmd = 0; + + if (send) + cmd = pdu->base.command; + + correct_endian_basic(&pdu->base, send); + + if (!send) + cmd = pdu->base.command; + + switch (cmd) { + case USBIP_CMD_SUBMIT: + correct_endian_cmd_submit(&pdu->u.cmd_submit, send); + break; + case USBIP_RET_SUBMIT: + correct_endian_ret_submit(&pdu->u.ret_submit, send); + break; + case USBIP_CMD_UNLINK: + correct_endian_cmd_unlink(&pdu->u.cmd_unlink, send); + break; + case USBIP_RET_UNLINK: + correct_endian_ret_unlink(&pdu->u.ret_unlink, send); + break; + default: + /* NOTREACHED */ + err("unknown command in pdu header: %d", cmd); + /* BUG(); */ + } +} +EXPORT_SYMBOL_GPL(usbip_header_correct_endian); + +static void usbip_iso_pakcet_correct_endian( + struct usbip_iso_packet_descriptor *iso, + int send) +{ + /* does not need all members. but copy all simply. */ + if (send) { + iso->offset = cpu_to_be32(iso->offset); + iso->length = cpu_to_be32(iso->length); + iso->status = cpu_to_be32(iso->status); + iso->actual_length = cpu_to_be32(iso->actual_length); + } else { + iso->offset = be32_to_cpu(iso->offset); + iso->length = be32_to_cpu(iso->length); + iso->status = be32_to_cpu(iso->status); + iso->actual_length = be32_to_cpu(iso->actual_length); + } +} + +static void usbip_pack_iso(struct usbip_iso_packet_descriptor *iso, + struct usb_iso_packet_descriptor *uiso, int pack) +{ + if (pack) { + iso->offset = uiso->offset; + iso->length = uiso->length; + iso->status = uiso->status; + iso->actual_length = uiso->actual_length; + } else { + uiso->offset = iso->offset; + uiso->length = iso->length; + uiso->status = iso->status; + uiso->actual_length = iso->actual_length; + } +} + + +/* must free buffer */ +void *usbip_alloc_iso_desc_pdu(struct urb *urb, ssize_t *bufflen) +{ + void *buff; + struct usbip_iso_packet_descriptor *iso; + int np = urb->number_of_packets; + ssize_t size = np * sizeof(*iso); + int i; + + buff = kzalloc(size, GFP_KERNEL); + if (!buff) + return NULL; + + for (i = 0; i < np; i++) { + iso = buff + (i * sizeof(*iso)); + + usbip_pack_iso(iso, &urb->iso_frame_desc[i], 1); + usbip_iso_pakcet_correct_endian(iso, 1); + } + + *bufflen = size; + + return buff; +} +EXPORT_SYMBOL_GPL(usbip_alloc_iso_desc_pdu); + +/* some members of urb must be substituted before. */ +int usbip_recv_iso(struct usbip_device *ud, struct urb *urb) +{ + void *buff; + struct usbip_iso_packet_descriptor *iso; + int np = urb->number_of_packets; + int size = np * sizeof(*iso); + int i; + int ret; + + if (!usb_pipeisoc(urb->pipe)) + return 0; + + /* my Bluetooth dongle gets ISO URBs which are np = 0 */ + if (np == 0) { + /* uinfo("iso np == 0\n"); */ + /* usbip_dump_urb(urb); */ + return 0; + } + + buff = kzalloc(size, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + ret = usbip_xmit(0, ud->tcp_socket, buff, size, 0); + if (ret != size) { + dev_err(&urb->dev->dev, "recv iso_frame_descriptor, %d\n", + ret); + kfree(buff); + + if (ud->side == USBIP_STUB) + usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); + else + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + + return -EPIPE; + } + + for (i = 0; i < np; i++) { + iso = buff + (i * sizeof(*iso)); + + usbip_iso_pakcet_correct_endian(iso, 0); + usbip_pack_iso(iso, &urb->iso_frame_desc[i], 0); + } + + + kfree(buff); + + return ret; +} +EXPORT_SYMBOL_GPL(usbip_recv_iso); + + +/* some members of urb must be substituted before. */ +int usbip_recv_xbuff(struct usbip_device *ud, struct urb *urb) +{ + int ret; + int size; + + if (ud->side == USBIP_STUB) { + /* stub_rx.c */ + /* the direction of urb must be OUT. */ + if (usb_pipein(urb->pipe)) + return 0; + + size = urb->transfer_buffer_length; + } else { + /* vhci_rx.c */ + /* the direction of urb must be IN. */ + if (usb_pipeout(urb->pipe)) + return 0; + + size = urb->actual_length; + } + + /* no need to recv xbuff */ + if (!(size > 0)) + return 0; + + ret = usbip_xmit(0, ud->tcp_socket, (char *)urb->transfer_buffer, + size, 0); + if (ret != size) { + dev_err(&urb->dev->dev, "recv xbuf, %d\n", ret); + if (ud->side == USBIP_STUB) { + usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); + } else { + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + return -EPIPE; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(usbip_recv_xbuff); + + +/*-------------------------------------------------------------------------*/ + +static int __init usbip_common_init(void) +{ + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "" DRIVER_VERSION); + + return 0; +} + +static void __exit usbip_common_exit(void) +{ + return; +} + + + + +module_init(usbip_common_init); +module_exit(usbip_common_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/usbip/usbip_common.h b/drivers/staging/usbip/usbip_common.h new file mode 100644 index 00000000000..b0186b76637 --- /dev/null +++ b/drivers/staging/usbip/usbip_common.h @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * + * This 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef __VHCI_COMMON_H +#define __VHCI_COMMON_H + + +#include <linux/version.h> +#include <linux/usb.h> +#include <asm/byteorder.h> +#include <net/sock.h> + +/*-------------------------------------------------------------------------*/ + +/* + * define macros to print messages + */ + +/** + * udbg - print debug messages if CONFIG_USB_DEBUG is defined + * @fmt: + * @args: + */ + +#ifdef CONFIG_USB_DEBUG + +#define udbg(fmt, args...) \ + do { \ + printk(KERN_DEBUG "%-10s:(%s,%d) %s: " fmt, \ + (in_interrupt() ? "interrupt" : (current)->comm),\ + __FILE__, __LINE__, __func__, ##args); \ + } while (0) + +#else /* CONFIG_USB_DEBUG */ + +#define udbg(fmt, args...) do { } while (0) + +#endif /* CONFIG_USB_DEBUG */ + + +enum { + usbip_debug_xmit = (1 << 0), + usbip_debug_sysfs = (1 << 1), + usbip_debug_urb = (1 << 2), + usbip_debug_eh = (1 << 3), + + usbip_debug_stub_cmp = (1 << 8), + usbip_debug_stub_dev = (1 << 9), + usbip_debug_stub_rx = (1 << 10), + usbip_debug_stub_tx = (1 << 11), + + usbip_debug_vhci_rh = (1 << 8), + usbip_debug_vhci_hc = (1 << 9), + usbip_debug_vhci_rx = (1 << 10), + usbip_debug_vhci_tx = (1 << 11), + usbip_debug_vhci_sysfs = (1 << 12) +}; + +#define dbg_flag_xmit (usbip_debug_flag & usbip_debug_xmit) +#define dbg_flag_vhci_rh (usbip_debug_flag & usbip_debug_vhci_rh) +#define dbg_flag_vhci_hc (usbip_debug_flag & usbip_debug_vhci_hc) +#define dbg_flag_vhci_rx (usbip_debug_flag & usbip_debug_vhci_rx) +#define dbg_flag_vhci_tx (usbip_debug_flag & usbip_debug_vhci_tx) +#define dbg_flag_vhci_sysfs (usbip_debug_flag & usbip_debug_vhci_sysfs) +#define dbg_flag_stub_rx (usbip_debug_flag & usbip_debug_stub_rx) +#define dbg_flag_stub_tx (usbip_debug_flag & usbip_debug_stub_tx) + +extern unsigned long usbip_debug_flag; +extern struct device_attribute dev_attr_usbip_debug; + +#define dbg_with_flag(flag, fmt, args...) \ + do { \ + if (flag & usbip_debug_flag) \ + udbg(fmt , ##args); \ + } while (0) + +#define dbg_sysfs(fmt, args...) \ + dbg_with_flag(usbip_debug_sysfs, fmt , ##args) +#define dbg_xmit(fmt, args...) \ + dbg_with_flag(usbip_debug_xmit, fmt , ##args) +#define dbg_urb(fmt, args...) \ + dbg_with_flag(usbip_debug_urb, fmt , ##args) +#define dbg_eh(fmt, args...) \ + dbg_with_flag(usbip_debug_eh, fmt , ##args) + +#define dbg_vhci_rh(fmt, args...) \ + dbg_with_flag(usbip_debug_vhci_rh, fmt , ##args) +#define dbg_vhci_hc(fmt, args...) \ + dbg_with_flag(usbip_debug_vhci_hc, fmt , ##args) +#define dbg_vhci_rx(fmt, args...) \ + dbg_with_flag(usbip_debug_vhci_rx, fmt , ##args) +#define dbg_vhci_tx(fmt, args...) \ + dbg_with_flag(usbip_debug_vhci_tx, fmt , ##args) +#define dbg_vhci_sysfs(fmt, args...) \ + dbg_with_flag(usbip_debug_vhci_sysfs, fmt , ##args) + +#define dbg_stub_cmp(fmt, args...) \ + dbg_with_flag(usbip_debug_stub_cmp, fmt , ##args) +#define dbg_stub_rx(fmt, args...) \ + dbg_with_flag(usbip_debug_stub_rx, fmt , ##args) +#define dbg_stub_tx(fmt, args...) \ + dbg_with_flag(usbip_debug_stub_tx, fmt , ##args) + + +/** + * uerr - print error messages + * @fmt: + * @args: + */ +#define uerr(fmt, args...) \ + do { \ + printk(KERN_ERR "%-10s: ***ERROR*** (%s,%d) %s: " fmt, \ + (in_interrupt() ? "interrupt" : (current)->comm),\ + __FILE__, __LINE__, __func__, ##args); \ + } while (0) + +/** + * uinfo - print information messages + * @fmt: + * @args: + */ +#define uinfo(fmt, args...) \ + do { \ + printk(KERN_INFO "usbip: " fmt , ## args); \ + } while (0) + + +/*-------------------------------------------------------------------------*/ + +/* + * USB/IP request headers. + * Currently, we define 4 request types: + * + * - CMD_SUBMIT transfers a USB request, corresponding to usb_submit_urb(). + * (client to server) + * - RET_RETURN transfers the result of CMD_SUBMIT. + * (server to client) + * - CMD_UNLINK transfers an unlink request of a pending USB request. + * (client to server) + * - RET_UNLINK transfers the result of CMD_UNLINK. + * (server to client) + * + * Note: The below request formats are based on the USB subsystem of Linux. Its + * details will be defined when other implementations come. + * + * + */ + +/* + * A basic header followed by other additional headers. + */ +struct usbip_header_basic { +#define USBIP_CMD_SUBMIT 0x0001 +#define USBIP_CMD_UNLINK 0x0002 +#define USBIP_RET_SUBMIT 0x0003 +#define USBIP_RET_UNLINK 0x0004 + __u32 command; + + /* sequencial number which identifies requests. + * incremented per connections */ + __u32 seqnum; + + /* devid is used to specify a remote USB device uniquely instead + * of busnum and devnum in Linux. In the case of Linux stub_driver, + * this value is ((busnum << 16) | devnum) */ + __u32 devid; + +#define USBIP_DIR_OUT 0 +#define USBIP_DIR_IN 1 + __u32 direction; + __u32 ep; /* endpoint number */ +} __attribute__ ((packed)); + +/* + * An additional header for a CMD_SUBMIT packet. + */ +struct usbip_header_cmd_submit { + /* these values are basically the same as in a URB. */ + + /* the same in a URB. */ + __u32 transfer_flags; + + /* set the following data size (out), + * or expected reading data size (in) */ + __s32 transfer_buffer_length; + + /* it is difficult for usbip to sync frames (reserved only?) */ + __s32 start_frame; + + /* the number of iso descriptors that follows this header */ + __s32 number_of_packets; + + /* the maximum time within which this request works in a host + * controller of a server side */ + __s32 interval; + + /* set setup packet data for a CTRL request */ + unsigned char setup[8]; +} __attribute__ ((packed)); + +/* + * An additional header for a RET_SUBMIT packet. + */ +struct usbip_header_ret_submit { + __s32 status; + __s32 actual_length; /* returned data length */ + __s32 start_frame; /* ISO and INT */ + __s32 number_of_packets; /* ISO only */ + __s32 error_count; /* ISO only */ +} __attribute__ ((packed)); + +/* + * An additional header for a CMD_UNLINK packet. + */ +struct usbip_header_cmd_unlink { + __u32 seqnum; /* URB's seqnum which will be unlinked */ +} __attribute__ ((packed)); + + +/* + * An additional header for a RET_UNLINK packet. + */ +struct usbip_header_ret_unlink { + __s32 status; +} __attribute__ ((packed)); + + +/* the same as usb_iso_packet_descriptor but packed for pdu */ +struct usbip_iso_packet_descriptor { + __u32 offset; + __u32 length; /* expected length */ + __u32 actual_length; + __u32 status; +} __attribute__ ((packed)); + + +/* + * All usbip packets use a common header to keep code simple. + */ +struct usbip_header { + struct usbip_header_basic base; + + union { + struct usbip_header_cmd_submit cmd_submit; + struct usbip_header_ret_submit ret_submit; + struct usbip_header_cmd_unlink cmd_unlink; + struct usbip_header_ret_unlink ret_unlink; + } u; +} __attribute__ ((packed)); + + + + +/*-------------------------------------------------------------------------*/ + + +int usbip_xmit(int, struct socket *, char *, int, int); +int usbip_sendmsg(struct socket *, struct msghdr *, int); + + +static inline int interface_to_busnum(struct usb_interface *interface) +{ + struct usb_device *udev = interface_to_usbdev(interface); + return udev->bus->busnum; +} + +static inline int interface_to_devnum(struct usb_interface *interface) +{ + struct usb_device *udev = interface_to_usbdev(interface); + return udev->devnum; +} + +static inline int interface_to_infnum(struct usb_interface *interface) +{ + return interface->cur_altsetting->desc.bInterfaceNumber; +} + +#if 0 +int setnodelay(struct socket *); +int setquickack(struct socket *); +int setkeepalive(struct socket *socket); +void setreuse(struct socket *); +#endif + +struct socket *sockfd_to_socket(unsigned int); +int set_sockaddr(struct socket *socket, struct sockaddr_storage *ss); + +void usbip_dump_urb(struct urb *purb); +void usbip_dump_header(struct usbip_header *pdu); + + +struct usbip_device; + +struct usbip_task { + struct task_struct *thread; + struct completion thread_done; + char *name; + void (*loop_ops)(struct usbip_task *); +}; + +enum usbip_side { + USBIP_VHCI, + USBIP_STUB, +}; + +enum usbip_status { + /* sdev is available. */ + SDEV_ST_AVAILABLE = 0x01, + /* sdev is now used. */ + SDEV_ST_USED, + /* sdev is unusable because of a fatal error. */ + SDEV_ST_ERROR, + + /* vdev does not connect a remote device. */ + VDEV_ST_NULL, + /* vdev is used, but the USB address is not assigned yet */ + VDEV_ST_NOTASSIGNED, + VDEV_ST_USED, + VDEV_ST_ERROR +}; + +/* a common structure for stub_device and vhci_device */ +struct usbip_device { + enum usbip_side side; + + enum usbip_status status; + + /* lock for status */ + spinlock_t lock; + + struct socket *tcp_socket; + + struct usbip_task tcp_rx; + struct usbip_task tcp_tx; + + /* event handler */ +#define USBIP_EH_SHUTDOWN (1 << 0) +#define USBIP_EH_BYE (1 << 1) +#define USBIP_EH_RESET (1 << 2) +#define USBIP_EH_UNUSABLE (1 << 3) + +#define SDEV_EVENT_REMOVED (USBIP_EH_SHUTDOWN | USBIP_EH_RESET | USBIP_EH_BYE) +#define SDEV_EVENT_DOWN (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) +#define SDEV_EVENT_ERROR_TCP (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) +#define SDEV_EVENT_ERROR_SUBMIT (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) +#define SDEV_EVENT_ERROR_MALLOC (USBIP_EH_SHUTDOWN | USBIP_EH_UNUSABLE) + +#define VDEV_EVENT_REMOVED (USBIP_EH_SHUTDOWN | USBIP_EH_BYE) +#define VDEV_EVENT_DOWN (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) +#define VDEV_EVENT_ERROR_TCP (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) +#define VDEV_EVENT_ERROR_MALLOC (USBIP_EH_SHUTDOWN | USBIP_EH_UNUSABLE) + + unsigned long event; + struct usbip_task eh; + wait_queue_head_t eh_waitq; + + struct eh_ops { + void (*shutdown)(struct usbip_device *); + void (*reset)(struct usbip_device *); + void (*unusable)(struct usbip_device *); + } eh_ops; +}; + + +void usbip_task_init(struct usbip_task *ut, char *, + void (*loop_ops)(struct usbip_task *)); + +void usbip_start_threads(struct usbip_device *ud); +void usbip_stop_threads(struct usbip_device *ud); +int usbip_thread(void *param); + +void usbip_pack_pdu(struct usbip_header *pdu, struct urb *urb, int cmd, + int pack); + +void usbip_header_correct_endian(struct usbip_header *pdu, int send); +/* some members of urb must be substituted before. */ +int usbip_recv_xbuff(struct usbip_device *ud, struct urb *urb); +/* some members of urb must be substituted before. */ +int usbip_recv_iso(struct usbip_device *ud, struct urb *urb); +void *usbip_alloc_iso_desc_pdu(struct urb *urb, ssize_t *bufflen); + + +/* usbip_event.c */ +void usbip_start_eh(struct usbip_device *ud); +void usbip_stop_eh(struct usbip_device *ud); +void usbip_event_add(struct usbip_device *ud, unsigned long event); +int usbip_event_happend(struct usbip_device *ud); + + +#endif diff --git a/drivers/staging/usbip/usbip_event.c b/drivers/staging/usbip/usbip_event.c new file mode 100644 index 00000000000..4318553ab20 --- /dev/null +++ b/drivers/staging/usbip/usbip_event.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * + * This 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include "usbip_common.h" + +static int event_handler(struct usbip_device *ud) +{ + dbg_eh("enter\n"); + + /* + * Events are handled by only this thread. + */ + while (usbip_event_happend(ud)) { + dbg_eh("pending event %lx\n", ud->event); + + /* + * NOTE: shutdown must come first. + * Shutdown the device. + */ + if (ud->event & USBIP_EH_SHUTDOWN) { + ud->eh_ops.shutdown(ud); + + ud->event &= ~USBIP_EH_SHUTDOWN; + + break; + } + + /* Stop the error handler. */ + if (ud->event & USBIP_EH_BYE) + return -1; + + /* Reset the device. */ + if (ud->event & USBIP_EH_RESET) { + ud->eh_ops.reset(ud); + + ud->event &= ~USBIP_EH_RESET; + + break; + } + + /* Mark the device as unusable. */ + if (ud->event & USBIP_EH_UNUSABLE) { + ud->eh_ops.unusable(ud); + + ud->event &= ~USBIP_EH_UNUSABLE; + + break; + } + + /* NOTREACHED */ + printk(KERN_ERR "%s: unknown event\n", __func__); + return -1; + } + + return 0; +} + +static void event_handler_loop(struct usbip_task *ut) +{ + struct usbip_device *ud = container_of(ut, struct usbip_device, eh); + + while (1) { + if (signal_pending(current)) { + dbg_eh("signal catched!\n"); + break; + } + + if (event_handler(ud) < 0) + break; + + wait_event_interruptible(ud->eh_waitq, usbip_event_happend(ud)); + dbg_eh("wakeup\n"); + } +} + +void usbip_start_eh(struct usbip_device *ud) +{ + struct usbip_task *eh = &ud->eh; + + init_waitqueue_head(&ud->eh_waitq); + ud->event = 0; + + usbip_task_init(eh, "usbip_eh", event_handler_loop); + + kernel_thread(usbip_thread, (void *)eh, 0); + + wait_for_completion(&eh->thread_done); +} +EXPORT_SYMBOL_GPL(usbip_start_eh); + +void usbip_stop_eh(struct usbip_device *ud) +{ + struct usbip_task *eh = &ud->eh; + + wait_for_completion(&eh->thread_done); + dbg_eh("usbip_eh has finished\n"); +} +EXPORT_SYMBOL_GPL(usbip_stop_eh); + +void usbip_event_add(struct usbip_device *ud, unsigned long event) +{ + spin_lock(&ud->lock); + + ud->event |= event; + + wake_up(&ud->eh_waitq); + + spin_unlock(&ud->lock); +} +EXPORT_SYMBOL_GPL(usbip_event_add); + +int usbip_event_happend(struct usbip_device *ud) +{ + int happend = 0; + + spin_lock(&ud->lock); + + if (ud->event != 0) + happend = 1; + + spin_unlock(&ud->lock); + + return happend; +} +EXPORT_SYMBOL_GPL(usbip_event_happend); diff --git a/drivers/staging/usbip/vhci.h b/drivers/staging/usbip/vhci.h new file mode 100644 index 00000000000..5e375173bbc --- /dev/null +++ b/drivers/staging/usbip/vhci.h @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * + * This 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include <linux/platform_device.h> +#include "../../usb/core/hcd.h" + + +struct vhci_device { + struct usb_device *udev; + + /* + * devid specifies a remote usb device uniquely instead + * of combination of busnum and devnum. + */ + __u32 devid; + + /* speed of a remote device */ + enum usb_device_speed speed; + + /* vhci root-hub port to which this device is attached */ + __u32 rhport; + + struct usbip_device ud; + + + /* lock for the below link lists */ + spinlock_t priv_lock; + + /* vhci_priv is linked to one of them. */ + struct list_head priv_tx; + struct list_head priv_rx; + + /* vhci_unlink is linked to one of them */ + struct list_head unlink_tx; + struct list_head unlink_rx; + + /* vhci_tx thread sleeps for this queue */ + wait_queue_head_t waitq_tx; +}; + + +/* urb->hcpriv, use container_of() */ +struct vhci_priv { + unsigned long seqnum; + struct list_head list; + + struct vhci_device *vdev; + struct urb *urb; +}; + + +struct vhci_unlink { + /* seqnum of this request */ + unsigned long seqnum; + + struct list_head list; + + /* seqnum of the unlink target */ + unsigned long unlink_seqnum; +}; + +/* + * The number of ports is less than 16 ? + * USB_MAXCHILDREN is statically defined to 16 in usb.h. Its maximum value + * would be 31 because the event_bits[1] of struct usb_hub is defined as + * unsigned long in hub.h + */ +#define VHCI_NPORTS 8 + +/* for usb_bus.hcpriv */ +struct vhci_hcd { + spinlock_t lock; + + u32 port_status[VHCI_NPORTS]; + + unsigned resuming:1; + unsigned long re_timeout; + + atomic_t seqnum; + + /* + * NOTE: + * wIndex shows the port number and begins from 1. + * But, the index of this array begins from 0. + */ + struct vhci_device vdev[VHCI_NPORTS]; + + /* vhci_device which has not been assiged its address yet */ + int pending_port; +}; + + +extern struct vhci_hcd *the_controller; +extern struct attribute_group dev_attr_group; + + +/*-------------------------------------------------------------------------*/ +/* prototype declaration */ + +/* vhci_hcd.c */ +void rh_port_connect(int rhport, enum usb_device_speed speed); +void rh_port_disconnect(int rhport); +void vhci_rx_loop(struct usbip_task *ut); +void vhci_tx_loop(struct usbip_task *ut); + +#define hardware (&the_controller->pdev.dev) + +static inline struct vhci_device *port_to_vdev(__u32 port) +{ + return &the_controller->vdev[port]; +} + +static inline struct vhci_hcd *hcd_to_vhci(struct usb_hcd *hcd) +{ + return (struct vhci_hcd *) (hcd->hcd_priv); +} + +static inline struct usb_hcd *vhci_to_hcd(struct vhci_hcd *vhci) +{ + return container_of((void *) vhci, struct usb_hcd, hcd_priv); +} + +static inline struct device *vhci_dev(struct vhci_hcd *vhci) +{ + return vhci_to_hcd(vhci)->self.controller; +} diff --git a/drivers/staging/usbip/vhci_hcd.c b/drivers/staging/usbip/vhci_hcd.c new file mode 100644 index 00000000000..5b5a2e348ec --- /dev/null +++ b/drivers/staging/usbip/vhci_hcd.c @@ -0,0 +1,1275 @@ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * + * This 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + + +#include "usbip_common.h" +#include "vhci.h" + +#define DRIVER_VERSION "1.0" +#define DRIVER_AUTHOR "Takahiro Hirofuchi" +#define DRIVER_DESC "Virtual Host Controller Interface Driver for USB/IP" +#define DRIVER_LICENCE "GPL" +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENCE); + + + +/* + * TODO + * - update root hub emulation + * - move the emulation code to userland ? + * porting to other operating systems + * minimize kernel code + * - add suspend/resume code + * - clean up everything + */ + + +/* See usb gadget dummy hcd */ + + +static int vhci_hub_status(struct usb_hcd *hcd, char *buff); +static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buff, u16 wLength); +static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags); +static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status); +static int vhci_start(struct usb_hcd *vhci_hcd); +static void vhci_stop(struct usb_hcd *hcd); +static int vhci_get_frame_number(struct usb_hcd *hcd); + +static const char driver_name[] = "vhci_hcd"; +static const char driver_desc[] = "USB/IP Virtual Host Contoroller"; + +struct vhci_hcd *the_controller; + +static const char *bit_desc[] = { + "CONNECTION", /*0*/ + "ENABLE", /*1*/ + "SUSPEND", /*2*/ + "OVER_CURRENT", /*3*/ + "RESET", /*4*/ + "R5", /*5*/ + "R6", /*6*/ + "R7", /*7*/ + "POWER", /*8*/ + "LOWSPEED", /*9*/ + "HIGHSPEED", /*10*/ + "PORT_TEST", /*11*/ + "INDICATOR", /*12*/ + "R13", /*13*/ + "R14", /*14*/ + "R15", /*15*/ + "C_CONNECTION", /*16*/ + "C_ENABLE", /*17*/ + "C_SUSPEND", /*18*/ + "C_OVER_CURRENT", /*19*/ + "C_RESET", /*20*/ + "R21", /*21*/ + "R22", /*22*/ + "R23", /*23*/ + "R24", /*24*/ + "R25", /*25*/ + "R26", /*26*/ + "R27", /*27*/ + "R28", /*28*/ + "R29", /*29*/ + "R30", /*30*/ + "R31", /*31*/ +}; + + +static void dump_port_status(u32 status) +{ + int i = 0; + + printk(KERN_DEBUG "status %08x:", status); + for (i = 0; i < 32; i++) { + if (status & (1 << i)) + printk(" %s", bit_desc[i]); + } + + printk("\n"); +} + + + +void rh_port_connect(int rhport, enum usb_device_speed speed) +{ + unsigned long flags; + + dbg_vhci_rh("rh_port_connect %d\n", rhport); + + spin_lock_irqsave(&the_controller->lock, flags); + + the_controller->port_status[rhport] |= USB_PORT_STAT_CONNECTION + | (1 << USB_PORT_FEAT_C_CONNECTION); + + switch (speed) { + case USB_SPEED_HIGH: + the_controller->port_status[rhport] |= USB_PORT_STAT_HIGH_SPEED; + break; + case USB_SPEED_LOW: + the_controller->port_status[rhport] |= USB_PORT_STAT_LOW_SPEED; + break; + default: + break; + } + + /* spin_lock(&the_controller->vdev[rhport].ud.lock); + * the_controller->vdev[rhport].ud.status = VDEV_CONNECT; + * spin_unlock(&the_controller->vdev[rhport].ud.lock); */ + + the_controller->pending_port = rhport; + + spin_unlock_irqrestore(&the_controller->lock, flags); + + usb_hcd_poll_rh_status(vhci_to_hcd(the_controller)); +} + +void rh_port_disconnect(int rhport) +{ + unsigned long flags; + + dbg_vhci_rh("rh_port_disconnect %d\n", rhport); + + spin_lock_irqsave(&the_controller->lock, flags); + /* stop_activity(dum, driver); */ + the_controller->port_status[rhport] &= ~USB_PORT_STAT_CONNECTION; + the_controller->port_status[rhport] |= + (1 << USB_PORT_FEAT_C_CONNECTION); + + + /* not yet complete the disconnection + * spin_lock(&vdev->ud.lock); + * vdev->ud.status = VHC_ST_DISCONNECT; + * spin_unlock(&vdev->ud.lock); */ + + spin_unlock_irqrestore(&the_controller->lock, flags); +} + + + +/*----------------------------------------------------------------------*/ + +#define PORT_C_MASK \ + ((USB_PORT_STAT_C_CONNECTION \ + | USB_PORT_STAT_C_ENABLE \ + | USB_PORT_STAT_C_SUSPEND \ + | USB_PORT_STAT_C_OVERCURRENT \ + | USB_PORT_STAT_C_RESET) << 16) + +/* + * This function is almostly the same as dummy_hcd.c:dummy_hub_status() without + * suspend/resume support. But, it is modified to provide multiple ports. + * + * @buf: a bitmap to show which port status has been changed. + * bit 0: reserved or used for another purpose? + * bit 1: the status of port 0 has been changed. + * bit 2: the status of port 1 has been changed. + * ... + * bit 7: the status of port 6 has been changed. + * bit 8: the status of port 7 has been changed. + * ... + * bit 15: the status of port 14 has been changed. + * + * So, the maximum number of ports is 31 ( port 0 to port 30) ? + * + * The return value is the actual transfered length in byte. If nothing has + * been changed, return 0. In the case that the number of ports is less than or + * equal to 6 (VHCI_NPORTS==7), return 1. + * + */ +static int vhci_hub_status(struct usb_hcd *hcd, char *buf) +{ + struct vhci_hcd *vhci; + unsigned long flags; + int retval = 0; + + /* the enough buffer is allocated according to USB_MAXCHILDREN */ + unsigned long *event_bits = (unsigned long *) buf; + int rhport; + int changed = 0; + + + *event_bits = 0; + + vhci = hcd_to_vhci(hcd); + + spin_lock_irqsave(&vhci->lock, flags); + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + dbg_vhci_rh("hw accessible flag in on?\n"); + goto done; + } + + /* check pseudo status register for each port */ + for (rhport = 0; rhport < VHCI_NPORTS; rhport++) { + if ((vhci->port_status[rhport] & PORT_C_MASK)) { + /* The status of a port has been changed, */ + dbg_vhci_rh("port %d is changed\n", rhport); + + *event_bits |= 1 << (rhport + 1); + changed = 1; + } + } + + uinfo("changed %d\n", changed); + + if (hcd->state == HC_STATE_SUSPENDED) + usb_hcd_resume_root_hub(hcd); + + if (changed) + retval = 1 + (VHCI_NPORTS / 8); + else + retval = 0; + +done: + spin_unlock_irqrestore(&vhci->lock, flags); + return retval; +} + +/* See hub_configure in hub.c */ +static inline void hub_descriptor(struct usb_hub_descriptor *desc) +{ + memset(desc, 0, sizeof(*desc)); + desc->bDescriptorType = 0x29; + desc->bDescLength = 9; + desc->wHubCharacteristics = (__force __u16) + (__constant_cpu_to_le16(0x0001)); + desc->bNbrPorts = VHCI_NPORTS; + desc->bitmap[0] = 0xff; + desc->bitmap[1] = 0xff; +} + +static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct vhci_hcd *dum; + int retval = 0; + unsigned long flags; + int rhport; + + u32 prev_port_status[VHCI_NPORTS]; + + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) + return -ETIMEDOUT; + + /* + * NOTE: + * wIndex shows the port number and begins from 1. + */ + dbg_vhci_rh("typeReq %x wValue %x wIndex %x\n", typeReq, wValue, + wIndex); + if (wIndex > VHCI_NPORTS) + printk(KERN_ERR "%s: invalid port number %d\n", __func__, wIndex); + rhport = ((__u8)(wIndex & 0x00ff)) - 1; + + dum = hcd_to_vhci(hcd); + + spin_lock_irqsave(&dum->lock, flags); + + /* store old status and compare now and old later */ + if (dbg_flag_vhci_rh) { + int i = 0; + for (i = 0; i < VHCI_NPORTS; i++) + prev_port_status[i] = dum->port_status[i]; + } + + switch (typeReq) { + case ClearHubFeature: + dbg_vhci_rh(" ClearHubFeature\n"); + break; + case ClearPortFeature: + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + if (dum->port_status[rhport] & USB_PORT_STAT_SUSPEND) { + /* 20msec signaling */ + dum->resuming = 1; + dum->re_timeout = + jiffies + msecs_to_jiffies(20); + } + break; + case USB_PORT_FEAT_POWER: + dbg_vhci_rh(" ClearPortFeature: USB_PORT_FEAT_POWER\n"); + dum->port_status[rhport] = 0; + /* dum->address = 0; */ + /* dum->hdev = 0; */ + dum->resuming = 0; + break; + case USB_PORT_FEAT_C_RESET: + dbg_vhci_rh(" ClearPortFeature: " + "USB_PORT_FEAT_C_RESET\n"); + switch (dum->vdev[rhport].speed) { + case USB_SPEED_HIGH: + dum->port_status[rhport] |= + USB_PORT_STAT_HIGH_SPEED; + break; + case USB_SPEED_LOW: + dum->port_status[rhport] |= + USB_PORT_STAT_LOW_SPEED; + break; + default: + break; + } + default: + dbg_vhci_rh(" ClearPortFeature: default %x\n", wValue); + dum->port_status[rhport] &= ~(1 << wValue); + } + break; + case GetHubDescriptor: + dbg_vhci_rh(" GetHubDescriptor\n"); + hub_descriptor((struct usb_hub_descriptor *) buf); + break; + case GetHubStatus: + dbg_vhci_rh(" GetHubStatus\n"); + *(__le32 *) buf = __constant_cpu_to_le32(0); + break; + case GetPortStatus: + dbg_vhci_rh(" GetPortStatus port %x\n", wIndex); + if (wIndex > VHCI_NPORTS || wIndex < 1) { + printk(KERN_ERR "%s: invalid port number %d\n", + __func__, wIndex); + retval = -EPIPE; + } + + /* we do no care of resume. */ + + /* whoever resets or resumes must GetPortStatus to + * complete it!! + * */ + if (dum->resuming && time_after(jiffies, dum->re_timeout)) { + printk(KERN_ERR "%s: not yet\n", __func__); + dum->port_status[rhport] |= + (1 << USB_PORT_FEAT_C_SUSPEND); + dum->port_status[rhport] &= + ~(1 << USB_PORT_FEAT_SUSPEND); + dum->resuming = 0; + dum->re_timeout = 0; + /* if (dum->driver && dum->driver->resume) { + * spin_unlock (&dum->lock); + * dum->driver->resume (&dum->gadget); + * spin_lock (&dum->lock); + * } */ + } + + if ((dum->port_status[rhport] & (1 << USB_PORT_FEAT_RESET)) != + 0 && time_after(jiffies, dum->re_timeout)) { + dum->port_status[rhport] |= + (1 << USB_PORT_FEAT_C_RESET); + dum->port_status[rhport] &= + ~(1 << USB_PORT_FEAT_RESET); + dum->re_timeout = 0; + + if (dum->vdev[rhport].ud.status == + VDEV_ST_NOTASSIGNED) { + dbg_vhci_rh(" enable rhport %d (status %u)\n", + rhport, + dum->vdev[rhport].ud.status); + dum->port_status[rhport] |= + USB_PORT_STAT_ENABLE; + } +#if 0 + if (dum->driver) { + + dum->port_status[rhport] |= + USB_PORT_STAT_ENABLE; + /* give it the best speed we agree on */ + dum->gadget.speed = dum->driver->speed; + dum->gadget.ep0->maxpacket = 64; + switch (dum->gadget.speed) { + case USB_SPEED_HIGH: + dum->port_status[rhport] |= + USB_PORT_STAT_HIGH_SPEED; + break; + case USB_SPEED_LOW: + dum->gadget.ep0->maxpacket = 8; + dum->port_status[rhport] |= + USB_PORT_STAT_LOW_SPEED; + break; + default: + dum->gadget.speed = USB_SPEED_FULL; + break; + } + } +#endif + + } + ((u16 *) buf)[0] = cpu_to_le16(dum->port_status[rhport]); + ((u16 *) buf)[1] = + cpu_to_le16(dum->port_status[rhport] >> 16); + + dbg_vhci_rh(" GetPortStatus bye %x %x\n", ((u16 *)buf)[0], + ((u16 *)buf)[1]); + break; + case SetHubFeature: + dbg_vhci_rh(" SetHubFeature\n"); + retval = -EPIPE; + break; + case SetPortFeature: + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + dbg_vhci_rh(" SetPortFeature: " + "USB_PORT_FEAT_SUSPEND\n"); + printk(KERN_ERR "%s: not yet\n", __func__); +#if 0 + dum->port_status[rhport] |= + (1 << USB_PORT_FEAT_SUSPEND); + if (dum->driver->suspend) { + spin_unlock(&dum->lock); + dum->driver->suspend(&dum->gadget); + spin_lock(&dum->lock); + } +#endif + break; + case USB_PORT_FEAT_RESET: + dbg_vhci_rh(" SetPortFeature: USB_PORT_FEAT_RESET\n"); + /* if it's already running, disconnect first */ + if (dum->port_status[rhport] & USB_PORT_STAT_ENABLE) { + dum->port_status[rhport] &= + ~(USB_PORT_STAT_ENABLE | + USB_PORT_STAT_LOW_SPEED | + USB_PORT_STAT_HIGH_SPEED); +#if 0 + if (dum->driver) { + dev_dbg(hardware, "disconnect\n"); + stop_activity(dum, dum->driver); + } +#endif + + /* FIXME test that code path! */ + } + /* 50msec reset signaling */ + dum->re_timeout = jiffies + msecs_to_jiffies(50); + + /* FALLTHROUGH */ + default: + dbg_vhci_rh(" SetPortFeature: default %d\n", wValue); + dum->port_status[rhport] |= (1 << wValue); + } + break; + + default: + printk(KERN_ERR "%s: default: no such request\n", __func__); + /* dev_dbg (hardware, + * "hub control req%04x v%04x i%04x l%d\n", + * typeReq, wValue, wIndex, wLength); */ + + /* "protocol stall" on error */ + retval = -EPIPE; + } + + if (dbg_flag_vhci_rh) { + printk(KERN_DEBUG "port %d\n", rhport); + dump_port_status(prev_port_status[rhport]); + dump_port_status(dum->port_status[rhport]); + } + dbg_vhci_rh(" bye\n"); + + spin_unlock_irqrestore(&dum->lock, flags); + + return retval; +} + + + +/*----------------------------------------------------------------------*/ + +static struct vhci_device *get_vdev(struct usb_device *udev) +{ + int i; + + if (!udev) + return NULL; + + for (i = 0; i < VHCI_NPORTS; i++) + if (the_controller->vdev[i].udev == udev) + return port_to_vdev(i); + + return NULL; +} + +static void vhci_tx_urb(struct urb *urb) +{ + struct vhci_device *vdev = get_vdev(urb->dev); + struct vhci_priv *priv; + unsigned long flag; + + if (!vdev) { + err("could not get virtual device"); + /* BUG(); */ + return; + } + + spin_lock_irqsave(&vdev->priv_lock, flag); + + priv = kzalloc(sizeof(struct vhci_priv), GFP_ATOMIC); + if (!priv) { + dev_err(&urb->dev->dev, "malloc vhci_priv\n"); + spin_unlock_irqrestore(&vdev->priv_lock, flag); + usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_MALLOC); + return; + } + + priv->seqnum = atomic_inc_return(&the_controller->seqnum); + if (priv->seqnum == 0xffff) + uinfo("seqnum max\n"); + + priv->vdev = vdev; + priv->urb = urb; + + urb->hcpriv = (void *) priv; + + + list_add_tail(&priv->list, &vdev->priv_tx); + + wake_up(&vdev->waitq_tx); + spin_unlock_irqrestore(&vdev->priv_lock, flag); +} + +static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct device *dev = &urb->dev->dev; + int ret = 0; + unsigned long flags; + + dbg_vhci_hc("enter, usb_hcd %p urb %p mem_flags %d\n", + hcd, urb, mem_flags); + + /* patch to usb_sg_init() is in 2.5.60 */ + BUG_ON(!urb->transfer_buffer && urb->transfer_buffer_length); + + spin_lock_irqsave(&the_controller->lock, flags); + + /* check HC is active or not */ + if (!HC_IS_RUNNING(hcd->state)) { + dev_err(dev, "HC is not running\n"); + spin_unlock_irqrestore(&the_controller->lock, flags); + return -ENODEV; + } + + if (urb->status != -EINPROGRESS) { + dev_err(dev, "URB already unlinked!, status %d\n", urb->status); + spin_unlock_irqrestore(&the_controller->lock, flags); + return urb->status; + } + + ret = usb_hcd_link_urb_to_ep(hcd, urb); + if (ret) + goto no_need_unlink; + + /* + * The enumelation process is as follows; + * + * 1. Get_Descriptor request to DevAddrs(0) EndPoint(0) + * to get max packet length of default pipe + * + * 2. Set_Address request to DevAddr(0) EndPoint(0) + * + */ + + if (usb_pipedevice(urb->pipe) == 0) { + __u8 type = usb_pipetype(urb->pipe); + struct usb_ctrlrequest *ctrlreq = + (struct usb_ctrlrequest *) urb->setup_packet; + struct vhci_device *vdev = + port_to_vdev(the_controller->pending_port); + + if (type != PIPE_CONTROL || !ctrlreq) { + dev_err(dev, "invalid request to devnum 0\n"); + ret = EINVAL; + goto no_need_xmit; + } + + switch (ctrlreq->bRequest) { + case USB_REQ_SET_ADDRESS: + /* set_address may come when a device is reset */ + dev_info(dev, "SetAddress Request (%d) to port %d\n", + ctrlreq->wValue, vdev->rhport); + + vdev->udev = urb->dev; + + spin_lock(&vdev->ud.lock); + vdev->ud.status = VDEV_ST_USED; + spin_unlock(&vdev->ud.lock); + + if (urb->status == -EINPROGRESS) { + /* This request is successfully completed. */ + /* If not -EINPROGRESS, possibly unlinked. */ + urb->status = 0; + } + + goto no_need_xmit; + + case USB_REQ_GET_DESCRIPTOR: + if (ctrlreq->wValue == (USB_DT_DEVICE << 8)) + dbg_vhci_hc("Not yet?: " + "Get_Descriptor to device 0 " + "(get max pipe size)\n"); + + /* FIXME: reference count? (usb_get_dev()) */ + vdev->udev = urb->dev; + goto out; + + default: + /* NOT REACHED */ + dev_err(dev, "invalid request to devnum 0 bRequest %u, " + "wValue %u\n", ctrlreq->bRequest, + ctrlreq->wValue); + ret = -EINVAL; + goto no_need_xmit; + } + + } + +out: + vhci_tx_urb(urb); + + spin_unlock_irqrestore(&the_controller->lock, flags); + + return 0; + +no_need_xmit: + usb_hcd_unlink_urb_from_ep(hcd, urb); +no_need_unlink: + spin_unlock_irqrestore(&the_controller->lock, flags); + + usb_hcd_giveback_urb(vhci_to_hcd(the_controller), urb, urb->status); + + return 0; +} + +/* + * vhci_rx gives back the urb after receiving the reply of the urb. If an + * unlink pdu is sent or not, vhci_rx receives a normal return pdu and gives + * back its urb. For the driver unlinking the urb, the content of the urb is + * not important, but the calling to its completion handler is important; the + * completion of unlinking is notified by the completion handler. + * + * + * CLIENT SIDE + * + * - When vhci_hcd receives RET_SUBMIT, + * + * - case 1a). the urb of the pdu is not unlinking. + * - normal case + * => just give back the urb + * + * - case 1b). the urb of the pdu is unlinking. + * - usbip.ko will return a reply of the unlinking request. + * => give back the urb now and go to case 2b). + * + * - When vhci_hcd receives RET_UNLINK, + * + * - case 2a). a submit request is still pending in vhci_hcd. + * - urb was really pending in usbip.ko and urb_unlink_urb() was + * completed there. + * => free a pending submit request + * => notify unlink completeness by giving back the urb + * + * - case 2b). a submit request is *not* pending in vhci_hcd. + * - urb was already given back to the core driver. + * => do not give back the urb + * + * + * SERVER SIDE + * + * - When usbip receives CMD_UNLINK, + * + * - case 3a). the urb of the unlink request is now in submission. + * => do usb_unlink_urb(). + * => after the unlink is completed, send RET_UNLINK. + * + * - case 3b). the urb of the unlink request is not in submission. + * - may be already completed or never be received + * => send RET_UNLINK + * + */ +static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + unsigned long flags; + struct vhci_priv *priv; + struct vhci_device *vdev; + + uinfo("vhci_hcd: dequeue a urb %p\n", urb); + + + spin_lock_irqsave(&the_controller->lock, flags); + + priv = urb->hcpriv; + if (!priv) { + /* URB was never linked! or will be soon given back by + * vhci_rx. */ + spin_unlock_irqrestore(&the_controller->lock, flags); + return 0; + } + + { + int ret = 0; + ret = usb_hcd_check_unlink_urb(hcd, urb, status); + if (ret) { + spin_unlock_irqrestore(&the_controller->lock, flags); + return 0; + } + } + + /* send unlink request here? */ + vdev = priv->vdev; + + if (!vdev->ud.tcp_socket) { + /* tcp connection is closed */ + unsigned long flags2; + + spin_lock_irqsave(&vdev->priv_lock, flags2); + + uinfo("vhci_hcd: device %p seems to be disconnected\n", vdev); + list_del(&priv->list); + kfree(priv); + urb->hcpriv = NULL; + + spin_unlock_irqrestore(&vdev->priv_lock, flags2); + + } else { + /* tcp connection is alive */ + unsigned long flags2; + struct vhci_unlink *unlink; + + spin_lock_irqsave(&vdev->priv_lock, flags2); + + /* setup CMD_UNLINK pdu */ + unlink = kzalloc(sizeof(struct vhci_unlink), GFP_ATOMIC); + if (!unlink) { + uerr("malloc vhci_unlink\n"); + spin_unlock_irqrestore(&vdev->priv_lock, flags2); + spin_unlock_irqrestore(&the_controller->lock, flags); + usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_MALLOC); + return -ENOMEM; + } + + unlink->seqnum = atomic_inc_return(&the_controller->seqnum); + if (unlink->seqnum == 0xffff) + uinfo("seqnum max\n"); + + unlink->unlink_seqnum = priv->seqnum; + + uinfo("vhci_hcd: device %p seems to be still connected\n", + vdev); + + /* send cmd_unlink and try to cancel the pending URB in the + * peer */ + list_add_tail(&unlink->list, &vdev->unlink_tx); + wake_up(&vdev->waitq_tx); + + spin_unlock_irqrestore(&vdev->priv_lock, flags2); + } + + + /* + * If tcp connection is alive, we have sent CMD_UNLINK. + * vhci_rx will receive RET_UNLINK and give back the URB. + * Otherwise, we give back it here. + */ + if (!vdev->ud.tcp_socket) { + /* tcp connection is closed */ + uinfo("vhci_hcd: vhci_urb_dequeue() gives back urb %p\n", urb); + + usb_hcd_unlink_urb_from_ep(hcd, urb); + + spin_unlock_irqrestore(&the_controller->lock, flags); + usb_hcd_giveback_urb(vhci_to_hcd(the_controller), urb, + urb->status); + spin_lock_irqsave(&the_controller->lock, flags); + } + + spin_unlock_irqrestore(&the_controller->lock, flags); + + dbg_vhci_hc("leave\n"); + return 0; +} + + +static void vhci_device_unlink_cleanup(struct vhci_device *vdev) +{ + struct vhci_unlink *unlink, *tmp; + + spin_lock(&vdev->priv_lock); + + list_for_each_entry_safe(unlink, tmp, &vdev->unlink_tx, list) { + list_del(&unlink->list); + kfree(unlink); + } + + list_for_each_entry_safe(unlink, tmp, &vdev->unlink_rx, list) { + list_del(&unlink->list); + kfree(unlink); + } + + spin_unlock(&vdev->priv_lock); +} + +/* + * The important thing is that only one context begins cleanup. + * This is why error handling and cleanup become simple. + * We do not want to consider race condition as possible. + */ +static void vhci_shutdown_connection(struct usbip_device *ud) +{ + struct vhci_device *vdev = container_of(ud, struct vhci_device, ud); + + /* need this? see stub_dev.c */ + if (ud->tcp_socket) { + udbg("shutdown tcp_socket %p\n", ud->tcp_socket); + kernel_sock_shutdown(ud->tcp_socket, SHUT_RDWR); + } + + usbip_stop_threads(&vdev->ud); + uinfo("stop threads\n"); + + /* active connection is closed */ + if (vdev->ud.tcp_socket != NULL) { + sock_release(vdev->ud.tcp_socket); + vdev->ud.tcp_socket = NULL; + } + uinfo("release socket\n"); + + vhci_device_unlink_cleanup(vdev); + + /* + * rh_port_disconnect() is a trigger of ... + * usb_disable_device(): + * disable all the endpoints for a USB device. + * usb_disable_endpoint(): + * disable endpoints. pending urbs are unlinked(dequeued). + * + * NOTE: After calling rh_port_disconnect(), the USB device drivers of a + * deteched device should release used urbs in a cleanup function(i.e. + * xxx_disconnect()). Therefore, vhci_hcd does not need to release + * pushed urbs and their private data in this function. + * + * NOTE: vhci_dequeue() must be considered carefully. When shutdowning + * a connection, vhci_shutdown_connection() expects vhci_dequeue() + * gives back pushed urbs and frees their private data by request of + * the cleanup function of a USB driver. When unlinking a urb with an + * active connection, vhci_dequeue() does not give back the urb which + * is actually given back by vhci_rx after receiving its return pdu. + * + */ + rh_port_disconnect(vdev->rhport); + + uinfo("disconnect device\n"); +} + + +static void vhci_device_reset(struct usbip_device *ud) +{ + struct vhci_device *vdev = container_of(ud, struct vhci_device, ud); + + spin_lock(&ud->lock); + + vdev->speed = 0; + vdev->devid = 0; + + ud->tcp_socket = NULL; + + ud->status = VDEV_ST_NULL; + + spin_unlock(&ud->lock); +} + +static void vhci_device_unusable(struct usbip_device *ud) +{ + spin_lock(&ud->lock); + + ud->status = VDEV_ST_ERROR; + + spin_unlock(&ud->lock); +} + +static void vhci_device_init(struct vhci_device *vdev) +{ + memset(vdev, 0, sizeof(*vdev)); + + usbip_task_init(&vdev->ud.tcp_rx, "vhci_rx", vhci_rx_loop); + usbip_task_init(&vdev->ud.tcp_tx, "vhci_tx", vhci_tx_loop); + + vdev->ud.side = USBIP_VHCI; + vdev->ud.status = VDEV_ST_NULL; + /* vdev->ud.lock = SPIN_LOCK_UNLOCKED; */ + spin_lock_init(&vdev->ud.lock); + + INIT_LIST_HEAD(&vdev->priv_rx); + INIT_LIST_HEAD(&vdev->priv_tx); + INIT_LIST_HEAD(&vdev->unlink_tx); + INIT_LIST_HEAD(&vdev->unlink_rx); + /* vdev->priv_lock = SPIN_LOCK_UNLOCKED; */ + spin_lock_init(&vdev->priv_lock); + + init_waitqueue_head(&vdev->waitq_tx); + + vdev->ud.eh_ops.shutdown = vhci_shutdown_connection; + vdev->ud.eh_ops.reset = vhci_device_reset; + vdev->ud.eh_ops.unusable = vhci_device_unusable; + + usbip_start_eh(&vdev->ud); +} + + +/*----------------------------------------------------------------------*/ + +static int vhci_start(struct usb_hcd *hcd) +{ + struct vhci_hcd *vhci = hcd_to_vhci(hcd); + int rhport; + int err = 0; + + dbg_vhci_hc("enter vhci_start\n"); + + + /* initialize private data of usb_hcd */ + + for (rhport = 0; rhport < VHCI_NPORTS; rhport++) { + struct vhci_device *vdev = &vhci->vdev[rhport]; + vhci_device_init(vdev); + vdev->rhport = rhport; + } + + atomic_set(&vhci->seqnum, 0); + spin_lock_init(&vhci->lock); + + + + hcd->power_budget = 0; /* no limit */ + hcd->state = HC_STATE_RUNNING; + hcd->uses_new_polling = 1; + + + /* vhci_hcd is now ready to be controlled through sysfs */ + err = sysfs_create_group(&vhci_dev(vhci)->kobj, &dev_attr_group); + if (err) { + uerr("create sysfs files\n"); + return err; + } + + return 0; +} + +static void vhci_stop(struct usb_hcd *hcd) +{ + struct vhci_hcd *vhci = hcd_to_vhci(hcd); + int rhport = 0; + + dbg_vhci_hc("stop VHCI controller\n"); + + + /* 1. remove the userland interface of vhci_hcd */ + sysfs_remove_group(&vhci_dev(vhci)->kobj, &dev_attr_group); + + /* 2. shutdown all the ports of vhci_hcd */ + for (rhport = 0 ; rhport < VHCI_NPORTS; rhport++) { + struct vhci_device *vdev = &vhci->vdev[rhport]; + + usbip_event_add(&vdev->ud, VDEV_EVENT_REMOVED); + usbip_stop_eh(&vdev->ud); + } + + + uinfo("vhci_stop done\n"); +} + +/*----------------------------------------------------------------------*/ + +static int vhci_get_frame_number(struct usb_hcd *hcd) +{ + uerr("Not yet implemented\n"); + return 0; +} + + +#ifdef CONFIG_PM + +/* FIXME: suspend/resume */ +static int vhci_bus_suspend(struct usb_hcd *hcd) +{ + struct vhci_hcd *vhci = hcd_to_vhci(hcd); + + dev_dbg(&hcd->self.root_hub->dev, "%s\n", __func__); + + spin_lock_irq(&vhci->lock); + /* vhci->rh_state = DUMMY_RH_SUSPENDED; + * set_link_state(vhci); */ + hcd->state = HC_STATE_SUSPENDED; + spin_unlock_irq(&vhci->lock); + + return 0; +} + +static int vhci_bus_resume(struct usb_hcd *hcd) +{ + struct vhci_hcd *vhci = hcd_to_vhci(hcd); + int rc = 0; + + dev_dbg(&hcd->self.root_hub->dev, "%s\n", __func__); + + spin_lock_irq(&vhci->lock); + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + rc = -ESHUTDOWN; + } else { + /* vhci->rh_state = DUMMY_RH_RUNNING; + * set_link_state(vhci); + * if (!list_empty(&vhci->urbp_list)) + * mod_timer(&vhci->timer, jiffies); */ + hcd->state = HC_STATE_RUNNING; + } + spin_unlock_irq(&vhci->lock); + return rc; + + return 0; +} + +#else + +#define vhci_bus_suspend NULL +#define vhci_bus_resume NULL +#endif + + + +static struct hc_driver vhci_hc_driver = { + .description = driver_name, + .product_desc = driver_desc, + .hcd_priv_size = sizeof(struct vhci_hcd), + + .flags = HCD_USB2, + + .start = vhci_start, + .stop = vhci_stop, + + .urb_enqueue = vhci_urb_enqueue, + .urb_dequeue = vhci_urb_dequeue, + + .get_frame_number = vhci_get_frame_number, + + .hub_status_data = vhci_hub_status, + .hub_control = vhci_hub_control, + .bus_suspend = vhci_bus_suspend, + .bus_resume = vhci_bus_resume, +}; + +static int vhci_hcd_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + int ret; + + uinfo("proving...\n"); + + dbg_vhci_hc("name %s id %d\n", pdev->name, pdev->id); + + /* will be removed */ + if (pdev->dev.dma_mask) { + dev_info(&pdev->dev, "vhci_hcd DMA not supported\n"); + return -EINVAL; + } + + /* + * Allocate and initialize hcd. + * Our private data is also allocated automatically. + */ + hcd = usb_create_hcd(&vhci_hc_driver, &pdev->dev, pdev->dev.bus_id); + if (!hcd) { + uerr("create hcd failed\n"); + return -ENOMEM; + } + + + /* this is private data for vhci_hcd */ + the_controller = hcd_to_vhci(hcd); + + /* + * Finish generic HCD structure initialization and register. + * Call the driver's reset() and start() routines. + */ + ret = usb_add_hcd(hcd, 0, 0); + if (ret != 0) { + uerr("usb_add_hcd failed %d\n", ret); + usb_put_hcd(hcd); + the_controller = NULL; + return ret; + } + + + dbg_vhci_hc("bye\n"); + return 0; +} + + +static int vhci_hcd_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + + hcd = platform_get_drvdata(pdev); + if (!hcd) + return 0; + + /* + * Disconnects the root hub, + * then reverses the effects of usb_add_hcd(), + * invoking the HCD's stop() methods. + */ + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + the_controller = NULL; + + + return 0; +} + + + +#ifdef CONFIG_PM + +/* what should happen for USB/IP under suspend/resume? */ +static int vhci_hcd_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct usb_hcd *hcd; + int rhport = 0; + int connected = 0; + int ret = 0; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + hcd = platform_get_drvdata(pdev); + + spin_lock(&the_controller->lock); + + for (rhport = 0; rhport < VHCI_NPORTS; rhport++) + if (the_controller->port_status[rhport] & + USB_PORT_STAT_CONNECTION) + connected += 1; + + spin_unlock(&the_controller->lock); + + if (connected > 0) { + uinfo("We have %d active connection%s. Do not suspend.\n", + connected, (connected == 1 ? "" : "s")); + ret = -EBUSY; + } else { + uinfo("suspend vhci_hcd"); + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + } + + return ret; +} + +static int vhci_hcd_resume(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + hcd = platform_get_drvdata(pdev); + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + usb_hcd_poll_rh_status(hcd); + + return 0; +} + +#else + +#define vhci_hcd_suspend NULL +#define vhci_hcd_resume NULL + +#endif + + +static struct platform_driver vhci_driver = { + .probe = vhci_hcd_probe, + .remove = __devexit_p(vhci_hcd_remove), + .suspend = vhci_hcd_suspend, + .resume = vhci_hcd_resume, + .driver = { + .name = (char *) driver_name, + .owner = THIS_MODULE, + }, +}; + +/*----------------------------------------------------------------------*/ + +/* + * The VHCI 'device' is 'virtual'; not a real plug&play hardware. + * We need to add this virtual device as a platform device arbitrarily: + * 1. platform_device_register() + */ +static void the_pdev_release(struct device *dev) +{ + return; +} + +static struct platform_device the_pdev = { + /* should be the same name as driver_name */ + .name = (char *) driver_name, + .id = -1, + .dev = { + /* .driver = &vhci_driver, */ + .release = the_pdev_release, + }, +}; + +static int __init vhci_init(void) +{ + int ret; + + dbg_vhci_hc("enter\n"); + if (usb_disabled()) + return -ENODEV; + + printk(KERN_INFO KBUILD_MODNAME ": %s, %s\n", driver_name, + DRIVER_VERSION); + + ret = platform_driver_register(&vhci_driver); + if (ret < 0) + goto err_driver_register; + + ret = platform_device_register(&the_pdev); + if (ret < 0) + goto err_platform_device_register; + + dbg_vhci_hc("bye\n"); + return ret; + + /* error occurred */ +err_platform_device_register: + platform_driver_unregister(&vhci_driver); + +err_driver_register: + dbg_vhci_hc("bye\n"); + return ret; +} +module_init(vhci_init); + +static void __exit vhci_cleanup(void) +{ + dbg_vhci_hc("enter\n"); + + platform_device_unregister(&the_pdev); + platform_driver_unregister(&vhci_driver); + + dbg_vhci_hc("bye\n"); +} +module_exit(vhci_cleanup); diff --git a/drivers/staging/usbip/vhci_rx.c b/drivers/staging/usbip/vhci_rx.c new file mode 100644 index 00000000000..933ccaf50af --- /dev/null +++ b/drivers/staging/usbip/vhci_rx.c @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * + * This 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include "usbip_common.h" +#include "vhci.h" + + +/* get URB from transmitted urb queue */ +static struct urb *pickup_urb_and_free_priv(struct vhci_device *vdev, + __u32 seqnum) +{ + struct vhci_priv *priv, *tmp; + struct urb *urb = NULL; + int status; + + spin_lock(&vdev->priv_lock); + + list_for_each_entry_safe(priv, tmp, &vdev->priv_rx, list) { + if (priv->seqnum == seqnum) { + urb = priv->urb; + status = urb->status; + + dbg_vhci_rx("find urb %p vurb %p seqnum %u\n", + urb, priv, seqnum); + + /* TODO: fix logic here to improve indent situtation */ + if (status != -EINPROGRESS) { + if (status == -ENOENT || + status == -ECONNRESET) + dev_info(&urb->dev->dev, + "urb %p was unlinked " + "%ssynchronuously.\n", urb, + status == -ENOENT ? "" : "a"); + else + dev_info(&urb->dev->dev, + "urb %p may be in a error, " + "status %d\n", urb, status); + } + + list_del(&priv->list); + kfree(priv); + urb->hcpriv = NULL; + + break; + } + } + + spin_unlock(&vdev->priv_lock); + + return urb; +} + +static void vhci_recv_ret_submit(struct vhci_device *vdev, + struct usbip_header *pdu) +{ + struct usbip_device *ud = &vdev->ud; + struct urb *urb; + + + urb = pickup_urb_and_free_priv(vdev, pdu->base.seqnum); + + + if (!urb) { + uerr("cannot find a urb of seqnum %u\n", pdu->base.seqnum); + uinfo("max seqnum %d\n", atomic_read(&the_controller->seqnum)); + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + return; + } + + + /* unpack the pdu to a urb */ + usbip_pack_pdu(pdu, urb, USBIP_RET_SUBMIT, 0); + + + /* recv transfer buffer */ + if (usbip_recv_xbuff(ud, urb) < 0) + return; + + + /* recv iso_packet_descriptor */ + if (usbip_recv_iso(ud, urb) < 0) + return; + + + if (dbg_flag_vhci_rx) + usbip_dump_urb(urb); + + + dbg_vhci_rx("now giveback urb %p\n", urb); + + spin_lock(&the_controller->lock); + usb_hcd_unlink_urb_from_ep(vhci_to_hcd(the_controller), urb); + spin_unlock(&the_controller->lock); + + usb_hcd_giveback_urb(vhci_to_hcd(the_controller), urb, urb->status); + + + dbg_vhci_rx("Leave\n"); + + return; +} + + +static struct vhci_unlink *dequeue_pending_unlink(struct vhci_device *vdev, + struct usbip_header *pdu) +{ + struct vhci_unlink *unlink, *tmp; + + spin_lock(&vdev->priv_lock); + + list_for_each_entry_safe(unlink, tmp, &vdev->unlink_rx, list) { + uinfo("unlink->seqnum %lu\n", unlink->seqnum); + if (unlink->seqnum == pdu->base.seqnum) { + dbg_vhci_rx("found pending unlink, %lu\n", + unlink->seqnum); + list_del(&unlink->list); + + spin_unlock(&vdev->priv_lock); + return unlink; + } + } + + spin_unlock(&vdev->priv_lock); + + return NULL; +} + + +static void vhci_recv_ret_unlink(struct vhci_device *vdev, + struct usbip_header *pdu) +{ + struct vhci_unlink *unlink; + struct urb *urb; + + usbip_dump_header(pdu); + + unlink = dequeue_pending_unlink(vdev, pdu); + if (!unlink) { + uinfo("cannot find the pending unlink %u\n", pdu->base.seqnum); + return; + } + + urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum); + if (!urb) { + /* + * I get the result of a unlink request. But, it seems that I + * already received the result of its submit result and gave + * back the URB. + */ + uinfo("the urb (seqnum %d) was already given backed\n", + pdu->base.seqnum); + } else { + dbg_vhci_rx("now giveback urb %p\n", urb); + + /* If unlink is succeed, status is -ECONNRESET */ + urb->status = pdu->u.ret_unlink.status; + uinfo("%d\n", urb->status); + + spin_lock(&the_controller->lock); + usb_hcd_unlink_urb_from_ep(vhci_to_hcd(the_controller), urb); + spin_unlock(&the_controller->lock); + + usb_hcd_giveback_urb(vhci_to_hcd(the_controller), urb, + urb->status); + } + + kfree(unlink); + + return; +} + +/* recv a pdu */ +static void vhci_rx_pdu(struct usbip_device *ud) +{ + int ret; + struct usbip_header pdu; + struct vhci_device *vdev = container_of(ud, struct vhci_device, ud); + + + dbg_vhci_rx("Enter\n"); + + memset(&pdu, 0, sizeof(pdu)); + + + /* 1. receive a pdu header */ + ret = usbip_xmit(0, ud->tcp_socket, (char *) &pdu, sizeof(pdu), 0); + if (ret != sizeof(pdu)) { + uerr("receiving pdu failed! size is %d, should be %d\n", + ret, sizeof(pdu)); + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + return; + } + + usbip_header_correct_endian(&pdu, 0); + + if (dbg_flag_vhci_rx) + usbip_dump_header(&pdu); + + switch (pdu.base.command) { + case USBIP_RET_SUBMIT: + vhci_recv_ret_submit(vdev, &pdu); + break; + case USBIP_RET_UNLINK: + vhci_recv_ret_unlink(vdev, &pdu); + break; + default: + /* NOTREACHED */ + uerr("unknown pdu %u\n", pdu.base.command); + usbip_dump_header(&pdu); + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + } +} + + +/*-------------------------------------------------------------------------*/ + +void vhci_rx_loop(struct usbip_task *ut) +{ + struct usbip_device *ud = container_of(ut, struct usbip_device, tcp_rx); + + + while (1) { + if (signal_pending(current)) { + dbg_vhci_rx("signal catched!\n"); + break; + } + + + if (usbip_event_happend(ud)) + break; + + vhci_rx_pdu(ud); + } +} + diff --git a/drivers/staging/usbip/vhci_sysfs.c b/drivers/staging/usbip/vhci_sysfs.c new file mode 100644 index 00000000000..24c2851a8f8 --- /dev/null +++ b/drivers/staging/usbip/vhci_sysfs.c @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * + * This 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include "usbip_common.h" +#include "vhci.h" + +#include <linux/in.h> + +/* TODO: refine locking ?*/ + +/* Sysfs entry to show port status */ +static ssize_t show_status(struct device *dev, struct device_attribute *attr, + char *out) +{ + char *s = out; + int i = 0; + + if (!the_controller || !out) + BUG(); + + spin_lock(&the_controller->lock); + + /* + * output example: + * prt sta spd dev socket local_busid + * 000 004 000 000 c5a7bb80 1-2.3 + * 001 004 000 000 d8cee980 2-3.4 + * + * IP address can be retrieved from a socket pointer address by looking + * up /proc/net/{tcp,tcp6}. Also, a userland program may remember a + * port number and its peer IP address. + */ + out += sprintf(out, "prt sta spd bus dev socket " + "local_busid\n"); + + for (i = 0; i < VHCI_NPORTS; i++) { + struct vhci_device *vdev = port_to_vdev(i); + + spin_lock(&vdev->ud.lock); + + out += sprintf(out, "%03u %03u ", i, vdev->ud.status); + + if (vdev->ud.status == VDEV_ST_USED) { + out += sprintf(out, "%03u %08x ", + vdev->speed, vdev->devid); + out += sprintf(out, "%16p ", vdev->ud.tcp_socket); + out += sprintf(out, "%s", vdev->udev->dev.bus_id); + + } else + out += sprintf(out, "000 000 000 0000000000000000 0-0"); + + out += sprintf(out, "\n"); + + spin_unlock(&vdev->ud.lock); + } + + spin_unlock(&the_controller->lock); + + return out - s; +} +static DEVICE_ATTR(status, S_IRUGO, show_status, NULL); + +/* Sysfs entry to shutdown a virtual connection */ +static int vhci_port_disconnect(__u32 rhport) +{ + struct vhci_device *vdev; + + dbg_vhci_sysfs("enter\n"); + + /* lock */ + spin_lock(&the_controller->lock); + + vdev = port_to_vdev(rhport); + + spin_lock(&vdev->ud.lock); + if (vdev->ud.status == VDEV_ST_NULL) { + uerr("not connected %d\n", vdev->ud.status); + + /* unlock */ + spin_unlock(&vdev->ud.lock); + spin_unlock(&the_controller->lock); + + return -EINVAL; + } + + /* unlock */ + spin_unlock(&vdev->ud.lock); + spin_unlock(&the_controller->lock); + + usbip_event_add(&vdev->ud, VDEV_EVENT_DOWN); + + return 0; +} + +static ssize_t store_detach(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + __u32 rhport = 0; + + sscanf(buf, "%u", &rhport); + + /* check rhport */ + if (rhport >= VHCI_NPORTS) { + uerr("invalid port %u\n", rhport); + return -EINVAL; + } + + err = vhci_port_disconnect(rhport); + if (err < 0) + return -EINVAL; + + dbg_vhci_sysfs("Leave\n"); + return count; +} +static DEVICE_ATTR(detach, S_IWUSR, NULL, store_detach); + +/* Sysfs entry to establish a virtual connection */ +static int valid_args(__u32 rhport, enum usb_device_speed speed) +{ + /* check rhport */ + if ((rhport < 0) || (rhport >= VHCI_NPORTS)) { + uerr("port %u\n", rhport); + return -EINVAL; + } + + /* check speed */ + switch (speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + case USB_SPEED_VARIABLE: + break; + default: + uerr("speed %d\n", speed); + return -EINVAL; + } + + return 0; +} + +/* + * To start a new USB/IP attachment, a userland program needs to setup a TCP + * connection and then write its socket descriptor with remote device + * information into this sysfs file. + * + * A remote device is virtually attached to the root-hub port of @rhport with + * @speed. @devid is embedded into a request to specify the remote device in a + * server host. + * + * write() returns 0 on success, else negative errno. + */ +static ssize_t store_attach(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct vhci_device *vdev; + struct socket *socket; + int sockfd = 0; + __u32 rhport = 0, devid = 0, speed = 0; + + /* + * @rhport: port number of vhci_hcd + * @sockfd: socket descriptor of an established TCP connection + * @devid: unique device identifier in a remote host + * @speed: usb device speed in a remote host + */ + sscanf(buf, "%u %u %u %u", &rhport, &sockfd, &devid, &speed); + + dbg_vhci_sysfs("rhport(%u) sockfd(%u) devid(%u) speed(%u)\n", + rhport, sockfd, devid, speed); + + + /* check received parameters */ + if (valid_args(rhport, speed) < 0) + return -EINVAL; + + /* check sockfd */ + socket = sockfd_to_socket(sockfd); + if (!socket) + return -EINVAL; + + /* now need lock until setting vdev status as used */ + + /* begin a lock */ + spin_lock(&the_controller->lock); + + vdev = port_to_vdev(rhport); + + spin_lock(&vdev->ud.lock); + + if (vdev->ud.status != VDEV_ST_NULL) { + /* end of the lock */ + spin_unlock(&vdev->ud.lock); + spin_unlock(&the_controller->lock); + + uerr("port %d already used\n", rhport); + return -EINVAL; + } + + uinfo("rhport(%u) sockfd(%d) devid(%u) speed(%u)\n", + rhport, sockfd, devid, speed); + + vdev->devid = devid; + vdev->speed = speed; + vdev->ud.tcp_socket = socket; + vdev->ud.status = VDEV_ST_NOTASSIGNED; + + spin_unlock(&vdev->ud.lock); + spin_unlock(&the_controller->lock); + /* end the lock */ + + /* + * this function will sleep, so should be out of the lock. but, it's ok + * because we already marked vdev as being used. really? + */ + usbip_start_threads(&vdev->ud); + + rh_port_connect(rhport, speed); + + return count; +} +static DEVICE_ATTR(attach, S_IWUSR, NULL, store_attach); + +static struct attribute *dev_attrs[] = { + &dev_attr_status.attr, + &dev_attr_detach.attr, + &dev_attr_attach.attr, + &dev_attr_usbip_debug.attr, + NULL, +}; + +struct attribute_group dev_attr_group = { + .attrs = dev_attrs, +}; diff --git a/drivers/staging/usbip/vhci_tx.c b/drivers/staging/usbip/vhci_tx.c new file mode 100644 index 00000000000..1f552a95f48 --- /dev/null +++ b/drivers/staging/usbip/vhci_tx.c @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * + * This 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include "usbip_common.h" +#include "vhci.h" + + +static void setup_cmd_submit_pdu(struct usbip_header *pdup, struct urb *urb) +{ + struct vhci_priv *priv = ((struct vhci_priv *)urb->hcpriv); + struct vhci_device *vdev = priv->vdev; + + dbg_vhci_tx("URB, local devnum %u, remote devid %u\n", + usb_pipedevice(urb->pipe), vdev->devid); + + pdup->base.command = USBIP_CMD_SUBMIT; + pdup->base.seqnum = priv->seqnum; + pdup->base.devid = vdev->devid; + if (usb_pipein(urb->pipe)) + pdup->base.direction = USBIP_DIR_IN; + else + pdup->base.direction = USBIP_DIR_OUT; + pdup->base.ep = usb_pipeendpoint(urb->pipe); + + usbip_pack_pdu(pdup, urb, USBIP_CMD_SUBMIT, 1); + + if (urb->setup_packet) + memcpy(pdup->u.cmd_submit.setup, urb->setup_packet, 8); +} + +static struct vhci_priv *dequeue_from_priv_tx(struct vhci_device *vdev) +{ + unsigned long flags; + struct vhci_priv *priv, *tmp; + + spin_lock_irqsave(&vdev->priv_lock, flags); + + list_for_each_entry_safe(priv, tmp, &vdev->priv_tx, list) { + list_move_tail(&priv->list, &vdev->priv_rx); + spin_unlock_irqrestore(&vdev->priv_lock, flags); + return priv; + } + + spin_unlock_irqrestore(&vdev->priv_lock, flags); + + return NULL; +} + + + +static int vhci_send_cmd_submit(struct vhci_device *vdev) +{ + struct vhci_priv *priv = NULL; + + struct msghdr msg; + struct kvec iov[3]; + size_t txsize; + + size_t total_size = 0; + + while ((priv = dequeue_from_priv_tx(vdev)) != NULL) { + int ret; + struct urb *urb = priv->urb; + struct usbip_header pdu_header; + void *iso_buffer = NULL; + + txsize = 0; + memset(&pdu_header, 0, sizeof(pdu_header)); + memset(&msg, 0, sizeof(msg)); + memset(&iov, 0, sizeof(iov)); + + dbg_vhci_tx("setup txdata urb %p\n", urb); + + + /* 1. setup usbip_header */ + setup_cmd_submit_pdu(&pdu_header, urb); + usbip_header_correct_endian(&pdu_header, 1); + + iov[0].iov_base = &pdu_header; + iov[0].iov_len = sizeof(pdu_header); + txsize += sizeof(pdu_header); + + /* 2. setup transfer buffer */ + if (!usb_pipein(urb->pipe) && urb->transfer_buffer_length > 0) { + iov[1].iov_base = urb->transfer_buffer; + iov[1].iov_len = urb->transfer_buffer_length; + txsize += urb->transfer_buffer_length; + } + + /* 3. setup iso_packet_descriptor */ + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + ssize_t len = 0; + + iso_buffer = usbip_alloc_iso_desc_pdu(urb, &len); + if (!iso_buffer) { + usbip_event_add(&vdev->ud, + SDEV_EVENT_ERROR_MALLOC); + return -1; + } + + iov[2].iov_base = iso_buffer; + iov[2].iov_len = len; + txsize += len; + } + + ret = kernel_sendmsg(vdev->ud.tcp_socket, &msg, iov, 3, txsize); + if (ret != txsize) { + uerr("sendmsg failed!, retval %d for %zd\n", ret, + txsize); + kfree(iso_buffer); + usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_TCP); + return -1; + } + + kfree(iso_buffer); + dbg_vhci_tx("send txdata\n"); + + total_size += txsize; + } + + return total_size; +} + + +/*-------------------------------------------------------------------------*/ + +static struct vhci_unlink *dequeue_from_unlink_tx(struct vhci_device *vdev) +{ + unsigned long flags; + struct vhci_unlink *unlink, *tmp; + + spin_lock_irqsave(&vdev->priv_lock, flags); + + list_for_each_entry_safe(unlink, tmp, &vdev->unlink_tx, list) { + list_move_tail(&unlink->list, &vdev->unlink_rx); + spin_unlock_irqrestore(&vdev->priv_lock, flags); + return unlink; + } + + spin_unlock_irqrestore(&vdev->priv_lock, flags); + + return NULL; +} + +static int vhci_send_cmd_unlink(struct vhci_device *vdev) +{ + struct vhci_unlink *unlink = NULL; + + struct msghdr msg; + struct kvec iov[3]; + size_t txsize; + + size_t total_size = 0; + + while ((unlink = dequeue_from_unlink_tx(vdev)) != NULL) { + int ret; + struct usbip_header pdu_header; + + txsize = 0; + memset(&pdu_header, 0, sizeof(pdu_header)); + memset(&msg, 0, sizeof(msg)); + memset(&iov, 0, sizeof(iov)); + + dbg_vhci_tx("setup cmd unlink, %lu \n", unlink->seqnum); + + + /* 1. setup usbip_header */ + pdu_header.base.command = USBIP_CMD_UNLINK; + pdu_header.base.seqnum = unlink->seqnum; + pdu_header.base.devid = vdev->devid; + pdu_header.base.ep = 0; + pdu_header.u.cmd_unlink.seqnum = unlink->unlink_seqnum; + + usbip_header_correct_endian(&pdu_header, 1); + + iov[0].iov_base = &pdu_header; + iov[0].iov_len = sizeof(pdu_header); + txsize += sizeof(pdu_header); + + ret = kernel_sendmsg(vdev->ud.tcp_socket, &msg, iov, 1, txsize); + if (ret != txsize) { + uerr("sendmsg failed!, retval %d for %zd\n", ret, + txsize); + usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_TCP); + return -1; + } + + + dbg_vhci_tx("send txdata\n"); + + total_size += txsize; + } + + return total_size; +} + + +/*-------------------------------------------------------------------------*/ + +void vhci_tx_loop(struct usbip_task *ut) +{ + struct usbip_device *ud = container_of(ut, struct usbip_device, tcp_tx); + struct vhci_device *vdev = container_of(ud, struct vhci_device, ud); + + while (1) { + if (signal_pending(current)) { + uinfo("vhci_tx signal catched\n"); + break; + } + + if (vhci_send_cmd_submit(vdev) < 0) + break; + + if (vhci_send_cmd_unlink(vdev) < 0) + break; + + wait_event_interruptible(vdev->waitq_tx, + (!list_empty(&vdev->priv_tx) || + !list_empty(&vdev->unlink_tx))); + + dbg_vhci_tx("pending urbs ?, now wake up\n"); + } +} |