[Spice-devel] [PATCH usbredir 1/8] Add a usbredir kernel module to allow redirection directly into a running Linux system.
Jeremy White
jwhite at codeweavers.com
Wed Dec 9 14:16:01 PST 2015
Signed-off-by: Jeremy White <jwhite at codeweavers.com>
---
kernel/.gitignore | 6 +
kernel/Makefile | 10 +
kernel/README | 24 +++
kernel/TODO | 7 +
kernel/device.c | 359 +++++++++++++++++++++++++++++++++++
kernel/hub.c | 503 +++++++++++++++++++++++++++++++++++++++++++++++++
kernel/includes.c | 3 +
kernel/main.c | 97 ++++++++++
kernel/redir.c | 545 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
kernel/rx.c | 40 ++++
kernel/sysfs.c | 164 ++++++++++++++++
kernel/tx.c | 151 +++++++++++++++
kernel/urb.c | 282 ++++++++++++++++++++++++++++
kernel/usbredir.h | 229 +++++++++++++++++++++++
14 files changed, 2420 insertions(+)
create mode 100644 kernel/.gitignore
create mode 100644 kernel/Makefile
create mode 100644 kernel/README
create mode 100644 kernel/TODO
create mode 100644 kernel/device.c
create mode 100644 kernel/hub.c
create mode 100644 kernel/includes.c
create mode 100644 kernel/main.c
create mode 100644 kernel/redir.c
create mode 100644 kernel/rx.c
create mode 100644 kernel/sysfs.c
create mode 100644 kernel/tx.c
create mode 100644 kernel/urb.c
create mode 100644 kernel/usbredir.h
diff --git a/kernel/.gitignore b/kernel/.gitignore
new file mode 100644
index 0000000..f10680c
--- /dev/null
+++ b/kernel/.gitignore
@@ -0,0 +1,6 @@
+*.cmd
+.tmp_versions/
+Module.symvers
+modules.order
+*.ko
+*.mod.c
diff --git a/kernel/Makefile b/kernel/Makefile
new file mode 100644
index 0000000..d14501e
--- /dev/null
+++ b/kernel/Makefile
@@ -0,0 +1,10 @@
+includes := -I$(PWD)/../usbredirparser/
+
+obj-m += usbredir.o
+usbredir-y := main.o sysfs.o hub.o device.o urb.o redir.o tx.o rx.o includes.o
+
+modules:
+ make ccflags-y="${includes} -DDEBUG" -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
+
+clean:
+ make ccflags-y="${includes}" -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean
diff --git a/kernel/README b/kernel/README
new file mode 100644
index 0000000..ea80854
--- /dev/null
+++ b/kernel/README
@@ -0,0 +1,24 @@
+USB Redirection Kernel Module
+
+This module allows a Linux system to instatiate USB devices
+that are located on a remote device. The USB data is transferred
+over a socket using the USBREDIR protocol, which is generally
+used in conjunction with the SPICE project.
+
+You will need the USBREDIR user space tools. They can
+be found at http://www.spice-space.org/page/UsbRedir.
+
+To use, start the usbredirserver on a remote system.
+For example,
+ ./usbredirserver --port 4000 125f:db8a
+will export my ADATA thumb drive on the remote system.
+
+Next, on the local system, build and insert the usbredir
+kernel module:
+ make && sudo modprobe ./usbredir.ko
+
+Finally, on the local system, connect a socket and relay that to
+the kernel module. The connectkernel utility will do this as follows:
+ ./connectkernel adata4000 my.remote.device.com 4000
+
+The device should attach and be usable on the local system.
diff --git a/kernel/TODO b/kernel/TODO
new file mode 100644
index 0000000..01f5e15
--- /dev/null
+++ b/kernel/TODO
@@ -0,0 +1,7 @@
+TODO:
+ * dummy_hcd suspend/resume/timeout code is unclear
+ Need to study, figure out what we need, and if we
+ need a timer
+ * Not obeying irq / spin lock rules correctly
+ * Study dummy_hcd / usbip and mirror
+ * Read and deal with the many TODOs
diff --git a/kernel/device.c b/kernel/device.c
new file mode 100644
index 0000000..e39e264
--- /dev/null
+++ b/kernel/device.c
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2015 Jeremy White based on work by
+ * Copyright (C) 2003-2008 Takahiro Hirofuchi
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kthread.h>
+#include <linux/slab.h>
+#include <linux/file.h>
+#include <linux/net.h>
+
+#include "usbredir.h"
+
+void usbredir_device_init(struct usbredir_device *udev, int port,
+ struct usbredir_hub *hub)
+{
+ memset(udev, 0, sizeof(*udev));
+
+ udev->rhport = port;
+ udev->hub = hub;
+ atomic_set(&udev->active, 0);
+ spin_lock_init(&udev->lock);
+
+ INIT_LIST_HEAD(&udev->urblist_rx);
+ INIT_LIST_HEAD(&udev->urblist_tx);
+ INIT_LIST_HEAD(&udev->unlink_tx);
+ INIT_LIST_HEAD(&udev->unlink_rx);
+
+ init_waitqueue_head(&udev->waitq_tx);
+}
+
+void usbredir_device_allocate(struct usbredir_device *udev,
+ const char *devid,
+ struct socket *socket)
+{
+ char pname[32];
+
+ udev->parser = redir_parser_init(udev);
+ if (!udev->parser) {
+ pr_err("Unable to allocate USBREDIR parser.\n");
+ return;
+ }
+
+ udev->devid = kstrdup(devid, GFP_ATOMIC);
+ usbredir_sysfs_expose_devid(udev);
+
+ udev->socket = socket;
+
+ udev->port_status = 0;
+
+ sprintf(pname, "usbredir/rx:%d", udev->rhport);
+ udev->rx = kthread_run(usbredir_rx_loop, udev, pname);
+ sprintf(pname, "usbredir/tx:%d", udev->rhport);
+ udev->tx = kthread_run(usbredir_tx_loop, udev, pname);
+}
+
+
+/* Caller must hold lock */
+static void usbredir_device_cleanup_unlink(struct usbredir_device *udev)
+{
+ struct usbredir_unlink *unlink, *tmp;
+
+ list_for_each_entry_safe(unlink, tmp, &udev->unlink_tx, list) {
+ list_del(&unlink->list);
+ kfree(unlink);
+ }
+
+ list_for_each_entry_safe(unlink, tmp, &udev->unlink_rx, list) {
+ list_del(&unlink->list);
+ kfree(unlink);
+ }
+}
+
+void usbredir_device_deallocate(struct usbredir_device *udev,
+ bool stoprx, bool stoptx)
+{
+ pr_debug("%s %d/%d (active %d)\n", __func__, udev->hub->id,
+ udev->rhport, atomic_read(&udev->active));
+
+ /* atomic_dec_if_positive is not available in 2.6.32 */
+ if (atomic_dec_return(&udev->active) < 0) {
+ atomic_inc(&udev->active);
+ return;
+ }
+
+ /* Release the rx thread */
+ if (udev->socket)
+ kernel_sock_shutdown(udev->socket, SHUT_RDWR);
+
+ /* Release the tx thread */
+ wake_up_interruptible(&udev->waitq_tx);
+
+ /* The key is that kthread_stop waits until that thread has exited,
+ * so we don't clean up resources still in use */
+ if (stoprx && udev->rx)
+ kthread_stop(udev->rx);
+
+ if (stoptx && udev->tx)
+ kthread_stop(udev->tx);
+
+ /* TODO - this lock is covering a bit too much... */
+ spin_lock(&udev->lock);
+
+ udev->rx = NULL;
+ udev->tx = NULL;
+
+ if (udev->socket) {
+ sockfd_put(udev->socket);
+ udev->socket = NULL;
+ }
+
+ usb_put_dev(udev->usb_dev);
+ udev->usb_dev = NULL;
+
+ usbredir_sysfs_remove_devid(udev);
+
+ kfree(udev->devid);
+ udev->devid = NULL;
+
+ if (udev->parser) {
+ usbredirparser_destroy(udev->parser);
+ udev->parser = NULL;
+ }
+
+ usbredir_device_cleanup_unlink(udev);
+ usbredir_urb_cleanup_urblists(udev);
+
+ spin_unlock(&udev->lock);
+}
+
+static u32 speed_to_portflag(enum usb_device_speed speed)
+{
+ switch (speed) {
+ case usb_redir_speed_low: return USB_PORT_STAT_LOW_SPEED;
+ case usb_redir_speed_high: return USB_PORT_STAT_HIGH_SPEED;
+
+ case usb_redir_speed_full:
+ case usb_redir_speed_super:
+ default: return 0;
+ }
+}
+
+/* TODO - no thought at all to Super speed stuff... */
+void usbredir_device_connect(struct usbredir_device *udev)
+{
+ spin_lock(&udev->lock);
+ pr_debug("%s %d/%d:%s\n", __func__,
+ udev->hub->id, udev->rhport, udev->devid);
+ udev->port_status |= USB_PORT_STAT_CONNECTION |
+ (1 << USB_PORT_FEAT_C_CONNECTION);
+ udev->port_status |= speed_to_portflag(udev->connect_header.speed);
+ spin_unlock(&udev->lock);
+
+ usb_hcd_poll_rh_status(udev->hub->hcd);
+}
+
+void usbredir_device_disconnect(struct usbredir_device *udev)
+{
+ spin_lock(&udev->lock);
+ pr_debug("%s %d/%d:%s\n", __func__,
+ udev->hub->id, udev->rhport, udev->devid);
+ udev->port_status &= ~USB_PORT_STAT_CONNECTION;
+ udev->port_status |= (1 << USB_PORT_FEAT_C_CONNECTION);
+ spin_unlock(&udev->lock);
+
+ usb_hcd_poll_rh_status(udev->hub->hcd);
+}
+
+
+
+static struct usbredir_device *usbredir_device_get(struct usbredir_hub *hub,
+ int rhport)
+{
+ struct usbredir_device *udev;
+
+ if (rhport < 0 || rhport >= hub->device_count) {
+ return NULL;
+ }
+ udev = hub->devices + rhport;
+
+ return udev;
+}
+
+int usbredir_device_clear_port_feature(struct usbredir_hub *hub,
+ int rhport, u16 wValue)
+{
+ struct usbredir_device *udev = usbredir_device_get(hub, rhport);
+ struct socket *shutdown = NULL;
+
+ if (!udev)
+ return -ENODEV;
+
+ spin_lock(&udev->lock);
+
+ switch (wValue) {
+ case USB_PORT_FEAT_SUSPEND:
+ pr_debug(" ClearPortFeature: USB_PORT_FEAT_SUSPEND\n");
+ if (udev->port_status & USB_PORT_STAT_SUSPEND) {
+ /* 20msec signaling */
+ /* TODO - see note on suspend/resume below */
+ hub->resuming = 1;
+ hub->re_timeout =
+ jiffies + msecs_to_jiffies(20);
+ }
+ break;
+ case USB_PORT_FEAT_POWER:
+ pr_debug(" ClearPortFeature: USB_PORT_FEAT_POWER\n");
+ udev->port_status = 0;
+ hub->resuming = 0;
+ break;
+ case USB_PORT_FEAT_C_RESET:
+ pr_debug(" ClearPortFeature: USB_PORT_FEAT_C_RESET\n");
+ /* TODO - USB 3.0 stuff as well? */
+ switch (udev->connect_header.speed) {
+ case usb_redir_speed_high:
+ udev->port_status |= USB_PORT_STAT_HIGH_SPEED;
+ break;
+ case usb_redir_speed_low:
+ udev->port_status |= USB_PORT_STAT_LOW_SPEED;
+ break;
+ default:
+ break;
+ }
+ udev->port_status &= ~(1 << wValue);
+ break;
+ case USB_PORT_FEAT_ENABLE:
+ pr_debug(" ClearPortFeature: USB_PORT_FEAT_ENABLE\n");
+ if (udev->socket)
+ shutdown = udev->socket;
+ udev->port_status &= ~(1 << wValue);
+ break;
+ default:
+ pr_debug(" ClearPortFeature: default %x\n", wValue);
+ udev->port_status &= ~(1 << wValue);
+ break;
+ }
+
+ spin_unlock(&udev->lock);
+ if (shutdown)
+ kernel_sock_shutdown(shutdown, SHUT_RDWR);
+
+ return 0;
+}
+
+int usbredir_device_port_status(struct usbredir_hub *hub, int rhport, char *buf)
+{
+ struct usbredir_device *udev = usbredir_device_get(hub, rhport);
+
+ if (!udev)
+ return -ENODEV;
+
+ pr_debug("%s %d/%d 0x%x\n", __func__,
+ udev->hub->id, rhport, udev->port_status);
+
+ /* TODO - the logic on resume/reset etc is really
+ * just blindly copied from USBIP. Make sure
+ * this eventually gets thoughtful review and testing. */
+
+ /* whoever resets or resumes must GetPortStatus to
+ * complete it!!
+ */
+ if (hub->resuming && time_after(jiffies, hub->re_timeout)) {
+ udev->port_status |= (1 << USB_PORT_FEAT_C_SUSPEND);
+ udev->port_status &= ~(1 << USB_PORT_FEAT_SUSPEND);
+ hub->resuming = 0;
+ hub->re_timeout = 0;
+ }
+
+ spin_lock(&udev->lock);
+ if ((udev->port_status & (1 << USB_PORT_FEAT_RESET)) &&
+ time_after(jiffies, hub->re_timeout)) {
+ udev->port_status |= (1 << USB_PORT_FEAT_C_RESET);
+ udev->port_status &= ~(1 << USB_PORT_FEAT_RESET);
+ hub->re_timeout = 0;
+
+ if (atomic_read(&udev->active)) {
+ pr_debug(" enable rhport %d\n", rhport);
+ udev->port_status |= USB_PORT_STAT_ENABLE;
+ }
+ }
+
+ ((__le16 *) buf)[0] = cpu_to_le16(udev->port_status);
+ ((__le16 *) buf)[1] =
+ cpu_to_le16(udev->port_status >> 16);
+
+ pr_debug(" GetPortStatus bye %x %x\n", ((u16 *)buf)[0],
+ ((u16 *)buf)[1]);
+
+ spin_unlock(&udev->lock);
+
+ return 0;
+}
+
+int usbredir_device_set_port_feature(struct usbredir_hub *hub,
+ int rhport, u16 wValue)
+{
+ struct usbredir_device *udev = usbredir_device_get(hub, rhport);
+
+ if (!udev)
+ return -ENODEV;
+
+ spin_lock(&udev->lock);
+
+ switch (wValue) {
+ case USB_PORT_FEAT_SUSPEND:
+ pr_debug(" SetPortFeature: USB_PORT_FEAT_SUSPEND\n");
+ break;
+ case USB_PORT_FEAT_RESET:
+ pr_debug(" SetPortFeature: USB_PORT_FEAT_RESET\n");
+ udev->port_status &= ~USB_PORT_STAT_ENABLE;
+
+ /* 50msec reset signaling */
+ /* TODO - why? Seems like matching core/hub.c
+ * SHORT_RESET_TIME would be better */
+ hub->re_timeout = jiffies + msecs_to_jiffies(50);
+
+ /* FALLTHROUGH */
+ default:
+ pr_debug(" SetPortFeature: default %d\n", wValue);
+ udev->port_status |= (1 << wValue);
+ break;
+ }
+
+ spin_unlock(&udev->lock);
+
+ return 0;
+}
+
+ssize_t usbredir_device_devid(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int id;
+ struct usb_hcd *hcd = dev_get_drvdata(dev);
+ struct usbredir_hub *hub = usbredir_hub_from_hcd(hcd);
+ struct usbredir_device *udev;
+
+ sscanf(attr->attr.name, "devid.%d", &id);
+
+ udev = usbredir_device_get(hub, id);
+ if (udev && udev->devid) {
+ spin_lock(&udev->lock);
+ sprintf(buf, "%s\n", udev->devid);
+ spin_unlock(&udev->lock);
+ return strlen(buf);
+ }
+
+ return 0;
+}
diff --git a/kernel/hub.c b/kernel/hub.c
new file mode 100644
index 0000000..66ba64b
--- /dev/null
+++ b/kernel/hub.c
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2015 Jeremy White based on work by
+ * Copyright (C) 2003-2008 Takahiro Hirofuchi
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+#include <linux/kthread.h>
+
+#include "usbredir.h"
+
+static spinlock_t hubs_lock;
+static struct list_head hubs;
+static atomic_t hub_count;
+
+static int usbredir_hcd_start(struct usb_hcd *hcd)
+{
+ struct usbredir_hub *hub = usbredir_hub_from_hcd(hcd);
+ int i;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hub->lock, flags);
+ pr_debug("%s %d\n", __func__, hub->id);
+
+ hub->device_count = devices_per_hub;
+ hub->devices = kcalloc(hub->device_count, sizeof(*hub->devices),
+ GFP_ATOMIC);
+ if (!hub->devices) {
+ spin_unlock_irqrestore(&hub->lock, flags);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < hub->device_count; i++)
+ usbredir_device_init(hub->devices + i, i, hub);
+
+ hcd->power_budget = 0; /* no limit */
+ hcd->uses_new_polling = 1;
+ atomic_set(&hub->aseqnum, 0);
+ spin_unlock_irqrestore(&hub->lock, flags);
+
+ return 0;
+}
+
+static void usbredir_hub_stop(struct usbredir_hub *hub)
+{
+ int i;
+ unsigned long flags;
+
+ pr_debug("%s %d\n", __func__, hub->id);
+
+ /* TODO - the dummy hcd does not have this equivalent in its stop... */
+ for (i = 0; i < hub->device_count && hub->devices; i++) {
+ usbredir_device_disconnect(hub->devices + i);
+ usbredir_device_deallocate(hub->devices + i, true, true);
+ }
+
+ spin_lock_irqsave(&hub->lock, flags);
+ kfree(hub->devices);
+ hub->devices = NULL;
+ hub->device_count = 0;
+ spin_unlock_irqrestore(&hub->lock, flags);
+}
+
+static void usbredir_hcd_stop(struct usb_hcd *hcd)
+{
+ usbredir_hub_stop(usbredir_hub_from_hcd(hcd));
+}
+
+static int usbredir_get_frame_number(struct usb_hcd *hcd)
+{
+ pr_err("TODO: get_frame_number: not implemented\n");
+ return 0;
+}
+
+static int usbredir_hub_status(struct usb_hcd *hcd, char *buf)
+{
+ struct usbredir_hub *hub = usbredir_hub_from_hcd(hcd);
+ int ret;
+ int rhport;
+ int changed = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hub->lock, flags);
+
+ pr_debug("%s %d\n", __func__, hub->id);
+
+ ret = DIV_ROUND_UP(hub->device_count + 1, 8);
+ memset(buf, 0, ret);
+
+ if (!HCD_HW_ACCESSIBLE(hcd)) {
+ pr_debug("hw accessible flag not on?\n");
+ spin_unlock_irqrestore(&hub->lock, flags);
+ return 0;
+ }
+
+ /* TODO - dummy_hcd checks resuming here */
+
+ /* check pseudo status register for each port */
+ for (rhport = 0; rhport < hub->device_count; rhport++) {
+ struct usbredir_device *udev = hub->devices + rhport;
+
+ spin_lock(&udev->lock);
+ if (udev->port_status &
+ ((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)) {
+
+ /* The status of a port has been changed, */
+ pr_debug("port %d status changed\n", rhport);
+
+ buf[(rhport + 1) / 8] |= 1 << (rhport + 1) % 8;
+ changed = 1;
+ }
+ spin_unlock(&udev->lock);
+ }
+
+ spin_unlock_irqrestore(&hub->lock, flags);
+
+ if ((hcd->state == HC_STATE_SUSPENDED) && (changed == 1))
+ usb_hcd_resume_root_hub(hcd);
+
+ pr_debug("%s %schanged\n", __func__, changed ? "" : "un");
+
+ return changed ? ret : 0;
+}
+
+static inline void usbredir_hub_descriptor(struct usbredir_hub *hub,
+ struct usb_hub_descriptor *desc)
+{
+ memset(desc, 0, sizeof(*desc));
+ desc->bDescriptorType = USB_DT_HUB;
+ desc->bDescLength = 9;
+ desc->wHubCharacteristics = cpu_to_le16(
+ HUB_CHAR_INDV_PORT_LPSM |
+ HUB_CHAR_COMMON_OCPM);
+ desc->bNbrPorts = hub->device_count;
+ /* All ports un removable by default */
+ desc->u.hs.DeviceRemovable[0] = 0xff;
+ desc->u.hs.DeviceRemovable[1] = 0xff;
+}
+
+static int usbredir_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
+ u16 wIndex, char *buf, u16 wLength)
+{
+ struct usbredir_hub *hub;
+ int ret = 0;
+ int rhport;
+
+ /* TODO - confirm this is still necessary */
+ if (!HCD_HW_ACCESSIBLE(hcd))
+ return -ETIMEDOUT;
+
+ hub = usbredir_hub_from_hcd(hcd);
+ /* TODO - spin lock irqsave */
+
+ pr_debug("%s hub %d: ", __func__, hub->id);
+ pr_debug("[wValue %x|wIndex%u|wLength %u]",
+ wValue, wIndex, wLength);
+
+ /* wIndex is 1 based */
+ rhport = ((__u8)(wIndex & 0x00ff)) - 1;
+
+ /* TODO - dummy has SetHubDepth */
+ /* TODO - dummy has DeviceRequest | USB_REQ_GET_DESCRIPTOR - USB3 */
+ /* TODO - dummy has GetPortErrorcount */
+ switch (typeReq) {
+ case ClearHubFeature:
+ pr_debug(" ClearHubFeature\n");
+ break;
+ case SetHubFeature:
+ pr_debug(" SetHubFeature\n");
+ ret = -EPIPE;
+ break;
+ case GetHubDescriptor:
+ /* TODO - USB 3 */
+ pr_debug(" GetHubDescriptor\n");
+ usbredir_hub_descriptor(hub, (struct usb_hub_descriptor *) buf);
+ break;
+ case GetHubStatus:
+ pr_debug(" GetHubStatus\n");
+ *(__le32 *) buf = cpu_to_le32(0);
+ break;
+ case ClearPortFeature:
+ pr_debug(" ClearPortFeature\n");
+ return usbredir_device_clear_port_feature(hub, rhport, wValue);
+ case SetPortFeature:
+ pr_debug(" SetPortFeature\n");
+ return usbredir_device_set_port_feature(hub, rhport, wValue);
+ case GetPortStatus:
+ pr_debug(" GetPortStatus\n");
+ return usbredir_device_port_status(hub, rhport, buf);
+ default:
+ pr_debug(" unknown type %x\n", typeReq);
+ pr_err("usbredir_hub_control: no handler for request %x\n",
+ typeReq);
+
+ /* "protocol stall" on error */
+ ret = -EPIPE;
+ }
+
+ /* TODO - dummy invokes a poll on certain status changes */
+ return ret;
+}
+
+#ifdef CONFIG_PM
+/* FIXME: suspend/resume */
+static int usbredir_bus_suspend(struct usb_hcd *hcd)
+{
+ dev_dbg(&hcd->self.root_hub->dev, "%s\n", __func__);
+
+ hcd->state = HC_STATE_SUSPENDED;
+
+ return 0;
+}
+
+static int usbredir_bus_resume(struct usb_hcd *hcd)
+{
+ int rc = 0;
+
+ dev_dbg(&hcd->self.root_hub->dev, "%s\n", __func__);
+
+ if (!HCD_HW_ACCESSIBLE(hcd))
+ rc = -ESHUTDOWN;
+ else
+ hcd->state = HC_STATE_RUNNING;
+ return rc;
+}
+#else
+
+#define usbredir_bus_suspend NULL
+#define usbredir_bus_resume NULL
+#endif
+
+
+static void usbredir_release_hub_dev(struct device *dev)
+{
+ /* TODO - what do we need to implement here? */
+ /* This is called to free memory when the last device ref is done */
+ /* Question: can we forcibly remove a device without unloading our
+ * module? If so, then this may be our entry point. */
+ pr_err("%s: not implemented\n", __func__);
+}
+
+static int usbredir_register_hub(struct usbredir_hub *hub)
+{
+ int ret;
+
+ hub->pdev.name = driver_name;
+ hub->pdev.id = hub->id;
+ hub->pdev.dev.release = usbredir_release_hub_dev;
+
+ ret = platform_device_register(&hub->pdev);
+ if (ret) {
+ pr_err("Unable to register platform device %d\n", hub->id);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void usbredir_unregister_hub(struct usbredir_hub *hub)
+{
+ platform_device_unregister(&hub->pdev);
+}
+
+
+static struct hc_driver usbredir_hc_driver = {
+ .description = driver_name,
+ .product_desc = driver_desc,
+ .hcd_priv_size = sizeof(struct usbredir_hub *),
+
+ /* TODO = what other flags are available and what of USB3|SHARED? */
+ .flags = HCD_USB2,
+
+ /* TODO - reset - aka setup? */
+ .start = usbredir_hcd_start,
+ .stop = usbredir_hcd_stop,
+
+ .urb_enqueue = usbredir_urb_enqueue,
+ .urb_dequeue = usbredir_urb_dequeue,
+
+ .get_frame_number = usbredir_get_frame_number,
+
+ .hub_status_data = usbredir_hub_status,
+ .hub_control = usbredir_hub_control,
+ .bus_suspend = usbredir_bus_suspend,
+ .bus_resume = usbredir_bus_resume,
+
+ /* TODO - alloc/free streams? */
+};
+
+
+static int usbredir_create_hcd(struct usbredir_hub *hub)
+{
+ int ret;
+
+ hub->hcd = usb_create_hcd(&usbredir_hc_driver, &hub->pdev.dev,
+ dev_name(&hub->pdev.dev));
+ if (!hub->hcd) {
+ pr_err("usb_create_hcd failed\n");
+ return -ENOMEM;
+ }
+
+ hub->hcd->has_tt = 1;
+
+ *((struct usbredir_hub **) hub->hcd->hcd_priv) = hub;
+
+ ret = usb_add_hcd(hub->hcd, 0, 0);
+ if (ret != 0) {
+ pr_err("usb_add_hcd failed %d\n", ret);
+ usb_put_hcd(hub->hcd);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void usbredir_destroy_hcd(struct usbredir_hub *hub)
+{
+ if (hub->hcd) {
+ usb_remove_hcd(hub->hcd);
+ usb_put_hcd(hub->hcd);
+ hub->hcd = NULL;
+ }
+}
+
+static struct usbredir_hub *usbredir_hub_create(void)
+{
+ struct usbredir_hub *hub;
+ int id = atomic_inc_return(&hub_count);
+
+ if (id > max_hubs)
+ goto dec_exit;
+
+ hub = kzalloc(sizeof(*hub), GFP_ATOMIC);
+ if (!hub)
+ goto dec_exit;
+ hub->id = id - 1;
+
+ if (usbredir_register_hub(hub)) {
+ kfree(hub);
+ goto dec_exit;
+ }
+
+ if (usbredir_create_hcd(hub)) {
+ usbredir_unregister_hub(hub);
+ kfree(hub);
+ goto dec_exit;
+ }
+
+ spin_lock(&hubs_lock);
+ list_add_tail(&hub->list, &hubs);
+ spin_unlock(&hubs_lock);
+ return hub;
+dec_exit:
+ atomic_dec(&hub_count);
+ return NULL;
+}
+
+static void usbredir_hub_destroy(struct usbredir_hub *hub)
+{
+ usbredir_hub_stop(hub);
+ usbredir_destroy_hcd(hub);
+ usbredir_unregister_hub(hub);
+}
+
+struct usbredir_device *usbredir_hub_find_device(const char *devid)
+{
+ struct usbredir_device *ret = NULL;
+ struct usbredir_hub *hub;
+ int i;
+ unsigned long flags;
+
+ spin_lock(&hubs_lock);
+ list_for_each_entry(hub, &hubs, list) {
+ spin_lock_irqsave(&hub->lock, flags);
+ for (i = 0; i < hub->device_count; i++) {
+ struct usbredir_device *udev = hub->devices + i;
+
+ spin_lock(&udev->lock);
+ if (atomic_read(&udev->active) &&
+ udev->devid &&
+ strcmp(udev->devid, devid) == 0)
+ ret = udev;
+ spin_unlock(&udev->lock);
+ if (ret)
+ break;
+ }
+ spin_unlock_irqrestore(&hub->lock, flags);
+ if (ret)
+ break;
+ }
+ spin_unlock(&hubs_lock);
+ return ret;
+}
+
+struct usbredir_device *usbredir_hub_allocate_device(const char *devid,
+ struct socket *socket)
+{
+ int found = 0;
+ struct usbredir_hub *hub;
+ struct usbredir_device *udev = NULL;
+ int i;
+ unsigned long flags;
+
+ spin_lock(&hubs_lock);
+ list_for_each_entry(hub, &hubs, list) {
+ spin_lock_irqsave(&hub->lock, flags);
+ for (i = 0; !found && i < hub->device_count; i++) {
+ udev = hub->devices + i;
+ spin_lock(&udev->lock);
+ if (!atomic_read(&udev->active)) {
+ atomic_set(&udev->active, 1);
+ found++;
+ }
+ spin_unlock(&udev->lock);
+ }
+ spin_unlock_irqrestore(&hub->lock, flags);
+ if (found)
+ break;
+ }
+ spin_unlock(&hubs_lock);
+
+ if (found) {
+ usbredir_device_allocate(udev, devid, socket);
+ return udev;
+ }
+
+ hub = usbredir_hub_create();
+ if (!hub)
+ return NULL;
+
+ return usbredir_hub_allocate_device(devid, socket);
+}
+
+int usbredir_hub_show_global_status(char *out)
+{
+ int count = 0;
+ int active = 0;
+ int used = 0;
+ unsigned long flags;
+
+ struct usbredir_hub *hub;
+ struct usbredir_device *udev;
+ int i;
+
+ spin_lock(&hubs_lock);
+ list_for_each_entry(hub, &hubs, list) {
+ spin_lock_irqsave(&hub->lock, flags);
+ for (i = 0; i < hub->device_count; count++, i++) {
+ udev = hub->devices + i;
+ spin_lock(&udev->lock);
+ active += atomic_read(&udev->active);
+ if (udev->usb_dev)
+ used++;
+ spin_unlock(&udev->lock);
+ }
+ spin_unlock_irqrestore(&hub->lock, flags);
+ }
+ spin_unlock(&hubs_lock);
+
+ sprintf(out, "%d/%d hubs. %d/%d devices (%d active, %d used).\n",
+ atomic_read(&hub_count), max_hubs,
+ count, max_hubs * devices_per_hub, active, used);
+
+ return strlen(out);
+}
+
+
+void usbredir_hub_init(void)
+{
+ INIT_LIST_HEAD(&hubs);
+ atomic_set(&hub_count, 0);
+ spin_lock_init(&hubs_lock);
+}
+
+void usbredir_hub_exit(void)
+{
+ struct usbredir_hub *hub, *tmp;
+
+ spin_lock(&hubs_lock);
+ list_for_each_entry_safe(hub, tmp, &hubs, list) {
+ usbredir_hub_destroy(hub);
+ list_del(&hub->list);
+ kfree(hub);
+ }
+ spin_unlock(&hubs_lock);
+}
diff --git a/kernel/includes.c b/kernel/includes.c
new file mode 100644
index 0000000..735ef83
--- /dev/null
+++ b/kernel/includes.c
@@ -0,0 +1,3 @@
+#include <strtok_r.c>
+#include <usbredirfilter.c>
+#include <usbredirparser.c>
diff --git a/kernel/main.c b/kernel/main.c
new file mode 100644
index 0000000..cf11cbb
--- /dev/null
+++ b/kernel/main.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 Jeremy White
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/usb.h>
+#include <linux/module.h>
+
+#include "usbredir.h"
+
+#define DRIVER_NAME "usbredir"
+#define DRIVER_AUTHOR "Jeremy White"
+#define DRIVER_DESC "USBREDIR Host Controller Driver"
+#define DRIVER_VERSION USBREDIR_MODULE_VERSION
+
+const char driver_name[] = DRIVER_NAME;
+const char driver_desc[] = DRIVER_DESC;
+
+
+static struct platform_driver usbredir_driver = {
+ .driver = {
+ .name = driver_name,
+ },
+ /* TODO - why not remove, suspend, and resume? */
+};
+
+static int __init usbredir_main_init(void)
+{
+ int ret;
+
+ pr_debug("usbredir loaded\n");
+
+ if (usb_disabled())
+ return -ENODEV;
+
+ if (devices_per_hub > USB_MAXCHILDREN) {
+ pr_err("Error: cannot use %d devices per hub; max %d\n",
+ devices_per_hub, USB_MAXCHILDREN);
+ return -ENODEV;
+ }
+
+
+ ret = platform_driver_register(&usbredir_driver);
+ if (ret) {
+ pr_err("Unable to register usbredir_driver.\n");
+ return ret;
+ }
+
+ usbredir_hub_init();
+
+ ret = usbredir_sysfs_register(&usbredir_driver.driver);
+ if (ret) {
+ pr_err("Unable to create sysfs files for usbredir driver.\n");
+ usbredir_hub_exit();
+ platform_driver_unregister(&usbredir_driver);
+ return ret;
+ }
+
+ return ret;
+}
+
+static void __exit usbredir_main_exit(void)
+{
+ usbredir_sysfs_unregister(&usbredir_driver.driver);
+ usbredir_hub_exit();
+ platform_driver_unregister(&usbredir_driver);
+ pr_debug("usbredir exited\n");
+}
+
+unsigned int max_hubs = 64;
+module_param(max_hubs, uint, S_IRUSR|S_IWUSR);
+MODULE_PARM_DESC(max_hubs, "Maximum number of USB hubs to create; default 64");
+
+unsigned int devices_per_hub = 16;
+module_param(devices_per_hub, uint, S_IRUSR|S_IWUSR);
+MODULE_PARM_DESC(devices_per_hub,
+ "Maximum number of devices per hub; default 16");
+
+module_init(usbredir_main_init);
+module_exit(usbredir_main_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRIVER_VERSION);
diff --git a/kernel/redir.c b/kernel/redir.c
new file mode 100644
index 0000000..5531707
--- /dev/null
+++ b/kernel/redir.c
@@ -0,0 +1,545 @@
+/*
+ * Copyright (C) 2015 Jeremy White
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+
+#include <linux/net.h>
+#include <linux/kthread.h>
+#include <net/sock.h>
+#include <linux/semaphore.h>
+#include <linux/slab.h>
+#include <linux/printk.h>
+
+#include "usbredirparser.h"
+#include "usbredir.h"
+
+
+#define TODO_IMPLEMENT pr_err("Error: %s unimplemented.\n", __func__)
+
+static void redir_log(void *priv, int level, const char *msg)
+{
+ switch (level) {
+ case usbredirparser_error:
+ pr_err("%s", msg);
+ break;
+
+ case usbredirparser_warning:
+ pr_warn("%s", msg);
+ break;
+
+ case usbredirparser_info:
+ pr_info("%s", msg);
+ break;
+
+ default:
+ pr_debug("%s", msg);
+ break;
+ }
+}
+
+static int redir_read(void *priv, uint8_t *data, int count)
+{
+ struct usbredir_device *udev = (struct usbredir_device *) priv;
+ struct msghdr msg;
+ struct kvec iov;
+ struct socket *socket;
+ int rc;
+
+ if (kthread_should_stop() || !atomic_read(&udev->active))
+ return -ESRCH;
+
+ spin_lock(&udev->lock);
+ socket = udev->socket;
+ /* TODO - reference/dereference the socket? */
+ spin_unlock(&udev->lock);
+
+ socket->sk->sk_allocation = GFP_NOIO;
+ iov.iov_base = data;
+ iov.iov_len = count;
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = MSG_NOSIGNAL;
+
+ rc = kernel_recvmsg(socket, &msg, &iov, 1, count, MSG_WAITALL);
+
+ return rc;
+}
+
+static int redir_write(void *priv, uint8_t *data, int count)
+{
+ struct usbredir_device *udev = (struct usbredir_device *) priv;
+ struct msghdr msg;
+ struct kvec iov;
+ int rc;
+ struct socket *socket;
+
+ memset(&msg, 0, sizeof(msg));
+ memset(&iov, 0, sizeof(iov));
+ msg.msg_flags = MSG_NOSIGNAL | MSG_DONTWAIT;
+ iov.iov_base = data;
+ iov.iov_len = count;
+
+ spin_lock(&udev->lock);
+ socket = udev->socket;
+ /* TODO - reference/dereference the socket? */
+ spin_unlock(&udev->lock);
+
+ while (!kthread_should_stop() && atomic_read(&udev->active)) {
+ rc = kernel_sendmsg(socket, &msg, &iov, 1, count);
+
+ if (rc == -EAGAIN) {
+ /* TODO - add schedule() ? */
+ continue;
+ }
+ /* TODO - In theory, a return of 0 should be okay,
+ * but, again, in theory, it will cause an error. */
+ if (rc <= 0)
+ pr_err("Error: TODO - unexpected write return code %d.\n", rc);
+
+ break;
+ }
+
+ return rc;
+}
+
+
+/* Locking functions for use by multithread apps */
+static void *redir_alloc_lock(void)
+{
+ struct semaphore *s = kmalloc(sizeof(*s), GFP_KERNEL);
+
+ sema_init(s, 1);
+ return s;
+}
+
+static void redir_lock(void *lock)
+{
+ while (down_interruptible((struct semaphore *) lock))
+ ;
+}
+
+static void redir_unlock(void *lock)
+{
+ up((struct semaphore *) lock);
+}
+
+static void redir_free_lock(void *lock)
+{
+ kfree(lock);
+}
+
+
+/* The below callbacks are called when a complete packet of the relevant
+ type has been received.
+
+ Note that the passed in packet-type-specific-header's lifetime is only
+ guarenteed to be that of the callback.
+
+*/
+static void redir_hello(void *priv, struct usb_redir_hello_header *hello)
+{
+ pr_debug("Hello!\n");
+}
+
+static void redir_device_connect(void *priv,
+ struct usb_redir_device_connect_header *device_connect)
+{
+ struct usbredir_device *udev = (struct usbredir_device *) priv;
+
+ pr_debug(" connect: class %2d subclass %2d protocol %2d",
+ device_connect->device_class, device_connect->device_subclass,
+ device_connect->device_protocol);
+ pr_debug(" vendor 0x%04x product %04x\n",
+ device_connect->vendor_id, device_connect->product_id);
+
+ spin_lock(&udev->lock);
+ udev->connect_header = *device_connect;
+ spin_unlock(&udev->lock);
+
+ usbredir_device_connect(udev);
+}
+
+static void redir_device_disconnect(void *priv)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_reset(void *priv)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_interface_info(void *priv,
+ struct usb_redir_interface_info_header *info)
+{
+ struct usbredir_device *udev = (struct usbredir_device *) priv;
+ int i;
+
+ for (i = 0; i < info->interface_count; i++) {
+ pr_debug("interface %d class %2d subclass %2d protocol %2d",
+ info->interface[i], info->interface_class[i],
+ info->interface_subclass[i],
+ info->interface_protocol[i]);
+ }
+
+ spin_lock(&udev->lock);
+ udev->info_header = *info;
+ spin_unlock(&udev->lock);
+}
+
+static void redir_ep_info(void *priv,
+ struct usb_redir_ep_info_header *ep_info)
+{
+ struct usbredir_device *udev = (struct usbredir_device *) priv;
+
+ spin_lock(&udev->lock);
+ udev->ep_info_header = *ep_info;
+ spin_unlock(&udev->lock);
+}
+
+static void redir_set_configuration(void *priv,
+ uint64_t id,
+ struct usb_redir_set_configuration_header *set_configuration)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_get_configuration(void *priv, uint64_t id)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_configuration_status(void *priv,
+ uint64_t id,
+ struct usb_redir_configuration_status_header *configuration_status)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_set_alt_setting(void *priv,
+ uint64_t id,
+ struct usb_redir_set_alt_setting_header *set_alt_setting)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_get_alt_setting(void *priv,
+ uint64_t id,
+ struct usb_redir_get_alt_setting_header *get_alt_setting)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_alt_setting_status(void *priv,
+ uint64_t id,
+ struct usb_redir_alt_setting_status_header *alt_setting_status)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_start_iso_stream(void *priv,
+ uint64_t id,
+ struct usb_redir_start_iso_stream_header *start_iso_stream)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_stop_iso_stream(void *priv,
+ uint64_t id,
+ struct usb_redir_stop_iso_stream_header *stop_iso_stream)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_iso_stream_status(void *priv,
+ uint64_t id,
+ struct usb_redir_iso_stream_status_header *iso_stream_status)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_start_interrupt_receiving(void *priv,
+ uint64_t id,
+ struct usb_redir_start_interrupt_receiving_header
+ *start_interrupt_receiving)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_stop_interrupt_receiving(void *priv,
+ uint64_t id,
+ struct usb_redir_stop_interrupt_receiving_header
+ *stop_interrupt_receiving)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_interrupt_receiving_status(void *priv,
+ uint64_t id,
+ struct usb_redir_interrupt_receiving_status_header
+ *interrupt_receiving_status)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_alloc_bulk_streams(void *priv,
+ uint64_t id,
+ struct usb_redir_alloc_bulk_streams_header *alloc_bulk_streams)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_free_bulk_streams(void *priv,
+ uint64_t id,
+ struct usb_redir_free_bulk_streams_header *free_bulk_streams)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_bulk_streams_status(void *priv,
+ uint64_t id,
+ struct usb_redir_bulk_streams_status_header *bulk_streams_status)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_cancel_data_packet(void *priv, uint64_t id)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_filter_reject(void *priv)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_filter_filter(void *priv,
+ struct usbredirfilter_rule *rules, int rules_count)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_device_disconnect_ack(void *priv)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_start_bulk_receiving(void *priv,
+ uint64_t id,
+ struct usb_redir_start_bulk_receiving_header *start_bulk_receiving)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_stop_bulk_receiving(void *priv,
+ uint64_t id,
+ struct usb_redir_stop_bulk_receiving_header *stop_bulk_receiving)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_bulk_receiving_status(void *priv,
+ uint64_t id,
+ struct usb_redir_bulk_receiving_status_header *bulk_receiving_status)
+{
+ TODO_IMPLEMENT;
+}
+
+static int redir_map_status(int redir_status)
+{
+ switch (redir_status) {
+ case usb_redir_success:
+ return 0;
+ case usb_redir_cancelled:
+ return -ENOENT;
+ case usb_redir_inval:
+ return -EINVAL;
+ case usb_redir_stall:
+ return -EPIPE;
+ case usb_redir_timeout:
+ return -ETIMEDOUT;
+ case usb_redir_babble:
+ return -EOVERFLOW;
+ /* Catchall error condition */
+ case usb_redir_ioerror:
+ default:
+ return -ENODEV;
+ }
+}
+
+
+static void redir_control_packet(void *priv,
+ uint64_t id,
+ struct usb_redir_control_packet_header *control_header,
+ uint8_t *data, int data_len)
+{
+ struct usbredir_device *udev = (struct usbredir_device *) priv;
+ struct urb *urb;
+
+ urb = usbredir_pop_rx_urb(udev, id);
+ if (!urb) {
+ pr_err("Error: control id %lu with no matching entry.\n",
+ (unsigned long) id);
+ return;
+ }
+
+ /* TODO - handle more than this flavor... */
+ urb->status = redir_map_status(control_header->status);
+ if (usb_pipein(urb->pipe)) {
+ urb->actual_length = min_t(u32, data_len,
+ urb->transfer_buffer_length);
+ if (urb->transfer_buffer)
+ memcpy(urb->transfer_buffer, data, urb->actual_length);
+ } else {
+ urb->actual_length = control_header->length;
+ }
+
+ usb_hcd_unlink_urb_from_ep(udev->hub->hcd, urb);
+ usb_hcd_giveback_urb(udev->hub->hcd, urb, urb->status);
+}
+
+static void redir_bulk_packet(void *priv,
+ uint64_t id,
+ struct usb_redir_bulk_packet_header *bulk_header,
+ uint8_t *data, int data_len)
+{
+ struct usbredir_device *udev = (struct usbredir_device *) priv;
+ struct urb *urb;
+
+ urb = usbredir_pop_rx_urb(udev, id);
+ if (!urb) {
+ pr_err("Error: bulk id %lu with no matching entry.\n",
+ (unsigned long) id);
+ return;
+ }
+
+ urb->status = redir_map_status(bulk_header->status);
+ if (usb_pipein(urb->pipe)) {
+ urb->actual_length = min_t(u32, data_len,
+ urb->transfer_buffer_length);
+ if (urb->transfer_buffer)
+ memcpy(urb->transfer_buffer, data, urb->actual_length);
+ } else {
+ urb->actual_length = bulk_header->length;
+ }
+
+ /* TODO - what to do with stream_id */
+ /* TODO - handle more than this flavor... */
+
+ usb_hcd_unlink_urb_from_ep(udev->hub->hcd, urb);
+ usb_hcd_giveback_urb(udev->hub->hcd, urb, urb->status);
+}
+
+static void redir_iso_packet(void *priv,
+ uint64_t id,
+ struct usb_redir_iso_packet_header *iso_header,
+ uint8_t *data, int data_len)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_interrupt_packet(void *priv,
+ uint64_t id,
+ struct usb_redir_interrupt_packet_header *interrupt_header,
+ uint8_t *data, int data_len)
+{
+ TODO_IMPLEMENT;
+}
+
+static void redir_buffered_bulk_packet(void *priv, uint64_t id,
+ struct usb_redir_buffered_bulk_packet_header *buffered_bulk_header,
+ uint8_t *data, int data_len)
+{
+ TODO_IMPLEMENT;
+}
+
+
+struct usbredirparser *redir_parser_init(void *priv)
+{
+ struct usbredirparser *parser;
+ char version[40];
+
+ uint32_t caps[USB_REDIR_CAPS_SIZE];
+
+ parser = usbredirparser_create();
+
+ parser->priv = priv;
+
+ parser->log_func = redir_log;
+ parser->read_func = redir_read;
+ parser->write_func = redir_write;
+ parser->device_connect_func = redir_device_connect;
+ parser->device_disconnect_func = redir_device_disconnect;
+ parser->reset_func = redir_reset;
+ parser->interface_info_func = redir_interface_info;
+ parser->ep_info_func = redir_ep_info;
+ parser->set_configuration_func = redir_set_configuration;
+ parser->get_configuration_func = redir_get_configuration;
+ parser->configuration_status_func = redir_configuration_status;
+ parser->set_alt_setting_func = redir_set_alt_setting;
+ parser->get_alt_setting_func = redir_get_alt_setting;
+ parser->alt_setting_status_func = redir_alt_setting_status;
+ parser->start_iso_stream_func = redir_start_iso_stream;
+ parser->stop_iso_stream_func = redir_stop_iso_stream;
+ parser->iso_stream_status_func = redir_iso_stream_status;
+ parser->start_interrupt_receiving_func =
+ redir_start_interrupt_receiving;
+ parser->stop_interrupt_receiving_func = redir_stop_interrupt_receiving;
+ parser->interrupt_receiving_status_func =
+ redir_interrupt_receiving_status;
+ parser->alloc_bulk_streams_func = redir_alloc_bulk_streams;
+ parser->free_bulk_streams_func = redir_free_bulk_streams;
+ parser->bulk_streams_status_func = redir_bulk_streams_status;
+ parser->cancel_data_packet_func = redir_cancel_data_packet;
+ parser->control_packet_func = redir_control_packet;
+ parser->bulk_packet_func = redir_bulk_packet;
+ parser->iso_packet_func = redir_iso_packet;
+ parser->interrupt_packet_func = redir_interrupt_packet;
+ parser->alloc_lock_func = redir_alloc_lock;
+ parser->lock_func = redir_lock;
+ parser->unlock_func = redir_unlock;
+ parser->free_lock_func = redir_free_lock;
+ parser->hello_func = redir_hello;
+ parser->filter_reject_func = redir_filter_reject;
+ parser->filter_filter_func = redir_filter_filter;
+ parser->device_disconnect_ack_func = redir_device_disconnect_ack;
+ parser->start_bulk_receiving_func = redir_start_bulk_receiving;
+ parser->stop_bulk_receiving_func = redir_stop_bulk_receiving;
+ parser->bulk_receiving_status_func = redir_bulk_receiving_status;
+ parser->buffered_bulk_packet_func = redir_buffered_bulk_packet;
+
+ memset(caps, 0, sizeof(caps));
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_32bits_bulk_length);
+
+ /* TODO - figure out which of these we really can use */
+#if defined(USE_ALL_CAPS)
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_streams);
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version);
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_filter);
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_device_disconnect_ack);
+ usbredirparser_caps_set_cap(caps,
+ usb_redir_cap_ep_info_max_packet_size);
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids);
+ usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_receiving);
+#endif
+
+ sprintf(version, "kmodule v%s. Protocol %x",
+ USBREDIR_MODULE_VERSION, USBREDIR_VERSION);
+ usbredirparser_init(parser, version, caps, USB_REDIR_CAPS_SIZE, 0);
+
+ return parser;
+}
+
diff --git a/kernel/rx.c b/kernel/rx.c
new file mode 100644
index 0000000..92ffa97
--- /dev/null
+++ b/kernel/rx.c
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 Jeremy White
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kthread.h>
+
+#include "usbredir.h"
+
+int usbredir_rx_loop(void *data)
+{
+ struct usbredir_device *udev = data;
+ int rc;
+
+ while (!kthread_should_stop() && atomic_read(&udev->active)) {
+ rc = usbredirparser_do_read(udev->parser);
+ if (rc != -EAGAIN) {
+ pr_info("usbredir/rx:%d connection closed\n",
+ udev->rhport);
+ break;
+ }
+ }
+
+ pr_debug("%s exit\n", __func__);
+
+ usbredir_device_disconnect(udev);
+ usbredir_device_deallocate(udev, false, true);
+
+ return 0;
+}
diff --git a/kernel/sysfs.c b/kernel/sysfs.c
new file mode 100644
index 0000000..434db97
--- /dev/null
+++ b/kernel/sysfs.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2015 Jeremy White based on work by
+ * Copyright (C) 2003-2008 Takahiro Hirofuchi
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/file.h>
+#include <linux/net.h>
+
+#include "usbredir.h"
+
+
+static ssize_t status_show(struct device_driver *driver, char *out)
+{
+ return usbredir_hub_show_global_status(out);
+}
+static DRIVER_ATTR(status, S_IRUSR, status_show, NULL);
+
+static ssize_t store_attach(struct device_driver *driver,
+ const char *buf, size_t count)
+{
+ struct socket *socket;
+ int sockfd = 0;
+ char devid[256];
+ int err;
+
+ /*
+ * usbredir sysfs attach file
+ * @sockfd: socket descriptor of an established TCP connection
+ * @devid: user supplied unique device identifier
+ */
+ memset(devid, 0, sizeof(devid));
+ if (sscanf(buf, "%u %255s", &sockfd, devid) != 2)
+ return -EINVAL;
+
+ pr_debug("attach sockfd(%u) devid(%s)\n", sockfd, devid);
+
+ socket = sockfd_lookup(sockfd, &err);
+ if (!socket)
+ return -EINVAL;
+
+ if (usbredir_hub_find_device(devid)) {
+ pr_err("%s: already in use\n", devid);
+ sockfd_put(socket);
+ return -EINVAL;
+ }
+
+ if (!usbredir_hub_allocate_device(devid, socket)) {
+ pr_err("%s: unable to create\n", devid);
+ sockfd_put(socket);
+ return -EINVAL;
+ }
+
+ return count;
+}
+static DRIVER_ATTR(attach, S_IWUSR, NULL, store_attach);
+
+
+static ssize_t store_detach(struct device_driver *driver,
+ const char *buf, size_t count)
+{
+ char devid[256];
+ struct usbredir_device *udev;
+
+ /*
+ * usbredir sysfs detach file
+ * @devid: user supplied unique device identifier
+ */
+ memset(devid, 0, sizeof(devid));
+ if (sscanf(buf, "%255s", devid) != 1)
+ return -EINVAL;
+
+ pr_debug("detach devid(%s)\n", devid);
+
+ udev = usbredir_hub_find_device(devid);
+ if (!udev) {
+ pr_warn("USBREDIR device %s detach requested, but not found\n",
+ devid);
+ return count;
+ }
+
+ usbredir_device_disconnect(udev);
+ usbredir_device_deallocate(udev, true, true);
+
+ return count;
+}
+static DRIVER_ATTR(detach, S_IWUSR, NULL, store_detach);
+
+
+/**
+ * usbredir_sysfs_register()
+ * @driver The platform driver associated with usbredir
+ *
+ * This function will register new sysfs files called 'attach', 'detach',
+ * and 'status'.
+ *
+ * To start a new connection, a user space program should establish
+ * a socket that is connected to a process that provides a USB device
+ * and that speaks the USBREDIR protocol. The usbredirserver program
+ * is one such example.
+ *
+ * Next, the user space program should write that socket as well as a
+ * unique device id of no more than 255 characters to the 'attach' file.
+ * That should begin a connection.
+ *
+ * Writing the same id to the 'detach' file should end the connection,
+ * and examining the contents of the 'status' file should show the number
+ * of connections.
+ *
+ */
+int usbredir_sysfs_register(struct device_driver *driver)
+{
+ int ret;
+
+ ret = driver_create_file(driver, &driver_attr_status);
+ if (ret)
+ return ret;
+
+ ret = driver_create_file(driver, &driver_attr_detach);
+ if (ret)
+ return ret;
+
+ return driver_create_file(driver, &driver_attr_attach);
+}
+
+/**
+ * usbredir_sysfs_unregister()
+ * @dev The device driver associated with usbredir
+ */
+void usbredir_sysfs_unregister(struct device_driver *dev)
+{
+ driver_remove_file(dev, &driver_attr_status);
+ driver_remove_file(dev, &driver_attr_detach);
+ driver_remove_file(dev, &driver_attr_attach);
+}
+
+void usbredir_sysfs_expose_devid(struct usbredir_device *udev)
+{
+ char aname[32];
+
+ sprintf(aname, "devid.%d", udev->rhport);
+ udev->attr.attr.mode = S_IRUSR;
+ udev->attr.attr.name = kstrdup(aname, GFP_ATOMIC);
+ udev->attr.show = usbredir_device_devid;
+
+ device_create_file(&udev->hub->pdev.dev, &udev->attr);
+}
+
+void usbredir_sysfs_remove_devid(struct usbredir_device *udev)
+{
+ device_remove_file(&udev->hub->pdev.dev, &udev->attr);
+ kfree(udev->attr.attr.name);
+}
diff --git a/kernel/tx.c b/kernel/tx.c
new file mode 100644
index 0000000..10e5e62
--- /dev/null
+++ b/kernel/tx.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2015 Jeremy White based on work by
+ * Copyright (C) 2003-2008 Takahiro Hirofuchi
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kthread.h>
+#include <linux/slab.h>
+
+#include "usbredir.h"
+
+static struct usbredir_urb *get_next_urb(struct usbredir_device *udev)
+{
+ struct usbredir_urb *uurb, *tmp;
+
+ spin_lock(&udev->lock);
+
+ list_for_each_entry_safe(uurb, tmp, &udev->urblist_tx, list) {
+ list_move_tail(&uurb->list, &udev->urblist_rx);
+ spin_unlock(&udev->lock);
+ return uurb;
+ }
+
+ spin_unlock(&udev->lock);
+
+ return NULL;
+}
+
+static void send_packet(struct usbredir_device *udev, struct usbredir_urb *uurb)
+{
+ struct urb *urb = uurb->urb;
+ __u8 type = usb_pipetype(urb->pipe);
+
+ if (type == PIPE_CONTROL && urb->setup_packet) {
+ struct usb_ctrlrequest *ctrlreq =
+ (struct usb_ctrlrequest *) urb->setup_packet;
+ struct usb_redir_control_packet_header ctrl;
+
+ ctrl.endpoint = usb_pipeendpoint(urb->pipe) |
+ usb_pipein(urb->pipe);
+ ctrl.request = ctrlreq->bRequest;
+ ctrl.requesttype = ctrlreq->bRequestType;
+ ctrl.status = 0;
+ ctrl.value = le16_to_cpu(ctrlreq->wValue);
+ ctrl.index = le16_to_cpu(ctrlreq->wIndex);
+ ctrl.length = le16_to_cpu(ctrlreq->wLength);
+
+ usbredirparser_send_control_packet(udev->parser,
+ uurb->seqnum, &ctrl,
+ usb_pipein(urb->pipe) ?
+ NULL : urb->transfer_buffer,
+ usb_pipein(urb->pipe) ?
+ 0 : urb->transfer_buffer_length);
+
+ }
+
+ if (type == PIPE_BULK) {
+ struct usb_redir_bulk_packet_header bulk;
+
+ bulk.endpoint = usb_pipeendpoint(urb->pipe) |
+ usb_pipein(urb->pipe);
+ bulk.status = 0;
+ bulk.length = urb->transfer_buffer_length & 0xFFFF;
+ bulk.stream_id = urb->stream_id;
+ bulk.length_high = urb->transfer_buffer_length >> 16;
+
+ usbredirparser_send_bulk_packet(udev->parser,
+ uurb->seqnum, &bulk,
+ usb_pipein(urb->pipe) ?
+ NULL : urb->transfer_buffer,
+ usb_pipein(urb->pipe) ?
+ 0 : urb->transfer_buffer_length);
+ }
+}
+
+static struct usbredir_unlink *get_next_unlink(struct usbredir_device *udev)
+{
+ struct usbredir_unlink *unlink, *tmp;
+
+ spin_lock(&udev->lock);
+
+ list_for_each_entry_safe(unlink, tmp, &udev->unlink_tx, list) {
+ list_move_tail(&unlink->list, &udev->unlink_rx);
+ spin_unlock(&udev->lock);
+ return unlink;
+ }
+
+ spin_unlock(&udev->lock);
+
+ return NULL;
+}
+
+static void send_unlink(struct usbredir_device *udev,
+ struct usbredir_unlink *unlink)
+{
+ /* This is a separate TODO; need to process unlink_rx... */
+ pr_debug("TODO partially unimplemented: unlink request of ");
+ pr_debug("seqnum %d, unlink seqnum %d\n",
+ unlink->seqnum, unlink->unlink_seqnum);
+
+ /* TODO - if the other side never responds, which it may
+ not do if the seqnum doesn't match, then we
+ never clear this entry. That's probably not ideal */
+ usbredirparser_send_cancel_data_packet(udev->parser,
+ unlink->unlink_seqnum);
+}
+
+int usbredir_tx_loop(void *data)
+{
+ struct usbredir_device *udev = data;
+ struct usbredir_urb *uurb;
+ struct usbredir_unlink *unlink;
+
+ while (!kthread_should_stop() && atomic_read(&udev->active)) {
+ if (usbredirparser_has_data_to_write(udev->parser))
+ if (usbredirparser_do_write(udev->parser))
+ break;
+
+ /* TODO - consider while versus if here */
+ while ((uurb = get_next_urb(udev)) != NULL)
+ send_packet(udev, uurb);
+
+ /* TODO - consider while versus if here */
+ while ((unlink = get_next_unlink(udev)) != NULL)
+ send_unlink(udev, unlink);
+
+ /* TODO - can I check list_empty without locking... */
+ wait_event_interruptible(udev->waitq_tx,
+ (!list_empty(&udev->urblist_tx) ||
+ !list_empty(&udev->unlink_tx) ||
+ kthread_should_stop() ||
+ usbredirparser_has_data_to_write(udev->parser) ||
+ !atomic_read(&udev->active)));
+ }
+
+ pr_debug("%s exit\n", __func__);
+ usbredir_device_disconnect(udev);
+ usbredir_device_deallocate(udev, true, false);
+
+ return 0;
+}
diff --git a/kernel/urb.c b/kernel/urb.c
new file mode 100644
index 0000000..a7566fc
--- /dev/null
+++ b/kernel/urb.c
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2015 Jeremy White based on work by
+ * Copyright (C) 2003-2008 Takahiro Hirofuchi
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#include "usbredir.h"
+
+/* Lock must be held by caller */
+static void queue_urb(struct usbredir_device *udev, struct urb *urb)
+{
+ struct usbredir_urb *uurb;
+
+ uurb = kzalloc(sizeof(struct usbredir_urb), GFP_ATOMIC);
+ if (!uurb) {
+ /* TODO - handle this failure... discon/dealloc? */
+ return;
+ }
+
+ uurb->seqnum = usbredir_hub_seqnum(udev->hub);
+
+ uurb->urb = urb;
+
+ urb->hcpriv = (void *) uurb;
+
+ list_add_tail(&uurb->list, &udev->urblist_tx);
+}
+
+static bool intercept_urb_request(struct usbredir_device *udev,
+ struct urb *urb, int *ret)
+{
+ struct device *dev = &urb->dev->dev;
+ __u8 type = usb_pipetype(urb->pipe);
+ struct usb_ctrlrequest *ctrlreq =
+ (struct usb_ctrlrequest *) urb->setup_packet;
+
+ if (usb_pipedevice(urb->pipe) != 0)
+ return false;
+
+ if (type != PIPE_CONTROL || !ctrlreq) {
+ dev_err(dev, "invalid request to devnum 0; type %x, req %p\n",
+ type, ctrlreq);
+ *ret = -EINVAL;
+ return true;
+ }
+
+ if (ctrlreq->bRequest == USB_REQ_GET_DESCRIPTOR) {
+ pr_debug("Requesting descriptor; wValue %x\n", ctrlreq->wValue);
+
+ usb_put_dev(udev->usb_dev);
+ udev->usb_dev = usb_get_dev(urb->dev);
+
+ if (ctrlreq->wValue == cpu_to_le16(USB_DT_DEVICE << 8))
+ pr_debug("TODO: GetDescriptor unexpected.\n");
+
+ return false;
+ }
+
+ if (ctrlreq->bRequest == USB_REQ_SET_ADDRESS) {
+ dev_info(dev, "SetAddress Request (%d) to port %d\n",
+ ctrlreq->wValue, udev->rhport);
+
+ usb_put_dev(udev->usb_dev);
+ udev->usb_dev = usb_get_dev(urb->dev);
+
+ if (urb->status == -EINPROGRESS) {
+ /* This request is successfully completed. */
+ /* If not -EINPROGRESS, possibly unlinked. */
+ urb->status = 0;
+ }
+ return true;
+ }
+
+ dev_err(dev,
+ "invalid request to devnum 0 bRequest %u, wValue %u\n",
+ ctrlreq->bRequest,
+ ctrlreq->wValue);
+ *ret = -EINVAL;
+
+ return true;
+}
+
+/* Caller must hold lock */
+void usbredir_urb_cleanup_urblists(struct usbredir_device *udev)
+{
+ struct usbredir_urb *uurb, *tmp;
+
+ list_for_each_entry_safe(uurb, tmp, &udev->urblist_rx, list) {
+ list_del(&uurb->list);
+ usb_hcd_unlink_urb_from_ep(udev->hub->hcd, uurb->urb);
+ /* TODO - kernel panics suggest we may need to unlock here */
+ usb_hcd_giveback_urb(udev->hub->hcd, uurb->urb, -ENODEV);
+ kfree(uurb);
+ }
+
+ list_for_each_entry_safe(uurb, tmp, &udev->urblist_tx, list) {
+ list_del(&uurb->list);
+ usb_hcd_unlink_urb_from_ep(udev->hub->hcd, uurb->urb);
+ /* TODO - kernel panics suggest we may need to unlock here */
+ usb_hcd_giveback_urb(udev->hub->hcd, uurb->urb, -ENODEV);
+ kfree(uurb);
+ }
+}
+
+
+
+int usbredir_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags)
+{
+ struct device *dev = &urb->dev->dev;
+ int ret = 0;
+ struct usbredir_hub *hub = usbredir_hub_from_hcd(hcd);
+ struct usbredir_device *udev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hub->lock, flags);
+
+ udev = hub->devices + urb->dev->portnum - 1;
+
+ if (!atomic_read(&udev->active)) {
+ dev_err(dev, "enqueue for inactive port %d\n", udev->rhport);
+ spin_unlock_irqrestore(&hub->lock, flags);
+ return -ENODEV;
+ }
+
+ ret = usb_hcd_link_urb_to_ep(hcd, urb);
+ if (ret) {
+ spin_unlock_irqrestore(&hub->lock, flags);
+ return ret;
+ }
+
+ if (intercept_urb_request(udev, urb, &ret)) {
+ usb_hcd_unlink_urb_from_ep(hcd, urb);
+ spin_unlock_irqrestore(&hub->lock, flags);
+ usb_hcd_giveback_urb(hub->hcd, urb, urb->status);
+ return 0;
+ }
+
+ queue_urb(udev, urb);
+ spin_unlock_irqrestore(&hub->lock, flags);
+
+ wake_up_interruptible(&udev->waitq_tx);
+
+ return 0;
+}
+
+static void usbredir_free_uurb(struct usbredir_device *udev, struct urb *urb)
+{
+ struct usbredir_urb *uurb = urb->hcpriv;
+ if (uurb) {
+ spin_lock(&udev->lock);
+ list_del(&uurb->list);
+ kfree(uurb);
+ urb->hcpriv = NULL;
+ spin_unlock(&udev->lock);
+ }
+}
+
+int usbredir_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
+{
+ struct usbredir_urb *uurb;
+ struct usbredir_device *udev;
+ struct usbredir_hub *hub = usbredir_hub_from_hcd(hcd);
+ int ret = 0;
+ unsigned long flags;
+
+ pr_debug("%s %p\n", __func__, urb);
+
+ uurb = urb->hcpriv;
+
+ spin_lock_irqsave(&hub->lock, flags);
+ udev = hub->devices + urb->dev->portnum - 1;
+
+ ret = usb_hcd_check_unlink_urb(hcd, urb, status);
+ if (ret) {
+ /* TODO - figure out if this is an unlink send case as well */
+ usbredir_free_uurb(udev, urb);
+ spin_unlock_irqrestore(&hub->lock, flags);
+ return ret;
+ }
+
+ if (usb_pipetype(urb->pipe) == PIPE_INTERRUPT) {
+ /* TODO - wrong in all kinds of ways... */
+ pr_debug("FIXME agreeably dequeing an INTERRUPT.\n");
+ usbredir_free_uurb(udev, urb);
+
+ usb_hcd_unlink_urb_from_ep(hcd, urb);
+ spin_unlock_irqrestore(&hub->lock, flags);
+
+ usb_hcd_giveback_urb(hub->hcd, urb, urb->status);
+ return ret;
+ }
+
+ if (atomic_read(&udev->active) && uurb) {
+ struct usbredir_unlink *unlink;
+
+ unlink = kzalloc(sizeof(struct usbredir_unlink), GFP_ATOMIC);
+ if (!unlink) {
+ /* TODO complain somehow... */
+ spin_unlock_irqrestore(&hub->lock, flags);
+ return -ENOMEM;
+ }
+
+ unlink->seqnum = usbredir_hub_seqnum(hub);
+ unlink->unlink_seqnum = uurb->seqnum;
+
+ /* TODO - are we failing to pass through the status here? */
+ spin_lock(&udev->lock);
+ list_add_tail(&unlink->list, &udev->unlink_tx);
+ spin_unlock(&udev->lock);
+
+ spin_unlock_irqrestore(&hub->lock, flags);
+
+ wake_up(&udev->waitq_tx);
+ } else {
+ /* Connection is dead already */
+ usbredir_free_uurb(udev, urb);
+
+ usb_hcd_unlink_urb_from_ep(hcd, urb);
+ spin_unlock_irqrestore(&hub->lock, flags);
+
+ usb_hcd_giveback_urb(hub->hcd, urb, urb->status);
+ }
+
+ return ret;
+}
+
+struct urb *usbredir_pop_rx_urb(struct usbredir_device *udev, int seqnum)
+{
+ struct usbredir_urb *uurb, *tmp;
+ struct urb *urb = NULL;
+ int status;
+
+ spin_lock(&udev->lock);
+
+ list_for_each_entry_safe(uurb, tmp, &udev->urblist_rx, list) {
+ if (uurb->seqnum != seqnum)
+ continue;
+
+ urb = uurb->urb;
+ status = urb->status;
+
+ switch (status) {
+ case -ENOENT:
+ /* fall through */
+ case -ECONNRESET:
+ dev_info(&urb->dev->dev,
+ "urb %p was unlinked %ssynchronuously.\n", urb,
+ status == -ENOENT ? "" : "a");
+ break;
+ case -EINPROGRESS:
+ /* no info output */
+ break;
+ default:
+ dev_info(&urb->dev->dev,
+ "urb %p may be in a error, status %d\n", urb,
+ status);
+ }
+
+ list_del(&uurb->list);
+ kfree(uurb);
+ urb->hcpriv = NULL;
+
+ break;
+ }
+ spin_unlock(&udev->lock);
+
+ return urb;
+}
diff --git a/kernel/usbredir.h b/kernel/usbredir.h
new file mode 100644
index 0000000..3263b5d
--- /dev/null
+++ b/kernel/usbredir.h
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2015 Jeremy White based on work by
+ * Copyright (C) 2003-2008 Takahiro Hirofuchi
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#ifndef __USBREDIR_H
+#define __USBREDIR_H
+
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/platform_device.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#include "usbredirparser.h"
+
+#define USBREDIR_MODULE_VERSION "1.0"
+
+
+/**
+ * struct usbredir_device - Describe a redirected usb device
+ * @lock spinlock for port_status, usb_dev, and other misc fields
+ * @active indicates whether the device is actively connected
+ * @usb_dev The usb device actively in use; captured on our first
+ * useful control urb. We mostly use it to signal that
+ * a device is in use.
+ * @hub The root hub that is associated with this device.
+ * @attr The sysfs atrribute to show our devid
+ * @port_status A status variable to track usb/core.c style status;
+ * e.g. USB_PORT_STAT_ENABLE et all
+ * @socket The socket used to connect to the remote device
+ * @parser A parser which drives the socket
+ * @rx The task structure for the receive thread
+ * @tx The task structure for the transmit thread
+ * @devid A user space provided id for this device; must be unique
+ * @connect_header Stored USBREDIR connection header information
+ * @info_header Stored USBREDIR connection information
+ * @ep_info_header Stored USBREDIR endpoint header info
+ * @rhport 0 based port number on our root hub
+ * @urblist_tx A list of urb's ready to be transmitted
+ * @urblist_rx A list of urbs already transmitted, awaiting
+ * a response
+ * @unlink_tx A list of urb's to be send to be unlinked
+ * @unlink_xx A list of urb's we have requested cancellation of
+ * @waitq_tx Wait queue the transmit thread sleeps on
+ */
+struct usbredir_device {
+ spinlock_t lock;
+
+ atomic_t active;
+
+ struct usb_device *usb_dev;
+ struct usbredir_hub *hub;
+ struct device_attribute attr;
+
+ u32 port_status;
+
+ struct socket *socket;
+ struct usbredirparser *parser;
+
+ struct task_struct *rx;
+ struct task_struct *tx;
+
+ char *devid;
+
+ struct usb_redir_device_connect_header connect_header;
+ struct usb_redir_interface_info_header info_header;
+ struct usb_redir_ep_info_header ep_info_header;
+
+ __u32 rhport;
+
+ spinlock_t lists_lock;
+
+ struct list_head urblist_tx;
+ struct list_head urblist_rx;
+
+ struct list_head unlink_tx;
+ struct list_head unlink_rx;
+
+ wait_queue_head_t waitq_tx;
+};
+
+/**
+ * struct usbredir_hub - Describe a virtual usb hub, which can hold
+ * redirected usb devices
+ *
+ * @lock Spinlock controlling access to variables,
+ * mostly needed for timeout and resuming flags
+ * @list Place holder for stashing inside a larger hub list
+ * @id A numeric identifier for this hub
+ * @pdev A registered platform device for this hub
+ * @hcd The usb_hcd associated with this hub
+ * @device_count The number of devices that can be connected to this hub
+ * @devices An array of devices
+ * @aseqnum Sequence number for transmissions
+ * @resuming Flag to indicate we are resuming
+ * @re_timeout General settle timeout for our hub
+ *
+ * The usbredir_hubs are allocated dynamically, as needed, but not freed.
+ * A new devices is assigned to the first hub with a free slot.
+ */
+struct usbredir_hub {
+ spinlock_t lock;
+ struct list_head list;
+ int id;
+ struct platform_device pdev;
+ struct usb_hcd *hcd;
+
+ int device_count;
+ struct usbredir_device *devices;
+
+ atomic_t aseqnum;
+
+ unsigned resuming:1;
+ unsigned long re_timeout;
+};
+
+/**
+ * struct usbredir_urb - Hold our information regarding a URB
+ * @seqnum Sequence number of the urb
+ * @list Place holder to keep it in device/urblist_[rt]x
+ * @urb A pointer to the associated urb
+ */
+struct usbredir_urb {
+ int seqnum;
+ struct list_head list;
+
+ struct urb *urb;
+};
+
+/**
+ * struct usbredir_unlink - Hold unlink requests
+ * @seqnum Sequence number of this request
+ * @list Place holder to keep it in device/unlink_[rt]x
+ * @unlink_seqnum Sequence number of the urb to unlink
+ */
+struct usbredir_unlink {
+ int seqnum;
+
+ struct list_head list;
+
+ int unlink_seqnum;
+};
+
+
+/* main.c */
+extern unsigned int max_hubs;
+extern unsigned int devices_per_hub;
+
+extern const char driver_name[];
+extern const char driver_desc[];
+
+/* sysfs.c */
+int usbredir_sysfs_register(struct device_driver *dev);
+void usbredir_sysfs_unregister(struct device_driver *dev);
+void usbredir_sysfs_expose_devid(struct usbredir_device *udev);
+void usbredir_sysfs_remove_devid(struct usbredir_device *udev);
+
+/* hub.c */
+void usbredir_hub_init(void);
+void usbredir_hub_exit(void);
+struct usbredir_device *usbredir_hub_allocate_device(const char *devid,
+ struct socket *socket);
+struct usbredir_device *usbredir_hub_find_device(const char *devid);
+int usbredir_hub_show_global_status(char *out);
+
+
+/* device.c */
+void usbredir_device_init(struct usbredir_device *udev, int port,
+ struct usbredir_hub *hub);
+void usbredir_device_allocate(struct usbredir_device *udev,
+ const char *devid,
+ struct socket *socket);
+void usbredir_device_deallocate(struct usbredir_device *udev,
+ bool stop, bool stoptx);
+void usbredir_device_connect(struct usbredir_device *udev);
+void usbredir_device_create_sysfs(struct usbredir_device *udev, struct device
+ *dev);
+void usbredir_device_disconnect(struct usbredir_device *udev);
+int usbredir_device_clear_port_feature(struct usbredir_hub *hub,
+ int rhport, u16 wValue);
+int usbredir_device_port_status(struct usbredir_hub *hub, int rhport,
+ char *buf);
+int usbredir_device_set_port_feature(struct usbredir_hub *hub,
+ int rhport, u16 wValue);
+ssize_t usbredir_device_devid(struct device *dev,
+ struct device_attribute *attr,
+ char *buf);
+
+/* redir.c */
+struct usbredirparser *redir_parser_init(void *priv);
+
+/* rx.c */
+int usbredir_rx_loop(void *data);
+
+/* tx.c */
+int usbredir_tx_loop(void *data);
+
+/* urb.c */
+int usbredir_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
+ gfp_t mem_flags);
+int usbredir_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status);
+struct urb *usbredir_pop_rx_urb(struct usbredir_device *udev, int seqnum);
+void usbredir_urb_cleanup_urblists(struct usbredir_device *udev);
+
+/* Fast lookup functions */
+static inline struct usbredir_hub *usbredir_hub_from_hcd(struct usb_hcd *hcd)
+{
+ return *(struct usbredir_hub **) hcd->hcd_priv;
+}
+
+static inline int usbredir_hub_seqnum(struct usbredir_hub *hub)
+{
+ int ret = atomic_inc_return(&hub->aseqnum);
+ /* Atomics are only guaranteed to 24 bits */
+ if (ret < 0 || ret > (1 << 23)) {
+ ret = 1;
+ atomic_set(&hub->aseqnum, 1);
+ }
+ return ret;
+}
+
+#endif /* __USBREDIR_H */
--
2.1.4
More information about the Spice-devel
mailing list