From 4352b8aee98005853aa63f57d6377282de17a33f Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 23 Aug 2026 13:28:57 -0700 Subject: Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux Pull I3C updates from Alexandre Belloni: "A new driver this cycle, for the AMD AXI I3C controller. There are also new features such as IBI wakeup support, SETAASA device discovery and ACPI support for the the DAA methods, meaning we can now communicate with the SPD devices on DDR5 modules. New driver: - AMD AXI I3C master controller Subsystem: - I3C Common Command Code (CCC) handling improvements, especially around GET CCCs - SETAASA device discovery support - ACPI support for all existing DAA methods like SETDASA, SETNEWDA as well as I2C devices on I3C bus - IBI-based wakeup support Drivers: - dw: SETAASA support - mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C controller support, PIO queue management support for HCI v1.2 - renesas: improve suspend to RAM support, add runtime PM support" * tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits) i3c: dw: reduce do_daa time if there's no client i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2 i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode i3c: dw: rename "pclk" to "apb" to match dt-binding i3c: dw: make struct dw_i3c_cmd smaller i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding i3c: renesas: Don't register devices when ENTDAA times out i3c: master: dw-i3c-master: fix OD timing for first broadcast i3c: mipi-i3c-hci: Advertise IBI wakeup capability i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev() i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI i3c: master: Reject IBI requests from non-IBI-capable devices i3c: master: Add helper to query bus wakeup requirements i3c: master: Report wakeup events for IBIs i3c: master: Support IBI-based wakeup capability i3c: master: Fix potential UAF in i3c_device_match() i3c: master: Fix potential UAF in i3c_device_uevent() i3c: Make dev->desc locking assumptions explicit i3c: master: Fix use-after-free of master->this ... --- drivers/usb/usbip/Kconfig | 77 ++ drivers/usb/usbip/Makefile | 14 + drivers/usb/usbip/stub.h | 105 +++ drivers/usb/usbip/stub_dev.c | 534 +++++++++++++ drivers/usb/usbip/stub_main.c | 428 ++++++++++ drivers/usb/usbip/stub_rx.c | 711 +++++++++++++++++ drivers/usb/usbip/stub_tx.c | 450 +++++++++++ drivers/usb/usbip/usbip_common.c | 920 +++++++++++++++++++++ drivers/usb/usbip/usbip_common.h | 340 ++++++++ drivers/usb/usbip/usbip_event.c | 196 +++++ drivers/usb/usbip/vhci.h | 174 ++++ drivers/usb/usbip/vhci_hcd.c | 1587 +++++++++++++++++++++++++++++++++++++ drivers/usb/usbip/vhci_rx.c | 270 +++++++ drivers/usb/usbip/vhci_sysfs.c | 532 +++++++++++++ drivers/usb/usbip/vhci_tx.c | 256 ++++++ drivers/usb/usbip/vudc.h | 177 +++++ drivers/usb/usbip/vudc_dev.c | 637 +++++++++++++++ drivers/usb/usbip/vudc_main.c | 110 +++ drivers/usb/usbip/vudc_rx.c | 243 ++++++ drivers/usb/usbip/vudc_sysfs.c | 268 +++++++ drivers/usb/usbip/vudc_transfer.c | 497 ++++++++++++ drivers/usb/usbip/vudc_tx.c | 284 +++++++ 22 files changed, 8810 insertions(+) create mode 100644 drivers/usb/usbip/Kconfig create mode 100644 drivers/usb/usbip/Makefile create mode 100644 drivers/usb/usbip/stub.h create mode 100644 drivers/usb/usbip/stub_dev.c create mode 100644 drivers/usb/usbip/stub_main.c create mode 100644 drivers/usb/usbip/stub_rx.c create mode 100644 drivers/usb/usbip/stub_tx.c create mode 100644 drivers/usb/usbip/usbip_common.c create mode 100644 drivers/usb/usbip/usbip_common.h create mode 100644 drivers/usb/usbip/usbip_event.c create mode 100644 drivers/usb/usbip/vhci.h create mode 100644 drivers/usb/usbip/vhci_hcd.c create mode 100644 drivers/usb/usbip/vhci_rx.c create mode 100644 drivers/usb/usbip/vhci_sysfs.c create mode 100644 drivers/usb/usbip/vhci_tx.c create mode 100644 drivers/usb/usbip/vudc.h create mode 100644 drivers/usb/usbip/vudc_dev.c create mode 100644 drivers/usb/usbip/vudc_main.c create mode 100644 drivers/usb/usbip/vudc_rx.c create mode 100644 drivers/usb/usbip/vudc_sysfs.c create mode 100644 drivers/usb/usbip/vudc_transfer.c create mode 100644 drivers/usb/usbip/vudc_tx.c (limited to 'drivers/usb/usbip') diff --git a/drivers/usb/usbip/Kconfig b/drivers/usb/usbip/Kconfig new file mode 100644 index 000000000..50945b6fa --- /dev/null +++ b/drivers/usb/usbip/Kconfig @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: GPL-2.0 + +config USBIP_CORE + tristate "USB/IP support" + depends on NET + select USB_COMMON + select SGL_ALLOC + help + This enables pushing USB packets over IP to allow remote + machines direct access to USB devices. It provides the + USB/IP core that is required by both drivers. + + For more details, and to get the userspace utility + programs, please see . + + To compile this as a module, choose M here: the module will + be called usbip-core. + + If unsure, say N. + +config USBIP_VHCI_HCD + tristate "VHCI hcd" + depends on USBIP_CORE && USB + help + This enables the USB/IP virtual host controller driver, + which is run on the remote machine. + + To compile this driver as a module, choose M here: the + module will be called vhci-hcd. + +config USBIP_VHCI_HC_PORTS + int "Number of ports per USB/IP virtual host controller" + range 1 15 + default 8 + depends on USBIP_VHCI_HCD + help + To increase number of ports available for USB/IP virtual + host controller driver, this defines number of ports per + USB/IP virtual host controller. + +config USBIP_VHCI_NR_HCS + int "Number of USB/IP virtual host controllers" + range 1 32 + default 1 + depends on USBIP_VHCI_HCD + help + To increase number of ports available for USB/IP virtual + host controller driver, this defines number of USB/IP + virtual host controllers as if adding physical host + controllers. + +config USBIP_HOST + tristate "Host driver" + depends on USBIP_CORE && USB + help + This enables the USB/IP host driver, which is run on the + machine that is sharing the USB devices. + + To compile this driver as a module, choose M here: the + module will be called usbip-host. + +config USBIP_VUDC + tristate "VUDC driver" + depends on USBIP_CORE && USB_GADGET + help + This enables the USB/IP virtual USB device controller + driver, which is run on the host machine, allowing the + machine itself to act as a device. + + To compile this driver as a module, choose M here: the + module will be called usbip-vudc. + +config USBIP_DEBUG + bool "Debug messages for USB/IP" + depends on USBIP_CORE + help + This enables the debug messages from the USB/IP drivers. diff --git a/drivers/usb/usbip/Makefile b/drivers/usb/usbip/Makefile new file mode 100644 index 000000000..f4c8f3840 --- /dev/null +++ b/drivers/usb/usbip/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +ccflags-$(CONFIG_USBIP_DEBUG) := -DDEBUG + +obj-$(CONFIG_USBIP_CORE) += usbip-core.o +usbip-core-y := usbip_common.o usbip_event.o + +obj-$(CONFIG_USBIP_VHCI_HCD) += vhci-hcd.o +vhci-hcd-y := vhci_sysfs.o vhci_tx.o vhci_rx.o vhci_hcd.o + +obj-$(CONFIG_USBIP_HOST) += usbip-host.o +usbip-host-y := stub_dev.o stub_main.o stub_rx.o stub_tx.o + +obj-$(CONFIG_USBIP_VUDC) += usbip-vudc.o +usbip-vudc-y := vudc_dev.o vudc_sysfs.o vudc_tx.o vudc_rx.o vudc_transfer.o vudc_main.o diff --git a/drivers/usb/usbip/stub.h b/drivers/usb/usbip/stub.h new file mode 100644 index 000000000..d11270560 --- /dev/null +++ b/drivers/usb/usbip/stub.h @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + */ + +#ifndef __USBIP_STUB_H +#define __USBIP_STUB_H + +#include +#include +#include +#include +#include +#include + +#define STUB_BUSID_OTHER 0 +#define STUB_BUSID_REMOV 1 +#define STUB_BUSID_ADDED 2 +#define STUB_BUSID_ALLOC 3 + +struct stub_device { + struct usb_device *udev; + + 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 **urbs; + struct scatterlist *sgl; + int num_urbs; + int completed_urbs; + int urb_status; + + int unlinking; +}; + +struct stub_unlink { + unsigned long seqnum; + struct list_head list; + __u32 status; +}; + +/* same as SYSFS_BUS_ID_SIZE */ +#define BUSID_SIZE 32 + +struct bus_id_priv { + char name[BUSID_SIZE]; + char status; + int interf_count; + struct stub_device *sdev; + struct usb_device *udev; + char shutdown_busid; + spinlock_t busid_lock; +}; + +/* stub_priv is allocated from stub_priv_cache */ +extern struct kmem_cache *stub_priv_cache; + +/* stub_dev.c */ +extern struct usb_device_driver stub_driver; + +/* stub_main.c */ +struct bus_id_priv *get_busid_priv(const char *busid); +void put_busid_priv(struct bus_id_priv *bid); +int del_match_busid(char *busid); +void stub_free_priv_and_urb(struct stub_priv *priv); +void stub_device_cleanup_urbs(struct stub_device *sdev); + +/* stub_rx.c */ +int stub_rx_loop(void *data); + +/* stub_tx.c */ +void stub_enqueue_ret_unlink(struct stub_device *sdev, __u32 seqnum, + __u32 status); +void stub_complete(struct urb *urb); +int stub_tx_loop(void *data); + +#endif /* __USBIP_STUB_H */ diff --git a/drivers/usb/usbip/stub_dev.c b/drivers/usb/usbip/stub_dev.c new file mode 100644 index 000000000..abfa11d6b --- /dev/null +++ b/drivers/usb/usbip/stub_dev.c @@ -0,0 +1,534 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + */ + +#include +#include +#include +#include + +#include "usbip_common.h" +#include "stub.h" + +/* + * usbip_status shows the status of usbip-host as long as this driver is bound + * to the target device. + */ +static ssize_t usbip_status_show(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_irq(&sdev->ud.lock); + status = sdev->ud.status; + spin_unlock_irq(&sdev->ud.lock); + + return sysfs_emit(buf, "%d\n", status); +} +static DEVICE_ATTR_RO(usbip_status); + +/* + * 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 usbip_sockfd_store(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; + int rv; + struct task_struct *tcp_rx = NULL; + struct task_struct *tcp_tx = NULL; + + if (!sdev) { + dev_err(dev, "sdev is null\n"); + return -ENODEV; + } + + rv = sscanf(buf, "%d", &sockfd); + if (rv != 1) + return -EINVAL; + + if (sockfd != -1) { + int err; + + dev_info(dev, "stub up\n"); + + mutex_lock(&sdev->ud.sysfs_lock); + spin_lock_irq(&sdev->ud.lock); + + if (sdev->ud.status != SDEV_ST_AVAILABLE) { + dev_err(dev, "not ready\n"); + goto err; + } + + socket = sockfd_lookup(sockfd, &err); + if (!socket) { + dev_err(dev, "failed to lookup sock"); + goto err; + } + + if (socket->type != SOCK_STREAM) { + dev_err(dev, "Expecting SOCK_STREAM - found %d", + socket->type); + goto sock_err; + } + + /* unlock and create threads and get tasks */ + spin_unlock_irq(&sdev->ud.lock); + tcp_rx = kthread_create(stub_rx_loop, &sdev->ud, "stub_rx"); + if (IS_ERR(tcp_rx)) { + sockfd_put(socket); + goto unlock_mutex; + } + tcp_tx = kthread_create(stub_tx_loop, &sdev->ud, "stub_tx"); + if (IS_ERR(tcp_tx)) { + kthread_stop(tcp_rx); + sockfd_put(socket); + goto unlock_mutex; + } + + /* get task structs now */ + get_task_struct(tcp_rx); + get_task_struct(tcp_tx); + + /* lock and update sdev->ud state */ + spin_lock_irq(&sdev->ud.lock); + sdev->ud.tcp_socket = socket; + sdev->ud.sockfd = sockfd; + sdev->ud.tcp_rx = tcp_rx; + sdev->ud.tcp_tx = tcp_tx; + sdev->ud.status = SDEV_ST_USED; + spin_unlock_irq(&sdev->ud.lock); + + wake_up_process(sdev->ud.tcp_rx); + wake_up_process(sdev->ud.tcp_tx); + + mutex_unlock(&sdev->ud.sysfs_lock); + + } else { + dev_info(dev, "stub down\n"); + + mutex_lock(&sdev->ud.sysfs_lock); + + spin_lock_irq(&sdev->ud.lock); + if (sdev->ud.status != SDEV_ST_USED) + goto err; + + spin_unlock_irq(&sdev->ud.lock); + + usbip_event_add(&sdev->ud, SDEV_EVENT_DOWN); + mutex_unlock(&sdev->ud.sysfs_lock); + } + + return count; + +sock_err: + sockfd_put(socket); +err: + spin_unlock_irq(&sdev->ud.lock); +unlock_mutex: + mutex_unlock(&sdev->ud.sysfs_lock); + return -EINVAL; +} +static DEVICE_ATTR_WO(usbip_sockfd); + +static struct attribute *usbip_attrs[] = { + &dev_attr_usbip_status.attr, + &dev_attr_usbip_sockfd.attr, + &dev_attr_usbip_debug.attr, + NULL, +}; +ATTRIBUTE_GROUPS(usbip); + +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) { + dev_dbg(&sdev->udev->dev, "shutdown sockfd %d\n", ud->sockfd); + kernel_sock_shutdown(ud->tcp_socket, SHUT_RDWR); + } + + /* 1. stop threads */ + if (ud->tcp_rx) { + kthread_stop_put(ud->tcp_rx); + ud->tcp_rx = NULL; + } + if (ud->tcp_tx) { + kthread_stop_put(ud->tcp_tx); + ud->tcp_tx = NULL; + } + + /* + * 2. close the socket + * + * tcp_socket is freed after threads are killed so that usbip_xmit does + * not touch NULL socket. + */ + if (ud->tcp_socket) { + sockfd_put(ud->tcp_socket); + ud->tcp_socket = NULL; + ud->sockfd = -1; + } + + /* 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 = sdev->udev; + int ret; + + dev_dbg(&udev->dev, "device reset"); + + ret = usb_lock_device_for_reset(udev, NULL); + if (ret < 0) { + dev_err(&udev->dev, "lock for reset\n"); + spin_lock_irq(&ud->lock); + ud->status = SDEV_ST_ERROR; + spin_unlock_irq(&ud->lock); + return; + } + + /* try to reset the device */ + ret = usb_reset_device(udev); + usb_unlock_device(udev); + + spin_lock_irq(&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_irq(&ud->lock); +} + +static void stub_device_unusable(struct usbip_device *ud) +{ + spin_lock_irq(&ud->lock); + ud->status = SDEV_ST_ERROR; + spin_unlock_irq(&ud->lock); +} + +/** + * stub_device_alloc - allocate a new stub_device struct + * @udev: usb_device of a new device + * + * Allocates and initializes a new stub_device struct. + */ +static struct stub_device *stub_device_alloc(struct usb_device *udev) +{ + struct stub_device *sdev; + int busnum = udev->bus->busnum; + int devnum = udev->devnum; + + dev_dbg(&udev->dev, "allocating stub device"); + + /* yes, it's a new device */ + sdev = kzalloc_obj(struct stub_device); + if (!sdev) + return NULL; + + sdev->udev = udev; + + /* + * 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; + sdev->ud.side = USBIP_STUB; + sdev->ud.status = SDEV_ST_AVAILABLE; + spin_lock_init(&sdev->ud.lock); + mutex_init(&sdev->ud.sysfs_lock); + sdev->ud.tcp_socket = NULL; + sdev->ud.sockfd = -1; + + 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); + 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); + + dev_dbg(&udev->dev, "register new device\n"); + + return sdev; +} + +static void stub_device_free(struct stub_device *sdev) +{ + kfree(sdev); +} + +static int stub_probe(struct usb_device *udev) +{ + struct stub_device *sdev = NULL; + const char *udev_busid = dev_name(&udev->dev); + struct bus_id_priv *busid_priv; + int rc = 0; + char save_status; + + dev_dbg(&udev->dev, "Enter probe\n"); + + /* Not sure if this is our device. Allocate here to avoid + * calling alloc while holding busid_table lock. + */ + sdev = stub_device_alloc(udev); + if (!sdev) + return -ENOMEM; + + /* check we should claim or not by busid_table */ + busid_priv = get_busid_priv(udev_busid); + if (!busid_priv || (busid_priv->status == STUB_BUSID_REMOV) || + (busid_priv->status == STUB_BUSID_OTHER)) { + dev_info(&udev->dev, + "%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 + */ + rc = -ENODEV; + if (!busid_priv) + goto sdev_free; + + goto call_put_busid_priv; + } + + if (udev->descriptor.bDeviceClass == USB_CLASS_HUB) { + dev_dbg(&udev->dev, "%s is a usb hub device... skip!\n", + udev_busid); + rc = -ENODEV; + goto call_put_busid_priv; + } + + if (!strcmp(udev->bus->bus_name, "vhci_hcd")) { + dev_dbg(&udev->dev, + "%s is attached on vhci_hcd... skip!\n", + udev_busid); + + rc = -ENODEV; + goto call_put_busid_priv; + } + + + dev_info(&udev->dev, + "usbip-host: register new device (bus %u dev %u)\n", + udev->bus->busnum, udev->devnum); + + busid_priv->shutdown_busid = 0; + + /* set private data to usb_device */ + dev_set_drvdata(&udev->dev, sdev); + + busid_priv->sdev = sdev; + busid_priv->udev = udev; + + save_status = busid_priv->status; + busid_priv->status = STUB_BUSID_ALLOC; + + /* release the busid_lock */ + put_busid_priv(busid_priv); + + /* + * Claim this hub port. + * It doesn't matter what value we pass as owner + * (struct dev_state) as long as it is unique. + */ + rc = usb_hub_claim_port(udev->parent, udev->portnum, + (struct usb_dev_state *) udev); + if (rc) { + dev_dbg(&udev->dev, "unable to claim port\n"); + goto err_port; + } + + return 0; + +err_port: + dev_set_drvdata(&udev->dev, NULL); + + /* we already have busid_priv, just lock busid_lock */ + spin_lock(&busid_priv->busid_lock); + busid_priv->sdev = NULL; + busid_priv->status = save_status; + spin_unlock(&busid_priv->busid_lock); + /* lock is released - go to free */ + goto sdev_free; + +call_put_busid_priv: + /* release the busid_lock */ + put_busid_priv(busid_priv); + +sdev_free: + stub_device_free(sdev); + + return rc; +} + +static void shutdown_busid(struct bus_id_priv *busid_priv) +{ + usbip_event_add(&busid_priv->sdev->ud, SDEV_EVENT_REMOVED); + + /* wait for the stop of the event handler */ + usbip_stop_eh(&busid_priv->sdev->ud); +} + +/* + * called in usb_disconnect() or usb_deregister() + * but only if actconfig(active configuration) exists + */ +static void stub_disconnect(struct usb_device *udev) +{ + struct stub_device *sdev; + const char *udev_busid = dev_name(&udev->dev); + struct bus_id_priv *busid_priv; + int rc; + + dev_dbg(&udev->dev, "Enter disconnect\n"); + + busid_priv = get_busid_priv(udev_busid); + if (!busid_priv) { + BUG(); + return; + } + + sdev = dev_get_drvdata(&udev->dev); + + /* get stub_device */ + if (!sdev) { + dev_err(&udev->dev, "could not get device"); + /* release busid_lock */ + put_busid_priv(busid_priv); + return; + } + + dev_set_drvdata(&udev->dev, NULL); + + /* release busid_lock before call to remove device files */ + put_busid_priv(busid_priv); + + /* + * NOTE: rx/tx threads are invoked for each usb_device. + */ + + /* release port */ + rc = usb_hub_release_port(udev->parent, udev->portnum, + (struct usb_dev_state *) udev); + /* + * NOTE: If a HUB disconnect triggered disconnect of the down stream + * device usb_hub_release_port will return -ENODEV so we can safely ignore + * that error here. + */ + if (rc && (rc != -ENODEV)) { + dev_dbg(&udev->dev, "unable to release port (%i)\n", rc); + return; + } + + /* If usb reset is called from event handler */ + if (usbip_in_eh(current)) + return; + + /* we already have busid_priv, just lock busid_lock */ + spin_lock(&busid_priv->busid_lock); + if (!busid_priv->shutdown_busid) + busid_priv->shutdown_busid = 1; + /* release busid_lock */ + spin_unlock(&busid_priv->busid_lock); + + /* shutdown the current connection */ + shutdown_busid(busid_priv); + + /* we already have busid_priv, just lock busid_lock */ + spin_lock(&busid_priv->busid_lock); + /* free sdev */ + busid_priv->sdev = NULL; + stub_device_free(sdev); + + if (busid_priv->status == STUB_BUSID_ALLOC) + busid_priv->status = STUB_BUSID_ADDED; + /* release busid_lock */ + spin_unlock(&busid_priv->busid_lock); + return; +} + +#ifdef CONFIG_PM + +/* These functions need usb_port_suspend and usb_port_resume, + * which reside in drivers/usb/core/usb.h. Skip for now. */ + +static int stub_suspend(struct usb_device *udev, pm_message_t message) +{ + dev_dbg(&udev->dev, "stub_suspend\n"); + + return 0; +} + +static int stub_resume(struct usb_device *udev, pm_message_t message) +{ + dev_dbg(&udev->dev, "stub_resume\n"); + + return 0; +} + +#endif /* CONFIG_PM */ + +struct usb_device_driver stub_driver = { + .name = "usbip-host", + .probe = stub_probe, + .disconnect = stub_disconnect, +#ifdef CONFIG_PM + .suspend = stub_suspend, + .resume = stub_resume, +#endif + .supports_autosuspend = 0, + .dev_groups = usbip_groups, +}; diff --git a/drivers/usb/usbip/stub_main.c b/drivers/usb/usbip/stub_main.c new file mode 100644 index 000000000..79110a69d --- /dev/null +++ b/drivers/usb/usbip/stub_main.c @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + */ + +#include +#include +#include +#include + +#include "usbip_common.h" +#include "stub.h" + +#define DRIVER_AUTHOR "Takahiro Hirofuchi" +#define DRIVER_DESC "USB/IP Host Driver" + +struct kmem_cache *stub_priv_cache; + +/* + * 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 struct bus_id_priv busid_table[MAX_BUSID]; +static DEFINE_SPINLOCK(busid_table_lock); + +static void init_busid_table(void) +{ + int i; + + /* + * This also sets the bus_table[i].status to + * STUB_BUSID_OTHER, which is 0. + */ + memset(busid_table, 0, sizeof(busid_table)); + + for (i = 0; i < MAX_BUSID; i++) + spin_lock_init(&busid_table[i].busid_lock); +} + +/* + * Find the index of the busid by name. + * Must be called with busid_table_lock held. + */ +static int get_busid_idx(const char *busid) +{ + int i; + int idx = -1; + + for (i = 0; i < MAX_BUSID; i++) { + spin_lock(&busid_table[i].busid_lock); + if (busid_table[i].name[0]) + if (!strncmp(busid_table[i].name, busid, BUSID_SIZE)) { + idx = i; + spin_unlock(&busid_table[i].busid_lock); + break; + } + spin_unlock(&busid_table[i].busid_lock); + } + return idx; +} + +/* Returns holding busid_lock. Should call put_busid_priv() to unlock */ +struct bus_id_priv *get_busid_priv(const char *busid) +{ + int idx; + struct bus_id_priv *bid = NULL; + + spin_lock(&busid_table_lock); + idx = get_busid_idx(busid); + if (idx >= 0) { + bid = &(busid_table[idx]); + /* get busid_lock before returning */ + spin_lock(&bid->busid_lock); + } + spin_unlock(&busid_table_lock); + + return bid; +} + +void put_busid_priv(struct bus_id_priv *bid) +{ + if (bid) + spin_unlock(&bid->busid_lock); +} + +static int add_match_busid(char *busid) +{ + int i; + int ret = -1; + + spin_lock(&busid_table_lock); + /* already registered? */ + if (get_busid_idx(busid) >= 0) { + ret = 0; + goto out; + } + + for (i = 0; i < MAX_BUSID; i++) { + spin_lock(&busid_table[i].busid_lock); + if (!busid_table[i].name[0]) { + strscpy(busid_table[i].name, busid, BUSID_SIZE); + if ((busid_table[i].status != STUB_BUSID_ALLOC) && + (busid_table[i].status != STUB_BUSID_REMOV)) + busid_table[i].status = STUB_BUSID_ADDED; + ret = 0; + spin_unlock(&busid_table[i].busid_lock); + break; + } + spin_unlock(&busid_table[i].busid_lock); + } + +out: + spin_unlock(&busid_table_lock); + + return ret; +} + +int del_match_busid(char *busid) +{ + int idx; + int ret = -1; + + spin_lock(&busid_table_lock); + idx = get_busid_idx(busid); + if (idx < 0) + goto out; + + /* found */ + ret = 0; + + spin_lock(&busid_table[idx].busid_lock); + + if (busid_table[idx].status == STUB_BUSID_OTHER) + memset(busid_table[idx].name, 0, BUSID_SIZE); + + if ((busid_table[idx].status != STUB_BUSID_OTHER) && + (busid_table[idx].status != STUB_BUSID_ADDED)) + busid_table[idx].status = STUB_BUSID_REMOV; + + spin_unlock(&busid_table[idx].busid_lock); +out: + spin_unlock(&busid_table_lock); + + return ret; +} + +static ssize_t match_busid_show(struct device_driver *drv, char *buf) +{ + int i; + char *out = buf; + + spin_lock(&busid_table_lock); + for (i = 0; i < MAX_BUSID; i++) { + spin_lock(&busid_table[i].busid_lock); + if (busid_table[i].name[0]) + out += sprintf(out, "%s ", busid_table[i].name); + spin_unlock(&busid_table[i].busid_lock); + } + spin_unlock(&busid_table_lock); + out += sprintf(out, "\n"); + + return out - buf; +} + +static ssize_t match_busid_store(struct device_driver *dev, const char *buf, + size_t count) +{ + char busid[BUSID_SIZE]; + + if (count < 5) + return -EINVAL; + + /* busid needs to include \0 termination */ + if (strscpy(busid, buf + 4, BUSID_SIZE) < 0) + return -EINVAL; + + if (!strncmp(buf, "add ", 4)) { + if (add_match_busid(busid) < 0) + return -ENOMEM; + + pr_debug("add busid %s\n", busid); + return count; + } + + if (!strncmp(buf, "del ", 4)) { + if (del_match_busid(busid) < 0) + return -ENODEV; + + pr_debug("del busid %s\n", busid); + return count; + } + + return -EINVAL; +} +static DRIVER_ATTR_RW(match_busid); + +static int do_rebind(char *busid, struct bus_id_priv *busid_priv) +{ + int ret = 0; + + /* device_attach() callers should hold parent lock for USB */ + if (busid_priv->udev->dev.parent) + device_lock(busid_priv->udev->dev.parent); + ret = device_attach(&busid_priv->udev->dev); + if (busid_priv->udev->dev.parent) + device_unlock(busid_priv->udev->dev.parent); + if (ret < 0) + dev_err(&busid_priv->udev->dev, "rebind failed\n"); + return ret; +} + +static void stub_device_rebind(void) +{ +#if IS_MODULE(CONFIG_USBIP_HOST) + struct bus_id_priv *busid_priv; + int i; + + /* update status to STUB_BUSID_OTHER so probe ignores the device */ + spin_lock(&busid_table_lock); + for (i = 0; i < MAX_BUSID; i++) { + if (busid_table[i].name[0] && + busid_table[i].shutdown_busid) { + busid_priv = &(busid_table[i]); + busid_priv->status = STUB_BUSID_OTHER; + } + } + spin_unlock(&busid_table_lock); + + /* now run rebind - no need to hold locks. driver files are removed */ + for (i = 0; i < MAX_BUSID; i++) { + if (busid_table[i].name[0] && + busid_table[i].shutdown_busid) { + busid_priv = &(busid_table[i]); + do_rebind(busid_table[i].name, busid_priv); + } + } +#endif +} + +static ssize_t rebind_store(struct device_driver *dev, const char *buf, + size_t count) +{ + int ret; + int len; + struct bus_id_priv *bid; + + /* buf length should be less that BUSID_SIZE */ + len = strnlen(buf, BUSID_SIZE); + + if (!(len < BUSID_SIZE)) + return -EINVAL; + + bid = get_busid_priv(buf); + if (!bid) + return -ENODEV; + + /* mark the device for deletion so probe ignores it during rescan */ + bid->status = STUB_BUSID_OTHER; + /* release the busid lock */ + put_busid_priv(bid); + + ret = do_rebind((char *) buf, bid); + if (ret < 0) + return ret; + + /* delete device from busid_table */ + del_match_busid((char *) buf); + + return count; +} + +static DRIVER_ATTR_WO(rebind); + +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_init(&priv->list); + return priv; + } + + return NULL; +} + +void stub_free_priv_and_urb(struct stub_priv *priv) +{ + struct urb *urb; + int i; + + for (i = 0; i < priv->num_urbs; i++) { + urb = priv->urbs[i]; + + if (!urb) + return; + + kfree(urb->setup_packet); + urb->setup_packet = NULL; + + + if (urb->transfer_buffer && !priv->sgl) { + kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; + } + + if (urb->num_sgs) { + sgl_free(urb->sg); + urb->sg = NULL; + urb->num_sgs = 0; + } + + usb_free_urb(urb); + } + if (!list_empty(&priv->list)) + list_del(&priv->list); + if (priv->sgl) + sgl_free(priv->sgl); + kfree(priv->urbs); + kmem_cache_free(stub_priv_cache, priv); +} + +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) + goto done; + + priv = stub_priv_pop_from_listhead(&sdev->priv_tx); + if (priv) + goto done; + + priv = stub_priv_pop_from_listhead(&sdev->priv_free); + +done: + spin_unlock_irqrestore(&sdev->priv_lock, flags); + + return priv; +} + +void stub_device_cleanup_urbs(struct stub_device *sdev) +{ + struct stub_priv *priv; + int i; + + dev_dbg(&sdev->udev->dev, "Stub device cleaning up urbs\n"); + + while ((priv = stub_priv_pop(sdev))) { + for (i = 0; i < priv->num_urbs; i++) + usb_kill_urb(priv->urbs[i]); + + stub_free_priv_and_urb(priv); + } +} + +static int __init usbip_host_init(void) +{ + int ret; + + init_busid_table(); + + stub_priv_cache = KMEM_CACHE(stub_priv, SLAB_HWCACHE_ALIGN); + if (!stub_priv_cache) { + pr_err("kmem_cache_create failed\n"); + return -ENOMEM; + } + + ret = usb_register_device_driver(&stub_driver, THIS_MODULE); + if (ret) { + pr_err("usb_register failed %d\n", ret); + goto err_usb_register; + } + + ret = driver_create_file(&stub_driver.driver, + &driver_attr_match_busid); + if (ret) { + pr_err("driver_create_file failed\n"); + goto err_create_file; + } + + ret = driver_create_file(&stub_driver.driver, + &driver_attr_rebind); + if (ret) { + pr_err("driver_create_file failed\n"); + goto err_create_file; + } + + return ret; + +err_create_file: + usb_deregister_device_driver(&stub_driver); +err_usb_register: + kmem_cache_destroy(stub_priv_cache); + return ret; +} + +static void __exit usbip_host_exit(void) +{ + driver_remove_file(&stub_driver.driver, + &driver_attr_match_busid); + + driver_remove_file(&stub_driver.driver, + &driver_attr_rebind); + + /* + * deregister() calls stub_disconnect() for all devices. Device + * specific data is cleared in stub_disconnect(). + */ + usb_deregister_device_driver(&stub_driver); + + /* initiate scan to attach devices */ + stub_device_rebind(); + + kmem_cache_destroy(stub_priv_cache); +} + +module_init(usbip_host_init); +module_exit(usbip_host_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/usbip/stub_rx.c b/drivers/usb/usbip/stub_rx.c new file mode 100644 index 000000000..1e9ae5788 --- /dev/null +++ b/drivers/usb/usbip/stub_rx.c @@ -0,0 +1,711 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + */ + +#include +#include +#include +#include +#include + +#include "usbip_common.h" +#include "stub.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)) { + usbip_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) + dev_err(&urb->dev->dev, + "usb_clear_halt error: devnum %d endp %d ret %d\n", + urb->dev->devnum, target_endp, ret); + else + dev_info(&urb->dev->dev, + "usb_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); + + usbip_dbg_stub_rx("set_interface: inf %u alt %u\n", + interface, alternate); + + ret = usb_set_interface(urb->dev, interface, alternate); + if (ret < 0) + dev_err(&urb->dev->dev, + "usb_set_interface error: inf %u alt %u ret %d\n", + interface, alternate, ret); + else + dev_info(&urb->dev->dev, + "usb_set_interface done: inf %u alt %u\n", + interface, alternate); + + return ret; +} + +static int tweak_set_configuration_cmd(struct urb *urb) +{ + struct stub_priv *priv = (struct stub_priv *) urb->context; + struct stub_device *sdev = priv->sdev; + struct usb_ctrlrequest *req; + __u16 config; + int err; + + req = (struct usb_ctrlrequest *) urb->setup_packet; + config = le16_to_cpu(req->wValue); + + usb_lock_device(sdev->udev); + err = usb_set_configuration(sdev->udev, config); + usb_unlock_device(sdev->udev); + if (err && err != -ENODEV) + dev_err(&sdev->udev->dev, "can't set config #%d, error %d\n", + config, err); + return err; +} + +static int tweak_reset_device_cmd(struct urb *urb) +{ + struct stub_priv *priv = (struct stub_priv *) urb->context; + struct stub_device *sdev = priv->sdev; + int err; + + dev_info(&urb->dev->dev, "usb_queue_reset_device\n"); + + err = usb_lock_device_for_reset(sdev->udev, NULL); + if (err < 0) { + dev_err(&urb->dev->dev, "could not obtain lock to reset device\n"); + return err; + } + err = usb_reset_device(sdev->udev); + usb_unlock_device(sdev->udev); + + return err; +} + +/* + * clear_halt, set_interface, and set_configuration require special tricks. + * Returns 1 if request was tweaked, 0 otherwise. + */ +static int tweak_special_requests(struct urb *urb) +{ + int err; + + if (!urb || !urb->setup_packet) + return 0; + + if (usb_pipetype(urb->pipe) != PIPE_CONTROL) + return 0; + + if (is_clear_halt_cmd(urb)) + /* tweak clear_halt */ + err = tweak_clear_halt_cmd(urb); + + else if (is_set_interface_cmd(urb)) + /* tweak set_interface */ + err = tweak_set_interface_cmd(urb); + + else if (is_set_configuration_cmd(urb)) + /* tweak set_configuration */ + err = tweak_set_configuration_cmd(urb); + + else if (is_reset_device_cmd(urb)) + err = tweak_reset_device_cmd(urb); + else { + usbip_dbg_stub_rx("no need to tweak\n"); + return 0; + } + + return !err; +} + +/* + * 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) +{ + int ret, i; + unsigned long flags; + struct stub_priv *priv; + + spin_lock_irqsave(&sdev->priv_lock, flags); + + list_for_each_entry(priv, &sdev->priv_init, list) { + if (priv->seqnum != pdu->u.cmd_unlink.seqnum) + continue; + + /* + * 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 ? + */ + for (i = priv->completed_urbs; i < priv->num_urbs; i++) { + ret = usb_unlink_urb(priv->urbs[i]); + if (ret != -EINPROGRESS) + dev_err(&priv->urbs[i]->dev->dev, + "failed to unlink %d/%d urb of seqnum %lu, ret %d\n", + i + 1, priv->num_urbs, + priv->seqnum, ret); + } + return 0; + } + + usbip_dbg_stub_rx("seqnum %u 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; + int valid = 0; + + if (pdu->base.devid == sdev->devid) { + spin_lock_irq(&ud->lock); + if (ud->status == SDEV_ST_USED) { + /* A request is valid. */ + valid = 1; + } + spin_unlock_irq(&ud->lock); + } + + return valid; +} + +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_zalloc(stub_priv_cache, GFP_ATOMIC); + if (!priv) { + dev_err(&sdev->udev->dev, "alloc stub_priv\n"); + spin_unlock_irqrestore(&sdev->priv_lock, flags); + usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC); + return NULL; + } + + 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 int get_pipe(struct stub_device *sdev, struct usbip_header *pdu) +{ + struct usb_device *udev = sdev->udev; + struct usb_host_endpoint *ep; + struct usb_endpoint_descriptor *epd = NULL; + int epnum = pdu->base.ep; + int dir = pdu->base.direction; + + if (epnum < 0 || epnum > 15) + goto err_ret; + + if (dir == USBIP_DIR_IN) + ep = udev->ep_in[epnum & 0x7f]; + else + ep = udev->ep_out[epnum & 0x7f]; + if (!ep) + goto err_ret; + + epd = &ep->desc; + + 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)) { + /* validate number of packets */ + if (pdu->u.cmd_submit.number_of_packets < 0 || + pdu->u.cmd_submit.number_of_packets > + USBIP_MAX_ISO_PACKETS) { + dev_err(&sdev->udev->dev, + "CMD_SUBMIT: isoc invalid num packets %d\n", + pdu->u.cmd_submit.number_of_packets); + return -1; + } + if (dir == USBIP_DIR_OUT) + return usb_sndisocpipe(udev, epnum); + else + return usb_rcvisocpipe(udev, epnum); + } + +err_ret: + /* NOT REACHED */ + dev_err(&sdev->udev->dev, "CMD_SUBMIT: invalid epnum %d\n", epnum); + return -1; +} + +static void masking_bogus_flags(struct urb *urb) +{ + int xfertype; + struct usb_device *dev; + struct usb_host_endpoint *ep; + int is_out; + unsigned int allowed; + + if (!urb || urb->hcpriv || !urb->complete) + return; + dev = urb->dev; + if ((!dev) || (dev->state < USB_STATE_UNAUTHENTICATED)) + return; + + ep = (usb_pipein(urb->pipe) ? dev->ep_in : dev->ep_out) + [usb_pipeendpoint(urb->pipe)]; + if (!ep) + return; + + xfertype = usb_endpoint_type(&ep->desc); + if (xfertype == USB_ENDPOINT_XFER_CONTROL) { + struct usb_ctrlrequest *setup = + (struct usb_ctrlrequest *) urb->setup_packet; + + if (!setup) + return; + is_out = !(setup->bRequestType & USB_DIR_IN) || + !setup->wLength; + } else { + is_out = usb_endpoint_dir_out(&ep->desc); + } + + /* enforce simple/standard policy */ + allowed = (URB_NO_TRANSFER_DMA_MAP | URB_NO_INTERRUPT | + URB_DIR_MASK | URB_FREE_BUFFER); + switch (xfertype) { + case USB_ENDPOINT_XFER_BULK: + if (is_out) + allowed |= URB_ZERO_PACKET; + fallthrough; + default: /* all non-iso endpoints */ + if (!is_out) + allowed |= URB_SHORT_NOT_OK; + break; + case USB_ENDPOINT_XFER_ISOC: + allowed |= URB_ISO_ASAP; + break; + } + urb->transfer_flags &= allowed; +} + +static int stub_recv_xbuff(struct usbip_device *ud, struct stub_priv *priv) +{ + int ret; + int i; + + for (i = 0; i < priv->num_urbs; i++) { + ret = usbip_recv_xbuff(ud, priv->urbs[i]); + if (ret < 0) + break; + } + + return ret; +} + +static void stub_recv_cmd_submit(struct stub_device *sdev, + struct usbip_header *pdu) +{ + struct stub_priv *priv; + struct usbip_device *ud = &sdev->ud; + struct usb_device *udev = sdev->udev; + struct scatterlist *sgl = NULL, *sg; + void *buffer = NULL; + unsigned long long buf_len; + int nents; + int num_urbs = 1; + int pipe = get_pipe(sdev, pdu); + int use_sg = pdu->u.cmd_submit.transfer_flags & USBIP_URB_DMA_MAP_SG; + int support_sg = 1; + int np = 0; + int ret, i; + int is_tweaked; + + if (pipe == -1) + return; + + /* + * Smatch reported the error case where use_sg is true and buf_len is 0. + * In this case, It adds SDEV_EVENT_ERROR_MALLOC and stub_priv will be + * released by stub event handler and connection will be shut down. + */ + priv = stub_priv_alloc(sdev, pdu); + if (!priv) + return; + + buf_len = (unsigned long long)pdu->u.cmd_submit.transfer_buffer_length; + + if (use_sg && !buf_len) { + dev_err(&udev->dev, "sg buffer with zero length\n"); + goto err_malloc; + } + + /* allocate urb transfer buffer, if needed */ + if (buf_len) { + if (use_sg) { + sgl = sgl_alloc(buf_len, GFP_KERNEL, &nents); + if (!sgl) + goto err_malloc; + + /* Check if the server's HCD supports SG */ + if (!udev->bus->sg_tablesize) { + /* + * If the server's HCD doesn't support SG, break + * a single SG request into several URBs and map + * each SG list entry to corresponding URB + * buffer. The previously allocated SG list is + * stored in priv->sgl (If the server's HCD + * support SG, SG list is stored only in + * urb->sg) and it is used as an indicator that + * the server split single SG request into + * several URBs. Later, priv->sgl is used by + * stub_complete() and stub_send_ret_submit() to + * reassemble the divied URBs. + */ + support_sg = 0; + num_urbs = nents; + priv->completed_urbs = 0; + pdu->u.cmd_submit.transfer_flags &= + ~USBIP_URB_DMA_MAP_SG; + } + } else { + buffer = kzalloc(buf_len, GFP_KERNEL); + if (!buffer) + goto err_malloc; + } + } + + /* allocate urb array */ + priv->num_urbs = num_urbs; + priv->urbs = kmalloc_objs(*priv->urbs, num_urbs); + if (!priv->urbs) + goto err_urbs; + + /* setup a urb */ + if (support_sg) { + if (usb_pipeisoc(pipe)) + np = pdu->u.cmd_submit.number_of_packets; + + priv->urbs[0] = usb_alloc_urb(np, GFP_KERNEL); + if (!priv->urbs[0]) + goto err_urb; + + if (buf_len) { + if (use_sg) { + priv->urbs[0]->sg = sgl; + priv->urbs[0]->num_sgs = nents; + priv->urbs[0]->transfer_buffer = NULL; + } else { + priv->urbs[0]->transfer_buffer = buffer; + } + } + + /* copy urb setup packet */ + priv->urbs[0]->setup_packet = kmemdup(&pdu->u.cmd_submit.setup, + 8, GFP_KERNEL); + if (!priv->urbs[0]->setup_packet) { + usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC); + return; + } + + usbip_pack_pdu(pdu, priv->urbs[0], USBIP_CMD_SUBMIT, 0); + } else { + for_each_sg(sgl, sg, nents, i) { + priv->urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + /* The URBs which is previously allocated will be freed + * in stub_device_cleanup_urbs() if error occurs. + */ + if (!priv->urbs[i]) + goto err_urb; + + usbip_pack_pdu(pdu, priv->urbs[i], USBIP_CMD_SUBMIT, 0); + priv->urbs[i]->transfer_buffer = sg_virt(sg); + priv->urbs[i]->transfer_buffer_length = sg->length; + } + priv->sgl = sgl; + } + + for (i = 0; i < num_urbs; i++) { + /* set other members from the base header of pdu */ + priv->urbs[i]->context = (void *) priv; + priv->urbs[i]->dev = udev; + priv->urbs[i]->pipe = pipe; + priv->urbs[i]->complete = stub_complete; + + /* + * all URBs belong to a single PDU, so a global is_tweaked flag is + * enough + */ + is_tweaked = tweak_special_requests(priv->urbs[i]); + + masking_bogus_flags(priv->urbs[i]); + } + + if (stub_recv_xbuff(ud, priv) < 0) + return; + + if (usbip_recv_iso(ud, priv->urbs[0]) < 0) + return; + + /* urb is now ready to submit */ + for (i = 0; i < priv->num_urbs; i++) { + if (!is_tweaked) { + ret = usb_submit_urb(priv->urbs[i], GFP_KERNEL); + + if (ret == 0) + usbip_dbg_stub_rx("submit urb ok, seqnum %u\n", + pdu->base.seqnum); + else { + dev_err(&udev->dev, "submit_urb error, %d\n", ret); + usbip_dump_header(pdu); + usbip_dump_urb(priv->urbs[i]); + + /* + * Pessimistic. + * This connection will be discarded. + */ + usbip_event_add(ud, SDEV_EVENT_ERROR_SUBMIT); + break; + } + } else { + /* + * An identical URB was already submitted in + * tweak_special_requests(). Skip submitting this URB to not + * duplicate the request. + */ + priv->urbs[i]->status = 0; + stub_complete(priv->urbs[i]); + } + } + + usbip_dbg_stub_rx("Leave\n"); + return; + +err_urb: + kfree(priv->urbs); +err_urbs: + kfree(buffer); + sgl_free(sgl); +err_malloc: + usbip_event_add(ud, SDEV_EVENT_ERROR_MALLOC); +} + +/* 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->udev->dev; + + usbip_dbg_stub_rx("Enter\n"); + + memset(&pdu, 0, sizeof(pdu)); + + /* receive a pdu header */ + ret = usbip_recv(ud->tcp_socket, &pdu, sizeof(pdu)); + 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 (usbip_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); + break; + } +} + +int stub_rx_loop(void *data) +{ + struct usbip_device *ud = data; + + while (!kthread_should_stop()) { + if (usbip_event_happened(ud)) + break; + + stub_rx_pdu(ud); + } + + return 0; +} diff --git a/drivers/usb/usbip/stub_tx.c b/drivers/usb/usbip/stub_tx.c new file mode 100644 index 000000000..4ffd34225 --- /dev/null +++ b/drivers/usb/usbip/stub_tx.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + */ + +#include +#include +#include +#include + +#include "usbip_common.h" +#include "stub.h" + +/* 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_obj(struct stub_unlink, GFP_ATOMIC); + if (!unlink) { + 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 + * + * 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; + + usbip_dbg_stub_tx("complete! status %d\n", urb->status); + + switch (urb->status) { + case 0: + /* OK */ + break; + case -ENOENT: + dev_info(&urb->dev->dev, + "stopped by a call to usb_kill_urb() because of cleaning up a virtual connection\n"); + return; + case -ECONNRESET: + dev_dbg(&urb->dev->dev, + "unlinked by a call to usb_unlink_urb()\n"); + break; + case -EPIPE: + dev_info(&urb->dev->dev, "endpoint %d is stalled\n", + usb_pipeendpoint(urb->pipe)); + break; + case -ESHUTDOWN: + dev_info(&urb->dev->dev, "device removed?\n"); + break; + default: + dev_info(&urb->dev->dev, + "urb completion with non-zero status %d\n", + urb->status); + break; + } + + /* + * If the server breaks single SG request into the several URBs, the + * URBs must be reassembled before sending completed URB to the vhci. + * Don't wake up the tx thread until all the URBs are completed. + */ + if (priv->sgl) { + priv->completed_urbs++; + + /* Only save the first error status */ + if (urb->status && !priv->urb_status) + priv->urb_status = urb->status; + + if (priv->completed_urbs < priv->num_urbs) + return; + } + + /* link a urb to the queue of tx. */ + spin_lock_irqsave(&sdev->priv_lock, flags); + if (sdev->ud.tcp_socket == NULL) { + usbip_dbg_stub_tx("ignore urb for closed connection\n"); + /* It will be freed in stub_device_cleanup_urbs(). */ + } else 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); +} + +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; +} + +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; + size_t txsize; + + size_t total_size = 0; + + while ((priv = dequeue_from_priv_tx(sdev)) != NULL) { + struct urb *urb = priv->urbs[0]; + struct usbip_header pdu_header; + struct usbip_iso_packet_descriptor *iso_buffer = NULL; + struct kvec *iov = NULL; + struct scatterlist *sg; + u32 actual_length = 0; + int iovnum = 0; + int ret; + int i; + + txsize = 0; + memset(&pdu_header, 0, sizeof(pdu_header)); + memset(&msg, 0, sizeof(msg)); + + if (urb->actual_length > 0 && !urb->transfer_buffer && + !urb->num_sgs) { + dev_err(&sdev->udev->dev, + "urb: actual_length %d transfer_buffer null\n", + urb->actual_length); + return -1; + } + + if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) + iovnum = 2 + urb->number_of_packets; + else if (usb_pipein(urb->pipe) && urb->actual_length > 0 && + urb->num_sgs) + iovnum = 1 + urb->num_sgs; + else if (usb_pipein(urb->pipe) && priv->sgl) + iovnum = 1 + priv->num_urbs; + else + iovnum = 2; + + iov = kzalloc_objs(struct kvec, iovnum); + + if (!iov) { + usbip_event_add(&sdev->ud, SDEV_EVENT_ERROR_MALLOC); + return -1; + } + + iovnum = 0; + + /* 1. setup usbip_header */ + setup_ret_submit_pdu(&pdu_header, urb); + usbip_dbg_stub_tx("setup txdata seqnum: %u\n", + pdu_header.base.seqnum); + + if (priv->sgl) { + for (i = 0; i < priv->num_urbs; i++) + actual_length += priv->urbs[i]->actual_length; + + pdu_header.u.ret_submit.status = priv->urb_status; + pdu_header.u.ret_submit.actual_length = actual_length; + } + + usbip_header_correct_endian(&pdu_header, 1); + + iov[iovnum].iov_base = &pdu_header; + iov[iovnum].iov_len = sizeof(pdu_header); + iovnum++; + txsize += sizeof(pdu_header); + + /* 2. setup transfer buffer */ + if (usb_pipein(urb->pipe) && priv->sgl) { + /* If the server split a single SG request into several + * URBs because the server's HCD doesn't support SG, + * reassemble the split URB buffers into a single + * return command. + */ + for (i = 0; i < priv->num_urbs; i++) { + iov[iovnum].iov_base = + priv->urbs[i]->transfer_buffer; + iov[iovnum].iov_len = + priv->urbs[i]->actual_length; + iovnum++; + } + txsize += actual_length; + } else if (usb_pipein(urb->pipe) && + usb_pipetype(urb->pipe) != PIPE_ISOCHRONOUS && + urb->actual_length > 0) { + if (urb->num_sgs) { + unsigned int copy = urb->actual_length; + unsigned int size; + + for_each_sg(urb->sg, sg, urb->num_sgs, i) { + if (copy == 0) + break; + + size = min(copy, sg->length); + iov[iovnum].iov_base = sg_virt(sg); + iov[iovnum].iov_len = size; + + iovnum++; + copy -= size; + } + } else { + iov[iovnum].iov_base = urb->transfer_buffer; + iov[iovnum].iov_len = urb->actual_length; + iovnum++; + } + txsize += urb->actual_length; + } else if (usb_pipein(urb->pipe) && + usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + /* + * For isochronous packets: actual length is the sum of + * the actual length of the individual, packets, but as + * the packet offsets are not changed there will be + * padding between the packets. To optimally use the + * bandwidth the padding is not transmitted. + */ + + int i; + + for (i = 0; i < urb->number_of_packets; i++) { + iov[iovnum].iov_base = urb->transfer_buffer + + urb->iso_frame_desc[i].offset; + iov[iovnum].iov_len = + urb->iso_frame_desc[i].actual_length; + iovnum++; + txsize += urb->iso_frame_desc[i].actual_length; + } + + if (txsize != sizeof(pdu_header) + urb->actual_length) { + dev_err(&sdev->udev->dev, + "actual length of urb %d does not match iso packet sizes %zu\n", + urb->actual_length, + txsize-sizeof(pdu_header)); + kfree(iov); + usbip_event_add(&sdev->ud, + SDEV_EVENT_ERROR_TCP); + return -1; + } + } + + /* 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); + kfree(iov); + return -1; + } + + iov[iovnum].iov_base = iso_buffer; + iov[iovnum].iov_len = len; + txsize += len; + iovnum++; + } + + ret = kernel_sendmsg(sdev->ud.tcp_socket, &msg, + iov, iovnum, txsize); + if (ret != txsize) { + dev_err(&sdev->udev->dev, + "sendmsg failed!, retval %d for %zd\n", + ret, txsize); + kfree(iov); + kfree(iso_buffer); + usbip_event_add(&sdev->ud, SDEV_EVENT_ERROR_TCP); + return -1; + } + + kfree(iov); + kfree(iso_buffer); + + 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; +} + +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)); + + usbip_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->udev->dev, + "sendmsg failed!, retval %d for %zd\n", + ret, txsize); + usbip_event_add(&sdev->ud, SDEV_EVENT_ERROR_TCP); + return -1; + } + + usbip_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; +} + +int stub_tx_loop(void *data) +{ + struct usbip_device *ud = data; + struct stub_device *sdev = container_of(ud, struct stub_device, ud); + + while (!kthread_should_stop()) { + if (usbip_event_happened(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) || + kthread_should_stop())); + } + + return 0; +} diff --git a/drivers/usb/usbip/usbip_common.c b/drivers/usb/usbip/usbip_common.c new file mode 100644 index 000000000..a5837c0fe --- /dev/null +++ b/drivers/usb/usbip/usbip_common.c @@ -0,0 +1,920 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * Copyright (C) 2015-2016 Samsung Electronics + * Krzysztof Opasiak + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usbip_common.h" + +#define DRIVER_AUTHOR "Takahiro Hirofuchi " +#define DRIVER_DESC "USB/IP Core" + +#ifdef CONFIG_USBIP_DEBUG +unsigned long usbip_debug_flag = 0xffffffff; +#else +unsigned long usbip_debug_flag; +#endif +EXPORT_SYMBOL_GPL(usbip_debug_flag); +module_param(usbip_debug_flag, ulong, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(usbip_debug_flag, "debug flags (defined in usbip_common.h)"); + +/* FIXME */ +struct device_attribute dev_attr_usbip_debug; +EXPORT_SYMBOL_GPL(dev_attr_usbip_debug); + +static ssize_t usbip_debug_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%lx\n", usbip_debug_flag); +} + +static ssize_t usbip_debug_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + if (sscanf(buf, "%lx", &usbip_debug_flag) != 1) + return -EINVAL; + return count; +} +DEVICE_ATTR_RW(usbip_debug); + +static void usbip_dump_buffer(char *buff, int bufflen) +{ + print_hex_dump(KERN_DEBUG, "usbip-core", DUMP_PREFIX_OFFSET, 16, 4, + buff, bufflen, false); +} + +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); + + pr_debug("dev(%d) ep(%d) [%s] ", dev, ep, dir ? "IN" : "OUT"); + + switch (type) { + case PIPE_ISOCHRONOUS: + pr_debug("ISO\n"); + break; + case PIPE_INTERRUPT: + pr_debug("INT\n"); + break; + case PIPE_CONTROL: + pr_debug("CTRL\n"); + break; + case PIPE_BULK: + pr_debug("BULK\n"); + break; + default: + pr_debug("ERR\n"); + break; + } +} + +static void usbip_dump_usb_device(struct usb_device *udev) +{ + struct device *dev = &udev->dev; + int i; + + dev_dbg(dev, " devnum(%d) devpath(%s) usb speed(%s)", + udev->devnum, udev->devpath, usb_speed_string(udev->speed)); + + pr_debug("tt hub ttport %d\n", udev->ttport); + + dev_dbg(dev, " "); + for (i = 0; i < 16; i++) + pr_debug(" %2u", i); + pr_debug("\n"); + + dev_dbg(dev, " toggle0(IN) :"); + for (i = 0; i < 16; i++) + pr_debug(" %2u", (udev->toggle[0] & (1 << i)) ? 1 : 0); + pr_debug("\n"); + + dev_dbg(dev, " toggle1(OUT):"); + for (i = 0; i < 16; i++) + pr_debug(" %2u", (udev->toggle[1] & (1 << i)) ? 1 : 0); + pr_debug("\n"); + + dev_dbg(dev, " epmaxp_in :"); + for (i = 0; i < 16; i++) { + if (udev->ep_in[i]) + pr_debug(" %2u", + le16_to_cpu(udev->ep_in[i]->desc.wMaxPacketSize)); + } + pr_debug("\n"); + + dev_dbg(dev, " epmaxp_out :"); + for (i = 0; i < 16; i++) { + if (udev->ep_out[i]) + pr_debug(" %2u", + le16_to_cpu(udev->ep_out[i]->desc.wMaxPacketSize)); + } + pr_debug("\n"); + + dev_dbg(dev, "parent %s, bus %s\n", dev_name(&udev->parent->dev), + udev->bus->bus_name); + + dev_dbg(dev, "have_langid %d, string_langid %d\n", + udev->have_langid, udev->string_langid); + + dev_dbg(dev, "maxchild %d\n", udev->maxchild); +} + +static void usbip_dump_request_type(__u8 rt) +{ + switch (rt & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + pr_debug("DEVICE"); + break; + case USB_RECIP_INTERFACE: + pr_debug("INTERF"); + break; + case USB_RECIP_ENDPOINT: + pr_debug("ENDPOI"); + break; + case USB_RECIP_OTHER: + pr_debug("OTHER "); + break; + default: + pr_debug("------"); + break; + } +} + +static void usbip_dump_usb_ctrlrequest(struct usb_ctrlrequest *cmd) +{ + if (!cmd) { + pr_debug(" : null pointer\n"); + return; + } + + pr_debug(" "); + pr_debug("bRequestType(%02X) bRequest(%02X) wValue(%04X) wIndex(%04X) wLength(%04X) ", + cmd->bRequestType, cmd->bRequest, + cmd->wValue, cmd->wIndex, cmd->wLength); + pr_debug("\n "); + + if ((cmd->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + pr_debug("STANDARD "); + switch (cmd->bRequest) { + case USB_REQ_GET_STATUS: + pr_debug("GET_STATUS\n"); + break; + case USB_REQ_CLEAR_FEATURE: + pr_debug("CLEAR_FEAT\n"); + break; + case USB_REQ_SET_FEATURE: + pr_debug("SET_FEAT\n"); + break; + case USB_REQ_SET_ADDRESS: + pr_debug("SET_ADDRRS\n"); + break; + case USB_REQ_GET_DESCRIPTOR: + pr_debug("GET_DESCRI\n"); + break; + case USB_REQ_SET_DESCRIPTOR: + pr_debug("SET_DESCRI\n"); + break; + case USB_REQ_GET_CONFIGURATION: + pr_debug("GET_CONFIG\n"); + break; + case USB_REQ_SET_CONFIGURATION: + pr_debug("SET_CONFIG\n"); + break; + case USB_REQ_GET_INTERFACE: + pr_debug("GET_INTERF\n"); + break; + case USB_REQ_SET_INTERFACE: + pr_debug("SET_INTERF\n"); + break; + case USB_REQ_SYNCH_FRAME: + pr_debug("SYNC_FRAME\n"); + break; + default: + pr_debug("REQ(%02X)\n", cmd->bRequest); + break; + } + usbip_dump_request_type(cmd->bRequestType); + } else if ((cmd->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) { + pr_debug("CLASS\n"); + } else if ((cmd->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) { + pr_debug("VENDOR\n"); + } else if ((cmd->bRequestType & USB_TYPE_MASK) == USB_TYPE_RESERVED) { + pr_debug("RESERVED\n"); + } +} + +void usbip_dump_urb(struct urb *urb) +{ + struct device *dev; + + if (!urb) { + pr_debug("urb: null pointer!!\n"); + return; + } + + if (!urb->dev) { + pr_debug("urb->dev: null pointer!!\n"); + return; + } + + dev = &urb->dev->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_length:%d\n", + urb->transfer_buffer_length); + dev_dbg(dev, " actual_length :%d\n", urb->actual_length); + + 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); +} +EXPORT_SYMBOL_GPL(usbip_dump_urb); + +void usbip_dump_header(struct usbip_header *pdu) +{ + pr_debug("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: + pr_debug("USBIP_CMD_SUBMIT: x_flags %u x_len %u sf %u #p %d iv %d\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: + pr_debug("USBIP_CMD_UNLINK: seq %u\n", + pdu->u.cmd_unlink.seqnum); + break; + case USBIP_RET_SUBMIT: + pr_debug("USBIP_RET_SUBMIT: st %d al %u sf %d #p %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.number_of_packets, + pdu->u.ret_submit.error_count); + break; + case USBIP_RET_UNLINK: + pr_debug("USBIP_RET_UNLINK: status %d\n", + pdu->u.ret_unlink.status); + break; + default: + /* NOT REACHED */ + pr_err("unknown command\n"); + break; + } +} +EXPORT_SYMBOL_GPL(usbip_dump_header); + +/* Receive data over TCP/IP. */ +int usbip_recv(struct socket *sock, void *buf, int size) +{ + int result; + struct kvec iov = {.iov_base = buf, .iov_len = size}; + struct msghdr msg = {.msg_flags = MSG_NOSIGNAL}; + int total = 0; + + if (!sock || !buf || !size) + return -EINVAL; + + iov_iter_kvec(&msg.msg_iter, ITER_DEST, &iov, 1, size); + + usbip_dbg_xmit("enter\n"); + + do { + sock->sk->sk_allocation = GFP_NOIO; + sock->sk->sk_use_task_frag = false; + + result = sock_recvmsg(sock, &msg, MSG_WAITALL); + if (result <= 0) + goto err; + + total += result; + } while (msg_data_left(&msg)); + + if (usbip_dbg_flag_xmit) { + pr_debug("receiving....\n"); + usbip_dump_buffer(buf, size); + pr_debug("received, osize %d ret %d size %zd total %d\n", + size, result, msg_data_left(&msg), total); + } + + return total; + +err: + return result; +} +EXPORT_SYMBOL_GPL(usbip_recv); + +/* there may be more cases to tweak the flags. */ +static unsigned int tweak_transfer_flags(unsigned int flags) +{ + flags &= ~URB_NO_TRANSFER_DMA_MAP; + return flags; +} + +/* + * USBIP driver packs URB transfer flags in PDUs that are exchanged + * between Server (usbip_host) and Client (vhci_hcd). URB_* flags + * are internal to kernel and could change. Where as USBIP URB flags + * exchanged in PDUs are USBIP user API must not change. + * + * USBIP_URB* flags are exported as explicit API and client and server + * do mapping from kernel flags to USBIP_URB*. Details as follows: + * + * Client tx path (USBIP_CMD_SUBMIT): + * - Maps URB_* to USBIP_URB_* when it sends USBIP_CMD_SUBMIT packet. + * + * Server rx path (USBIP_CMD_SUBMIT): + * - Maps USBIP_URB_* to URB_* when it receives USBIP_CMD_SUBMIT packet. + * + * Flags aren't included in USBIP_CMD_UNLINK and USBIP_RET_SUBMIT packets + * and no special handling is needed for them in the following cases: + * - Server rx path (USBIP_CMD_UNLINK) + * - Client rx path & Server tx path (USBIP_RET_SUBMIT) + * + * Code paths: + * usbip_pack_pdu() is the common routine that handles packing pdu from + * urb and unpack pdu to an urb. + * + * usbip_pack_cmd_submit() and usbip_pack_ret_submit() handle + * USBIP_CMD_SUBMIT and USBIP_RET_SUBMIT respectively. + * + * usbip_map_urb_to_usbip() and usbip_map_usbip_to_urb() are used + * by usbip_pack_cmd_submit() and usbip_pack_ret_submit() to map + * flags. + */ + +struct urb_to_usbip_flags { + u32 urb_flag; + u32 usbip_flag; +}; + +#define NUM_USBIP_FLAGS 17 + +static const struct urb_to_usbip_flags flag_map[NUM_USBIP_FLAGS] = { + {URB_SHORT_NOT_OK, USBIP_URB_SHORT_NOT_OK}, + {URB_ISO_ASAP, USBIP_URB_ISO_ASAP}, + {URB_NO_TRANSFER_DMA_MAP, USBIP_URB_NO_TRANSFER_DMA_MAP}, + {URB_ZERO_PACKET, USBIP_URB_ZERO_PACKET}, + {URB_NO_INTERRUPT, USBIP_URB_NO_INTERRUPT}, + {URB_FREE_BUFFER, USBIP_URB_FREE_BUFFER}, + {URB_DIR_IN, USBIP_URB_DIR_IN}, + {URB_DIR_OUT, USBIP_URB_DIR_OUT}, + {URB_DIR_MASK, USBIP_URB_DIR_MASK}, + {URB_DMA_MAP_SINGLE, USBIP_URB_DMA_MAP_SINGLE}, + {URB_DMA_MAP_PAGE, USBIP_URB_DMA_MAP_PAGE}, + {URB_DMA_MAP_SG, USBIP_URB_DMA_MAP_SG}, + {URB_MAP_LOCAL, USBIP_URB_MAP_LOCAL}, + {URB_SETUP_MAP_SINGLE, USBIP_URB_SETUP_MAP_SINGLE}, + {URB_SETUP_MAP_LOCAL, USBIP_URB_SETUP_MAP_LOCAL}, + {URB_DMA_SG_COMBINED, USBIP_URB_DMA_SG_COMBINED}, + {URB_ALIGNED_TEMP_BUFFER, USBIP_URB_ALIGNED_TEMP_BUFFER}, +}; + +static unsigned int urb_to_usbip(unsigned int flags) +{ + unsigned int map_flags = 0; + int loop; + + for (loop = 0; loop < NUM_USBIP_FLAGS; loop++) { + if (flags & flag_map[loop].urb_flag) + map_flags |= flag_map[loop].usbip_flag; + } + + return map_flags; +} + +static unsigned int usbip_to_urb(unsigned int flags) +{ + unsigned int map_flags = 0; + int loop; + + for (loop = 0; loop < NUM_USBIP_FLAGS; loop++) { + if (flags & flag_map[loop].usbip_flag) + map_flags |= flag_map[loop].urb_flag; + } + + return map_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) { + /* map after tweaking the urb flags */ + spdu->transfer_flags = urb_to_usbip(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 { + urb->transfer_flags = usbip_to_urb(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) { + rpdu->status = urb->status; + rpdu->actual_length = urb->actual_length; + rpdu->start_frame = urb->start_frame; + rpdu->number_of_packets = urb->number_of_packets; + rpdu->error_count = urb->error_count; + } else { + urb->status = rpdu->status; + urb->actual_length = rpdu->actual_length; + urb->start_frame = rpdu->start_frame; + /* + * The number_of_packets field determines the length of + * iso_frame_desc[], which is a flexible array allocated + * at URB creation time. A response must never claim more + * packets than originally submitted; doing so would cause + * an out-of-bounds write in usbip_recv_iso() and + * usbip_pad_iso(). Clamp to zero on violation so both + * functions safely return early. + */ + if (rpdu->number_of_packets < 0 || + rpdu->number_of_packets > urb->number_of_packets) + rpdu->number_of_packets = 0; + urb->number_of_packets = rpdu->number_of_packets; + 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: + /* NOT REACHED */ + pr_err("unknown command\n"); + break; + } +} +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->number_of_packets); + 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->number_of_packets); + 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: + /* NOT REACHED */ + pr_err("unknown command\n"); + break; + } +} +EXPORT_SYMBOL_GPL(usbip_header_correct_endian); + +static void usbip_iso_packet_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 */ +struct usbip_iso_packet_descriptor* +usbip_alloc_iso_desc_pdu(struct urb *urb, ssize_t *bufflen) +{ + struct usbip_iso_packet_descriptor *iso; + int np = urb->number_of_packets; + ssize_t size = np * sizeof(*iso); + int i; + + iso = kzalloc(size, GFP_KERNEL); + if (!iso) + return NULL; + + for (i = 0; i < np; i++) { + usbip_pack_iso(&iso[i], &urb->iso_frame_desc[i], 1); + usbip_iso_packet_correct_endian(&iso[i], 1); + } + + *bufflen = size; + + return iso; +} +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; + int i; + int ret; + u32 total_length = 0; + + if (!usb_pipeisoc(urb->pipe)) + return 0; + + if (np <= 0 || np > USBIP_MAX_ISO_PACKETS) { + dev_err(&urb->dev->dev, + "recv iso: invalid number_of_packets %d\n", np); + /* + * usbip_pack_ret_submit() already set urb->number_of_packets + * from the wire. Zero it so processcompl() does not iterate + * OOB descriptors on the way out. + */ + urb->number_of_packets = 0; + return -EPROTO; + } + + size = np * sizeof(*iso); + + buff = kcalloc(np, sizeof(*iso), GFP_KERNEL); + if (!buff) + return -ENOMEM; + + ret = usbip_recv(ud->tcp_socket, buff, size); + if (ret != size) { + dev_err(&urb->dev->dev, "recv iso_frame_descriptor, %d\n", + ret); + kfree(buff); + + if (ud->side == USBIP_STUB || ud->side == USBIP_VUDC) + usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); + else + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + + return -EPIPE; + } + + iso = (struct usbip_iso_packet_descriptor *) buff; + for (i = 0; i < np; i++) { + usbip_iso_packet_correct_endian(&iso[i], 0); + usbip_pack_iso(&iso[i], &urb->iso_frame_desc[i], 0); + if (urb->iso_frame_desc[i].actual_length > + (unsigned int)urb->transfer_buffer_length) { + dev_err(&urb->dev->dev, + "recv iso: frame actual_length %u exceeds buffer %d\n", + urb->iso_frame_desc[i].actual_length, + urb->transfer_buffer_length); + kfree(buff); + return -EPROTO; + } + total_length += urb->iso_frame_desc[i].actual_length; + } + + kfree(buff); + + if (total_length != (u32)urb->actual_length) { + dev_err(&urb->dev->dev, + "total length of iso packets %u not equal to actual length of buffer %d\n", + total_length, urb->actual_length); + + if (ud->side == USBIP_STUB || ud->side == USBIP_VUDC) + 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_iso); + +/* + * This functions restores the padding which was removed for optimizing + * the bandwidth during transfer over tcp/ip + * + * buffer and iso packets need to be stored and be in propeper endian in urb + * before calling this function + */ +void usbip_pad_iso(struct usbip_device *ud, struct urb *urb) +{ + int np = urb->number_of_packets; + int i; + int actualoffset = urb->actual_length; + + if (!usb_pipeisoc(urb->pipe)) + return; + + /* if no packets or length of data is 0, then nothing to unpack */ + if (np == 0 || urb->actual_length == 0) + return; + + /* + * if actual_length is transfer_buffer_length then no padding is + * present. + */ + if (urb->actual_length == urb->transfer_buffer_length) + return; + + /* + * loop over all packets from last to first (to prevent overwriting + * memory when padding) and move them into the proper place + */ + for (i = np-1; i > 0; i--) { + actualoffset -= urb->iso_frame_desc[i].actual_length; + + /* + * Validate source range: actualoffset can go negative + * via crafted actual_length values from the wire. + */ + if (actualoffset < 0 || + (unsigned int)actualoffset > + (unsigned int)urb->transfer_buffer_length || + urb->iso_frame_desc[i].actual_length > + (unsigned int)urb->transfer_buffer_length - + (unsigned int)actualoffset) { + dev_err(&urb->dev->dev, + "pad_iso: bad src off=%d len=%u bufsz=%d\n", + actualoffset, + urb->iso_frame_desc[i].actual_length, + urb->transfer_buffer_length); + return; + } + + /* + * Validate destination range: iso_frame_desc[i].offset + * is wire-supplied and must not exceed the buffer. + */ + if (urb->iso_frame_desc[i].offset > + (unsigned int)urb->transfer_buffer_length || + urb->iso_frame_desc[i].actual_length > + (unsigned int)urb->transfer_buffer_length - + urb->iso_frame_desc[i].offset) { + dev_err(&urb->dev->dev, + "pad_iso: bad dst off=%u len=%u bufsz=%d\n", + urb->iso_frame_desc[i].offset, + urb->iso_frame_desc[i].actual_length, + urb->transfer_buffer_length); + return; + } + + memmove(urb->transfer_buffer + urb->iso_frame_desc[i].offset, + urb->transfer_buffer + actualoffset, + urb->iso_frame_desc[i].actual_length); + } +} +EXPORT_SYMBOL_GPL(usbip_pad_iso); + +/* some members of urb must be substituted before. */ +int usbip_recv_xbuff(struct usbip_device *ud, struct urb *urb) +{ + struct scatterlist *sg; + int ret = 0; + int recv; + int size; + int copy; + int i; + + if (ud->side == USBIP_STUB || ud->side == USBIP_VUDC) { + /* the direction of urb must be OUT. */ + if (usb_pipein(urb->pipe)) + return 0; + + size = urb->transfer_buffer_length; + } else { + /* 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; + + if (size > urb->transfer_buffer_length) + /* should not happen, probably malicious packet */ + goto error; + + if (urb->num_sgs) { + copy = size; + for_each_sg(urb->sg, sg, urb->num_sgs, i) { + int recv_size; + + if (copy < sg->length) + recv_size = copy; + else + recv_size = sg->length; + + recv = usbip_recv(ud->tcp_socket, sg_virt(sg), + recv_size); + + if (recv != recv_size) + goto error; + + copy -= recv; + ret += recv; + + if (!copy) + break; + } + + if (ret != size) + goto error; + } else { + ret = usbip_recv(ud->tcp_socket, urb->transfer_buffer, size); + if (ret != size) + goto error; + } + + return ret; + +error: + dev_err(&urb->dev->dev, "recv xbuf, %d\n", ret); + if (ud->side == USBIP_STUB || ud->side == USBIP_VUDC) + usbip_event_add(ud, SDEV_EVENT_ERROR_TCP); + else + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + + return -EPIPE; +} +EXPORT_SYMBOL_GPL(usbip_recv_xbuff); + +static int __init usbip_core_init(void) +{ + return usbip_init_eh(); +} + +static void __exit usbip_core_exit(void) +{ + usbip_finish_eh(); + return; +} + +module_init(usbip_core_init); +module_exit(usbip_core_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/usbip/usbip_common.h b/drivers/usb/usbip/usbip_common.h new file mode 100644 index 000000000..be4c5e65a --- /dev/null +++ b/drivers/usb/usbip/usbip_common.h @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * Copyright (C) 2015-2016 Samsung Electronics + * Krzysztof Opasiak + */ + +#ifndef __USBIP_COMMON_H +#define __USBIP_COMMON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef pr_fmt + +#ifdef DEBUG +#define pr_fmt(fmt) KBUILD_MODNAME ": %s:%d: " fmt, __func__, __LINE__ +#else +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#endif + +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 usbip_dbg_flag_xmit (usbip_debug_flag & usbip_debug_xmit) +#define usbip_dbg_flag_vhci_rh (usbip_debug_flag & usbip_debug_vhci_rh) +#define usbip_dbg_flag_vhci_hc (usbip_debug_flag & usbip_debug_vhci_hc) +#define usbip_dbg_flag_vhci_rx (usbip_debug_flag & usbip_debug_vhci_rx) +#define usbip_dbg_flag_vhci_tx (usbip_debug_flag & usbip_debug_vhci_tx) +#define usbip_dbg_flag_stub_rx (usbip_debug_flag & usbip_debug_stub_rx) +#define usbip_dbg_flag_stub_tx (usbip_debug_flag & usbip_debug_stub_tx) +#define usbip_dbg_flag_vhci_sysfs (usbip_debug_flag & usbip_debug_vhci_sysfs) + +extern unsigned long usbip_debug_flag; +extern struct device_attribute dev_attr_usbip_debug; + +#define usbip_dbg_with_flag(flag, fmt, args...) \ + do { \ + if (flag & usbip_debug_flag) \ + pr_debug(fmt, ##args); \ + } while (0) + +#define usbip_dbg_sysfs(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_sysfs, fmt , ##args) +#define usbip_dbg_xmit(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_xmit, fmt , ##args) +#define usbip_dbg_urb(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_urb, fmt , ##args) +#define usbip_dbg_eh(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_eh, fmt , ##args) + +#define usbip_dbg_vhci_rh(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_vhci_rh, fmt , ##args) +#define usbip_dbg_vhci_hc(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_vhci_hc, fmt , ##args) +#define usbip_dbg_vhci_rx(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_vhci_rx, fmt , ##args) +#define usbip_dbg_vhci_tx(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_vhci_tx, fmt , ##args) +#define usbip_dbg_vhci_sysfs(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_vhci_sysfs, fmt , ##args) + +#define usbip_dbg_stub_cmp(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_stub_cmp, fmt , ##args) +#define usbip_dbg_stub_rx(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_stub_rx, fmt , ##args) +#define usbip_dbg_stub_tx(fmt, args...) \ + usbip_dbg_with_flag(usbip_debug_stub_tx, fmt , ##args) + +/* + * USB/IP request headers + * + * Each request is transferred across the network to its counterpart, which + * facilitates the normal USB communication. The values contained in the headers + * are basically the same as in a URB. Currently, four request types are + * defined: + * + * - USBIP_CMD_SUBMIT: a USB request block, corresponds to usb_submit_urb() + * (client to server) + * + * - USBIP_RET_SUBMIT: the result of USBIP_CMD_SUBMIT + * (server to client) + * + * - USBIP_CMD_UNLINK: an unlink request of a pending USBIP_CMD_SUBMIT, + * corresponds to usb_unlink_urb() + * (client to server) + * + * - USBIP_RET_UNLINK: the result of USBIP_CMD_UNLINK + * (server to client) + * + */ +#define USBIP_CMD_SUBMIT 0x0001 +#define USBIP_CMD_UNLINK 0x0002 +#define USBIP_RET_SUBMIT 0x0003 +#define USBIP_RET_UNLINK 0x0004 + +#define USBIP_DIR_OUT 0x00 +#define USBIP_DIR_IN 0x01 + +/* + * Arbitrary limit for the maximum number of isochronous packets in an URB, + * compare for example the uhci_submit_isochronous function in + * drivers/usb/host/uhci-q.c + */ +#define USBIP_MAX_ISO_PACKETS 1024 + +/** + * struct usbip_header_basic - data pertinent to every request + * @command: the usbip request type + * @seqnum: sequential number that identifies requests; incremented per + * connection + * @devid: specifies a remote USB device uniquely instead of busnum and devnum; + * in the stub driver, this value is ((busnum << 16) | devnum) + * @direction: direction of the transfer + * @ep: endpoint number + */ +struct usbip_header_basic { + __u32 command; + __u32 seqnum; + __u32 devid; + __u32 direction; + __u32 ep; +} __packed; + +/** + * struct usbip_header_cmd_submit - USBIP_CMD_SUBMIT packet header + * @transfer_flags: URB flags + * @transfer_buffer_length: the data size for (in) or (out) transfer + * @start_frame: initial frame for isochronous or interrupt transfers + * @number_of_packets: number of isochronous packets + * @interval: maximum time for the request on the server-side host controller + * @setup: setup data for a control request + */ +struct usbip_header_cmd_submit { + __u32 transfer_flags; + __s32 transfer_buffer_length; + + /* it is difficult for usbip to sync frames (reserved only?) */ + __s32 start_frame; + __s32 number_of_packets; + __s32 interval; + + unsigned char setup[8]; +} __packed; + +/** + * struct usbip_header_ret_submit - USBIP_RET_SUBMIT packet header + * @status: return status of a non-iso request + * @actual_length: number of bytes transferred + * @start_frame: initial frame for isochronous or interrupt transfers + * @number_of_packets: number of isochronous packets + * @error_count: number of errors for isochronous transfers + */ +struct usbip_header_ret_submit { + __s32 status; + __s32 actual_length; + __s32 start_frame; + __s32 number_of_packets; + __s32 error_count; +} __packed; + +/** + * struct usbip_header_cmd_unlink - USBIP_CMD_UNLINK packet header + * @seqnum: the URB seqnum to unlink + */ +struct usbip_header_cmd_unlink { + __u32 seqnum; +} __packed; + +/** + * struct usbip_header_ret_unlink - USBIP_RET_UNLINK packet header + * @status: return status of the request + */ +struct usbip_header_ret_unlink { + __s32 status; +} __packed; + +/** + * struct usbip_header - common header for all usbip packets + * @base: the basic header + * @u: packet type dependent header + */ +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; +} __packed; + +/* + * This is 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; +} __packed; + +enum usbip_side { + USBIP_VHCI, + USBIP_STUB, + USBIP_VUDC, +}; + +/* 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_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 VUDC_EVENT_REMOVED (USBIP_EH_SHUTDOWN | USBIP_EH_RESET | USBIP_EH_BYE) +#define VUDC_EVENT_DOWN (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) +#define VUDC_EVENT_ERROR_TCP (USBIP_EH_SHUTDOWN | USBIP_EH_RESET) +/* catastrophic emulated usb error */ +#define VUDC_EVENT_ERROR_USB (USBIP_EH_SHUTDOWN | USBIP_EH_UNUSABLE) +#define VUDC_EVENT_ERROR_MALLOC (USBIP_EH_SHUTDOWN | USBIP_EH_UNUSABLE) + +#define VDEV_EVENT_REMOVED (USBIP_EH_SHUTDOWN | USBIP_EH_RESET | 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) + +/* a common structure for stub_device and vhci_device */ +struct usbip_device { + enum usbip_side side; + enum usbip_device_status status; + + /* lock for status */ + spinlock_t lock; + + /* mutex for synchronizing sysfs store paths */ + struct mutex sysfs_lock; + + int sockfd; + struct socket *tcp_socket; + + struct task_struct *tcp_rx; + struct task_struct *tcp_tx; + + unsigned long event; + 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; + + struct kcov_common_handle_id kcov_handle; +}; + +#define kthread_get_run(threadfn, data, namefmt, ...) \ +({ \ + struct task_struct *__k \ + = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \ + if (!IS_ERR(__k)) { \ + get_task_struct(__k); \ + wake_up_process(__k); \ + } \ + __k; \ +}) + +/* usbip_common.c */ +void usbip_dump_urb(struct urb *purb); +void usbip_dump_header(struct usbip_header *pdu); + +int usbip_recv(struct socket *sock, void *buf, int size); + +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); + +struct usbip_iso_packet_descriptor* +usbip_alloc_iso_desc_pdu(struct urb *urb, ssize_t *bufflen); + +/* some members of urb must be substituted before. */ +int usbip_recv_iso(struct usbip_device *ud, struct urb *urb); +void usbip_pad_iso(struct usbip_device *ud, struct urb *urb); +int usbip_recv_xbuff(struct usbip_device *ud, struct urb *urb); + +/* usbip_event.c */ +int usbip_init_eh(void); +void usbip_finish_eh(void); +int 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_happened(struct usbip_device *ud); +int usbip_in_eh(struct task_struct *task); + +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; +} + +#endif /* __USBIP_COMMON_H */ diff --git a/drivers/usb/usbip/usbip_event.c b/drivers/usb/usbip/usbip_event.c new file mode 100644 index 000000000..0e00c2d00 --- /dev/null +++ b/drivers/usb/usbip/usbip_event.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * Copyright (C) 2015 Nobuo Iwata + */ + +#include +#include +#include +#include + +#include "usbip_common.h" + +struct usbip_event { + struct list_head node; + struct usbip_device *ud; +}; + +static DEFINE_SPINLOCK(event_lock); +static LIST_HEAD(event_list); + +static void set_event(struct usbip_device *ud, unsigned long event) +{ + unsigned long flags; + + spin_lock_irqsave(&ud->lock, flags); + ud->event |= event; + spin_unlock_irqrestore(&ud->lock, flags); +} + +static void unset_event(struct usbip_device *ud, unsigned long event) +{ + unsigned long flags; + + spin_lock_irqsave(&ud->lock, flags); + ud->event &= ~event; + spin_unlock_irqrestore(&ud->lock, flags); +} + +static struct usbip_device *get_event(void) +{ + struct usbip_event *ue = NULL; + struct usbip_device *ud = NULL; + unsigned long flags; + + spin_lock_irqsave(&event_lock, flags); + if (!list_empty(&event_list)) { + ue = list_first_entry(&event_list, struct usbip_event, node); + list_del(&ue->node); + } + spin_unlock_irqrestore(&event_lock, flags); + + if (ue) { + ud = ue->ud; + kfree(ue); + } + return ud; +} + +static struct task_struct *worker_context; + +static void event_handler(struct work_struct *work) +{ + struct usbip_device *ud; + + if (worker_context == NULL) { + worker_context = current; + } + + while ((ud = get_event()) != NULL) { + usbip_dbg_eh("pending event %lx\n", ud->event); + + mutex_lock(&ud->sysfs_lock); + /* + * NOTE: shutdown must come first. + * Shutdown the device. + */ + if (ud->event & USBIP_EH_SHUTDOWN) { + ud->eh_ops.shutdown(ud); + unset_event(ud, USBIP_EH_SHUTDOWN); + } + + /* Reset the device. */ + if (ud->event & USBIP_EH_RESET) { + ud->eh_ops.reset(ud); + unset_event(ud, USBIP_EH_RESET); + } + + /* Mark the device as unusable. */ + if (ud->event & USBIP_EH_UNUSABLE) { + ud->eh_ops.unusable(ud); + unset_event(ud, USBIP_EH_UNUSABLE); + } + mutex_unlock(&ud->sysfs_lock); + + wake_up(&ud->eh_waitq); + } +} + +int usbip_start_eh(struct usbip_device *ud) +{ + init_waitqueue_head(&ud->eh_waitq); + ud->event = 0; + return 0; +} +EXPORT_SYMBOL_GPL(usbip_start_eh); + +void usbip_stop_eh(struct usbip_device *ud) +{ + unsigned long pending = ud->event & ~USBIP_EH_BYE; + + if (!(ud->event & USBIP_EH_BYE)) + usbip_dbg_eh("usbip_eh stopping but not removed\n"); + + if (pending) + usbip_dbg_eh("usbip_eh waiting completion %lx\n", pending); + + wait_event_interruptible(ud->eh_waitq, !(ud->event & ~USBIP_EH_BYE)); + usbip_dbg_eh("usbip_eh has stopped\n"); +} +EXPORT_SYMBOL_GPL(usbip_stop_eh); + +#define WORK_QUEUE_NAME "usbip_event" + +static struct workqueue_struct *usbip_queue; +static DECLARE_WORK(usbip_work, event_handler); + +int usbip_init_eh(void) +{ + usbip_queue = create_singlethread_workqueue(WORK_QUEUE_NAME); + if (usbip_queue == NULL) { + pr_err("failed to create usbip_event\n"); + return -ENOMEM; + } + return 0; +} + +void usbip_finish_eh(void) +{ + destroy_workqueue(usbip_queue); + usbip_queue = NULL; +} + +void usbip_event_add(struct usbip_device *ud, unsigned long event) +{ + struct usbip_event *ue; + unsigned long flags; + + if (ud->event & USBIP_EH_BYE) + return; + + set_event(ud, event); + + spin_lock_irqsave(&event_lock, flags); + + list_for_each_entry_reverse(ue, &event_list, node) { + if (ue->ud == ud) + goto out; + } + + ue = kmalloc_obj(struct usbip_event, GFP_ATOMIC); + if (ue == NULL) + goto out; + + ue->ud = ud; + + list_add_tail(&ue->node, &event_list); + queue_work(usbip_queue, &usbip_work); + +out: + spin_unlock_irqrestore(&event_lock, flags); +} +EXPORT_SYMBOL_GPL(usbip_event_add); + +int usbip_event_happened(struct usbip_device *ud) +{ + int happened = 0; + unsigned long flags; + + spin_lock_irqsave(&ud->lock, flags); + if (ud->event != 0) + happened = 1; + spin_unlock_irqrestore(&ud->lock, flags); + + return happened; +} +EXPORT_SYMBOL_GPL(usbip_event_happened); + +int usbip_in_eh(struct task_struct *task) +{ + if (task == worker_context) + return 1; + + return 0; +} +EXPORT_SYMBOL_GPL(usbip_in_eh); diff --git a/drivers/usb/usbip/vhci.h b/drivers/usb/usbip/vhci.h new file mode 100644 index 000000000..5659dce15 --- /dev/null +++ b/drivers/usb/usbip/vhci.h @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * Copyright (C) 2015 Nobuo Iwata + */ + +#ifndef __USBIP_VHCI_H +#define __USBIP_VHCI_H + +#include +#include +#include +#include +#include +#include +#include +#include + +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; +}; + +enum hub_speed { + HUB_SPEED_HIGH = 0, + HUB_SPEED_SUPER, +}; + +/* Number of supported ports. Value has an upperbound of USB_MAXCHILDREN */ +#ifdef CONFIG_USBIP_VHCI_HC_PORTS +#define VHCI_HC_PORTS CONFIG_USBIP_VHCI_HC_PORTS +#else +#define VHCI_HC_PORTS 8 +#endif + +/* Each VHCI has 2 hubs (USB2 and USB3), each has VHCI_HC_PORTS ports */ +#define VHCI_PORTS (VHCI_HC_PORTS*2) + +#ifdef CONFIG_USBIP_VHCI_NR_HCS +#define VHCI_NR_HCS CONFIG_USBIP_VHCI_NR_HCS +#else +#define VHCI_NR_HCS 1 +#endif + +#define MAX_STATUS_NAME 16 + +struct vhci { + spinlock_t lock; + + struct platform_device *pdev; + + struct vhci_hcd *vhci_hcd_hs; + struct vhci_hcd *vhci_hcd_ss; +}; + +/* for usb_hcd.hcd_priv[0] */ +struct vhci_hcd { + struct vhci *vhci; + + u32 port_status[VHCI_HC_PORTS]; + + 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_HC_PORTS]; +}; + +extern int vhci_num_controllers; +extern struct vhci *vhcis; +extern struct attribute_group vhci_attr_group; + +/* vhci_hcd.c */ +void rh_port_connect(struct vhci_device *vdev, enum usb_device_speed speed); + +/* vhci_sysfs.c */ +int vhci_init_attr_group(void); +void vhci_finish_attr_group(void); + +/* vhci_rx.c */ +struct urb *pickup_urb_and_free_priv(struct vhci_device *vdev, __u32 seqnum); +int vhci_rx_loop(void *data); + +/* vhci_tx.c */ +int vhci_tx_loop(void *data); + +static inline __u32 port_to_rhport(__u32 port) +{ + return port % VHCI_HC_PORTS; +} + +static inline int port_to_pdev_nr(__u32 port) +{ + return port / VHCI_PORTS; +} + +static inline struct vhci_hcd *hcd_to_vhci_hcd(struct usb_hcd *hcd) +{ + return (struct vhci_hcd *) (hcd->hcd_priv); +} + +static inline struct device *hcd_dev(struct usb_hcd *hcd) +{ + return (hcd)->self.controller; +} + +static inline const char *hcd_name(struct usb_hcd *hcd) +{ + return (hcd)->self.bus_name; +} + +static inline struct usb_hcd *vhci_hcd_to_hcd(struct vhci_hcd *vhci_hcd) +{ + return container_of((void *) vhci_hcd, struct usb_hcd, hcd_priv); +} + +static inline struct vhci_hcd *vdev_to_vhci_hcd(struct vhci_device *vdev) +{ + return container_of((void *)(vdev - vdev->rhport), struct vhci_hcd, vdev); +} + +#endif /* __USBIP_VHCI_H */ diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c new file mode 100644 index 000000000..39e8faf4c --- /dev/null +++ b/drivers/usb/usbip/vhci_hcd.c @@ -0,0 +1,1587 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * Copyright (C) 2015-2016 Nobuo Iwata + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usbip_common.h" +#include "vhci.h" + +#define DRIVER_AUTHOR "Takahiro Hirofuchi" +#define DRIVER_DESC "USB/IP 'Virtual' Host Controller (VHCI) Driver" + +/* + * 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 Controller"; + +int vhci_num_controllers = VHCI_NR_HCS; +struct vhci *vhcis; + +static const char * const bit_desc[] = { + "CONNECTION", /*0*/ + "ENABLE", /*1*/ + "SUSPEND", /*2*/ + "OVER_CURRENT", /*3*/ + "RESET", /*4*/ + "L1", /*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*/ + "C_L1", /*21*/ + "R22", /*22*/ + "R23", /*23*/ + "R24", /*24*/ + "R25", /*25*/ + "R26", /*26*/ + "R27", /*27*/ + "R28", /*28*/ + "R29", /*29*/ + "R30", /*30*/ + "R31", /*31*/ +}; + +static const char * const bit_desc_ss[] = { + "CONNECTION", /*0*/ + "ENABLE", /*1*/ + "SUSPEND", /*2*/ + "OVER_CURRENT", /*3*/ + "RESET", /*4*/ + "L1", /*5*/ + "R6", /*6*/ + "R7", /*7*/ + "R8", /*8*/ + "POWER", /*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*/ + "C_BH_RESET", /*21*/ + "C_LINK_STATE", /*22*/ + "C_CONFIG_ERROR", /*23*/ + "R24", /*24*/ + "R25", /*25*/ + "R26", /*26*/ + "R27", /*27*/ + "R28", /*28*/ + "R29", /*29*/ + "R30", /*30*/ + "R31", /*31*/ +}; + +static void dump_port_status_diff(u32 prev_status, u32 new_status, bool usb3) +{ + int i = 0; + u32 bit = 1; + const char * const *desc = bit_desc; + + if (usb3) + desc = bit_desc_ss; + + pr_debug("status prev -> new: %08x -> %08x\n", prev_status, new_status); + while (bit) { + u32 prev = prev_status & bit; + u32 new = new_status & bit; + char change; + + if (!prev && new) + change = '+'; + else if (prev && !new) + change = '-'; + else + change = ' '; + + if (prev || new) { + pr_debug(" %c%s\n", change, desc[i]); + + if (bit == 1) /* USB_PORT_STAT_CONNECTION */ + pr_debug(" %c%s\n", change, "USB_PORT_STAT_SPEED_5GBPS"); + } + bit <<= 1; + i++; + } + pr_debug("\n"); +} + +void rh_port_connect(struct vhci_device *vdev, enum usb_device_speed speed) +{ + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct vhci *vhci = vhci_hcd->vhci; + int rhport = vdev->rhport; + u32 status; + unsigned long flags; + + usbip_dbg_vhci_rh("rh_port_connect %d\n", rhport); + + spin_lock_irqsave(&vhci->lock, flags); + + status = vhci_hcd->port_status[rhport]; + + status |= USB_PORT_STAT_CONNECTION | (1 << USB_PORT_FEAT_C_CONNECTION); + + switch (speed) { + case USB_SPEED_HIGH: + status |= USB_PORT_STAT_HIGH_SPEED; + break; + case USB_SPEED_LOW: + status |= USB_PORT_STAT_LOW_SPEED; + break; + default: + break; + } + + vhci_hcd->port_status[rhport] = status; + + spin_unlock_irqrestore(&vhci->lock, flags); + + usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci_hcd)); +} + +static void rh_port_disconnect(struct vhci_device *vdev) +{ + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct vhci *vhci = vhci_hcd->vhci; + int rhport = vdev->rhport; + u32 status; + unsigned long flags; + + usbip_dbg_vhci_rh("rh_port_disconnect %d\n", rhport); + + spin_lock_irqsave(&vhci->lock, flags); + + status = vhci_hcd->port_status[rhport]; + + status &= ~USB_PORT_STAT_CONNECTION; + status |= (1 << USB_PORT_FEAT_C_CONNECTION); + + vhci_hcd->port_status[rhport] = status; + + spin_unlock_irqrestore(&vhci->lock, flags); + usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci_hcd)); +} + +#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) + +/* + * Returns 0 if the status hasn't changed, or the number of bytes in buf. + * Ports are 0-indexed from the HCD point of view, + * and 1-indexed from the USB core pointer of view. + * + * @buf: a bitmap to show which port status has been changed. + * bit 0: reserved + * bit 1: the status of port 0 has been changed. + * bit 2: the status of port 1 has been changed. + * ... + */ +static int vhci_hub_status(struct usb_hcd *hcd, char *buf) +{ + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); + struct vhci *vhci = vhci_hcd->vhci; + int retval = DIV_ROUND_UP(VHCI_HC_PORTS + 1, 8); + int rhport; + int changed = 0; + unsigned long flags; + + memset(buf, 0, retval); + + spin_lock_irqsave(&vhci->lock, flags); + if (!HCD_HW_ACCESSIBLE(hcd)) { + usbip_dbg_vhci_rh("hw accessible flag not on?\n"); + goto done; + } + + /* check pseudo status register for each port */ + for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { + if ((vhci_hcd->port_status[rhport] & PORT_C_MASK)) { + /* The status of a port has been changed, */ + usbip_dbg_vhci_rh("port %d status changed\n", rhport); + + buf[(rhport + 1) / 8] |= 1 << (rhport + 1) % 8; + changed = 1; + } + } + + if ((hcd->state == HC_STATE_SUSPENDED) && (changed == 1)) + usb_hcd_resume_root_hub(hcd); + +done: + spin_unlock_irqrestore(&vhci->lock, flags); + return changed ? retval : 0; +} + +/* usb 3.0 root hub device descriptor */ +static struct { + struct usb_bos_descriptor bos; + struct usb_ss_cap_descriptor ss_cap; +} __packed usb3_bos_desc = { + + .bos = { + .bLength = USB_DT_BOS_SIZE, + .bDescriptorType = USB_DT_BOS, + .wTotalLength = cpu_to_le16(sizeof(usb3_bos_desc)), + .bNumDeviceCaps = 1, + }, + .ss_cap = { + .bLength = USB_DT_USB_SS_CAP_SIZE, + .bDescriptorType = USB_DT_DEVICE_CAPABILITY, + .bDevCapabilityType = USB_SS_CAP_TYPE, + .wSpeedSupported = cpu_to_le16(USB_5GBPS_OPERATION), + .bFunctionalitySupport = ilog2(USB_5GBPS_OPERATION), + }, +}; + +static inline void +ss_hub_descriptor(struct usb_hub_descriptor *desc) +{ + memset(desc, 0, sizeof *desc); + desc->bDescriptorType = USB_DT_SS_HUB; + desc->bDescLength = 12; + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_COMMON_OCPM); + desc->bNbrPorts = VHCI_HC_PORTS; + desc->u.ss.bHubHdrDecLat = 0x04; /* Worst case: 0.4 micro sec*/ + desc->u.ss.DeviceRemovable = 0xffff; +} + +static inline void hub_descriptor(struct usb_hub_descriptor *desc) +{ + int width; + + memset(desc, 0, sizeof(*desc)); + desc->bDescriptorType = USB_DT_HUB; + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_COMMON_OCPM); + + desc->bNbrPorts = VHCI_HC_PORTS; + BUILD_BUG_ON(VHCI_HC_PORTS > USB_MAXCHILDREN); + width = desc->bNbrPorts / 8 + 1; + desc->bDescLength = USB_DT_HUB_NONVAR_SIZE + 2 * width; + memset(&desc->u.hs.DeviceRemovable[0], 0, width); + memset(&desc->u.hs.DeviceRemovable[width], 0xff, width); +} + +static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct vhci_hcd *vhci_hcd; + struct vhci *vhci; + int retval = 0; + int rhport = -1; + unsigned long flags; + bool invalid_rhport = false; + + u32 prev_port_status[VHCI_HC_PORTS]; + + if (!HCD_HW_ACCESSIBLE(hcd)) + return -ETIMEDOUT; + + /* + * NOTE: + * wIndex (bits 0-7) shows the port number and begins from 1? + */ + wIndex = ((__u8)(wIndex & 0x00ff)); + usbip_dbg_vhci_rh("typeReq %x wValue %x wIndex %x\n", typeReq, wValue, + wIndex); + + /* + * wIndex can be 0 for some request types (typeReq). rhport is + * in valid range when wIndex >= 1 and < VHCI_HC_PORTS. + * + * Reference port_status[] only with valid rhport when + * invalid_rhport is false. + */ + if (wIndex < 1 || wIndex > VHCI_HC_PORTS) { + invalid_rhport = true; + if (wIndex > VHCI_HC_PORTS) + dev_err(hcd_dev(hcd), "invalid port number %d\n", wIndex); + } else { + rhport = wIndex - 1; + } + + vhci_hcd = hcd_to_vhci_hcd(hcd); + vhci = vhci_hcd->vhci; + + spin_lock_irqsave(&vhci->lock, flags); + + /* store old status and compare now and old later */ + if (usbip_dbg_flag_vhci_rh) { + if (!invalid_rhport) + memcpy(prev_port_status, vhci_hcd->port_status, + sizeof(prev_port_status)); + } + + switch (typeReq) { + case ClearHubFeature: + usbip_dbg_vhci_rh(" ClearHubFeature\n"); + break; + case ClearPortFeature: + if (invalid_rhport) { + dev_err(hcd_dev(hcd), "invalid port number %d\n", wIndex); + goto error; + } + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + if (hcd->speed >= HCD_USB3) { + dev_err(hcd_dev(hcd), + "ClearPortFeature: USB_PORT_FEAT_SUSPEND req not supported for USB 3.0 roothub\n"); + goto error; + } + usbip_dbg_vhci_rh( + " ClearPortFeature: USB_PORT_FEAT_SUSPEND\n"); + if (vhci_hcd->port_status[rhport] & USB_PORT_STAT_SUSPEND) { + /* 20msec signaling */ + vhci_hcd->resuming = 1; + vhci_hcd->re_timeout = jiffies + msecs_to_jiffies(20); + } + break; + case USB_PORT_FEAT_POWER: + usbip_dbg_vhci_rh( + " ClearPortFeature: USB_PORT_FEAT_POWER\n"); + if (hcd->speed >= HCD_USB3) + vhci_hcd->port_status[rhport] &= ~USB_SS_PORT_STAT_POWER; + else + vhci_hcd->port_status[rhport] &= ~USB_PORT_STAT_POWER; + break; + default: + usbip_dbg_vhci_rh(" ClearPortFeature: default %x\n", + wValue); + if (wValue >= 32) + goto error; + vhci_hcd->port_status[rhport] &= ~(1 << wValue); + break; + } + break; + case GetHubDescriptor: + usbip_dbg_vhci_rh(" GetHubDescriptor\n"); + if (hcd->speed >= HCD_USB3 && + (wLength < USB_DT_SS_HUB_SIZE || + wValue != (USB_DT_SS_HUB << 8))) { + dev_err(hcd_dev(hcd), + "Wrong hub descriptor type for USB 3.0 roothub.\n"); + goto error; + } + if (hcd->speed >= HCD_USB3) + ss_hub_descriptor((struct usb_hub_descriptor *) buf); + else + hub_descriptor((struct usb_hub_descriptor *) buf); + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + if (hcd->speed < HCD_USB3) + goto error; + + if ((wValue >> 8) != USB_DT_BOS) + goto error; + + memcpy(buf, &usb3_bos_desc, sizeof(usb3_bos_desc)); + retval = sizeof(usb3_bos_desc); + break; + case GetHubStatus: + usbip_dbg_vhci_rh(" GetHubStatus\n"); + *(__le32 *) buf = cpu_to_le32(0); + break; + case GetPortStatus: + usbip_dbg_vhci_rh(" GetPortStatus port %x\n", wIndex); + if (invalid_rhport) { + dev_err(hcd_dev(hcd), "invalid port number %d\n", wIndex); + retval = -EPIPE; + goto error; + } + + /* we do not care about resume. */ + + /* whoever resets or resumes must GetPortStatus to + * complete it!! + */ + if (vhci_hcd->resuming && time_after(jiffies, vhci_hcd->re_timeout)) { + vhci_hcd->port_status[rhport] |= (1 << USB_PORT_FEAT_C_SUSPEND); + vhci_hcd->port_status[rhport] &= ~(1 << USB_PORT_FEAT_SUSPEND); + vhci_hcd->resuming = 0; + vhci_hcd->re_timeout = 0; + } + + if ((vhci_hcd->port_status[rhport] & (1 << USB_PORT_FEAT_RESET)) != + 0 && time_after(jiffies, vhci_hcd->re_timeout)) { + vhci_hcd->port_status[rhport] |= (1 << USB_PORT_FEAT_C_RESET); + vhci_hcd->port_status[rhport] &= ~(1 << USB_PORT_FEAT_RESET); + vhci_hcd->re_timeout = 0; + + /* + * A few drivers do usb reset during probe when + * the device could be in VDEV_ST_USED state + */ + if (vhci_hcd->vdev[rhport].ud.status == + VDEV_ST_NOTASSIGNED || + vhci_hcd->vdev[rhport].ud.status == + VDEV_ST_USED) { + usbip_dbg_vhci_rh( + " enable rhport %d (status %u)\n", + rhport, + vhci_hcd->vdev[rhport].ud.status); + vhci_hcd->port_status[rhport] |= + USB_PORT_STAT_ENABLE; + } + + if (hcd->speed < HCD_USB3) { + switch (vhci_hcd->vdev[rhport].speed) { + case USB_SPEED_HIGH: + vhci_hcd->port_status[rhport] |= + USB_PORT_STAT_HIGH_SPEED; + break; + case USB_SPEED_LOW: + vhci_hcd->port_status[rhport] |= + USB_PORT_STAT_LOW_SPEED; + break; + default: + dev_err(hcd_dev(hcd), "vhci_device speed not set\n"); + break; + } + } + } + ((__le16 *) buf)[0] = cpu_to_le16(vhci_hcd->port_status[rhport]); + ((__le16 *) buf)[1] = + cpu_to_le16(vhci_hcd->port_status[rhport] >> 16); + + usbip_dbg_vhci_rh(" GetPortStatus bye %x %x\n", ((u16 *)buf)[0], + ((u16 *)buf)[1]); + break; + case SetHubFeature: + usbip_dbg_vhci_rh(" SetHubFeature\n"); + retval = -EPIPE; + break; + case SetPortFeature: + switch (wValue) { + case USB_PORT_FEAT_LINK_STATE: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_LINK_STATE\n"); + if (hcd->speed < HCD_USB3) { + dev_err(hcd_dev(hcd), + "USB_PORT_FEAT_LINK_STATE req not supported for USB 2.0 roothub\n"); + goto error; + } + /* + * Since this is dummy we don't have an actual link so + * there is nothing to do for the SET_LINK_STATE cmd + */ + break; + case USB_PORT_FEAT_U1_TIMEOUT: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_U1_TIMEOUT\n"); + fallthrough; + case USB_PORT_FEAT_U2_TIMEOUT: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_U2_TIMEOUT\n"); + /* TODO: add suspend/resume support! */ + if (hcd->speed < HCD_USB3) { + dev_err(hcd_dev(hcd), + "USB_PORT_FEAT_U1/2_TIMEOUT req not supported for USB 2.0 roothub\n"); + goto error; + } + break; + case USB_PORT_FEAT_SUSPEND: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_SUSPEND\n"); + /* Applicable only for USB2.0 hub */ + if (hcd->speed >= HCD_USB3) { + dev_err(hcd_dev(hcd), + "USB_PORT_FEAT_SUSPEND req not supported for USB 3.0 roothub\n"); + goto error; + } + + if (invalid_rhport) { + dev_err(hcd_dev(hcd), "invalid port number %d\n", wIndex); + goto error; + } + + vhci_hcd->port_status[rhport] |= USB_PORT_STAT_SUSPEND; + break; + case USB_PORT_FEAT_POWER: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_POWER\n"); + if (invalid_rhport) { + dev_err(hcd_dev(hcd), "invalid port number %d\n", wIndex); + goto error; + } + if (hcd->speed >= HCD_USB3) + vhci_hcd->port_status[rhport] |= USB_SS_PORT_STAT_POWER; + else + vhci_hcd->port_status[rhport] |= USB_PORT_STAT_POWER; + break; + case USB_PORT_FEAT_BH_PORT_RESET: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_BH_PORT_RESET\n"); + if (invalid_rhport) { + dev_err(hcd_dev(hcd), "invalid port number %d\n", wIndex); + goto error; + } + /* Applicable only for USB3.0 hub */ + if (hcd->speed < HCD_USB3) { + dev_err(hcd_dev(hcd), + "USB_PORT_FEAT_BH_PORT_RESET req not supported for USB 2.0 roothub\n"); + goto error; + } + fallthrough; + case USB_PORT_FEAT_RESET: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_RESET\n"); + if (invalid_rhport) { + dev_err(hcd_dev(hcd), "invalid port number %d\n", wIndex); + goto error; + } + /* if it's already enabled, disable */ + if (hcd->speed >= HCD_USB3) { + vhci_hcd->port_status[rhport] = 0; + vhci_hcd->port_status[rhport] = + (USB_SS_PORT_STAT_POWER | + USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_RESET); + } else if (vhci_hcd->port_status[rhport] & USB_PORT_STAT_ENABLE) { + vhci_hcd->port_status[rhport] &= ~(USB_PORT_STAT_ENABLE + | USB_PORT_STAT_LOW_SPEED + | USB_PORT_STAT_HIGH_SPEED); + } + + /* 50msec reset signaling */ + vhci_hcd->re_timeout = jiffies + msecs_to_jiffies(50); + fallthrough; + default: + usbip_dbg_vhci_rh(" SetPortFeature: default %d\n", + wValue); + if (invalid_rhport) { + dev_err(hcd_dev(hcd), "invalid port number %d\n", wIndex); + goto error; + } + if (wValue >= 32) + goto error; + if (hcd->speed >= HCD_USB3) { + if ((vhci_hcd->port_status[rhport] & + USB_SS_PORT_STAT_POWER) != 0) { + vhci_hcd->port_status[rhport] |= (1 << wValue); + } + } else + if ((vhci_hcd->port_status[rhport] & + USB_PORT_STAT_POWER) != 0) { + vhci_hcd->port_status[rhport] |= (1 << wValue); + } + } + break; + case GetPortErrorCount: + usbip_dbg_vhci_rh(" GetPortErrorCount\n"); + if (hcd->speed < HCD_USB3) { + dev_err(hcd_dev(hcd), + "GetPortErrorCount req not supported for USB 2.0 roothub\n"); + goto error; + } + /* We'll always return 0 since this is a dummy hub */ + *(__le32 *) buf = cpu_to_le32(0); + break; + case SetHubDepth: + usbip_dbg_vhci_rh(" SetHubDepth\n"); + if (hcd->speed < HCD_USB3) { + dev_err(hcd_dev(hcd), + "SetHubDepth req not supported for USB 2.0 roothub\n"); + goto error; + } + break; + default: + dev_err(hcd_dev(hcd), + "default hub control req: %04x v%04x i%04x l%d\n", + typeReq, wValue, wIndex, wLength); +error: + /* "protocol stall" on error */ + retval = -EPIPE; + } + + if (usbip_dbg_flag_vhci_rh) { + dev_dbg(hcd_dev(hcd), "%s port %d\n", __func__, rhport); + /* Only dump valid port status */ + if (!invalid_rhport) { + dump_port_status_diff(prev_port_status[rhport], + vhci_hcd->port_status[rhport], + hcd->speed >= HCD_USB3); + } + } + usbip_dbg_vhci_rh(" bye\n"); + + spin_unlock_irqrestore(&vhci->lock, flags); + + if (!invalid_rhport && + (vhci_hcd->port_status[rhport] & PORT_C_MASK) != 0) { + usb_hcd_poll_rh_status(hcd); + } + + return retval; +} + +static void vhci_tx_urb(struct urb *urb, struct vhci_device *vdev) +{ + struct vhci_priv *priv; + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + unsigned long flags; + + priv = kzalloc_obj(struct vhci_priv, GFP_ATOMIC); + if (!priv) { + usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_MALLOC); + return; + } + + spin_lock_irqsave(&vdev->priv_lock, flags); + + priv->seqnum = (u32)atomic_inc_return(&vhci_hcd->seqnum); + if (priv->seqnum == 0xffff) + dev_info(&urb->dev->dev, "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, flags); +} + +static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) +{ + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); + struct vhci *vhci = vhci_hcd->vhci; + struct device *dev = &urb->dev->dev; + u8 portnum = urb->dev->portnum; + int ret = 0; + struct vhci_device *vdev; + unsigned long flags; + + if (portnum > VHCI_HC_PORTS) { + dev_err(hcd_dev(hcd), "invalid port number %d\n", portnum); + return -ENODEV; + } + vdev = &vhci_hcd->vdev[portnum-1]; + + if (!urb->transfer_buffer && !urb->num_sgs && + urb->transfer_buffer_length) { + dev_dbg(dev, "Null URB transfer buffer\n"); + return -EINVAL; + } + + spin_lock_irqsave(&vhci->lock, flags); + + if (urb->status != -EINPROGRESS) { + dev_err(dev, "URB already unlinked!, status %d\n", urb->status); + spin_unlock_irqrestore(&vhci->lock, flags); + return urb->status; + } + + /* refuse enqueue for dead connection */ + spin_lock(&vdev->ud.lock); + if (vdev->ud.status == VDEV_ST_NULL || + vdev->ud.status == VDEV_ST_ERROR) { + dev_err(dev, "enqueue for inactive port %d\n", vdev->rhport); + spin_unlock(&vdev->ud.lock); + spin_unlock_irqrestore(&vhci->lock, flags); + return -ENODEV; + } + spin_unlock(&vdev->ud.lock); + + ret = usb_hcd_link_urb_to_ep(hcd, urb); + if (ret) + goto no_need_unlink; + + /* + * The enumeration 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) { + struct usb_device *old; + __u8 type = usb_pipetype(urb->pipe); + struct usb_ctrlrequest *ctrlreq = + (struct usb_ctrlrequest *) urb->setup_packet; + + if (type != PIPE_CONTROL || !ctrlreq) { + dev_err(dev, "invalid request to devnum 0\n"); + ret = -EINVAL; + goto no_need_xmit; + } + + old = vdev->udev; + 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 = usb_get_dev(urb->dev); + /* + * NOTE: A similar operation has been done via + * USB_REQ_GET_DESCRIPTOR handler below, which is + * supposed to always precede USB_REQ_SET_ADDRESS. + * + * It's not entirely clear if operating on a different + * usb_device instance here is a real possibility, + * otherwise this call and vdev->udev assignment above + * should be dropped. + */ + dev_pm_syscore_device(&vdev->udev->dev, true); + usb_put_dev(old); + + 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 == cpu_to_le16(USB_DT_DEVICE << 8)) + usbip_dbg_vhci_hc( + "Not yet?:Get_Descriptor to device 0 (get max pipe size)\n"); + + vdev->udev = usb_get_dev(urb->dev); + /* + * Set syscore PM flag for the virtually attached + * devices to ensure they will not enter suspend on + * the client side. + * + * Note this doesn't have any impact on the physical + * devices attached to the host system on the server + * side, hence there is no need to undo the operation + * on disconnect. + */ + dev_pm_syscore_device(&vdev->udev->dev, true); + usb_put_dev(old); + 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, vdev); + spin_unlock_irqrestore(&vhci->lock, flags); + + return 0; + +no_need_xmit: + usb_hcd_unlink_urb_from_ep(hcd, urb); +no_need_unlink: + if (!ret) { + /* usb_hcd_giveback_urb() should be called with + * irqs disabled + */ + spin_unlock(&vhci->lock); + usb_hcd_giveback_urb(hcd, urb, urb->status); + spin_lock(&vhci->lock); + } + spin_unlock_irqrestore(&vhci->lock, flags); + return ret; +} + +/* + * 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) +{ + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); + struct vhci *vhci = vhci_hcd->vhci; + struct vhci_priv *priv; + struct vhci_device *vdev; + unsigned long flags; + + spin_lock_irqsave(&vhci->lock, flags); + + priv = urb->hcpriv; + if (!priv) { + /* URB was never linked! or will be soon given back by + * vhci_rx. */ + spin_unlock_irqrestore(&vhci->lock, flags); + return -EIDRM; + } + + { + int ret = 0; + + ret = usb_hcd_check_unlink_urb(hcd, urb, status); + if (ret) { + spin_unlock_irqrestore(&vhci->lock, flags); + return ret; + } + } + + /* send unlink request here? */ + vdev = priv->vdev; + + if (!vdev->ud.tcp_socket) { + /* tcp connection is closed */ + spin_lock(&vdev->priv_lock); + + list_del(&priv->list); + kfree(priv); + urb->hcpriv = NULL; + + spin_unlock(&vdev->priv_lock); + + /* + * 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. + */ + usb_hcd_unlink_urb_from_ep(hcd, urb); + + spin_unlock_irqrestore(&vhci->lock, flags); + usb_hcd_giveback_urb(hcd, urb, urb->status); + spin_lock_irqsave(&vhci->lock, flags); + + } else { + /* tcp connection is alive */ + struct vhci_unlink *unlink; + + spin_lock(&vdev->priv_lock); + + /* setup CMD_UNLINK pdu */ + unlink = kzalloc_obj(struct vhci_unlink, GFP_ATOMIC); + if (!unlink) { + spin_unlock(&vdev->priv_lock); + spin_unlock_irqrestore(&vhci->lock, flags); + usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_MALLOC); + return -ENOMEM; + } + + unlink->seqnum = atomic_inc_return(&vhci_hcd->seqnum); + if (unlink->seqnum == 0xffff) + dev_info(hcd_dev(hcd), "seqnum max\n"); + + unlink->unlink_seqnum = priv->seqnum; + + /* 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(&vdev->priv_lock); + } + + spin_unlock_irqrestore(&vhci->lock, flags); + + usbip_dbg_vhci_hc("leave\n"); + return 0; +} + +static void vhci_cleanup_unlink_list(struct vhci_device *vdev, + struct list_head *unlink_list) +{ + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct usb_hcd *hcd = vhci_hcd_to_hcd(vhci_hcd); + struct vhci *vhci = vhci_hcd->vhci; + struct vhci_unlink *unlink, *tmp; + unsigned long flags; + + spin_lock_irqsave(&vhci->lock, flags); + spin_lock(&vdev->priv_lock); + + list_for_each_entry_safe(unlink, tmp, unlink_list, list) { + struct urb *urb; + + urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum); + if (!urb) { + list_del(&unlink->list); + kfree(unlink); + continue; + } + + urb->status = -ENODEV; + + usb_hcd_unlink_urb_from_ep(hcd, urb); + + list_del(&unlink->list); + + spin_unlock(&vdev->priv_lock); + spin_unlock_irqrestore(&vhci->lock, flags); + + usb_hcd_giveback_urb(hcd, urb, urb->status); + + spin_lock_irqsave(&vhci->lock, flags); + spin_lock(&vdev->priv_lock); + + kfree(unlink); + } + + spin_unlock(&vdev->priv_lock); + spin_unlock_irqrestore(&vhci->lock, flags); +} + +static void vhci_device_unlink_cleanup(struct vhci_device *vdev) +{ + /* give back URB of unsent unlink request */ + vhci_cleanup_unlink_list(vdev, &vdev->unlink_tx); + + /* give back URB of unanswered unlink request */ + vhci_cleanup_unlink_list(vdev, &vdev->unlink_rx); +} + +/* + * 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); + struct usb_hcd *hcd = vhci_hcd_to_hcd(vdev_to_vhci_hcd(vdev)); + + /* need this? see stub_dev.c */ + if (ud->tcp_socket) { + dev_dbg(hcd_dev(hcd), "shutdown tcp_socket %d\n", ud->sockfd); + kernel_sock_shutdown(ud->tcp_socket, SHUT_RDWR); + } + + /* kill threads related to this sdev */ + if (vdev->ud.tcp_rx) { + kthread_stop_put(vdev->ud.tcp_rx); + vdev->ud.tcp_rx = NULL; + } + if (vdev->ud.tcp_tx) { + kthread_stop_put(vdev->ud.tcp_tx); + vdev->ud.tcp_tx = NULL; + } + dev_info(hcd_dev(hcd), "stop threads\n"); + + /* active connection is closed */ + if (vdev->ud.tcp_socket) { + sockfd_put(vdev->ud.tcp_socket); + vdev->ud.tcp_socket = NULL; + vdev->ud.sockfd = -1; + } + dev_info(hcd_dev(hcd), "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 + * detached 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 shutting down + * 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); + + dev_info(hcd_dev(hcd), "disconnect device\n"); +} + +static void vhci_device_reset(struct usbip_device *ud) +{ + struct vhci_device *vdev = container_of(ud, struct vhci_device, ud); + struct usb_device *old = vdev->udev; + unsigned long flags; + + spin_lock_irqsave(&ud->lock, flags); + + vdev->speed = 0; + vdev->devid = 0; + + vdev->udev = NULL; + usb_put_dev(old); + + if (ud->tcp_socket) { + sockfd_put(ud->tcp_socket); + ud->tcp_socket = NULL; + ud->sockfd = -1; + } + ud->status = VDEV_ST_NULL; + + spin_unlock_irqrestore(&ud->lock, flags); +} + +static void vhci_device_unusable(struct usbip_device *ud) +{ + unsigned long flags; + + spin_lock_irqsave(&ud->lock, flags); + ud->status = VDEV_ST_ERROR; + spin_unlock_irqrestore(&ud->lock, flags); +} + +static void vhci_device_init(struct vhci_device *vdev) +{ + memset(vdev, 0, sizeof(struct vhci_device)); + + vdev->ud.side = USBIP_VHCI; + vdev->ud.status = VDEV_ST_NULL; + spin_lock_init(&vdev->ud.lock); + mutex_init(&vdev->ud.sysfs_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); + 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 hcd_name_to_id(const char *name) +{ + char *c; + long val; + int ret; + + c = strchr(name, '.'); + if (c == NULL) + return 0; + + ret = kstrtol(c+1, 10, &val); + if (ret < 0) + return ret; + + return val; +} + +static int vhci_setup(struct usb_hcd *hcd) +{ + struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller)); + + if (usb_hcd_is_primary_hcd(hcd)) { + vhci->vhci_hcd_hs = hcd_to_vhci_hcd(hcd); + vhci->vhci_hcd_hs->vhci = vhci; + /* + * Mark the first roothub as being USB 2.0. + * The USB 3.0 roothub will be registered later by + * vhci_hcd_probe() + */ + hcd->speed = HCD_USB2; + hcd->self.root_hub->speed = USB_SPEED_HIGH; + } else { + vhci->vhci_hcd_ss = hcd_to_vhci_hcd(hcd); + vhci->vhci_hcd_ss->vhci = vhci; + hcd->speed = HCD_USB31; + hcd->self.root_hub->speed = USB_SPEED_SUPER_PLUS; + } + + /* accept arbitrarily long scatter-gather lists */ + hcd->self.sg_tablesize = ~0; + hcd->self.no_sg_constraint = 1; + + return 0; +} + +static int vhci_start(struct usb_hcd *hcd) +{ + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); + int id, rhport; + int err; + + usbip_dbg_vhci_hc("enter vhci_start\n"); + + if (usb_hcd_is_primary_hcd(hcd)) + spin_lock_init(&vhci_hcd->vhci->lock); + + /* initialize private data of usb_hcd */ + + for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { + struct vhci_device *vdev = &vhci_hcd->vdev[rhport]; + + vhci_device_init(vdev); + vdev->rhport = rhport; + } + + atomic_set(&vhci_hcd->seqnum, 0); + + hcd->power_budget = 0; /* no limit */ + hcd->uses_new_polling = 1; + +#ifdef CONFIG_USB_OTG + hcd->self.otg_port = 1; +#endif + + id = hcd_name_to_id(hcd_name(hcd)); + if (id < 0) { + dev_err(hcd_dev(hcd), "invalid vhci name %s\n", hcd_name(hcd)); + return -EINVAL; + } + + /* vhci_hcd is now ready to be controlled through sysfs */ + if (id == 0 && usb_hcd_is_primary_hcd(hcd)) { + err = vhci_init_attr_group(); + if (err) { + dev_err(hcd_dev(hcd), "init attr group failed, err = %d\n", err); + return err; + } + err = sysfs_create_group(&hcd_dev(hcd)->kobj, &vhci_attr_group); + if (err) { + dev_err(hcd_dev(hcd), "create sysfs files failed, err = %d\n", err); + vhci_finish_attr_group(); + return err; + } + dev_info(hcd_dev(hcd), "created sysfs %s\n", hcd_name(hcd)); + } + + return 0; +} + +static void vhci_stop(struct usb_hcd *hcd) +{ + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); + int id, rhport; + + usbip_dbg_vhci_hc("stop VHCI controller\n"); + + /* 1. remove the userland interface of vhci_hcd */ + id = hcd_name_to_id(hcd_name(hcd)); + if (id == 0 && usb_hcd_is_primary_hcd(hcd)) { + sysfs_remove_group(&hcd_dev(hcd)->kobj, &vhci_attr_group); + vhci_finish_attr_group(); + } + + /* 2. shutdown all the ports of vhci_hcd */ + for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { + struct vhci_device *vdev = &vhci_hcd->vdev[rhport]; + + usbip_event_add(&vdev->ud, VDEV_EVENT_REMOVED); + usbip_stop_eh(&vdev->ud); + } +} + +static int vhci_get_frame_number(struct usb_hcd *hcd) +{ + dev_err_ratelimited(&hcd->self.root_hub->dev, "Not yet implemented\n"); + return 0; +} + +#ifdef CONFIG_PM + +/* FIXME: suspend/resume */ +static int vhci_bus_suspend(struct usb_hcd *hcd) +{ + struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller)); + unsigned long flags; + + dev_dbg(&hcd->self.root_hub->dev, "%s\n", __func__); + + spin_lock_irqsave(&vhci->lock, flags); + hcd->state = HC_STATE_SUSPENDED; + spin_unlock_irqrestore(&vhci->lock, flags); + + return 0; +} + +static int vhci_bus_resume(struct usb_hcd *hcd) +{ + struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller)); + int rc = 0; + unsigned long flags; + + dev_dbg(&hcd->self.root_hub->dev, "%s\n", __func__); + + spin_lock_irqsave(&vhci->lock, flags); + if (!HCD_HW_ACCESSIBLE(hcd)) + rc = -ESHUTDOWN; + else + hcd->state = HC_STATE_RUNNING; + spin_unlock_irqrestore(&vhci->lock, flags); + + return rc; +} + +#else + +#define vhci_bus_suspend NULL +#define vhci_bus_resume NULL +#endif + +/* Change a group of bulk endpoints to support multiple stream IDs */ +static int vhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps, + unsigned int num_streams, gfp_t mem_flags) +{ + dev_dbg(&hcd->self.root_hub->dev, "vhci_alloc_streams not implemented\n"); + return 0; +} + +/* Reverts a group of bulk endpoints back to not using stream IDs. */ +static int vhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps, + gfp_t mem_flags) +{ + dev_dbg(&hcd->self.root_hub->dev, "vhci_free_streams not implemented\n"); + return 0; +} + +static const struct hc_driver vhci_hc_driver = { + .description = driver_name, + .product_desc = driver_desc, + .hcd_priv_size = sizeof(struct vhci_hcd), + + .flags = HCD_USB31 | HCD_SHARED, + + .reset = vhci_setup, + .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, + + .alloc_streams = vhci_alloc_streams, + .free_streams = vhci_free_streams, +}; + +static int vhci_hcd_probe(struct platform_device *pdev) +{ + struct vhci *vhci = *((void **)dev_get_platdata(&pdev->dev)); + struct usb_hcd *hcd_hs; + struct usb_hcd *hcd_ss; + int ret; + + usbip_dbg_vhci_hc("name %s id %d\n", pdev->name, pdev->id); + + /* + * Allocate and initialize hcd. + * Our private data is also allocated automatically. + */ + hcd_hs = usb_create_hcd(&vhci_hc_driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd_hs) + return dev_err_probe(&pdev->dev, -ENOMEM, "create primary hcd failed\n"); + + hcd_hs->has_tt = 1; + + /* + * Finish generic HCD structure initialization and register. + * Call the driver's reset() and start() routines. + */ + ret = usb_add_hcd(hcd_hs, 0, 0); + if (ret) { + dev_err_probe(&pdev->dev, ret, "usb_add_hcd hs failed\n"); + goto put_usb2_hcd; + } + + hcd_ss = usb_create_shared_hcd(&vhci_hc_driver, &pdev->dev, + dev_name(&pdev->dev), hcd_hs); + if (!hcd_ss) { + ret = dev_err_probe(&pdev->dev, -ENOMEM, "create shared hcd failed\n"); + goto remove_usb2_hcd; + } + + ret = usb_add_hcd(hcd_ss, 0, 0); + if (ret) { + dev_err_probe(&pdev->dev, ret, "usb_add_hcd ss failed\n"); + goto put_usb3_hcd; + } + + usbip_dbg_vhci_hc("bye\n"); + return 0; + +put_usb3_hcd: + usb_put_hcd(hcd_ss); +remove_usb2_hcd: + usb_remove_hcd(hcd_hs); +put_usb2_hcd: + usb_put_hcd(hcd_hs); + vhci->vhci_hcd_hs = NULL; + vhci->vhci_hcd_ss = NULL; + return ret; +} + +static void vhci_hcd_remove(struct platform_device *pdev) +{ + struct vhci *vhci = *((void **)dev_get_platdata(&pdev->dev)); + + /* + * Disconnects the root hub, + * then reverses the effects of usb_add_hcd(), + * invoking the HCD's stop() methods. + */ + usb_remove_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_ss)); + usb_put_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_ss)); + + usb_remove_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_hs)); + usb_put_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_hs)); + + vhci->vhci_hcd_hs = NULL; + vhci->vhci_hcd_ss = NULL; +} + +#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; + struct vhci *vhci; + int rhport; + int connected = 0; + int ret = 0; + unsigned long flags; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + hcd = platform_get_drvdata(pdev); + if (!hcd) + return 0; + + vhci = *((void **)dev_get_platdata(hcd->self.controller)); + + spin_lock_irqsave(&vhci->lock, flags); + + for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { + if (vhci->vhci_hcd_hs->port_status[rhport] & + USB_PORT_STAT_CONNECTION) + connected += 1; + + if (vhci->vhci_hcd_ss->port_status[rhport] & + USB_PORT_STAT_CONNECTION) + connected += 1; + } + + spin_unlock_irqrestore(&vhci->lock, flags); + + if (connected > 0) { + dev_info(&pdev->dev, + "We have %d active connection%s. Do not suspend.\n", + connected, str_plural(connected)); + ret = -EBUSY; + } else { + dev_info(&pdev->dev, "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); + if (!hcd) + return 0; + 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 = vhci_hcd_remove, + .suspend = vhci_hcd_suspend, + .resume = vhci_hcd_resume, + .driver = { + .name = driver_name, + }, +}; + +static void del_platform_devices(void) +{ + int i; + + for (i = 0; i < vhci_num_controllers; i++) { + platform_device_unregister(vhcis[i].pdev); + vhcis[i].pdev = NULL; + } + sysfs_remove_link(&platform_bus.kobj, driver_name); +} + +static int __init vhci_hcd_init(void) +{ + int i, ret; + + if (usb_disabled()) + return -ENODEV; + + if (vhci_num_controllers < 1) + vhci_num_controllers = 1; + + vhcis = kzalloc_objs(struct vhci, vhci_num_controllers); + if (vhcis == NULL) + return -ENOMEM; + + ret = platform_driver_register(&vhci_driver); + if (ret) + goto err_driver_register; + + for (i = 0; i < vhci_num_controllers; i++) { + void *vhci = &vhcis[i]; + struct platform_device_info pdevinfo = { + .name = driver_name, + .id = i, + .data = &vhci, + .size_data = sizeof(void *), + }; + + vhcis[i].pdev = platform_device_register_full(&pdevinfo); + ret = PTR_ERR_OR_ZERO(vhcis[i].pdev); + if (ret < 0) { + while (i--) + platform_device_unregister(vhcis[i].pdev); + goto err_add_hcd; + } + } + + return 0; + +err_add_hcd: + platform_driver_unregister(&vhci_driver); +err_driver_register: + kfree(vhcis); + return ret; +} + +static void __exit vhci_hcd_exit(void) +{ + del_platform_devices(); + platform_driver_unregister(&vhci_driver); + kfree(vhcis); +} + +module_init(vhci_hcd_init); +module_exit(vhci_hcd_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/usbip/vhci_rx.c b/drivers/usb/usbip/vhci_rx.c new file mode 100644 index 000000000..a678e7c89 --- /dev/null +++ b/drivers/usb/usbip/vhci_rx.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + */ + +#include +#include + +#include "usbip_common.h" +#include "vhci.h" + +/* get URB from transmitted urb queue. caller must hold vdev->priv_lock */ +struct urb *pickup_urb_and_free_priv(struct vhci_device *vdev, __u32 seqnum) +{ + struct vhci_priv *priv, *tmp; + struct urb *urb = NULL; + int status; + + list_for_each_entry_safe(priv, tmp, &vdev->priv_rx, list) { + if (priv->seqnum != seqnum) + continue; + + urb = priv->urb; + status = urb->status; + + usbip_dbg_vhci_rx("find urb seqnum %u\n", seqnum); + + switch (status) { + case -ENOENT: + fallthrough; + case -ECONNRESET: + dev_dbg(&urb->dev->dev, + "urb seq# %u was unlinked %ssynchronously\n", + seqnum, status == -ENOENT ? "" : "a"); + break; + case -EINPROGRESS: + /* no info output */ + break; + default: + dev_dbg(&urb->dev->dev, + "urb seq# %u may be in a error, status %d\n", + seqnum, status); + } + + list_del(&priv->list); + kfree(priv); + urb->hcpriv = NULL; + + break; + } + + return urb; +} + +static void vhci_recv_ret_submit(struct vhci_device *vdev, + struct usbip_header *pdu) +{ + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct vhci *vhci = vhci_hcd->vhci; + struct usbip_device *ud = &vdev->ud; + struct urb *urb; + unsigned long flags; + + spin_lock_irqsave(&vdev->priv_lock, flags); + urb = pickup_urb_and_free_priv(vdev, pdu->base.seqnum); + spin_unlock_irqrestore(&vdev->priv_lock, flags); + + if (!urb) { + pr_err("cannot find a urb of seqnum %u max seqnum %u\n", + pdu->base.seqnum, + atomic_read(&vhci_hcd->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) { + urb->status = -EPROTO; + goto error; + } + + /* recv iso_packet_descriptor */ + if (usbip_recv_iso(ud, urb) < 0) { + urb->status = -EPROTO; + goto error; + } + + /* restore the padding in iso packets */ + usbip_pad_iso(ud, urb); + +error: + if (usbip_dbg_flag_vhci_rx) + usbip_dump_urb(urb); + + if (urb->num_sgs) + urb->transfer_flags &= ~URB_DMA_MAP_SG; + + usbip_dbg_vhci_rx("now giveback urb %u\n", pdu->base.seqnum); + + spin_lock_irqsave(&vhci->lock, flags); + usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci_hcd), urb); + spin_unlock_irqrestore(&vhci->lock, flags); + + usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci_hcd), urb, urb->status); + + usbip_dbg_vhci_rx("Leave\n"); +} + +static struct vhci_unlink *dequeue_pending_unlink(struct vhci_device *vdev, + struct usbip_header *pdu) +{ + struct vhci_unlink *unlink, *tmp; + unsigned long flags; + + spin_lock_irqsave(&vdev->priv_lock, flags); + + list_for_each_entry_safe(unlink, tmp, &vdev->unlink_rx, list) { + pr_info("unlink->seqnum %lu\n", unlink->seqnum); + if (unlink->seqnum == pdu->base.seqnum) { + usbip_dbg_vhci_rx("found pending unlink, %lu\n", + unlink->seqnum); + list_del(&unlink->list); + + spin_unlock_irqrestore(&vdev->priv_lock, flags); + return unlink; + } + } + + spin_unlock_irqrestore(&vdev->priv_lock, flags); + + return NULL; +} + +static void vhci_recv_ret_unlink(struct vhci_device *vdev, + struct usbip_header *pdu) +{ + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct vhci *vhci = vhci_hcd->vhci; + struct vhci_unlink *unlink; + struct urb *urb; + unsigned long flags; + + usbip_dump_header(pdu); + + unlink = dequeue_pending_unlink(vdev, pdu); + if (!unlink) { + pr_info("cannot find the pending unlink %u\n", + pdu->base.seqnum); + return; + } + + spin_lock_irqsave(&vdev->priv_lock, flags); + urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum); + spin_unlock_irqrestore(&vdev->priv_lock, flags); + + 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. + */ + pr_info("the urb (seqnum %u) was already given back\n", + pdu->base.seqnum); + } else { + usbip_dbg_vhci_rx("now giveback urb %u\n", pdu->base.seqnum); + + /* If unlink is successful, status is -ECONNRESET */ + urb->status = pdu->u.ret_unlink.status; + pr_info("urb->status %d\n", urb->status); + + spin_lock_irqsave(&vhci->lock, flags); + usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci_hcd), urb); + spin_unlock_irqrestore(&vhci->lock, flags); + + usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci_hcd), urb, urb->status); + } + + kfree(unlink); +} + +static int vhci_priv_tx_empty(struct vhci_device *vdev) +{ + int empty = 0; + unsigned long flags; + + spin_lock_irqsave(&vdev->priv_lock, flags); + empty = list_empty(&vdev->priv_rx); + spin_unlock_irqrestore(&vdev->priv_lock, flags); + + return empty; +} + +/* 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); + + usbip_dbg_vhci_rx("Enter\n"); + + memset(&pdu, 0, sizeof(pdu)); + + /* receive a pdu header */ + ret = usbip_recv(ud->tcp_socket, &pdu, sizeof(pdu)); + if (ret < 0) { + if (ret == -ECONNRESET) + pr_info("connection reset by peer\n"); + else if (ret == -EAGAIN) { + /* ignore if connection was idle */ + if (vhci_priv_tx_empty(vdev)) + return; + pr_info("connection timed out with pending urbs\n"); + } else if (ret != -ERESTARTSYS) + pr_info("xmit failed %d\n", ret); + + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + return; + } + if (ret == 0) { + pr_info("connection closed"); + usbip_event_add(ud, VDEV_EVENT_DOWN); + return; + } + if (ret != sizeof(pdu)) { + pr_err("received pdu size is %d, should be %d\n", ret, + (unsigned int)sizeof(pdu)); + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + return; + } + + usbip_header_correct_endian(&pdu, 0); + + if (usbip_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: + /* NOT REACHED */ + pr_err("unknown pdu %u\n", pdu.base.command); + usbip_dump_header(&pdu); + usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); + break; + } +} + +int vhci_rx_loop(void *data) +{ + struct usbip_device *ud = data; + + while (!kthread_should_stop()) { + if (usbip_event_happened(ud)) + break; + + kcov_remote_start_common(ud->kcov_handle); + vhci_rx_pdu(ud); + kcov_remote_stop(); + } + + return 0; +} diff --git a/drivers/usb/usbip/vhci_sysfs.c b/drivers/usb/usbip/vhci_sysfs.c new file mode 100644 index 000000000..a7ede6fb3 --- /dev/null +++ b/drivers/usb/usbip/vhci_sysfs.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + * Copyright (C) 2015-2016 Nobuo Iwata + */ + +#include +#include +#include +#include +#include + +/* Hardening for Spectre-v1 */ +#include + +#include "usbip_common.h" +#include "vhci.h" + +/* TODO: refine locking ?*/ + +/* + * output example: + * hub port sta spd dev sockfd local_busid + * hs 0000 004 000 00000000 000003 1-2.3 + * ................................................ + * ss 0008 004 000 00000000 000004 2-3.4 + * ................................................ + * + * Output includes socket fd instead of socket pointer address to avoid + * leaking kernel memory address in: + * /sys/devices/platform/vhci_hcd.0/status and in debug output. + * The socket pointer address is not used at the moment and it was made + * visible as a convenient way to find IP address from socket pointer + * address by looking up /proc/net/{tcp,tcp6}. As this opens a security + * hole, the change is made to use sockfd instead. + * + */ +static void port_show_vhci(char **out, int hub, int port, struct vhci_device *vdev) +{ + if (hub == HUB_SPEED_HIGH) + *out += sprintf(*out, "hs %04u %03u ", + port, vdev->ud.status); + else /* hub == HUB_SPEED_SUPER */ + *out += sprintf(*out, "ss %04u %03u ", + port, vdev->ud.status); + + if (vdev->ud.status == VDEV_ST_USED) { + *out += sprintf(*out, "%03u %08x ", + vdev->speed, vdev->devid); + *out += sprintf(*out, "%06u %s", + vdev->ud.sockfd, + dev_name(&vdev->udev->dev)); + + } else { + *out += sprintf(*out, "000 00000000 "); + *out += sprintf(*out, "000000 0-0"); + } + + *out += sprintf(*out, "\n"); +} + +static ssize_t status_show_not_ready(int pdev_nr, char *out) +{ + char *s = out; + int i = 0; + + for (i = 0; i < VHCI_HC_PORTS; i++) { + out += sprintf(out, "hs %04u %03u ", + (pdev_nr * VHCI_PORTS) + i, + VDEV_ST_NOTASSIGNED); + out += sprintf(out, "000 00000000 0000000000000000 0-0"); + out += sprintf(out, "\n"); + } + + for (i = 0; i < VHCI_HC_PORTS; i++) { + out += sprintf(out, "ss %04u %03u ", + (pdev_nr * VHCI_PORTS) + VHCI_HC_PORTS + i, + VDEV_ST_NOTASSIGNED); + out += sprintf(out, "000 00000000 0000000000000000 0-0"); + out += sprintf(out, "\n"); + } + return out - s; +} + +/* Sysfs entry to show port status */ +static ssize_t status_show_vhci(int pdev_nr, char *out) +{ + struct platform_device *pdev = vhcis[pdev_nr].pdev; + struct vhci *vhci; + struct usb_hcd *hcd; + struct vhci_hcd *vhci_hcd; + char *s = out; + int i; + unsigned long flags; + + if (!pdev || !out) { + usbip_dbg_vhci_sysfs("show status error\n"); + return 0; + } + + hcd = platform_get_drvdata(pdev); + + if (!hcd) { + usbip_dbg_vhci_sysfs("show status error (hcd is NULL)\n"); + return status_show_not_ready(pdev_nr, out); + } + + vhci_hcd = hcd_to_vhci_hcd(hcd); + vhci = vhci_hcd->vhci; + + spin_lock_irqsave(&vhci->lock, flags); + + for (i = 0; i < VHCI_HC_PORTS; i++) { + struct vhci_device *vdev = &vhci->vhci_hcd_hs->vdev[i]; + + spin_lock(&vdev->ud.lock); + port_show_vhci(&out, HUB_SPEED_HIGH, + pdev_nr * VHCI_PORTS + i, vdev); + spin_unlock(&vdev->ud.lock); + } + + for (i = 0; i < VHCI_HC_PORTS; i++) { + struct vhci_device *vdev = &vhci->vhci_hcd_ss->vdev[i]; + + spin_lock(&vdev->ud.lock); + port_show_vhci(&out, HUB_SPEED_SUPER, + pdev_nr * VHCI_PORTS + VHCI_HC_PORTS + i, vdev); + spin_unlock(&vdev->ud.lock); + } + + spin_unlock_irqrestore(&vhci->lock, flags); + + return out - s; +} + +static int status_name_to_id(const char *name) +{ + char *c; + long val; + int ret; + + c = strchr(name, '.'); + if (c == NULL) + return 0; + + ret = kstrtol(c+1, 10, &val); + if (ret < 0) + return ret; + + return val; +} + +static ssize_t status_show(struct device *dev, + struct device_attribute *attr, char *out) +{ + char *s = out; + int pdev_nr; + + out += sprintf(out, + "hub port sta spd dev sockfd local_busid\n"); + + pdev_nr = status_name_to_id(attr->attr.name); + if (pdev_nr < 0) + out += status_show_not_ready(pdev_nr, out); + else + out += status_show_vhci(pdev_nr, out); + + return out - s; +} + +static ssize_t nports_show(struct device *dev, struct device_attribute *attr, + char *out) +{ + char *s = out; + + /* + * Half the ports are for SPEED_HIGH and half for SPEED_SUPER, + * thus the * 2. + */ + out += sprintf(out, "%d\n", VHCI_PORTS * vhci_num_controllers); + return out - s; +} +static DEVICE_ATTR_RO(nports); + +/* Sysfs entry to shutdown a virtual connection */ +static int vhci_port_disconnect(struct vhci_hcd *vhci_hcd, __u32 rhport) +{ + struct vhci_device *vdev = &vhci_hcd->vdev[rhport]; + struct vhci *vhci = vhci_hcd->vhci; + unsigned long flags; + + usbip_dbg_vhci_sysfs("enter\n"); + + mutex_lock(&vdev->ud.sysfs_lock); + + /* lock */ + spin_lock_irqsave(&vhci->lock, flags); + spin_lock(&vdev->ud.lock); + + if (vdev->ud.status == VDEV_ST_NULL) { + pr_err("not connected %d\n", vdev->ud.status); + + /* unlock */ + spin_unlock(&vdev->ud.lock); + spin_unlock_irqrestore(&vhci->lock, flags); + mutex_unlock(&vdev->ud.sysfs_lock); + + return -EINVAL; + } + + /* unlock */ + spin_unlock(&vdev->ud.lock); + spin_unlock_irqrestore(&vhci->lock, flags); + + usbip_event_add(&vdev->ud, VDEV_EVENT_DOWN); + + mutex_unlock(&vdev->ud.sysfs_lock); + + return 0; +} + +static int valid_port(__u32 *pdev_nr, __u32 *rhport) +{ + if (*pdev_nr >= vhci_num_controllers) { + pr_err("pdev %u\n", *pdev_nr); + return 0; + } + *pdev_nr = array_index_nospec(*pdev_nr, vhci_num_controllers); + + if (*rhport >= VHCI_HC_PORTS) { + pr_err("rhport %u\n", *rhport); + return 0; + } + *rhport = array_index_nospec(*rhport, VHCI_HC_PORTS); + + return 1; +} + +static ssize_t detach_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + __u32 port = 0, pdev_nr = 0, rhport = 0; + struct usb_hcd *hcd; + struct vhci_hcd *vhci_hcd; + int ret; + + if (kstrtoint(buf, 10, &port) < 0) + return -EINVAL; + + pdev_nr = port_to_pdev_nr(port); + rhport = port_to_rhport(port); + + if (!valid_port(&pdev_nr, &rhport)) + return -EINVAL; + + hcd = platform_get_drvdata(vhcis[pdev_nr].pdev); + if (hcd == NULL) { + dev_err(dev, "port is not ready %u\n", port); + return -EAGAIN; + } + + usbip_dbg_vhci_sysfs("rhport %d\n", rhport); + + if ((port / VHCI_HC_PORTS) % 2) + vhci_hcd = hcd_to_vhci_hcd(hcd)->vhci->vhci_hcd_ss; + else + vhci_hcd = hcd_to_vhci_hcd(hcd)->vhci->vhci_hcd_hs; + + ret = vhci_port_disconnect(vhci_hcd, rhport); + if (ret < 0) + return -EINVAL; + + usbip_dbg_vhci_sysfs("Leave\n"); + + return count; +} +static DEVICE_ATTR_WO(detach); + +static int valid_args(__u32 *pdev_nr, __u32 *rhport, + enum usb_device_speed speed) +{ + if (!valid_port(pdev_nr, rhport)) { + return 0; + } + + switch (speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + case USB_SPEED_WIRELESS: + case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: + break; + default: + pr_err("Failed attach request for unsupported USB speed: %s\n", + usb_speed_string(speed)); + return 0; + } + + return 1; +} + +/* Sysfs entry to establish a virtual connection */ +/* + * 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 attach_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct socket *socket; + int sockfd = 0; + __u32 port = 0, pdev_nr = 0, rhport = 0, devid = 0, speed = 0; + struct usb_hcd *hcd; + struct vhci_hcd *vhci_hcd; + struct vhci_device *vdev; + struct vhci *vhci; + int err; + unsigned long flags; + struct task_struct *tcp_rx = NULL; + struct task_struct *tcp_tx = NULL; + + /* + * @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 + */ + if (sscanf(buf, "%u %u %u %u", &port, &sockfd, &devid, &speed) != 4) + return -EINVAL; + pdev_nr = port_to_pdev_nr(port); + rhport = port_to_rhport(port); + + usbip_dbg_vhci_sysfs("port(%u) pdev(%d) rhport(%u)\n", + port, pdev_nr, rhport); + usbip_dbg_vhci_sysfs("sockfd(%u) devid(%u) speed(%u)\n", + sockfd, devid, speed); + + /* check received parameters */ + if (!valid_args(&pdev_nr, &rhport, speed)) + return -EINVAL; + + hcd = platform_get_drvdata(vhcis[pdev_nr].pdev); + if (hcd == NULL) { + dev_err(dev, "port %d is not ready\n", port); + return -EAGAIN; + } + + vhci_hcd = hcd_to_vhci_hcd(hcd); + vhci = vhci_hcd->vhci; + + if (speed >= USB_SPEED_SUPER) + vdev = &vhci->vhci_hcd_ss->vdev[rhport]; + else + vdev = &vhci->vhci_hcd_hs->vdev[rhport]; + + mutex_lock(&vdev->ud.sysfs_lock); + + /* Extract socket from fd. */ + socket = sockfd_lookup(sockfd, &err); + if (!socket) { + dev_err(dev, "failed to lookup sock"); + err = -EINVAL; + goto unlock_mutex; + } + if (socket->type != SOCK_STREAM) { + dev_err(dev, "Expecting SOCK_STREAM - found %d", + socket->type); + sockfd_put(socket); + err = -EINVAL; + goto unlock_mutex; + } + + /* create threads before locking */ + tcp_rx = kthread_create(vhci_rx_loop, &vdev->ud, "vhci_rx"); + if (IS_ERR(tcp_rx)) { + sockfd_put(socket); + err = -EINVAL; + goto unlock_mutex; + } + tcp_tx = kthread_create(vhci_tx_loop, &vdev->ud, "vhci_tx"); + if (IS_ERR(tcp_tx)) { + kthread_stop(tcp_rx); + sockfd_put(socket); + err = -EINVAL; + goto unlock_mutex; + } + + /* get task structs now */ + get_task_struct(tcp_rx); + get_task_struct(tcp_tx); + + /* now begin lock until setting vdev status set */ + spin_lock_irqsave(&vhci->lock, flags); + spin_lock(&vdev->ud.lock); + + if (vdev->ud.status != VDEV_ST_NULL) { + /* end of the lock */ + spin_unlock(&vdev->ud.lock); + spin_unlock_irqrestore(&vhci->lock, flags); + + sockfd_put(socket); + kthread_stop_put(tcp_rx); + kthread_stop_put(tcp_tx); + + dev_err(dev, "port %d already used\n", rhport); + /* + * Will be retried from userspace + * if there's another free port. + */ + err = -EBUSY; + goto unlock_mutex; + } + + dev_info(dev, "pdev(%u) rhport(%u) sockfd(%d)\n", + pdev_nr, rhport, sockfd); + dev_info(dev, "devid(%u) speed(%u) speed_str(%s)\n", + devid, speed, usb_speed_string(speed)); + + vdev->devid = devid; + vdev->speed = speed; + vdev->ud.sockfd = sockfd; + vdev->ud.tcp_socket = socket; + vdev->ud.tcp_rx = tcp_rx; + vdev->ud.tcp_tx = tcp_tx; + vdev->ud.status = VDEV_ST_NOTASSIGNED; + vdev->ud.kcov_handle = kcov_common_handle(); + + spin_unlock(&vdev->ud.lock); + spin_unlock_irqrestore(&vhci->lock, flags); + /* end the lock */ + + wake_up_process(vdev->ud.tcp_rx); + wake_up_process(vdev->ud.tcp_tx); + + rh_port_connect(vdev, speed); + + dev_info(dev, "Device attached\n"); + + mutex_unlock(&vdev->ud.sysfs_lock); + + return count; + +unlock_mutex: + mutex_unlock(&vdev->ud.sysfs_lock); + return err; +} +static DEVICE_ATTR_WO(attach); + +#define MAX_STATUS_NAME 16 + +struct status_attr { + struct device_attribute attr; + char name[MAX_STATUS_NAME+1]; +}; + +static struct status_attr *status_attrs; + +static void set_status_attr(int id) +{ + struct status_attr *status; + + status = status_attrs + id; + if (id == 0) + strscpy(status->name, "status"); + else + snprintf(status->name, MAX_STATUS_NAME+1, "status.%d", id); + status->attr.attr.name = status->name; + status->attr.attr.mode = S_IRUGO; + status->attr.show = status_show; + sysfs_attr_init(&status->attr.attr); +} + +static int init_status_attrs(void) +{ + int id; + + status_attrs = kzalloc_objs(struct status_attr, vhci_num_controllers); + if (status_attrs == NULL) + return -ENOMEM; + + for (id = 0; id < vhci_num_controllers; id++) + set_status_attr(id); + + return 0; +} + +static void finish_status_attrs(void) +{ + kfree(status_attrs); +} + +struct attribute_group vhci_attr_group = { + .attrs = NULL, +}; + +int vhci_init_attr_group(void) +{ + struct attribute **attrs; + int ret, i; + + attrs = kzalloc_objs(struct attribute *, (vhci_num_controllers + 5)); + if (attrs == NULL) + return -ENOMEM; + + ret = init_status_attrs(); + if (ret) { + kfree(attrs); + return ret; + } + *attrs = &dev_attr_nports.attr; + *(attrs + 1) = &dev_attr_detach.attr; + *(attrs + 2) = &dev_attr_attach.attr; + *(attrs + 3) = &dev_attr_usbip_debug.attr; + for (i = 0; i < vhci_num_controllers; i++) + *(attrs + i + 4) = &((status_attrs + i)->attr.attr); + vhci_attr_group.attrs = attrs; + return 0; +} + +void vhci_finish_attr_group(void) +{ + finish_status_attrs(); + kfree(vhci_attr_group.attrs); +} diff --git a/drivers/usb/usbip/vhci_tx.c b/drivers/usb/usbip/vhci_tx.c new file mode 100644 index 000000000..32e6fabcc --- /dev/null +++ b/drivers/usb/usbip/vhci_tx.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2003-2008 Takahiro Hirofuchi + */ + +#include +#include +#include + +#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; + + usbip_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; + pdup->base.direction = usb_pipein(urb->pipe) ? + USBIP_DIR_IN : 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) +{ + struct vhci_priv *priv, *tmp; + unsigned long flags; + + 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 usbip_iso_packet_descriptor *iso_buffer = NULL; + struct vhci_priv *priv = NULL; + struct scatterlist *sg; + + struct msghdr msg; + struct kvec *iov; + size_t txsize; + + size_t total_size = 0; + int iovnum; + int err = -ENOMEM; + int i; + + while ((priv = dequeue_from_priv_tx(vdev)) != NULL) { + int ret; + struct urb *urb = priv->urb; + struct usbip_header pdu_header; + + txsize = 0; + memset(&pdu_header, 0, sizeof(pdu_header)); + memset(&msg, 0, sizeof(msg)); + memset(&iov, 0, sizeof(iov)); + + usbip_dbg_vhci_tx("setup txdata urb seqnum %lu\n", + priv->seqnum); + + if (urb->num_sgs && usb_pipeout(urb->pipe)) + iovnum = 2 + urb->num_sgs; + else + iovnum = 3; + + iov = kzalloc_objs(*iov, iovnum); + if (!iov) { + usbip_event_add(&vdev->ud, SDEV_EVENT_ERROR_MALLOC); + return -ENOMEM; + } + + if (urb->num_sgs) + urb->transfer_flags |= URB_DMA_MAP_SG; + + /* 1. setup usbip_header */ + setup_cmd_submit_pdu(&pdu_header, urb); + usbip_header_correct_endian(&pdu_header, 1); + iovnum = 0; + + iov[iovnum].iov_base = &pdu_header; + iov[iovnum].iov_len = sizeof(pdu_header); + txsize += sizeof(pdu_header); + iovnum++; + + /* 2. setup transfer buffer */ + if (!usb_pipein(urb->pipe) && urb->transfer_buffer_length > 0) { + if (urb->num_sgs && + !usb_endpoint_xfer_isoc(&urb->ep->desc)) { + for_each_sg(urb->sg, sg, urb->num_sgs, i) { + iov[iovnum].iov_base = sg_virt(sg); + iov[iovnum].iov_len = sg->length; + iovnum++; + } + } else { + iov[iovnum].iov_base = urb->transfer_buffer; + iov[iovnum].iov_len = + urb->transfer_buffer_length; + iovnum++; + } + 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); + goto err_iso_buffer; + } + + iov[iovnum].iov_base = iso_buffer; + iov[iovnum].iov_len = len; + iovnum++; + txsize += len; + } + + ret = kernel_sendmsg(vdev->ud.tcp_socket, &msg, iov, iovnum, + txsize); + if (ret != txsize) { + pr_err("sendmsg failed!, ret=%d for %zd\n", ret, + txsize); + usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_TCP); + err = -EPIPE; + goto err_tx; + } + + kfree(iov); + /* This is only for isochronous case */ + kfree(iso_buffer); + iso_buffer = NULL; + + usbip_dbg_vhci_tx("send txdata\n"); + + total_size += txsize; + } + + return total_size; + +err_tx: + kfree(iso_buffer); +err_iso_buffer: + kfree(iov); + + return err; +} + +static struct vhci_unlink *dequeue_from_unlink_tx(struct vhci_device *vdev) +{ + struct vhci_unlink *unlink, *tmp; + unsigned long flags; + + 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; + size_t txsize; + size_t total_size = 0; + + while ((unlink = dequeue_from_unlink_tx(vdev)) != NULL) { + int ret; + struct usbip_header pdu_header; + + memset(&pdu_header, 0, sizeof(pdu_header)); + memset(&msg, 0, sizeof(msg)); + memset(&iov, 0, sizeof(iov)); + + usbip_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.iov_base = &pdu_header; + iov.iov_len = sizeof(pdu_header); + txsize = sizeof(pdu_header); + + ret = kernel_sendmsg(vdev->ud.tcp_socket, &msg, &iov, 1, txsize); + if (ret != txsize) { + pr_err("sendmsg failed!, ret=%d for %zd\n", ret, + txsize); + usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_TCP); + return -1; + } + + usbip_dbg_vhci_tx("send txdata\n"); + + total_size += txsize; + } + + return total_size; +} + +int vhci_tx_loop(void *data) +{ + struct usbip_device *ud = data; + struct vhci_device *vdev = container_of(ud, struct vhci_device, ud); + + while (!kthread_should_stop()) { + 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) || + kthread_should_stop())); + + usbip_dbg_vhci_tx("pending urbs ?, now wake up\n"); + } + + return 0; +} diff --git a/drivers/usb/usbip/vudc.h b/drivers/usb/usbip/vudc.h new file mode 100644 index 000000000..5ef0e7d9b --- /dev/null +++ b/drivers/usb/usbip/vudc.h @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Karol Kosik + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski + * Krzysztof Opasiak + */ + +#ifndef __USBIP_VUDC_H +#define __USBIP_VUDC_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usbip_common.h" + +#define GADGET_NAME "usbip-vudc" + +struct vep { + struct usb_ep ep; + unsigned type:2; /* type, as USB_ENDPOINT_XFER_* */ + char name[8]; /* space for ep name */ + + const struct usb_endpoint_descriptor *desc; + struct usb_gadget *gadget; + struct list_head req_queue; /* Request queue */ + unsigned halted:1; + unsigned wedged:1; + unsigned already_seen:1; + unsigned setup_stage:1; +}; + +struct vrequest { + struct usb_request req; + struct list_head req_entry; /* Request queue */ +}; + +struct urbp { + struct urb *urb; + struct vep *ep; + struct list_head urb_entry; /* urb queue */ + unsigned long seqnum; + unsigned type:2; /* for tx, since ep type can change after */ + unsigned new:1; +}; + +struct v_unlink { + unsigned long seqnum; + __u32 status; +}; + +enum tx_type { + TX_UNLINK, + TX_SUBMIT, +}; + +struct tx_item { + struct list_head tx_entry; + enum tx_type type; + union { + struct urbp *s; + struct v_unlink *u; + }; +}; + +enum tr_state { + VUDC_TR_RUNNING, + VUDC_TR_IDLE, + VUDC_TR_STOPPED, +}; + +struct transfer_timer { + struct timer_list timer; + enum tr_state state; + unsigned long frame_start; + int frame_limit; +}; + +struct vudc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct platform_device *pdev; + + struct usb_device_descriptor dev_desc; + + struct usbip_device ud; + struct transfer_timer tr_timer; + struct timespec64 start_time; + + struct list_head urb_queue; + + spinlock_t lock_tx; + struct list_head tx_queue; + wait_queue_head_t tx_waitq; + + spinlock_t lock; + struct vep *ep; + int address; + u16 devstatus; + + unsigned pullup:1; + unsigned connected:1; + unsigned desc_cached:1; +}; + +struct vudc_device { + struct platform_device *pdev; + struct list_head dev_entry; +}; + +extern const struct attribute_group *vudc_groups[]; + +/* visible everywhere */ + +static inline struct vep *to_vep(struct usb_ep *_ep) +{ + return container_of(_ep, struct vep, ep); +} + +static inline struct vrequest *to_vrequest( + struct usb_request *_req) +{ + return container_of(_req, struct vrequest, req); +} + +static inline struct vudc *usb_gadget_to_vudc( + struct usb_gadget *_gadget) +{ + return container_of(_gadget, struct vudc, gadget); +} + +static inline struct vudc *ep_to_vudc(struct vep *ep) +{ + return container_of(ep->gadget, struct vudc, gadget); +} + +/* vudc_sysfs.c */ + +int get_gadget_descs(struct vudc *udc); + +/* vudc_tx.c */ + +int v_tx_loop(void *data); +void v_enqueue_ret_unlink(struct vudc *udc, __u32 seqnum, __u32 status); +void v_enqueue_ret_submit(struct vudc *udc, struct urbp *urb_p); + +/* vudc_rx.c */ + +int v_rx_loop(void *data); + +/* vudc_transfer.c */ + +void v_init_timer(struct vudc *udc); +void v_start_timer(struct vudc *udc); +void v_kick_timer(struct vudc *udc, unsigned long time); +void v_stop_timer(struct vudc *udc); + +/* vudc_dev.c */ + +struct urbp *alloc_urbp(void); +void free_urbp_and_urb(struct urbp *urb_p); + +struct vep *vudc_find_endpoint(struct vudc *udc, u8 address); + +struct vudc_device *alloc_vudc_device(int devid); +void put_vudc_device(struct vudc_device *udc_dev); + +int vudc_probe(struct platform_device *pdev); +void vudc_remove(struct platform_device *pdev); + +#endif /* __USBIP_VUDC_H */ diff --git a/drivers/usb/usbip/vudc_dev.c b/drivers/usb/usbip/vudc_dev.c new file mode 100644 index 000000000..5ef881179 --- /dev/null +++ b/drivers/usb/usbip/vudc_dev.c @@ -0,0 +1,637 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Karol Kosik + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski + * Krzysztof Opasiak + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usbip_common.h" +#include "vudc.h" + +#define VIRTUAL_ENDPOINTS (1 /* ep0 */ + 15 /* in eps */ + 15 /* out eps */) + +/* urb-related structures alloc / free */ + + +static void free_urb(struct urb *urb) +{ + if (!urb) + return; + + kfree(urb->setup_packet); + urb->setup_packet = NULL; + + kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; + + usb_free_urb(urb); +} + +struct urbp *alloc_urbp(void) +{ + struct urbp *urb_p; + + urb_p = kzalloc_obj(*urb_p); + if (!urb_p) + return urb_p; + + urb_p->urb = NULL; + urb_p->ep = NULL; + INIT_LIST_HEAD(&urb_p->urb_entry); + return urb_p; +} + +static void free_urbp(struct urbp *urb_p) +{ + kfree(urb_p); +} + +void free_urbp_and_urb(struct urbp *urb_p) +{ + if (!urb_p) + return; + free_urb(urb_p->urb); + free_urbp(urb_p); +} + + +/* utilities ; almost verbatim from dummy_hcd.c */ + +/* called with spinlock held */ +static void nuke(struct vudc *udc, struct vep *ep) +{ + struct vrequest *req; + + while (!list_empty(&ep->req_queue)) { + req = list_first_entry(&ep->req_queue, struct vrequest, + req_entry); + list_del_init(&req->req_entry); + req->req.status = -ESHUTDOWN; + + spin_unlock(&udc->lock); + usb_gadget_giveback_request(&ep->ep, &req->req); + spin_lock(&udc->lock); + } +} + +/* caller must hold lock */ +static void stop_activity(struct vudc *udc) +{ + int i; + struct urbp *urb_p, *tmp; + + udc->address = 0; + + for (i = 0; i < VIRTUAL_ENDPOINTS; i++) + nuke(udc, &udc->ep[i]); + + list_for_each_entry_safe(urb_p, tmp, &udc->urb_queue, urb_entry) { + list_del(&urb_p->urb_entry); + free_urbp_and_urb(urb_p); + } +} + +struct vep *vudc_find_endpoint(struct vudc *udc, u8 address) +{ + int i; + + if ((address & ~USB_DIR_IN) == 0) + return &udc->ep[0]; + + for (i = 1; i < VIRTUAL_ENDPOINTS; i++) { + struct vep *ep = &udc->ep[i]; + + if (!ep->desc) + continue; + if (ep->desc->bEndpointAddress == address) + return ep; + } + return NULL; +} + +/* gadget ops */ + +static int vgadget_get_frame(struct usb_gadget *_gadget) +{ + struct timespec64 now; + struct vudc *udc = usb_gadget_to_vudc(_gadget); + + ktime_get_ts64(&now); + return ((now.tv_sec - udc->start_time.tv_sec) * 1000 + + (now.tv_nsec - udc->start_time.tv_nsec) / NSEC_PER_MSEC) + & 0x7FF; +} + +static int vgadget_set_selfpowered(struct usb_gadget *_gadget, int value) +{ + struct vudc *udc = usb_gadget_to_vudc(_gadget); + + if (value) + udc->devstatus |= (1 << USB_DEVICE_SELF_POWERED); + else + udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); + return 0; +} + +static int vgadget_pullup(struct usb_gadget *_gadget, int value) +{ + struct vudc *udc = usb_gadget_to_vudc(_gadget); + unsigned long flags; + int ret; + + + spin_lock_irqsave(&udc->lock, flags); + value = !!value; + if (value == udc->pullup) + goto unlock; + + udc->pullup = value; + if (value) { + udc->gadget.speed = min_t(u8, USB_SPEED_HIGH, + udc->driver->max_speed); + udc->ep[0].ep.maxpacket = 64; + /* + * This is the first place where we can ask our + * gadget driver for descriptors. + */ + ret = get_gadget_descs(udc); + if (ret) { + dev_err(&udc->gadget.dev, "Unable go get desc: %d", ret); + goto unlock; + } + + spin_unlock_irqrestore(&udc->lock, flags); + usbip_start_eh(&udc->ud); + } else { + /* Invalidate descriptors */ + udc->desc_cached = 0; + + spin_unlock_irqrestore(&udc->lock, flags); + usbip_event_add(&udc->ud, VUDC_EVENT_REMOVED); + usbip_stop_eh(&udc->ud); /* Wait for eh completion */ + } + + return 0; + +unlock: + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static int vgadget_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct vudc *udc = usb_gadget_to_vudc(g); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + udc->driver = driver; + udc->pullup = udc->connected = udc->desc_cached = 0; + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int vgadget_udc_stop(struct usb_gadget *g) +{ + struct vudc *udc = usb_gadget_to_vudc(g); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + udc->driver = NULL; + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +static const struct usb_gadget_ops vgadget_ops = { + .get_frame = vgadget_get_frame, + .set_selfpowered = vgadget_set_selfpowered, + .pullup = vgadget_pullup, + .udc_start = vgadget_udc_start, + .udc_stop = vgadget_udc_stop, +}; + + +/* endpoint ops */ + +static int vep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct vep *ep; + struct vudc *udc; + unsigned int maxp; + unsigned long flags; + + ep = to_vep(_ep); + udc = ep_to_vudc(ep); + + if (!_ep || !desc || ep->desc || _ep->caps.type_control + || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + + if (!udc->driver) + return -ESHUTDOWN; + + spin_lock_irqsave(&udc->lock, flags); + + maxp = usb_endpoint_maxp(desc); + _ep->maxpacket = maxp; + ep->desc = desc; + ep->type = usb_endpoint_type(desc); + ep->halted = ep->wedged = 0; + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int vep_disable(struct usb_ep *_ep) +{ + struct vep *ep; + struct vudc *udc; + unsigned long flags; + + ep = to_vep(_ep); + udc = ep_to_vudc(ep); + if (!_ep || !ep->desc || _ep->caps.type_control) + return -EINVAL; + + spin_lock_irqsave(&udc->lock, flags); + ep->desc = NULL; + nuke(udc, ep); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static struct usb_request *vep_alloc_request(struct usb_ep *_ep, + gfp_t mem_flags) +{ + struct vrequest *req; + + if (!_ep) + return NULL; + + req = kzalloc_obj(*req, mem_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->req_entry); + + return &req->req; +} + +static void vep_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct vrequest *req; + + /* ep is always valid here - see usb_ep_free_request() */ + if (!_req) + return; + + req = to_vrequest(_req); + kfree(req); +} + +static int vep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t mem_flags) +{ + struct vep *ep; + struct vrequest *req; + struct vudc *udc; + unsigned long flags; + + if (!_ep || !_req) + return -EINVAL; + + ep = to_vep(_ep); + req = to_vrequest(_req); + udc = ep_to_vudc(ep); + + spin_lock_irqsave(&udc->lock, flags); + _req->actual = 0; + _req->status = -EINPROGRESS; + + list_add_tail(&req->req_entry, &ep->req_queue); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int vep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct vep *ep; + struct vudc *udc; + struct vrequest *lst; + unsigned long flags; + int ret = -EINVAL; + + if (!_ep || !_req) + return ret; + + ep = to_vep(_ep); + udc = ep_to_vudc(ep); + + if (!udc->driver) + return -ESHUTDOWN; + + spin_lock_irqsave(&udc->lock, flags); + list_for_each_entry(lst, &ep->req_queue, req_entry) { + if (&lst->req == _req) { + list_del_init(&lst->req_entry); + _req->status = -ECONNRESET; + ret = 0; + break; + } + } + spin_unlock_irqrestore(&udc->lock, flags); + + if (ret == 0) + usb_gadget_giveback_request(_ep, _req); + + return ret; +} + +static int +vep_set_halt_and_wedge(struct usb_ep *_ep, int value, int wedged) +{ + struct vep *ep; + struct vudc *udc; + unsigned long flags; + int ret = 0; + + ep = to_vep(_ep); + if (!_ep) + return -EINVAL; + + udc = ep_to_vudc(ep); + if (!udc->driver) + return -ESHUTDOWN; + + spin_lock_irqsave(&udc->lock, flags); + if (!value) + ep->halted = ep->wedged = 0; + else if (ep->desc && (ep->desc->bEndpointAddress & USB_DIR_IN) && + !list_empty(&ep->req_queue)) + ret = -EAGAIN; + else { + ep->halted = 1; + if (wedged) + ep->wedged = 1; + } + + spin_unlock_irqrestore(&udc->lock, flags); + return ret; +} + +static int +vep_set_halt(struct usb_ep *_ep, int value) +{ + return vep_set_halt_and_wedge(_ep, value, 0); +} + +static int vep_set_wedge(struct usb_ep *_ep) +{ + return vep_set_halt_and_wedge(_ep, 1, 1); +} + +static const struct usb_ep_ops vep_ops = { + .enable = vep_enable, + .disable = vep_disable, + + .alloc_request = vep_alloc_request, + .free_request = vep_free_request, + + .queue = vep_queue, + .dequeue = vep_dequeue, + + .set_halt = vep_set_halt, + .set_wedge = vep_set_wedge, +}; + + +/* shutdown / reset / error handlers */ + +static void vudc_shutdown(struct usbip_device *ud) +{ + struct vudc *udc = container_of(ud, struct vudc, ud); + int call_disconnect = 0; + unsigned long flags; + + dev_dbg(&udc->pdev->dev, "device shutdown"); + if (ud->tcp_socket) + kernel_sock_shutdown(ud->tcp_socket, SHUT_RDWR); + + if (ud->tcp_rx) { + kthread_stop_put(ud->tcp_rx); + ud->tcp_rx = NULL; + } + if (ud->tcp_tx) { + kthread_stop_put(ud->tcp_tx); + ud->tcp_tx = NULL; + } + + if (ud->tcp_socket) { + sockfd_put(ud->tcp_socket); + ud->tcp_socket = NULL; + } + + spin_lock_irqsave(&udc->lock, flags); + stop_activity(udc); + if (udc->connected && udc->driver->disconnect) + call_disconnect = 1; + udc->connected = 0; + spin_unlock_irqrestore(&udc->lock, flags); + if (call_disconnect) + udc->driver->disconnect(&udc->gadget); +} + +static void vudc_device_reset(struct usbip_device *ud) +{ + struct vudc *udc = container_of(ud, struct vudc, ud); + unsigned long flags; + + dev_dbg(&udc->pdev->dev, "device reset"); + spin_lock_irqsave(&udc->lock, flags); + stop_activity(udc); + spin_unlock_irqrestore(&udc->lock, flags); + if (udc->driver) + usb_gadget_udc_reset(&udc->gadget, udc->driver); + spin_lock_irqsave(&ud->lock, flags); + ud->status = SDEV_ST_AVAILABLE; + spin_unlock_irqrestore(&ud->lock, flags); +} + +static void vudc_device_unusable(struct usbip_device *ud) +{ + unsigned long flags; + + spin_lock_irqsave(&ud->lock, flags); + ud->status = SDEV_ST_ERROR; + spin_unlock_irqrestore(&ud->lock, flags); +} + +/* device setup / cleanup */ + +struct vudc_device *alloc_vudc_device(int devid) +{ + struct vudc_device *udc_dev; + + udc_dev = kzalloc_obj(*udc_dev); + if (!udc_dev) + return NULL; + + INIT_LIST_HEAD(&udc_dev->dev_entry); + + udc_dev->pdev = platform_device_alloc(GADGET_NAME, devid); + if (!udc_dev->pdev) { + kfree(udc_dev); + udc_dev = NULL; + } + + return udc_dev; +} + +void put_vudc_device(struct vudc_device *udc_dev) +{ + platform_device_put(udc_dev->pdev); + kfree(udc_dev); +} + +static int init_vudc_hw(struct vudc *udc) +{ + int i; + struct usbip_device *ud = &udc->ud; + struct vep *ep; + + udc->ep = kzalloc_objs(*udc->ep, VIRTUAL_ENDPOINTS); + if (!udc->ep) + goto nomem_ep; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + + /* create ep0 and 15 in, 15 out general purpose eps */ + for (i = 0; i < VIRTUAL_ENDPOINTS; ++i) { + int is_out = i % 2; + int num = (i + 1) / 2; + + ep = &udc->ep[i]; + + sprintf(ep->name, "ep%d%s", num, + i ? (is_out ? "out" : "in") : ""); + ep->ep.name = ep->name; + + ep->ep.ops = &vep_ops; + + usb_ep_set_maxpacket_limit(&ep->ep, ~0); + ep->ep.max_streams = 16; + ep->gadget = &udc->gadget; + INIT_LIST_HEAD(&ep->req_queue); + + if (i == 0) { + /* ep0 */ + ep->ep.caps.type_control = true; + ep->ep.caps.dir_out = true; + ep->ep.caps.dir_in = true; + + udc->gadget.ep0 = &ep->ep; + } else { + /* All other eps */ + ep->ep.caps.type_iso = true; + ep->ep.caps.type_int = true; + ep->ep.caps.type_bulk = true; + + if (is_out) + ep->ep.caps.dir_out = true; + else + ep->ep.caps.dir_in = true; + + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + } + } + + spin_lock_init(&udc->lock); + spin_lock_init(&udc->lock_tx); + INIT_LIST_HEAD(&udc->urb_queue); + INIT_LIST_HEAD(&udc->tx_queue); + init_waitqueue_head(&udc->tx_waitq); + + spin_lock_init(&ud->lock); + mutex_init(&ud->sysfs_lock); + ud->status = SDEV_ST_AVAILABLE; + ud->side = USBIP_VUDC; + + ud->eh_ops.shutdown = vudc_shutdown; + ud->eh_ops.reset = vudc_device_reset; + ud->eh_ops.unusable = vudc_device_unusable; + + v_init_timer(udc); + return 0; + +nomem_ep: + return -ENOMEM; +} + +static void cleanup_vudc_hw(struct vudc *udc) +{ + kfree(udc->ep); +} + +/* platform driver ops */ + +int vudc_probe(struct platform_device *pdev) +{ + struct vudc *udc; + int ret = -ENOMEM; + + udc = kzalloc_obj(*udc); + if (!udc) + goto out; + + udc->gadget.name = GADGET_NAME; + udc->gadget.ops = &vgadget_ops; + udc->gadget.max_speed = USB_SPEED_HIGH; + udc->gadget.dev.parent = &pdev->dev; + udc->pdev = pdev; + + ret = init_vudc_hw(udc); + if (ret) + goto err_init_vudc_hw; + + ret = usb_add_gadget_udc(&pdev->dev, &udc->gadget); + if (ret < 0) + goto err_add_udc; + + platform_set_drvdata(pdev, udc); + + return ret; + +err_add_udc: + cleanup_vudc_hw(udc); +err_init_vudc_hw: + kfree(udc); +out: + return ret; +} + +void vudc_remove(struct platform_device *pdev) +{ + struct vudc *udc = platform_get_drvdata(pdev); + + v_stop_timer(udc); + usb_del_gadget_udc(&udc->gadget); + cleanup_vudc_hw(udc); + kfree(udc); +} diff --git a/drivers/usb/usbip/vudc_main.c b/drivers/usb/usbip/vudc_main.c new file mode 100644 index 000000000..993e721cb --- /dev/null +++ b/drivers/usb/usbip/vudc_main.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Karol Kosik + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski + * Krzysztof Opasiak + */ + +#include +#include +#include + +#include "vudc.h" + +static unsigned int vudc_number = 1; + +module_param_named(num, vudc_number, uint, S_IRUGO); +MODULE_PARM_DESC(num, "number of emulated controllers"); + +static struct platform_driver vudc_driver = { + .probe = vudc_probe, + .remove = vudc_remove, + .driver = { + .name = GADGET_NAME, + .dev_groups = vudc_groups, + }, +}; + +static LIST_HEAD(vudc_devices); + +static int __init vudc_init(void) +{ + int retval = -ENOMEM; + int i; + struct vudc_device *udc_dev = NULL, *udc_dev2 = NULL; + + if (usb_disabled()) + return -ENODEV; + + if (vudc_number < 1) { + pr_err("Number of emulated UDC must be no less than 1"); + return -EINVAL; + } + + retval = platform_driver_register(&vudc_driver); + if (retval < 0) + goto out; + + for (i = 0; i < vudc_number; i++) { + udc_dev = alloc_vudc_device(i); + if (!udc_dev) { + retval = -ENOMEM; + goto cleanup; + } + + retval = platform_device_add(udc_dev->pdev); + if (retval < 0) { + put_vudc_device(udc_dev); + goto cleanup; + } + + list_add_tail(&udc_dev->dev_entry, &vudc_devices); + if (!platform_get_drvdata(udc_dev->pdev)) { + /* + * The udc was added successfully but its probe + * function failed for some reason. + */ + retval = -EINVAL; + goto cleanup; + } + } + goto out; + +cleanup: + list_for_each_entry_safe(udc_dev, udc_dev2, &vudc_devices, dev_entry) { + list_del(&udc_dev->dev_entry); + /* + * Just do platform_device_del() here, put_vudc_device() + * calls the platform_device_put() + */ + platform_device_del(udc_dev->pdev); + put_vudc_device(udc_dev); + } + + platform_driver_unregister(&vudc_driver); +out: + return retval; +} +module_init(vudc_init); + +static void __exit vudc_cleanup(void) +{ + struct vudc_device *udc_dev = NULL, *udc_dev2 = NULL; + + list_for_each_entry_safe(udc_dev, udc_dev2, &vudc_devices, dev_entry) { + list_del(&udc_dev->dev_entry); + /* + * Just do platform_device_del() here, put_vudc_device() + * calls the platform_device_put() + */ + platform_device_del(udc_dev->pdev); + put_vudc_device(udc_dev); + } + platform_driver_unregister(&vudc_driver); +} +module_exit(vudc_cleanup); + +MODULE_DESCRIPTION("USB over IP Device Controller"); +MODULE_AUTHOR("Krzysztof Opasiak, Karol Kosik, Igor Kotrasinski"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/usbip/vudc_rx.c b/drivers/usb/usbip/vudc_rx.c new file mode 100644 index 000000000..51bb70837 --- /dev/null +++ b/drivers/usb/usbip/vudc_rx.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Karol Kosik + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski + */ + +#include +#include +#include + +#include "usbip_common.h" +#include "vudc.h" + +static int alloc_urb_from_cmd(struct urb **urbp, + struct usbip_header *pdu, u8 type) +{ + struct urb *urb; + + if (type == USB_ENDPOINT_XFER_ISOC) + urb = usb_alloc_urb(pdu->u.cmd_submit.number_of_packets, + GFP_KERNEL); + else + urb = usb_alloc_urb(0, GFP_KERNEL); + + if (!urb) + goto err; + + usbip_pack_pdu(pdu, urb, USBIP_CMD_SUBMIT, 0); + + if (urb->transfer_buffer_length > 0) { + urb->transfer_buffer = kzalloc(urb->transfer_buffer_length, + GFP_KERNEL); + if (!urb->transfer_buffer) + goto free_urb; + } + + urb->setup_packet = kmemdup(&pdu->u.cmd_submit.setup, 8, + GFP_KERNEL); + if (!urb->setup_packet) + goto free_buffer; + + /* + * FIXME - we only setup pipe enough for usbip functions + * to behave nicely + */ + urb->pipe |= pdu->base.direction == USBIP_DIR_IN ? + USB_DIR_IN : USB_DIR_OUT; + + *urbp = urb; + return 0; + +free_buffer: + kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; +free_urb: + usb_free_urb(urb); +err: + return -ENOMEM; +} + +static int v_recv_cmd_unlink(struct vudc *udc, + struct usbip_header *pdu) +{ + unsigned long flags; + struct urbp *urb_p; + + spin_lock_irqsave(&udc->lock, flags); + list_for_each_entry(urb_p, &udc->urb_queue, urb_entry) { + if (urb_p->seqnum != pdu->u.cmd_unlink.seqnum) + continue; + urb_p->urb->unlinked = -ECONNRESET; + urb_p->seqnum = pdu->base.seqnum; + v_kick_timer(udc, jiffies); + spin_unlock_irqrestore(&udc->lock, flags); + return 0; + } + /* Not found, completed / not queued */ + spin_lock(&udc->lock_tx); + v_enqueue_ret_unlink(udc, pdu->base.seqnum, 0); + wake_up(&udc->tx_waitq); + spin_unlock(&udc->lock_tx); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int v_recv_cmd_submit(struct vudc *udc, + struct usbip_header *pdu) +{ + int ret = 0; + struct urbp *urb_p; + u8 address; + unsigned long flags; + + urb_p = alloc_urbp(); + if (!urb_p) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_MALLOC); + return -ENOMEM; + } + + /* base.ep is pipeendpoint(pipe) */ + address = pdu->base.ep; + if (pdu->base.direction == USBIP_DIR_IN) + address |= USB_DIR_IN; + + spin_lock_irqsave(&udc->lock, flags); + urb_p->ep = vudc_find_endpoint(udc, address); + if (!urb_p->ep) { + /* we don't know the type, there may be isoc data! */ + dev_err(&udc->pdev->dev, "request to nonexistent endpoint"); + spin_unlock_irqrestore(&udc->lock, flags); + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_TCP); + ret = -EPIPE; + goto free_urbp; + } + urb_p->type = urb_p->ep->type; + spin_unlock_irqrestore(&udc->lock, flags); + + urb_p->new = 1; + urb_p->seqnum = pdu->base.seqnum; + + if (urb_p->ep->type == USB_ENDPOINT_XFER_ISOC) { + /* validate packet size and number of packets */ + unsigned int maxp, packets, bytes; + + maxp = usb_endpoint_maxp(urb_p->ep->desc); + maxp *= usb_endpoint_maxp_mult(urb_p->ep->desc); + bytes = pdu->u.cmd_submit.transfer_buffer_length; + packets = DIV_ROUND_UP(bytes, maxp); + + if (pdu->u.cmd_submit.number_of_packets < 0 || + pdu->u.cmd_submit.number_of_packets > packets) { + dev_err(&udc->gadget.dev, + "CMD_SUBMIT: isoc invalid num packets %d\n", + pdu->u.cmd_submit.number_of_packets); + ret = -EMSGSIZE; + goto free_urbp; + } + } + + ret = alloc_urb_from_cmd(&urb_p->urb, pdu, urb_p->ep->type); + if (ret) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_MALLOC); + ret = -ENOMEM; + goto free_urbp; + } + + urb_p->urb->status = -EINPROGRESS; + + /* FIXME: more pipe setup to please usbip_common */ + BUILD_BUG_ON_MSG(PIPE_BULK != 3, "PIPE_* doesn't range from 0 to 3"); + + urb_p->urb->pipe &= ~(PIPE_BULK << 30); + switch (urb_p->ep->type) { + case USB_ENDPOINT_XFER_BULK: + urb_p->urb->pipe |= (PIPE_BULK << 30); + break; + case USB_ENDPOINT_XFER_INT: + urb_p->urb->pipe |= (PIPE_INTERRUPT << 30); + break; + case USB_ENDPOINT_XFER_CONTROL: + urb_p->urb->pipe |= (PIPE_CONTROL << 30); + break; + case USB_ENDPOINT_XFER_ISOC: + urb_p->urb->pipe |= (PIPE_ISOCHRONOUS << 30); + break; + } + ret = usbip_recv_xbuff(&udc->ud, urb_p->urb); + if (ret < 0) + goto free_urbp; + + ret = usbip_recv_iso(&udc->ud, urb_p->urb); + if (ret < 0) + goto free_urbp; + + spin_lock_irqsave(&udc->lock, flags); + v_kick_timer(udc, jiffies); + list_add_tail(&urb_p->urb_entry, &udc->urb_queue); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; + +free_urbp: + free_urbp_and_urb(urb_p); + return ret; +} + +static int v_rx_pdu(struct usbip_device *ud) +{ + int ret; + struct usbip_header pdu; + struct vudc *udc = container_of(ud, struct vudc, ud); + + memset(&pdu, 0, sizeof(pdu)); + ret = usbip_recv(ud->tcp_socket, &pdu, sizeof(pdu)); + if (ret != sizeof(pdu)) { + usbip_event_add(ud, VUDC_EVENT_ERROR_TCP); + if (ret >= 0) + return -EPIPE; + return ret; + } + usbip_header_correct_endian(&pdu, 0); + + spin_lock_irq(&ud->lock); + ret = (ud->status == SDEV_ST_USED); + spin_unlock_irq(&ud->lock); + if (!ret) { + usbip_event_add(ud, VUDC_EVENT_ERROR_TCP); + return -EBUSY; + } + + switch (pdu.base.command) { + case USBIP_CMD_UNLINK: + ret = v_recv_cmd_unlink(udc, &pdu); + break; + case USBIP_CMD_SUBMIT: + ret = v_recv_cmd_submit(udc, &pdu); + break; + default: + ret = -EPIPE; + pr_err("rx: unknown command"); + break; + } + return ret; +} + +int v_rx_loop(void *data) +{ + struct usbip_device *ud = data; + int ret = 0; + + while (!kthread_should_stop()) { + if (usbip_event_happened(ud)) + break; + ret = v_rx_pdu(ud); + if (ret < 0) { + pr_warn("v_rx exit with error %d", ret); + break; + } + } + return ret; +} diff --git a/drivers/usb/usbip/vudc_sysfs.c b/drivers/usb/usbip/vudc_sysfs.c new file mode 100644 index 000000000..47f3a7d51 --- /dev/null +++ b/drivers/usb/usbip/vudc_sysfs.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Karol Kosik + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski + * Krzysztof Opasiak + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "usbip_common.h" +#include "vudc.h" + +#include + +/* called with udc->lock held */ +int get_gadget_descs(struct vudc *udc) +{ + struct vrequest *usb_req; + struct vep *ep0 = to_vep(udc->gadget.ep0); + struct usb_device_descriptor *ddesc = &udc->dev_desc; + struct usb_ctrlrequest req; + int ret; + + if (!udc->driver || !udc->pullup) + return -EINVAL; + + req.bRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE; + req.bRequest = USB_REQ_GET_DESCRIPTOR; + req.wValue = cpu_to_le16(USB_DT_DEVICE << 8); + req.wIndex = cpu_to_le16(0); + req.wLength = cpu_to_le16(sizeof(*ddesc)); + + spin_unlock(&udc->lock); + ret = udc->driver->setup(&(udc->gadget), &req); + spin_lock(&udc->lock); + if (ret < 0) + goto out; + + /* assuming request queue is empty; request is now on top */ + usb_req = list_last_entry(&ep0->req_queue, struct vrequest, req_entry); + list_del(&usb_req->req_entry); + + if (usb_req->req.length > sizeof(*ddesc)) { + ret = -EOVERFLOW; + goto giveback_req; + } + + memcpy(ddesc, usb_req->req.buf, sizeof(*ddesc)); + udc->desc_cached = 1; + ret = 0; +giveback_req: + usb_req->req.status = 0; + usb_req->req.actual = usb_req->req.length; + usb_gadget_giveback_request(&(ep0->ep), &(usb_req->req)); +out: + return ret; +} + +/* + * Exposes device descriptor from the gadget driver. + */ +static ssize_t dev_desc_read(struct file *file, struct kobject *kobj, + const struct bin_attribute *attr, char *out, + loff_t off, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct vudc *udc = (struct vudc *)dev_get_drvdata(dev); + char *desc_ptr = (char *) &udc->dev_desc; + unsigned long flags; + int ret; + + spin_lock_irqsave(&udc->lock, flags); + if (!udc->desc_cached) { + ret = -ENODEV; + goto unlock; + } + + memcpy(out, desc_ptr + off, count); + ret = count; +unlock: + spin_unlock_irqrestore(&udc->lock, flags); + return ret; +} +static const BIN_ATTR_RO(dev_desc, sizeof(struct usb_device_descriptor)); + +static ssize_t usbip_sockfd_store(struct device *dev, + struct device_attribute *attr, + const char *in, size_t count) +{ + struct vudc *udc = (struct vudc *) dev_get_drvdata(dev); + int rv; + int sockfd = 0; + int err; + struct socket *socket; + unsigned long flags; + int ret; + struct task_struct *tcp_rx = NULL; + struct task_struct *tcp_tx = NULL; + + rv = kstrtoint(in, 0, &sockfd); + if (rv != 0) + return -EINVAL; + + if (!udc) { + dev_err(dev, "no device"); + return -ENODEV; + } + mutex_lock(&udc->ud.sysfs_lock); + spin_lock_irqsave(&udc->lock, flags); + /* Don't export what we don't have */ + if (!udc->driver || !udc->pullup) { + dev_err(dev, "gadget not bound"); + ret = -ENODEV; + goto unlock; + } + + if (sockfd != -1) { + if (udc->connected) { + dev_err(dev, "Device already connected"); + ret = -EBUSY; + goto unlock; + } + + spin_lock(&udc->ud.lock); + + if (udc->ud.status != SDEV_ST_AVAILABLE) { + ret = -EINVAL; + goto unlock_ud; + } + + socket = sockfd_lookup(sockfd, &err); + if (!socket) { + dev_err(dev, "failed to lookup sock"); + ret = -EINVAL; + goto unlock_ud; + } + + if (socket->type != SOCK_STREAM) { + dev_err(dev, "Expecting SOCK_STREAM - found %d", + socket->type); + ret = -EINVAL; + goto sock_err; + } + + /* unlock and create threads and get tasks */ + spin_unlock(&udc->ud.lock); + spin_unlock_irqrestore(&udc->lock, flags); + + tcp_rx = kthread_create(&v_rx_loop, &udc->ud, "vudc_rx"); + if (IS_ERR(tcp_rx)) { + sockfd_put(socket); + mutex_unlock(&udc->ud.sysfs_lock); + return -EINVAL; + } + tcp_tx = kthread_create(&v_tx_loop, &udc->ud, "vudc_tx"); + if (IS_ERR(tcp_tx)) { + kthread_stop(tcp_rx); + sockfd_put(socket); + mutex_unlock(&udc->ud.sysfs_lock); + return -EINVAL; + } + + /* get task structs now */ + get_task_struct(tcp_rx); + get_task_struct(tcp_tx); + + /* lock and update udc->ud state */ + spin_lock_irqsave(&udc->lock, flags); + spin_lock(&udc->ud.lock); + + udc->ud.tcp_socket = socket; + udc->ud.tcp_rx = tcp_rx; + udc->ud.tcp_tx = tcp_tx; + udc->ud.status = SDEV_ST_USED; + + spin_unlock(&udc->ud.lock); + + ktime_get_ts64(&udc->start_time); + v_start_timer(udc); + udc->connected = 1; + + spin_unlock_irqrestore(&udc->lock, flags); + + wake_up_process(udc->ud.tcp_rx); + wake_up_process(udc->ud.tcp_tx); + + mutex_unlock(&udc->ud.sysfs_lock); + return count; + + } else { + if (!udc->connected) { + dev_err(dev, "Device not connected"); + ret = -EINVAL; + goto unlock; + } + + spin_lock(&udc->ud.lock); + if (udc->ud.status != SDEV_ST_USED) { + ret = -EINVAL; + goto unlock_ud; + } + spin_unlock(&udc->ud.lock); + + usbip_event_add(&udc->ud, VUDC_EVENT_DOWN); + } + + spin_unlock_irqrestore(&udc->lock, flags); + mutex_unlock(&udc->ud.sysfs_lock); + + return count; + +sock_err: + sockfd_put(socket); +unlock_ud: + spin_unlock(&udc->ud.lock); +unlock: + spin_unlock_irqrestore(&udc->lock, flags); + mutex_unlock(&udc->ud.sysfs_lock); + + return ret; +} +static DEVICE_ATTR_WO(usbip_sockfd); + +static ssize_t usbip_status_show(struct device *dev, + struct device_attribute *attr, char *out) +{ + struct vudc *udc = (struct vudc *) dev_get_drvdata(dev); + int status; + + if (!udc) { + dev_err(dev, "no device"); + return -ENODEV; + } + spin_lock_irq(&udc->ud.lock); + status = udc->ud.status; + spin_unlock_irq(&udc->ud.lock); + + return sysfs_emit(out, "%d\n", status); +} +static DEVICE_ATTR_RO(usbip_status); + +static struct attribute *dev_attrs[] = { + &dev_attr_usbip_sockfd.attr, + &dev_attr_usbip_status.attr, + NULL, +}; + +static const struct bin_attribute *const dev_bin_attrs[] = { + &bin_attr_dev_desc, + NULL, +}; + +static const struct attribute_group vudc_attr_group = { + .attrs = dev_attrs, + .bin_attrs = dev_bin_attrs, +}; + +const struct attribute_group *vudc_groups[] = { + &vudc_attr_group, + NULL, +}; diff --git a/drivers/usb/usbip/vudc_transfer.c b/drivers/usb/usbip/vudc_transfer.c new file mode 100644 index 000000000..d4ce85c4c --- /dev/null +++ b/drivers/usb/usbip/vudc_transfer.c @@ -0,0 +1,497 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Karol Kosik + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski + * + * Based on dummy_hcd.c, which is: + * Copyright (C) 2003 David Brownell + * Copyright (C) 2003-2005 Alan Stern + */ + +#include +#include +#include + +#include "vudc.h" + +#define DEV_REQUEST (USB_TYPE_STANDARD | USB_RECIP_DEVICE) +#define DEV_INREQUEST (DEV_REQUEST | USB_DIR_IN) +#define INTF_REQUEST (USB_TYPE_STANDARD | USB_RECIP_INTERFACE) +#define INTF_INREQUEST (INTF_REQUEST | USB_DIR_IN) +#define EP_REQUEST (USB_TYPE_STANDARD | USB_RECIP_ENDPOINT) +#define EP_INREQUEST (EP_REQUEST | USB_DIR_IN) + +static int get_frame_limit(enum usb_device_speed speed) +{ + switch (speed) { + case USB_SPEED_LOW: + return 8 /*bytes*/ * 12 /*packets*/; + case USB_SPEED_FULL: + return 64 /*bytes*/ * 19 /*packets*/; + case USB_SPEED_HIGH: + return 512 /*bytes*/ * 13 /*packets*/ * 8 /*uframes*/; + case USB_SPEED_SUPER: + /* Bus speed is 500000 bytes/ms, so use a little less */ + return 490000; + default: + /* error */ + return -1; + } + +} + +/* + * handle_control_request() - handles all control transfers + * @udc: pointer to vudc + * @urb: the urb request to handle + * @setup: pointer to the setup data for a USB device control + * request + * @status: pointer to request handling status + * + * Return 0 - if the request was handled + * 1 - if the request wasn't handles + * error code on error + * + * Adapted from drivers/usb/gadget/udc/dummy_hcd.c + */ +static int handle_control_request(struct vudc *udc, struct urb *urb, + struct usb_ctrlrequest *setup, + int *status) +{ + struct vep *ep2; + int ret_val = 1; + unsigned int w_index; + unsigned int w_value; + + w_index = le16_to_cpu(setup->wIndex); + w_value = le16_to_cpu(setup->wValue); + switch (setup->bRequest) { + case USB_REQ_SET_ADDRESS: + if (setup->bRequestType != DEV_REQUEST) + break; + udc->address = w_value; + ret_val = 0; + *status = 0; + break; + case USB_REQ_SET_FEATURE: + if (setup->bRequestType == DEV_REQUEST) { + ret_val = 0; + switch (w_value) { + case USB_DEVICE_REMOTE_WAKEUP: + break; + case USB_DEVICE_B_HNP_ENABLE: + udc->gadget.b_hnp_enable = 1; + break; + case USB_DEVICE_A_HNP_SUPPORT: + udc->gadget.a_hnp_support = 1; + break; + case USB_DEVICE_A_ALT_HNP_SUPPORT: + udc->gadget.a_alt_hnp_support = 1; + break; + default: + ret_val = -EOPNOTSUPP; + } + if (ret_val == 0) { + udc->devstatus |= (1 << w_value); + *status = 0; + } + } else if (setup->bRequestType == EP_REQUEST) { + /* endpoint halt */ + ep2 = vudc_find_endpoint(udc, w_index); + if (!ep2 || ep2->ep.name == udc->ep[0].ep.name) { + ret_val = -EOPNOTSUPP; + break; + } + ep2->halted = 1; + ret_val = 0; + *status = 0; + } + break; + case USB_REQ_CLEAR_FEATURE: + if (setup->bRequestType == DEV_REQUEST) { + ret_val = 0; + switch (w_value) { + case USB_DEVICE_REMOTE_WAKEUP: + w_value = USB_DEVICE_REMOTE_WAKEUP; + break; + + case USB_DEVICE_U1_ENABLE: + case USB_DEVICE_U2_ENABLE: + case USB_DEVICE_LTM_ENABLE: + ret_val = -EOPNOTSUPP; + break; + default: + ret_val = -EOPNOTSUPP; + break; + } + if (ret_val == 0) { + udc->devstatus &= ~(1 << w_value); + *status = 0; + } + } else if (setup->bRequestType == EP_REQUEST) { + /* endpoint halt */ + ep2 = vudc_find_endpoint(udc, w_index); + if (!ep2) { + ret_val = -EOPNOTSUPP; + break; + } + if (!ep2->wedged) + ep2->halted = 0; + ret_val = 0; + *status = 0; + } + break; + case USB_REQ_GET_STATUS: + if (setup->bRequestType == DEV_INREQUEST + || setup->bRequestType == INTF_INREQUEST + || setup->bRequestType == EP_INREQUEST) { + char *buf; + /* + * device: remote wakeup, selfpowered + * interface: nothing + * endpoint: halt + */ + buf = (char *)urb->transfer_buffer; + if (urb->transfer_buffer_length > 0) { + if (setup->bRequestType == EP_INREQUEST) { + ep2 = vudc_find_endpoint(udc, w_index); + if (!ep2) { + ret_val = -EOPNOTSUPP; + break; + } + buf[0] = ep2->halted; + } else if (setup->bRequestType == + DEV_INREQUEST) { + buf[0] = (u8)udc->devstatus; + } else + buf[0] = 0; + } + if (urb->transfer_buffer_length > 1) + buf[1] = 0; + urb->actual_length = min_t(u32, 2, + urb->transfer_buffer_length); + ret_val = 0; + *status = 0; + } + break; + } + return ret_val; +} + +/* Adapted from dummy_hcd.c ; caller must hold lock */ +static int transfer(struct vudc *udc, + struct urb *urb, struct vep *ep, int limit) +{ + struct vrequest *req; + int sent = 0; +top: + /* if there's no request queued, the device is NAKing; return */ + list_for_each_entry(req, &ep->req_queue, req_entry) { + unsigned int host_len, dev_len, len; + void *ubuf_pos, *rbuf_pos; + int is_short, to_host; + int rescan = 0; + + /* + * 1..N packets of ep->ep.maxpacket each ... the last one + * may be short (including zero length). + * + * writer can send a zlp explicitly (length 0) or implicitly + * (length mod maxpacket zero, and 'zero' flag); they always + * terminate reads. + */ + host_len = urb->transfer_buffer_length - urb->actual_length; + dev_len = req->req.length - req->req.actual; + len = min(host_len, dev_len); + + to_host = usb_pipein(urb->pipe); + if (unlikely(len == 0)) + is_short = 1; + else { + /* send multiple of maxpacket first, then remainder */ + if (len >= ep->ep.maxpacket) { + is_short = 0; + if (len % ep->ep.maxpacket > 0) + rescan = 1; + len -= len % ep->ep.maxpacket; + } else { + is_short = 1; + } + + ubuf_pos = urb->transfer_buffer + urb->actual_length; + rbuf_pos = req->req.buf + req->req.actual; + + if (urb->pipe & USB_DIR_IN) + memcpy(ubuf_pos, rbuf_pos, len); + else + memcpy(rbuf_pos, ubuf_pos, len); + + urb->actual_length += len; + req->req.actual += len; + sent += len; + } + + /* + * short packets terminate, maybe with overflow/underflow. + * it's only really an error to write too much. + * + * partially filling a buffer optionally blocks queue advances + * (so completion handlers can clean up the queue) but we don't + * need to emulate such data-in-flight. + */ + if (is_short) { + if (host_len == dev_len) { + req->req.status = 0; + urb->status = 0; + } else if (to_host) { + req->req.status = 0; + if (dev_len > host_len) + urb->status = -EOVERFLOW; + else + urb->status = 0; + } else { + urb->status = 0; + if (host_len > dev_len) + req->req.status = -EOVERFLOW; + else + req->req.status = 0; + } + + /* many requests terminate without a short packet */ + /* also check if we need to send zlp */ + } else { + if (req->req.length == req->req.actual) { + if (req->req.zero && to_host) + rescan = 1; + else + req->req.status = 0; + } + if (urb->transfer_buffer_length == urb->actual_length) { + if (urb->transfer_flags & URB_ZERO_PACKET && + !to_host) + rescan = 1; + else + urb->status = 0; + } + } + + /* device side completion --> continuable */ + if (req->req.status != -EINPROGRESS) { + + list_del_init(&req->req_entry); + spin_unlock(&udc->lock); + usb_gadget_giveback_request(&ep->ep, &req->req); + spin_lock(&udc->lock); + + /* requests might have been unlinked... */ + rescan = 1; + } + + /* host side completion --> terminate */ + if (urb->status != -EINPROGRESS) + break; + + /* rescan to continue with any other queued i/o */ + if (rescan) + goto top; + } + return sent; +} + +static void v_timer(struct timer_list *t) +{ + struct vudc *udc = timer_container_of(udc, t, tr_timer.timer); + struct transfer_timer *timer = &udc->tr_timer; + struct urbp *urb_p, *tmp; + unsigned long flags; + struct usb_ep *_ep; + struct vep *ep; + int ret = 0; + int total, limit; + + spin_lock_irqsave(&udc->lock, flags); + + total = get_frame_limit(udc->gadget.speed); + if (total < 0) { /* unknown speed, or not set yet */ + timer->state = VUDC_TR_IDLE; + spin_unlock_irqrestore(&udc->lock, flags); + return; + } + /* is it next frame now? */ + if (time_after(jiffies, timer->frame_start + msecs_to_jiffies(1))) { + timer->frame_limit = total; + /* FIXME: how to make it accurate? */ + timer->frame_start = jiffies; + } else { + total = timer->frame_limit; + } + + /* We have to clear ep0 flags separately as it's not on the list */ + udc->ep[0].already_seen = 0; + list_for_each_entry(_ep, &udc->gadget.ep_list, ep_list) { + ep = to_vep(_ep); + ep->already_seen = 0; + } + +restart: + list_for_each_entry_safe(urb_p, tmp, &udc->urb_queue, urb_entry) { + struct urb *urb = urb_p->urb; + + ep = urb_p->ep; + if (urb->unlinked) + goto return_urb; + if (timer->state != VUDC_TR_RUNNING) + continue; + + if (!ep) { + urb->status = -EPROTO; + goto return_urb; + } + + /* Used up bandwidth? */ + if (total <= 0 && ep->type == USB_ENDPOINT_XFER_BULK) + continue; + + if (ep->already_seen) + continue; + ep->already_seen = 1; + if (ep == &udc->ep[0] && urb_p->new) { + ep->setup_stage = 1; + urb_p->new = 0; + } + if (ep->halted && !ep->setup_stage) { + urb->status = -EPIPE; + goto return_urb; + } + + if (ep == &udc->ep[0] && ep->setup_stage) { + /* TODO - flush any stale requests */ + ep->setup_stage = 0; + ep->halted = 0; + + ret = handle_control_request(udc, urb, + (struct usb_ctrlrequest *) urb->setup_packet, + (&urb->status)); + if (ret > 0) { + spin_unlock(&udc->lock); + ret = udc->driver->setup(&udc->gadget, + (struct usb_ctrlrequest *) + urb->setup_packet); + spin_lock(&udc->lock); + } + if (ret >= 0) { + /* no delays (max 64kb data stage) */ + limit = 64 * 1024; + goto treat_control_like_bulk; + } else { + urb->status = -EPIPE; + urb->actual_length = 0; + goto return_urb; + } + } + + limit = total; + switch (ep->type) { + case USB_ENDPOINT_XFER_ISOC: + /* TODO: support */ + urb->status = -EXDEV; + break; + + case USB_ENDPOINT_XFER_INT: + /* + * TODO: figure out bandwidth guarantees + * for now, give unlimited bandwidth + */ + limit += urb->transfer_buffer_length; + fallthrough; + default: +treat_control_like_bulk: + total -= transfer(udc, urb, ep, limit); + } + if (urb->status == -EINPROGRESS) + continue; + +return_urb: + if (ep) + ep->already_seen = ep->setup_stage = 0; + + spin_lock(&udc->lock_tx); + list_del(&urb_p->urb_entry); + if (!urb->unlinked) { + v_enqueue_ret_submit(udc, urb_p); + } else { + v_enqueue_ret_unlink(udc, urb_p->seqnum, + urb->unlinked); + free_urbp_and_urb(urb_p); + } + wake_up(&udc->tx_waitq); + spin_unlock(&udc->lock_tx); + + goto restart; + } + + /* TODO - also wait on empty usb_request queues? */ + if (list_empty(&udc->urb_queue)) + timer->state = VUDC_TR_IDLE; + else + mod_timer(&timer->timer, + timer->frame_start + msecs_to_jiffies(1)); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +/* All timer functions are run with udc->lock held */ + +void v_init_timer(struct vudc *udc) +{ + struct transfer_timer *t = &udc->tr_timer; + + timer_setup(&t->timer, v_timer, 0); + t->state = VUDC_TR_STOPPED; +} + +void v_start_timer(struct vudc *udc) +{ + struct transfer_timer *t = &udc->tr_timer; + + dev_dbg(&udc->pdev->dev, "timer start"); + switch (t->state) { + case VUDC_TR_RUNNING: + return; + case VUDC_TR_IDLE: + return v_kick_timer(udc, jiffies); + case VUDC_TR_STOPPED: + t->state = VUDC_TR_IDLE; + t->frame_start = jiffies; + t->frame_limit = get_frame_limit(udc->gadget.speed); + return v_kick_timer(udc, jiffies); + } +} + +void v_kick_timer(struct vudc *udc, unsigned long time) +{ + struct transfer_timer *t = &udc->tr_timer; + + dev_dbg(&udc->pdev->dev, "timer kick"); + switch (t->state) { + case VUDC_TR_RUNNING: + return; + case VUDC_TR_IDLE: + t->state = VUDC_TR_RUNNING; + fallthrough; + case VUDC_TR_STOPPED: + /* we may want to kick timer to unqueue urbs */ + mod_timer(&t->timer, time); + } +} + +void v_stop_timer(struct vudc *udc) +{ + struct transfer_timer *t = &udc->tr_timer; + + /* Delete the timer synchronously before teardown frees udc. */ + dev_dbg(&udc->pdev->dev, "timer stop"); + timer_delete_sync(&t->timer); + t->state = VUDC_TR_STOPPED; +} diff --git a/drivers/usb/usbip/vudc_tx.c b/drivers/usb/usbip/vudc_tx.c new file mode 100644 index 000000000..88edfbcc0 --- /dev/null +++ b/drivers/usb/usbip/vudc_tx.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Karol Kosik + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski + */ + +#include +#include +#include + +#include "usbip_common.h" +#include "vudc.h" + +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 urbp *urb_p) +{ + setup_base_pdu(&rpdu->base, USBIP_RET_SUBMIT, urb_p->seqnum); + usbip_pack_pdu(rpdu, urb_p->urb, USBIP_RET_SUBMIT, 1); +} + +static void setup_ret_unlink_pdu(struct usbip_header *rpdu, + struct v_unlink *unlink) +{ + setup_base_pdu(&rpdu->base, USBIP_RET_UNLINK, unlink->seqnum); + rpdu->u.ret_unlink.status = unlink->status; +} + +static int v_send_ret_unlink(struct vudc *udc, struct v_unlink *unlink) +{ + struct msghdr msg; + struct kvec iov[1]; + size_t txsize; + + 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)); + + /* 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(udc->ud.tcp_socket, &msg, iov, + 1, txsize); + if (ret != txsize) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_TCP); + if (ret >= 0) + return -EPIPE; + return ret; + } + kfree(unlink); + + return txsize; +} + +static int v_send_ret_submit(struct vudc *udc, struct urbp *urb_p) +{ + struct urb *urb = urb_p->urb; + struct usbip_header pdu_header; + struct usbip_iso_packet_descriptor *iso_buffer = NULL; + struct kvec *iov = NULL; + int iovnum = 0; + int ret = 0; + size_t txsize; + struct msghdr msg; + + txsize = 0; + memset(&pdu_header, 0, sizeof(pdu_header)); + memset(&msg, 0, sizeof(msg)); + + if (urb->actual_length > 0 && !urb->transfer_buffer) { + dev_err(&udc->gadget.dev, + "urb: actual_length %d transfer_buffer null\n", + urb->actual_length); + return -1; + } + + if (urb_p->type == USB_ENDPOINT_XFER_ISOC) + iovnum = 2 + urb->number_of_packets; + else + iovnum = 2; + + iov = kzalloc_objs(*iov, iovnum); + if (!iov) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_MALLOC); + ret = -ENOMEM; + goto out; + } + iovnum = 0; + + /* 1. setup usbip_header */ + setup_ret_submit_pdu(&pdu_header, urb_p); + usbip_dbg_stub_tx("setup txdata seqnum: %u\n", + pdu_header.base.seqnum); + usbip_header_correct_endian(&pdu_header, 1); + + iov[iovnum].iov_base = &pdu_header; + iov[iovnum].iov_len = sizeof(pdu_header); + iovnum++; + txsize += sizeof(pdu_header); + + /* 2. setup transfer buffer */ + if (urb_p->type != USB_ENDPOINT_XFER_ISOC && + usb_pipein(urb->pipe) && urb->actual_length > 0) { + iov[iovnum].iov_base = urb->transfer_buffer; + iov[iovnum].iov_len = urb->actual_length; + iovnum++; + txsize += urb->actual_length; + } else if (urb_p->type == USB_ENDPOINT_XFER_ISOC && + usb_pipein(urb->pipe)) { + /* FIXME - copypasted from stub_tx, refactor */ + int i; + + for (i = 0; i < urb->number_of_packets; i++) { + iov[iovnum].iov_base = urb->transfer_buffer + + urb->iso_frame_desc[i].offset; + iov[iovnum].iov_len = + urb->iso_frame_desc[i].actual_length; + iovnum++; + txsize += urb->iso_frame_desc[i].actual_length; + } + + if (txsize != sizeof(pdu_header) + urb->actual_length) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_TCP); + ret = -EPIPE; + goto out; + } + } + /* else - no buffer to send */ + + /* 3. setup iso_packet_descriptor */ + if (urb_p->type == USB_ENDPOINT_XFER_ISOC) { + ssize_t len = 0; + + iso_buffer = usbip_alloc_iso_desc_pdu(urb, &len); + if (!iso_buffer) { + usbip_event_add(&udc->ud, + VUDC_EVENT_ERROR_MALLOC); + ret = -ENOMEM; + goto out; + } + + iov[iovnum].iov_base = iso_buffer; + iov[iovnum].iov_len = len; + txsize += len; + iovnum++; + } + + ret = kernel_sendmsg(udc->ud.tcp_socket, &msg, + iov, iovnum, txsize); + if (ret != txsize) { + usbip_event_add(&udc->ud, VUDC_EVENT_ERROR_TCP); + if (ret >= 0) + ret = -EPIPE; + goto out; + } + +out: + kfree(iov); + kfree(iso_buffer); + free_urbp_and_urb(urb_p); + if (ret < 0) + return ret; + return txsize; +} + +static int v_send_ret(struct vudc *udc) +{ + unsigned long flags; + struct tx_item *txi; + size_t total_size = 0; + int ret = 0; + + spin_lock_irqsave(&udc->lock_tx, flags); + while (!list_empty(&udc->tx_queue)) { + txi = list_first_entry(&udc->tx_queue, struct tx_item, + tx_entry); + list_del(&txi->tx_entry); + spin_unlock_irqrestore(&udc->lock_tx, flags); + + switch (txi->type) { + case TX_SUBMIT: + ret = v_send_ret_submit(udc, txi->s); + break; + case TX_UNLINK: + ret = v_send_ret_unlink(udc, txi->u); + break; + } + kfree(txi); + + if (ret < 0) + return ret; + + total_size += ret; + + spin_lock_irqsave(&udc->lock_tx, flags); + } + + spin_unlock_irqrestore(&udc->lock_tx, flags); + return total_size; +} + + +int v_tx_loop(void *data) +{ + struct usbip_device *ud = (struct usbip_device *) data; + struct vudc *udc = container_of(ud, struct vudc, ud); + int ret; + + while (!kthread_should_stop()) { + if (usbip_event_happened(&udc->ud)) + break; + ret = v_send_ret(udc); + if (ret < 0) { + pr_warn("v_tx exit with error %d", ret); + break; + } + wait_event_interruptible(udc->tx_waitq, + (!list_empty(&udc->tx_queue) || + kthread_should_stop())); + } + + return 0; +} + +/* called with spinlocks held */ +void v_enqueue_ret_unlink(struct vudc *udc, __u32 seqnum, __u32 status) +{ + struct tx_item *txi; + struct v_unlink *unlink; + + txi = kzalloc_obj(*txi, GFP_ATOMIC); + if (!txi) { + usbip_event_add(&udc->ud, VDEV_EVENT_ERROR_MALLOC); + return; + } + unlink = kzalloc_obj(*unlink, GFP_ATOMIC); + if (!unlink) { + kfree(txi); + usbip_event_add(&udc->ud, VDEV_EVENT_ERROR_MALLOC); + return; + } + + unlink->seqnum = seqnum; + unlink->status = status; + txi->type = TX_UNLINK; + txi->u = unlink; + + list_add_tail(&txi->tx_entry, &udc->tx_queue); +} + +/* called with spinlocks held */ +void v_enqueue_ret_submit(struct vudc *udc, struct urbp *urb_p) +{ + struct tx_item *txi; + + txi = kzalloc_obj(*txi, GFP_ATOMIC); + if (!txi) { + usbip_event_add(&udc->ud, VDEV_EVENT_ERROR_MALLOC); + return; + } + + txi->type = TX_SUBMIT; + txi->s = urb_p; + + list_add_tail(&txi->tx_entry, &udc->tx_queue); +} -- cgit v1.3.1