[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