Signed-off-by: Jeremy White <jwhite@xxxxxxxxxxxxxxx> --- 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 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel