Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753300AbbF3WAs (ORCPT ); Tue, 30 Jun 2015 18:00:48 -0400 Received: from mail.codeweavers.com ([216.251.189.131]:46128 "EHLO mail.codeweavers.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751398AbbF3WAZ (ORCPT ); Tue, 30 Jun 2015 18:00:25 -0400 X-Greylist: delayed 965 seconds by postgrey-1.27 at vger.kernel.org; Tue, 30 Jun 2015 18:00:24 EDT From: Jeremy White To: hdegoede@redhat.com, spice-devel@lists.freedesktop.org, linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFC PATCH 1/1] Add a usbredir kernel module to remotely connect USB devices over IP. Date: Tue, 30 Jun 2015 16:44:10 -0500 Message-Id: <1435700650-640-2-git-send-email-jwhite@codeweavers.com> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1435700650-640-1-git-send-email-jwhite@codeweavers.com> References: <1435700650-640-1-git-send-email-jwhite@codeweavers.com> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 181751 Lines: 5675 This module uses the usbredir protocol and user space tools, which are used by the SPICE project. Signed-off-by: Jeremy White --- MAINTAINERS | 6 + drivers/usb/Kconfig | 2 + drivers/usb/Makefile | 1 + drivers/usb/usbredir/Kconfig | 25 + drivers/usb/usbredir/Makefile | 4 + drivers/usb/usbredir/README | 20 + drivers/usb/usbredir/TODO | 12 + drivers/usb/usbredir/device.c | 327 +++++ drivers/usb/usbredir/hub.c | 489 ++++++++ drivers/usb/usbredir/main.c | 100 ++ drivers/usb/usbredir/redir.c | 535 ++++++++ drivers/usb/usbredir/rx.c | 40 + drivers/usb/usbredir/strtok_r.c | 69 + drivers/usb/usbredir/strtok_r.h | 26 + drivers/usb/usbredir/sysfs.c | 145 +++ drivers/usb/usbredir/tx.c | 151 +++ drivers/usb/usbredir/urb.c | 266 ++++ drivers/usb/usbredir/usbredir.h | 225 ++++ drivers/usb/usbredir/usbredirfilter.c | 294 +++++ drivers/usb/usbredir/usbredirfilter.h | 144 +++ drivers/usb/usbredir/usbredirparser.c | 1795 +++++++++++++++++++++++++++ drivers/usb/usbredir/usbredirparser.h | 386 ++++++ drivers/usb/usbredir/usbredirproto-compat.h | 88 ++ drivers/usb/usbredir/usbredirproto.h | 309 +++++ 24 files changed, 5459 insertions(+) create mode 100644 drivers/usb/usbredir/Kconfig create mode 100644 drivers/usb/usbredir/Makefile create mode 100644 drivers/usb/usbredir/README create mode 100644 drivers/usb/usbredir/TODO create mode 100644 drivers/usb/usbredir/device.c create mode 100644 drivers/usb/usbredir/hub.c create mode 100644 drivers/usb/usbredir/main.c create mode 100644 drivers/usb/usbredir/redir.c create mode 100644 drivers/usb/usbredir/rx.c create mode 100644 drivers/usb/usbredir/strtok_r.c create mode 100644 drivers/usb/usbredir/strtok_r.h create mode 100644 drivers/usb/usbredir/sysfs.c create mode 100644 drivers/usb/usbredir/tx.c create mode 100644 drivers/usb/usbredir/urb.c create mode 100644 drivers/usb/usbredir/usbredir.h create mode 100644 drivers/usb/usbredir/usbredirfilter.c create mode 100644 drivers/usb/usbredir/usbredirfilter.h create mode 100644 drivers/usb/usbredir/usbredirparser.c create mode 100644 drivers/usb/usbredir/usbredirparser.h create mode 100644 drivers/usb/usbredir/usbredirproto-compat.h create mode 100644 drivers/usb/usbredir/usbredirproto.h diff --git a/MAINTAINERS b/MAINTAINERS index d8afd29..738a80d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10363,6 +10363,12 @@ S: Maintained F: drivers/usb/usbip/ F: tools/usb/usbip/ +USBREDIR IP DRIVER +M: Jeremy White +L: linux-usb@vger.kernel.org +S: Maintained +F: drivers/usb/usbredir/ + USB PEGASUS DRIVER M: Petko Manolov L: linux-usb@vger.kernel.org diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 8ed451d..18940f5 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -94,6 +94,8 @@ source "drivers/usb/image/Kconfig" source "drivers/usb/usbip/Kconfig" +source "drivers/usb/usbredir/Kconfig" + endif source "drivers/usb/musb/Kconfig" diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index d8926c6..384fd9f 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -62,3 +62,4 @@ obj-$(CONFIG_USB_GADGET) += gadget/ obj-$(CONFIG_USB_COMMON) += common/ obj-$(CONFIG_USBIP_CORE) += usbip/ +obj-$(CONFIG_USBREDIR) += usbredir/ diff --git a/drivers/usb/usbredir/Kconfig b/drivers/usb/usbredir/Kconfig new file mode 100644 index 0000000..284fd02 --- /dev/null +++ b/drivers/usb/usbredir/Kconfig @@ -0,0 +1,25 @@ +config USBREDIR + tristate "USBREDIR support" + depends on USB && NET + ---help--- + This enables connecting a remote USB device over IP using + the USBREDIR protocol. This module provides a sysfs attach + interface which, if given a socket connected to a remote + usbredirserver, will enable the remote device to behave as + though it were connected to the system running this module. + + For more information and user space tools, refer to the + USBREDIR project, which can be found at + http://www.spice-space.org/page/UsbRedir. + + To compile this as a module, choose M here: the module will + be called usbredir. + + If unsure, say N. + +config USBREDIR_DEBUG + bool "Debug messages for USBREDIR" + depends on USBREDIR + ---help--- + This enables the debug messages from the USBREDIR drivers. + diff --git a/drivers/usb/usbredir/Makefile b/drivers/usb/usbredir/Makefile new file mode 100644 index 0000000..531ff31 --- /dev/null +++ b/drivers/usb/usbredir/Makefile @@ -0,0 +1,4 @@ +ccflags-$(CONFIG_USBREDIR_DEBUG) := -DDEBUG + +obj-$(CONFIG_USBREDIR) += usbredir.o +usbredir-y := main.o sysfs.o hub.o device.o urb.o redir.o tx.o rx.o usbredirparser.o strtok_r.o usbredirfilter.o diff --git a/drivers/usb/usbredir/README b/drivers/usb/usbredir/README new file mode 100644 index 0000000..217a2e4 --- /dev/null +++ b/drivers/usb/usbredir/README @@ -0,0 +1,20 @@ +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, 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/drivers/usb/usbredir/TODO b/drivers/usb/usbredir/TODO new file mode 100644 index 0000000..605551a --- /dev/null +++ b/drivers/usb/usbredir/TODO @@ -0,0 +1,12 @@ +TODO: + * Note - during testing, having a device die unexpectedly + could go on to cause fairly serious problems. Need to + explore that. + * Still getting sporadic failures + - One clue - got an unexpected rc from the write in redir.c + * Hmm. KFP_ATOMIC on our mallocs may be better than _KERNEL... + * Read and deal with the many TODOs + * Not just hacking the usbredirparser, but proper patches + - The filter needs love. Use strsep, not strtok? + * Expose a sysfs attribute and have udev use it + * Turn on more capabilities and watch it go diff --git a/drivers/usb/usbredir/device.c b/drivers/usb/usbredir/device.c new file mode 100644 index 0000000..114bc48 --- /dev/null +++ b/drivers/usb/usbredir/device.c @@ -0,0 +1,327 @@ +/* + * 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 +#include +#include +#include + +#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); + spin_lock_init(&udev->lists_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); +} + +/* Presumes udev->lock is held on entry */ +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); + atomic_set(&udev->active, 1); + udev->socket = socket; + + udev->port_status = 0; + + /* TODO - safe to hold udev lock through thread creation? */ + 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); +} + +void usbredir_device_cleanup_unlink(struct usbredir_device *udev) +{ + struct usbredir_unlink *unlink, *tmp; + + spin_lock(&udev->lists_lock); + 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); + } + spin_unlock(&udev->lists_lock); +} + +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)); + if (atomic_dec_if_positive(&udev->active) < 0) + return; + + /* Release the rx thread */ + spin_lock(&udev->lock); + if (udev->socket) + kernel_sock_shutdown(udev->socket, SHUT_RDWR); + spin_unlock(&udev->lock); + + /* 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); + + 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; + + 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) { + pr_err("invalid port number %d\n", rhport); + return NULL; + } + udev = hub->devices + rhport; + + spin_lock(&hub->lock); + spin_lock(&udev->lock); + return udev; +} + +static void usbredir_device_put(struct usbredir_device *udev) +{ + spin_unlock(&udev->lock); + spin_unlock(&udev->hub->lock); +} + +int usbredir_device_clear_port_feature(struct usbredir_hub *hub, + int rhport, u16 wValue) +{ + struct usbredir_device *udev = usbredir_device_get(hub, rhport); + + if (!udev) + return -ENODEV; + + 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; + } + default: + pr_debug(" ClearPortFeature: default %x\n", wValue); + udev->port_status &= ~(1 << wValue); + break; + } + + usbredir_device_put(udev); + + 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; + } + + 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]); + + usbredir_device_put(udev); + + 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; + + 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; + } + + usbredir_device_put(udev); + + return 0; +} diff --git a/drivers/usb/usbredir/hub.c b/drivers/usb/usbredir/hub.c new file mode 100644 index 0000000..f6eeb20 --- /dev/null +++ b/drivers/usb/usbredir/hub.c @@ -0,0 +1,489 @@ +/* + * 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 +#include +#include +#include +#include + +#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; + + spin_lock(&hub->lock); + 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(&hub->lock); + 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(&hub->lock); + + return 0; +} + +static void usbredir_hub_stop(struct usbredir_hub *hub) +{ + int i; + + pr_debug("%s %d\n", __func__, hub->id); + + 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(&hub->lock); + kfree(hub->devices); + hub->devices = NULL; + hub->device_count = 0; + spin_unlock(&hub->lock); +} + +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; + + spin_lock(&hub->lock); + + 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(&hub->lock); + return 0; + } + + /* 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(&hub->lock); + + 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)); +/* TODO - where do these magic numbers come from? */ + desc->bDescriptorType = 0x29; + desc->bDescLength = 9; + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_COMMON_OCPM); + desc->bNbrPorts = hub->device_count; + 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; + + if (!HCD_HW_ACCESSIBLE(hcd)) + return -ETIMEDOUT; + + hub = usbredir_hub_from_hcd(hcd); + + 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; + + switch (typeReq) { + case ClearHubFeature: + pr_debug(" ClearHubFeature\n"); + break; + case SetHubFeature: + pr_debug(" SetHubFeature\n"); + ret = -EPIPE; + break; + case GetHubDescriptor: + 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; + } + 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? */ + 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? */ + .flags = HCD_USB2, + + .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, +}; + + +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; + } + + /* TODO - review if we want to has_tt, and anything like it... */ + hub->hcd->has_tt = 1; + + /* TODO - no one else stores a pointer + * may want to rethink the structure. + * Question: do we really need to create the pdev first? */ + *((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; +} + +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; +} + +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; + + spin_lock(&hubs_lock); + list_for_each_entry(hub, &hubs, list) { + spin_lock(&hub->lock); + 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(&hub->lock); + 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; + int i; + + spin_lock(&hubs_lock); + list_for_each_entry(hub, &hubs, list) { + spin_lock(&hub->lock); + for (i = 0; i < hub->device_count; i++) { + udev = hub->devices + i; + spin_lock(&udev->lock); + if (!atomic_read(&udev->active)) { + found++; + /* Note: lock is *held* */ + break; + } + spin_unlock(&udev->lock); + } + spin_unlock(&hub->lock); + if (found) + break; + } + spin_unlock(&hubs_lock); + + if (!found) { + hub = usbredir_hub_create(); + if (!hub) + return NULL; + + return usbredir_hub_allocate_device(devid, socket); + } + + usbredir_device_allocate(udev, devid, socket); + + spin_unlock(&udev->lock); + + return udev; +} + +int usbredir_hub_show_global_status(char *out) +{ + int count = 0; + int active = 0; + int used = 0; + + struct usbredir_hub *hub; + struct usbredir_device *udev; + int i; + + spin_lock(&hubs_lock); + list_for_each_entry(hub, &hubs, list) { + spin_lock(&hub->lock); + 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(&hub->lock); + } + 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); +} + + +int usbredir_hub_init(void) +{ + INIT_LIST_HEAD(&hubs); + atomic_set(&hub_count, 0); + spin_lock_init(&hubs_lock); + + return 0; +} + +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/drivers/usb/usbredir/main.c b/drivers/usb/usbredir/main.c new file mode 100644 index 0000000..b63e6c6 --- /dev/null +++ b/drivers/usb/usbredir/main.c @@ -0,0 +1,100 @@ +/* + * 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 +#include +#include +#include + +#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, + }, +}; + +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; + } + + ret = usbredir_hub_init(); + if (ret) { + platform_driver_unregister(&usbredir_driver); + return ret; + } + + 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/drivers/usb/usbredir/redir.c b/drivers/usb/usbredir/redir.c new file mode 100644 index 0000000..33d9475 --- /dev/null +++ b/drivers/usb/usbredir/redir.c @@ -0,0 +1,535 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#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; + spin_unlock(&udev->lock); + + rc = kernel_sendmsg(socket, &msg, &iov, 1, count); + /* 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); + + 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/drivers/usb/usbredir/rx.c b/drivers/usb/usbredir/rx.c new file mode 100644 index 0000000..92ffa97 --- /dev/null +++ b/drivers/usb/usbredir/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 + +#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/drivers/usb/usbredir/strtok_r.c b/drivers/usb/usbredir/strtok_r.c new file mode 100644 index 0000000..2c761e1 --- /dev/null +++ b/drivers/usb/usbredir/strtok_r.c @@ -0,0 +1,69 @@ +/* Reentrant string tokenizer. Generic version. + Copyright (C) 1991,1996-1999,2001,2004 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#if defined(__KERNEL__) +#include +#else +#include +#endif + +/* Parse S into tokens separated by characters in DELIM. + If S is NULL, the saved pointer in SAVE_PTR is used as + the next starting point. For example: + char s[] = "-abc-=-def"; + char *sp; + x = strtok_r(s, "-", &sp); // x = "abc", sp = "=-def" + x = strtok_r(NULL, "-=", &sp); // x = "def", sp = NULL + x = strtok_r(NULL, "=", &sp); // x = NULL + // s = "abc\0-def\0" +*/ +char * +glibc_strtok_r (char *s, const char *delim, char **save_ptr) +{ + char *token; + + if (s == NULL) + s = *save_ptr; + + /* Scan leading delimiters. */ + s += strspn (s, delim); + if (*s == '\0') + { + *save_ptr = s; + return NULL; + } + + /* Find the end of the token. */ + token = s; + s = strpbrk (token, delim); + if (s == NULL) + /* This token finishes the string. */ + *save_ptr = strchr (token, '\0'); + else + { + /* Terminate the token and make *SAVE_PTR point past it. */ + *s = '\0'; + *save_ptr = s + 1; + } + return token; +} diff --git a/drivers/usb/usbredir/strtok_r.h b/drivers/usb/usbredir/strtok_r.h new file mode 100644 index 0000000..50e6e0e --- /dev/null +++ b/drivers/usb/usbredir/strtok_r.h @@ -0,0 +1,26 @@ +/* Reentrant string tokenizer. Generic version. + Copyright (C) 1991,1996-1999,2001,2004 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#ifndef _USBREDIRPARSER_STRTOK_H_ +#define _USBREDIRPARSER_STRTOK_H_ + +char * +glibc_strtok_r(char *s, const char *delim, char **save_ptr); + +#endif diff --git a/drivers/usb/usbredir/sysfs.c b/drivers/usb/usbredir/sysfs.c new file mode 100644 index 0000000..e8edef3 --- /dev/null +++ b/drivers/usb/usbredir/sysfs.c @@ -0,0 +1,145 @@ +/* + * 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 +#include + +#include "usbredir.h" + + +static ssize_t status_show(struct device_driver *driver, char *out) +{ + return usbredir_hub_show_global_status(out); +} +static DRIVER_ATTR_RO(status); + +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); +} diff --git a/drivers/usb/usbredir/tx.c b/drivers/usb/usbredir/tx.c new file mode 100644 index 0000000..aae7a10 --- /dev/null +++ b/drivers/usb/usbredir/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 +#include + +#include "usbredir.h" + +static struct usbredir_urb *get_next_urb(struct usbredir_device *udev) +{ + struct usbredir_urb *uurb, *tmp; + + spin_lock(&udev->lists_lock); + + list_for_each_entry_safe(uurb, tmp, &udev->urblist_tx, list) { + list_move_tail(&uurb->list, &udev->urblist_rx); + spin_unlock(&udev->lists_lock); + return uurb; + } + + spin_unlock(&udev->lists_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->lists_lock); + + list_for_each_entry_safe(unlink, tmp, &udev->unlink_tx, list) { + list_move_tail(&unlink->list, &udev->unlink_rx); + spin_unlock(&udev->lists_lock); + return unlink; + } + + spin_unlock(&udev->lists_lock); + + return NULL; +} + +static void send_unlink(struct usbredir_device *udev, + struct usbredir_unlink *unlink) +{ + while ((unlink = get_next_unlink(udev)) != NULL) { + /* 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; + + while ((uurb = get_next_urb(udev)) != NULL) + send_packet(udev, uurb); + + 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/drivers/usb/usbredir/urb.c b/drivers/usb/usbredir/urb.c new file mode 100644 index 0000000..50bec2f --- /dev/null +++ b/drivers/usb/usbredir/urb.c @@ -0,0 +1,266 @@ +/* + * 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 +#include +#include + +#include "usbredir.h" + +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) { + usbredir_device_disconnect(udev); + usbredir_device_deallocate(udev, true, true); + return; + } + + spin_lock(&udev->lists_lock); + + uurb->seqnum = usbredir_hub_seqnum(udev->hub); + + uurb->udev = udev; + uurb->urb = urb; + + urb->hcpriv = (void *) uurb; + + list_add_tail(&uurb->list, &udev->urblist_tx); + spin_unlock(&udev->lists_lock); +} + +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; +} + +void usbredir_urb_cleanup_urblists(struct usbredir_device *udev) +{ + struct usbredir_urb *uurb, *tmp; + + spin_lock(&udev->lists_lock); + 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); + 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); + usb_hcd_giveback_urb(udev->hub->hcd, uurb->urb, -ENODEV); + kfree(uurb); + } + spin_unlock(&udev->lists_lock); +} + + + +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; + + pr_debug("%s: enter, usb_hcd %p urb %p mem_flags %d\n", + __func__, hcd, urb, mem_flags); + + if (urb->status != -EINPROGRESS) { + dev_err(dev, "URB already handled!, status %d\n", urb->status); + return urb->status; + } + + udev = hub->devices + urb->dev->portnum - 1; + + if (!atomic_read(&udev->active)) { + dev_err(dev, "enqueue for inactive port %d\n", udev->rhport); + return -ENODEV; + } + + ret = usb_hcd_link_urb_to_ep(hcd, urb); + if (ret) { + usb_hcd_giveback_urb(hub->hcd, urb, urb->status); + return ret; + } + + if (intercept_urb_request(udev, urb, &ret)) { + usb_hcd_unlink_urb_from_ep(hcd, urb); + usb_hcd_giveback_urb(hub->hcd, urb, urb->status); + return ret; + } + + queue_urb(udev, urb); + wake_up_interruptible(&udev->waitq_tx); + return ret; +} + +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; + + pr_debug("%s %p\n", __func__, urb); + + uurb = urb->hcpriv; + if (!uurb) { + /* URB was never linked! or will be soon given back by + * rx_loop. */ + return 0; + } + + ret = usb_hcd_check_unlink_urb(hcd, urb, status); + if (ret) { + /* TODO - figure out if this is an unlink send case as well */ + return ret; + } + + udev = uurb->udev; + if (atomic_read(&udev->active)) { + struct usbredir_unlink *unlink; + + spin_lock(&udev->lists_lock); + + unlink = kzalloc(sizeof(struct usbredir_unlink), GFP_ATOMIC); + if (!unlink) { + spin_unlock(&udev->lists_lock); + /* TODO complain somehow... */ + return -ENOMEM; + } + + unlink->seqnum = usbredir_hub_seqnum(hub); + unlink->unlink_seqnum = uurb->seqnum; + list_add_tail(&unlink->list, &udev->unlink_tx); + /* TODO - are we failing to pass through the status here? */ + + spin_unlock(&udev->lists_lock); + + wake_up(&udev->waitq_tx); + } else { + /* Connection is dead already */ + spin_lock(&udev->lists_lock); + + list_del(&uurb->list); + kfree(uurb); + urb->hcpriv = NULL; + + spin_unlock(&udev->lists_lock); + + pr_info("gives back urb %p\n", urb); + + usb_hcd_unlink_urb_from_ep(hcd, urb); + 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->lists_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->lists_lock); + + return urb; +} diff --git a/drivers/usb/usbredir/usbredir.h b/drivers/usb/usbredir/usbredir.h new file mode 100644 index 0000000..fd80059 --- /dev/null +++ b/drivers/usb/usbredir/usbredir.h @@ -0,0 +1,225 @@ +/* + * 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 +#include +#include +#include +#include + +#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. + * @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 + * @lists_lock A spinlock just for the urb and unlink lists + * @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; + + 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 + * @udev A pointer to our associated device + * @urb A pointer to the associated urb + */ +struct usbredir_urb { + int seqnum; + struct list_head list; + + struct usbredir_device *udev; + 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); + +/* hub.c */ +int usbredir_hub_init(void); +void usbredir_hub_exit(void); +struct usbredir_hub *usbredir_hub_create(void); +void usbredir_hub_destroy(struct usbredir_hub *hub); +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_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); + +/* 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 */ diff --git a/drivers/usb/usbredir/usbredirfilter.c b/drivers/usb/usbredir/usbredirfilter.c new file mode 100644 index 0000000..4e3655a --- /dev/null +++ b/drivers/usb/usbredir/usbredirfilter.c @@ -0,0 +1,294 @@ +/* usbredirfilter.h usb redirection filter header + + Copyright 2012 Red Hat, Inc. + + Red Hat Authors: + Hans de Goede + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . +*/ + +#if defined(__KERNEL__) +#include +#include +#else +#include +#include +#include +#endif + +#if defined(WIN32) || defined(__KERNEL__) +#include "strtok_r.h" +#define strtok_r glibc_strtok_r +#endif + +#if defined(__KERNEL__) +#define CALLOC(a, b) kcalloc((a), (b), GFP_KERNEL) +#define MALLOC(a) kmalloc((a), GFP_KERNEL) +#define STRDUP(a) kstrdup((a), GFP_KERNEL) +#define FREE kfree +#else +#define CALLOC calloc +#define MALLOC malloc +#define STRDUP strdup +#define FREE free +#endif +#include "usbredirfilter.h" + +int filter_strtoi(char *str, int *res) +{ + long l; + int rc = 0; +#if defined(__KERNEL__) + rc = kstrtol(str, 0, &l); +#else + char *ep; + + l = strtol(str, &ep, 0); + if (*ep) + rc = -1; +#endif + if (rc == 0) + *res = (int) l; + return rc; +} + +int usbredirfilter_string_to_rules( + const char *filter_str, const char *token_sep, const char *rule_sep, + struct usbredirfilter_rule **rules_ret, int *rules_count_ret) +{ + char *rule, *rule_saveptr, *token, *token_saveptr; + struct usbredirfilter_rule *rules = NULL; + int i, rules_count, *values, ret = 0; + char *buf = NULL; + const char *r; + + *rules_ret = NULL; + *rules_count_ret = 0; + + /* Figure out how much rules there are in the file, so we know how + much memory we must allocate for the rules array. + Note this will come up with a slightly too large number if there are + empty rule strings in the set. */ + r = filter_str; + rules_count = 0; + while (r) { + r = strchr(r, rule_sep[0]); + if (r) + r++; + rules_count++; + } + + rules = CALLOC(rules_count, sizeof(struct usbredirfilter_rule)); + if (!rules) + return -ENOMEM; + + /* Make a copy since strtok mangles the string */ + buf = STRDUP(filter_str); + if (!buf) { + ret = -ENOMEM; + goto leave; + } + + /* And actually parse the string */ + rules_count = 0; + rule = strtok_r(buf, rule_sep, &rule_saveptr); + while (rule) { + /* We treat the filter rule as an array of ints for easier parsing */ + values = (int *)&rules[rules_count]; + token = strtok_r(rule, token_sep, &token_saveptr); + for (i = 0; i < 5 && token; i++) { + if (filter_strtoi(token, &values[i])) + break; + token = strtok_r(NULL, token_sep, &token_saveptr); + } + if (i != 5 || token != NULL || + usbredirfilter_verify(&rules[rules_count], 1)) { + ret = -EINVAL; + goto leave; + } + rules_count++; + rule = strtok_r(NULL, rule_sep, &rule_saveptr); + } + + *rules_ret = rules; + *rules_count_ret = rules_count; + +leave: + if (ret) + FREE(rules); + FREE(buf); + return ret; +} + +char *usbredirfilter_rules_to_string(const struct usbredirfilter_rule *rules, + int rules_count, const char *token_sep, const char *rule_sep) +{ + int i; + char *str, *p; + + if (usbredirfilter_verify(rules, rules_count)) + return NULL; + + /* We need 28 bytes per rule in the worst case */ + str = MALLOC(28 * rules_count + 1); + if (!str) + return NULL; + + p = str; + for (i = 0; i < rules_count; i++) { + if (rules[i].device_class != -1) + p += sprintf(p, "0x%02x%c", rules[i].device_class, *token_sep); + else + p += sprintf(p, "-1%c", *token_sep); + + if (rules[i].vendor_id != -1) + p += sprintf(p, "0x%04x%c", rules[i].vendor_id, *token_sep); + else + p += sprintf(p, "-1%c", *token_sep); + + if (rules[i].product_id != -1) + p += sprintf(p, "0x%04x%c", rules[i].product_id, *token_sep); + else + p += sprintf(p, "-1%c", *token_sep); + + if (rules[i].device_version_bcd != -1) + p += sprintf(p, "0x%04x%c", rules[i].device_version_bcd, *token_sep); + else + p += sprintf(p, "-1%c", *token_sep); + + p += sprintf(p, "%d%c", rules[i].allow ? 1:0, *rule_sep); + } + + return str; +} + +static int usbredirfilter_check1(const struct usbredirfilter_rule *rules, + int rules_count, uint8_t device_class, uint16_t vendor_id, + uint16_t product_id, uint16_t device_version_bcd, int default_allow) +{ + int i; + + for (i = 0; i < rules_count; i++) { + if ((rules[i].device_class == -1 || + rules[i].device_class == device_class) && + (rules[i].vendor_id == -1 || + rules[i].vendor_id == vendor_id) && + (rules[i].product_id == -1 || + rules[i].product_id == product_id) && + (rules[i].device_version_bcd == -1 || + rules[i].device_version_bcd == device_version_bcd)) { + /* Found a match ! */ + return rules[i].allow ? 0 : -EPERM; + } + } + + return default_allow ? 0 : -ENOENT; +} + +int usbredirfilter_check( + const struct usbredirfilter_rule *rules, int rules_count, + uint8_t device_class, uint8_t device_subclass, uint8_t device_protocol, + uint8_t *interface_class, uint8_t *interface_subclass, + uint8_t *interface_protocol, int interface_count, + uint16_t vendor_id, uint16_t product_id, uint16_t device_version_bcd, + int flags) +{ + int i, rc; + + if (usbredirfilter_verify(rules, rules_count)) + return -EINVAL; + + /* Check the device_class */ + if (device_class != 0x00 && device_class != 0xef) { + rc = usbredirfilter_check1(rules, rules_count, device_class, + vendor_id, product_id, device_version_bcd, + flags & usbredirfilter_fl_default_allow); + if (rc) + return rc; + } + + /* Check the interface classes */ + for (i = 0; i < interface_count; i++) { + if (!(flags & usbredirfilter_fl_dont_skip_non_boot_hid) && + interface_count > 1 && interface_class[i] == 0x03 && + interface_subclass[i] == 0x00 && interface_protocol[i] == 0x00) + continue; + + rc = usbredirfilter_check1(rules, rules_count, interface_class[i], + vendor_id, product_id, device_version_bcd, + flags & usbredirfilter_fl_default_allow); + if (rc) + return rc; + } + + return 0; +} + +int usbredirfilter_verify( + const struct usbredirfilter_rule *rules, int rules_count) +{ + int i; + + for (i = 0; i < rules_count; i++) { + if (rules[i].device_class < -1 || rules[i].device_class > 255) + return -EINVAL; + if (rules[i].vendor_id < -1 || rules[i].vendor_id > 65535) + return -EINVAL; + if (rules[i].product_id < -1 || rules[i].product_id > 65535) + return -EINVAL; + if (rules[i].device_version_bcd < -1 || + rules[i].device_version_bcd > 65535) + return -EINVAL; + } + return 0; +} + +#if ! defined(__KERNEL__) +void usbredirfilter_print( + const struct usbredirfilter_rule *rules, int rules_count, FILE *out) +{ + int i; + char device_class[16], vendor[16], product[16], version[16]; + + for (i = 0; i < rules_count; i++) { + if (rules[i].device_class != -1) + sprintf(device_class, " %02x", rules[i].device_class); + else + strcpy(device_class, "ANY"); + + if (rules[i].vendor_id != -1) + sprintf(vendor, "%04x", rules[i].vendor_id); + else + strcpy(vendor, " ANY"); + + if (rules[i].product_id != -1) + sprintf(product, "%04x", rules[i].product_id); + else + strcpy(product, " ANY"); + + if (rules[i].device_version_bcd != -1) + sprintf(version, "%2d.%02d", + ((rules[i].device_version_bcd & 0xf000) >> 12) * 10 + + ((rules[i].device_version_bcd & 0x0f00) >> 8), + ((rules[i].device_version_bcd & 0x00f0) >> 4) * 10 + + ((rules[i].device_version_bcd & 0x000f))); + else + strcpy(version, " ANY"); + + fprintf(out, "Class %s ID %s:%s Version %s %s\n", device_class, vendor, + product, version, rules[i].allow ? "Allow":"Block"); + } +} +#endif diff --git a/drivers/usb/usbredir/usbredirfilter.h b/drivers/usb/usbredir/usbredirfilter.h new file mode 100644 index 0000000..42773a8 --- /dev/null +++ b/drivers/usb/usbredir/usbredirfilter.h @@ -0,0 +1,144 @@ +/* usbredirfilter.h usb redirection filter header + + Copyright 2012 Red Hat, Inc. + + Red Hat Authors: + Hans de Goede + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . +*/ +#ifndef __USBREDIRFILTER_H +#define __USBREDIRFILTER_H + +#if ! defined(__KERNEL__) +#include +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct usbredirfilter_rule { + int device_class; /* 0-255, -1 to match any class */ + int vendor_id; /* 0-65535, -1 to match any id */ + int product_id; /* 0-65535, -1 to match any id */ + int device_version_bcd; /* 0-255, -1 to match any version */ + int allow; /* 0: deny redir for this device, non 0: allow */ +}; + +/* Read a filter string and parse it into an array of usbredirfilter_rule-s. + + Where each rule has the form of: + ,,,, + Assuming "," as the specified token_sep character. + + And the rules are themselves are separated by the rule_sep character, ie: + || + + Assuming "|" as the rule_sep character. Note that with the seperator used + in this example the format matches the format as written by the RHEV-M USB + filter editor tool. + + Note that the seperators must be single character strings! + + On success the rules get returned in rules_ret and rules_count_ret, the + returned rules array should be freed with free() when the caller is done + with it. + + Return value: 0 on success, -ENOMEM when allocating the rules array fails, + or -EINVAL when there is on parsing error. +*/ +int usbredirfilter_string_to_rules( + const char *filter_str, const char *token_sep, const char *rule_sep, + struct usbredirfilter_rule **rules_ret, int *rules_count_ret); + +/* Convert a set of rules back to a string suitable for passing to + usbredirfilter_string_to_rules(); The returned string must be free()-ed + by the caller when it is done with it. + + Return value: The string on sucess, or NULL if the rules fail verification, + or when allocating the string fails. +*/ +char *usbredirfilter_rules_to_string(const struct usbredirfilter_rule *rules, + int rules_count, const char *token_sep, const char *rule_sep); + +/* Check if redirection of a device with the passed in device info is allowed + by the passed set of filter rules. + + Since a device has class info at both the device level and the interface + level, this function does multiple passes. + + First the rules are checked one by one against the given device info using + the device class info, if a matching rule is found, the result of the check + is that of that rule. + + Then the same is done for each interface the device has, substituting the + device class info with the class info from the interfaces. + + Note that under certain circumstances some passes are skipped: + - For devices with a device class of 0x00 or 0xef, the pass which checks the + device class is skipped. + - If the usbredirfilter_fl_dont_skip_non_boot_hid flag is not passed then + for devices with more then 1 interface and an interface with an interface + class of 0x03, an interface subclass of 0x00 and an interface protocol + of 0x00. the check is skipped for that interface. This allows to skip ie + checking the interface for volume buttons one some usbaudio class devices. + + If the result of all (not skipped) passes is allow, then 0 will be returned, + which indicates that redirection should be allowed. + + If the result of a matching rule is deny, then processing stops and + -EPERM will be returned. + + If a given pass does not match any rules, then processing stops and + -ENOENT will be returned. This behavior can be changed with the + usbredirfilter_fl_default_allow flag, if this flag is set the result of a + pass with no matching rules will be allow. + + Return value: + 0 Redirection is allowed + -EINVAL Invalid parameters + -EPERM Redirection is blocked by the filter rules + -ENOENT None of the rules matched the device (during one of the passes) +*/ +enum { + usbredirfilter_fl_default_allow = 0x01, + usbredirfilter_fl_dont_skip_non_boot_hid = 0x02, +}; +int usbredirfilter_check( + const struct usbredirfilter_rule *rules, int rules_count, + uint8_t device_class, uint8_t device_subclass, uint8_t device_protocol, + uint8_t *interface_class, uint8_t *interface_subclass, + uint8_t *interface_protocol, int interface_count, + uint16_t vendor_id, uint16_t product_id, uint16_t device_version_bcd, + int flags); + +/* Sanity check the passed in rules + + Return value: 0 on success, -EINVAL when some values are out of bound. */ +int usbredirfilter_verify( + const struct usbredirfilter_rule *rules, int rules_count); + +#if ! defined(__KERNEL__) +/* Print the passed in rules to FILE out in human readable format */ +void usbredirfilter_print( + const struct usbredirfilter_rule *rules, int rules_count, FILE *out); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/usb/usbredir/usbredirparser.c b/drivers/usb/usbredir/usbredirparser.c new file mode 100644 index 0000000..154d95b --- /dev/null +++ b/drivers/usb/usbredir/usbredirparser.c @@ -0,0 +1,1795 @@ +/* usbredirparser.c usb redirection protocol parser + + Copyright 2010-2012 Red Hat, Inc. + + Red Hat Authors: + Hans de Goede + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . +*/ +#if defined(__KERNEL__) +#include +#include +#include +#include +#else +#include "config.h" + +#include +#include +#include +#include +#endif + +#include "usbredirproto-compat.h" +#include "usbredirparser.h" +#include "usbredirfilter.h" + +#if defined(__KERNEL__) +#define CALLOC(a, b) kcalloc((a), (b), GFP_KERNEL) +#define MALLOC(a) kmalloc((a), GFP_KERNEL) +#define REALLOC(a, b) krealloc((a), (b), GFP_KERNEL) +#define FREE kfree +#else +#define CALLOC calloc +#define MALLOC malloc +#define REALLOC realloc +#define FREE free +#endif + +/* Put *some* upper limit on bulk transfer sizes */ +#define MAX_BULK_TRANSFER_SIZE (128u * 1024u * 1024u) + +/* Locking convenience macros */ +#define LOCK(parser) \ + do { \ + if ((parser)->lock) \ + (parser)->callb.lock_func((parser)->lock); \ + } while (0) + +#define UNLOCK(parser) \ + do { \ + if ((parser)->lock) \ + (parser)->callb.unlock_func((parser)->lock); \ + } while (0) + +struct usbredirparser_buf { + uint8_t *buf; + int pos; + int len; + + struct usbredirparser_buf *next; +}; + +struct usbredirparser_priv { + struct usbredirparser callb; + int flags; + + int have_peer_caps; + uint32_t our_caps[USB_REDIR_CAPS_SIZE]; + uint32_t peer_caps[USB_REDIR_CAPS_SIZE]; + + void *lock; + + union { + struct usb_redir_header header; + struct usb_redir_header_32bit_id header_32bit_id; + }; + uint8_t type_header[288]; + int header_read; + int type_header_len; + int type_header_read; + uint8_t *data; + int data_len; + int data_read; + int to_skip; + struct usbredirparser_buf *write_buf; + int write_buf_count; +}; + +static void +#if defined __GNUC__ +__attribute__((format(printf, 3, 4))) +#endif +va_log(struct usbredirparser_priv *parser, int verbose, const char *fmt, ...) +{ + char buf[512]; + va_list ap; + int n; + + n = sprintf(buf, "usbredirparser: "); + va_start(ap, fmt); + vsnprintf(buf + n, sizeof(buf) - n, fmt, ap); + va_end(ap); + + parser->callb.log_func(parser->callb.priv, verbose, buf); +} + +#define ERROR(...) va_log(parser, usbredirparser_error, __VA_ARGS__) +#define WARNING(...) va_log(parser, usbredirparser_warning, __VA_ARGS__) +#define INFO(...) va_log(parser, usbredirparser_info, __VA_ARGS__) +#define DEBUGP(...) va_log(parser, usbredirparser_debug, __VA_ARGS__) + +#if 0 /* Can be enabled and called from random place to test serialization */ +static void serialize_test(struct usbredirparser *parser_pub) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + struct usbredirparser_buf *wbuf, *next_wbuf; + uint8_t *data; + int len; + + if (usbredirparser_serialize(parser_pub, &data, &len)) + return; + + wbuf = parser->write_buf; + while (wbuf) { + next_wbuf = wbuf->next; + FREE(wbuf->buf); + FREE(wbuf); + wbuf = next_wbuf; + } + parser->write_buf = NULL; + parser->write_buf_count = 0; + + FREE(parser->data); + parser->data = NULL; + + parser->type_header_len = parser->data_len = parser->have_peer_caps = 0; + + usbredirparser_unserialize(parser_pub, data, len); + FREE(data); +} +#endif + +static void usbredirparser_queue(struct usbredirparser *parser, uint32_t type, + uint64_t id, void *type_header_in, uint8_t *data_in, int data_len); +static int usbredirparser_caps_get_cap(struct usbredirparser_priv *parser, + uint32_t *caps, int cap); + +struct usbredirparser *usbredirparser_create(void) +{ + return CALLOC(1, sizeof(struct usbredirparser_priv)); +} + +static void usbredirparser_verify_caps(struct usbredirparser_priv *parser, + uint32_t *caps, const char *desc) +{ + if (usbredirparser_caps_get_cap(parser, caps, + usb_redir_cap_bulk_streams) && + !usbredirparser_caps_get_cap(parser, caps, + usb_redir_cap_ep_info_max_packet_size)) { + ERROR("error %s caps contains cap_bulk_streams without" + "cap_ep_info_max_packet_size", desc); + caps[0] &= ~(1 << usb_redir_cap_bulk_streams); + } +} + +void usbredirparser_init(struct usbredirparser *parser_pub, + const char *version, uint32_t *caps, int caps_len, int flags) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + struct usb_redir_hello_header hello = { { 0 }, }; + + parser->flags = (flags & ~usbredirparser_fl_no_hello); + if (parser->callb.alloc_lock_func) { + parser->lock = parser->callb.alloc_lock_func(); + } + + snprintf(hello.version, sizeof(hello.version), "%s", version); + if (caps_len > USB_REDIR_CAPS_SIZE) { + caps_len = USB_REDIR_CAPS_SIZE; + } + memcpy(parser->our_caps, caps, caps_len * sizeof(uint32_t)); + /* libusbredirparser handles sending the ack internally */ + if (!(flags & usbredirparser_fl_usb_host)) + usbredirparser_caps_set_cap(parser->our_caps, + usb_redir_cap_device_disconnect_ack); + usbredirparser_verify_caps(parser, parser->our_caps, "our"); + if (!(flags & usbredirparser_fl_no_hello)) + usbredirparser_queue(parser_pub, usb_redir_hello, 0, &hello, + (uint8_t *)parser->our_caps, + USB_REDIR_CAPS_SIZE * sizeof(uint32_t)); +} + +void usbredirparser_destroy(struct usbredirparser *parser_pub) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + struct usbredirparser_buf *wbuf, *next_wbuf; + + wbuf = parser->write_buf; + while (wbuf) { + next_wbuf = wbuf->next; + FREE(wbuf->buf); + FREE(wbuf); + wbuf = next_wbuf; + } + + if (parser->lock) + parser->callb.free_lock_func(parser->lock); + + FREE(parser); +} + +static int usbredirparser_caps_get_cap(struct usbredirparser_priv *parser, + uint32_t *caps, int cap) +{ + if (cap / 32 >= USB_REDIR_CAPS_SIZE) { + ERROR("error request for out of bounds cap: %d", cap); + return 0; + } + if (caps[cap / 32] & (1 << (cap % 32))) { + return 1; + } else { + return 0; + } +} + +void usbredirparser_caps_set_cap(uint32_t *caps, int cap) +{ + caps[cap / 32] |= 1 << (cap % 32); +} + +int usbredirparser_have_peer_caps(struct usbredirparser *parser_pub) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + + return parser->have_peer_caps; +} + +int usbredirparser_peer_has_cap(struct usbredirparser *parser_pub, int cap) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + return usbredirparser_caps_get_cap(parser, parser->peer_caps, cap); +} + +int usbredirparser_have_cap(struct usbredirparser *parser_pub, int cap) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + return usbredirparser_caps_get_cap(parser, parser->our_caps, cap); +} + +static int usbredirparser_using_32bits_ids(struct usbredirparser *parser_pub) +{ + return !usbredirparser_have_cap(parser_pub, usb_redir_cap_64bits_ids) || + !usbredirparser_peer_has_cap(parser_pub, usb_redir_cap_64bits_ids); +} + +static void usbredirparser_handle_hello(struct usbredirparser *parser_pub, + struct usb_redir_hello_header *hello, uint8_t *data, int data_len) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + uint32_t *peer_caps = (uint32_t *)data; + char buf[64]; + int i; + + if (parser->have_peer_caps) { + ERROR("Received second hello message, ignoring"); + return; + } + + /* In case hello->version is not 0 terminated (which would be a protocol + violation)_ */ + snprintf(buf, sizeof(buf), "%s", hello->version); + + memset(parser->peer_caps, 0, sizeof(parser->peer_caps)); + if (data_len > sizeof(parser->peer_caps)) { + data_len = sizeof(parser->peer_caps); + } + for (i = 0; i < data_len / sizeof(uint32_t); i++) { + parser->peer_caps[i] = peer_caps[i]; + } + usbredirparser_verify_caps(parser, parser->peer_caps, "peer"); + parser->have_peer_caps = 1; + FREE(data); + + INFO("Peer version: %s, using %d-bits ids", buf, + usbredirparser_using_32bits_ids(parser_pub) ? 32 : 64); + + /* Added in 0.3.2, so no guarantee it is there */ + if (parser->callb.hello_func) + parser->callb.hello_func(parser->callb.priv, hello); +} + +static int usbredirparser_get_header_len(struct usbredirparser *parser_pub) +{ + if (usbredirparser_using_32bits_ids(parser_pub)) + return sizeof(struct usb_redir_header_32bit_id); + else + return sizeof(struct usb_redir_header); +} + +static int usbredirparser_get_type_header_len( + struct usbredirparser *parser_pub, int32_t type, int send) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + int command_for_host = 0; + + if (parser->flags & usbredirparser_fl_usb_host) { + command_for_host = 1; + } + if (send) { + command_for_host = !command_for_host; + } + + switch (type) { + case usb_redir_hello: + return sizeof(struct usb_redir_hello_header); + case usb_redir_device_connect: + if (!command_for_host) { + if (usbredirparser_have_cap(parser_pub, + usb_redir_cap_connect_device_version) && + usbredirparser_peer_has_cap(parser_pub, + usb_redir_cap_connect_device_version)) { + return sizeof(struct usb_redir_device_connect_header); + } else { + return sizeof(struct usb_redir_device_connect_header_no_device_version); + } + } else { + return -1; + } + case usb_redir_device_disconnect: + if (!command_for_host) { + return 0; + } else { + return -1; + } + case usb_redir_reset: + if (command_for_host) { + return 0; /* No packet type specific header */ + } else { + return -1; + } + case usb_redir_interface_info: + if (!command_for_host) { + return sizeof(struct usb_redir_interface_info_header); + } else { + return -1; + } + case usb_redir_ep_info: + if (!command_for_host) { + if (usbredirparser_have_cap(parser_pub, + usb_redir_cap_bulk_streams) && + usbredirparser_peer_has_cap(parser_pub, + usb_redir_cap_bulk_streams)) { + return sizeof(struct usb_redir_ep_info_header); + } else if (usbredirparser_have_cap(parser_pub, + usb_redir_cap_ep_info_max_packet_size) && + usbredirparser_peer_has_cap(parser_pub, + usb_redir_cap_ep_info_max_packet_size)) { + return sizeof(struct usb_redir_ep_info_header_no_max_streams); + } else { + return sizeof(struct usb_redir_ep_info_header_no_max_pktsz); + } + } else { + return -1; + } + case usb_redir_set_configuration: + if (command_for_host) { + return sizeof(struct usb_redir_set_configuration_header); + } else { + return -1; /* Should never be send to a guest */ + } + case usb_redir_get_configuration: + if (command_for_host) { + return 0; /* No packet type specific header */ + } else { + return -1; + } + case usb_redir_configuration_status: + if (!command_for_host) { + return sizeof(struct usb_redir_configuration_status_header); + } else { + return -1; + } + case usb_redir_set_alt_setting: + if (command_for_host) { + return sizeof(struct usb_redir_set_alt_setting_header); + } else { + return -1; + } + case usb_redir_get_alt_setting: + if (command_for_host) { + return sizeof(struct usb_redir_get_alt_setting_header); + } else { + return -1; + } + case usb_redir_alt_setting_status: + if (!command_for_host) { + return sizeof(struct usb_redir_alt_setting_status_header); + } else { + return -1; + } + case usb_redir_start_iso_stream: + if (command_for_host) { + return sizeof(struct usb_redir_start_iso_stream_header); + } else { + return -1; + } + case usb_redir_stop_iso_stream: + if (command_for_host) { + return sizeof(struct usb_redir_stop_iso_stream_header); + } else { + return -1; + } + case usb_redir_iso_stream_status: + if (!command_for_host) { + return sizeof(struct usb_redir_iso_stream_status_header); + } else { + return -1; + } + case usb_redir_start_interrupt_receiving: + if (command_for_host) { + return sizeof(struct usb_redir_start_interrupt_receiving_header); + } else { + return -1; + } + case usb_redir_stop_interrupt_receiving: + if (command_for_host) { + return sizeof(struct usb_redir_stop_interrupt_receiving_header); + } else { + return -1; + } + case usb_redir_interrupt_receiving_status: + if (!command_for_host) { + return sizeof(struct usb_redir_interrupt_receiving_status_header); + } else { + return -1; + } + case usb_redir_alloc_bulk_streams: + if (command_for_host) { + return sizeof(struct usb_redir_alloc_bulk_streams_header); + } else { + return -1; + } + case usb_redir_free_bulk_streams: + if (command_for_host) { + return sizeof(struct usb_redir_free_bulk_streams_header); + } else { + return -1; + } + case usb_redir_bulk_streams_status: + if (!command_for_host) { + return sizeof(struct usb_redir_bulk_streams_status_header); + } else { + return -1; + } + case usb_redir_cancel_data_packet: + if (command_for_host) { + return 0; /* No packet type specific header */ + } else { + return -1; + } + case usb_redir_filter_reject: + if (command_for_host) { + return 0; + } else { + return -1; + } + case usb_redir_filter_filter: + return 0; + case usb_redir_device_disconnect_ack: + if (command_for_host) { + return 0; + } else { + return -1; + } + case usb_redir_start_bulk_receiving: + if (command_for_host) { + return sizeof(struct usb_redir_start_bulk_receiving_header); + } else { + return -1; + } + case usb_redir_stop_bulk_receiving: + if (command_for_host) { + return sizeof(struct usb_redir_stop_bulk_receiving_header); + } else { + return -1; + } + case usb_redir_bulk_receiving_status: + if (!command_for_host) { + return sizeof(struct usb_redir_bulk_receiving_status_header); + } else { + return -1; + } + case usb_redir_control_packet: + return sizeof(struct usb_redir_control_packet_header); + case usb_redir_bulk_packet: + if (usbredirparser_have_cap(parser_pub, + usb_redir_cap_32bits_bulk_length) && + usbredirparser_peer_has_cap(parser_pub, + usb_redir_cap_32bits_bulk_length)) { + return sizeof(struct usb_redir_bulk_packet_header); + } else { + return sizeof(struct usb_redir_bulk_packet_header_16bit_length); + } + case usb_redir_iso_packet: + return sizeof(struct usb_redir_iso_packet_header); + case usb_redir_interrupt_packet: + return sizeof(struct usb_redir_interrupt_packet_header); + case usb_redir_buffered_bulk_packet: + if (!command_for_host) { + return sizeof(struct usb_redir_buffered_bulk_packet_header); + } else { + return -1; + } + default: + return -1; + } +} + +/* Note this function only checks if extra data is allowed for the + packet type being read at all, a check if it is actually allowed + given the direction of the packet + ep is done in _erify_type_header */ +static int usbredirparser_expect_extra_data(struct usbredirparser_priv *parser) +{ + switch (parser->header.type) { + case usb_redir_hello: /* For the variable length capabilities array */ + case usb_redir_filter_filter: + case usb_redir_control_packet: + case usb_redir_bulk_packet: + case usb_redir_iso_packet: + case usb_redir_interrupt_packet: + case usb_redir_buffered_bulk_packet: + return 1; + default: + return 0; + } +} + +static int usbredirparser_verify_bulk_recv_cap( + struct usbredirparser *parser_pub, int send) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + + if ((send && !usbredirparser_peer_has_cap(parser_pub, + usb_redir_cap_bulk_receiving)) || + (!send && !usbredirparser_have_cap(parser_pub, + usb_redir_cap_bulk_receiving))) { + ERROR("error bulk_receiving without cap_bulk_receiving"); + return 0; + } + return 1; /* Verify ok */ +} + +static int usbredirparser_verify_type_header( + struct usbredirparser *parser_pub, + int32_t type, void *header, uint8_t *data, int data_len, int send) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + int command_for_host = 0, expect_extra_data = 0; + int length = 0, ep = -1; + + if (parser->flags & usbredirparser_fl_usb_host) { + command_for_host = 1; + } + if (send) { + command_for_host = !command_for_host; + } + + switch (type) { + case usb_redir_interface_info: { + struct usb_redir_interface_info_header *intf_info = header; + + if (intf_info->interface_count > 32) { + ERROR("error interface_count > 32"); + return 0; + } + break; + } + case usb_redir_start_interrupt_receiving: { + struct usb_redir_start_interrupt_receiving_header *start_int = header; + + if (!(start_int->endpoint & 0x80)) { + ERROR("start int receiving on non input ep %02x", + start_int->endpoint); + return 0; + } + break; + } + case usb_redir_stop_interrupt_receiving: { + struct usb_redir_stop_interrupt_receiving_header *stop_int = header; + + if (!(stop_int->endpoint & 0x80)) { + ERROR("stop int receiving on non input ep %02x", + stop_int->endpoint); + return 0; + } + break; + } + case usb_redir_interrupt_receiving_status: { + struct usb_redir_interrupt_receiving_status_header *int_status = header; + + if (!(int_status->endpoint & 0x80)) { + ERROR("int receiving status for non input ep %02x", + int_status->endpoint); + return 0; + } + break; + } + case usb_redir_filter_reject: + if ((send && !usbredirparser_peer_has_cap(parser_pub, + usb_redir_cap_filter)) || + (!send && !usbredirparser_have_cap(parser_pub, + usb_redir_cap_filter))) { + ERROR("error filter_reject without cap_filter"); + return 0; + } + break; + case usb_redir_filter_filter: + if ((send && !usbredirparser_peer_has_cap(parser_pub, + usb_redir_cap_filter)) || + (!send && !usbredirparser_have_cap(parser_pub, + usb_redir_cap_filter))) { + ERROR("error filter_filter without cap_filter"); + return 0; + } + if (data_len < 1) { + ERROR("error filter_filter without data"); + return 0; + } + if (data[data_len - 1] != 0) { + ERROR("error non 0 terminated filter_filter data"); + return 0; + } + break; + case usb_redir_device_disconnect_ack: + if ((send && !usbredirparser_peer_has_cap(parser_pub, + usb_redir_cap_device_disconnect_ack)) || + (!send && !usbredirparser_have_cap(parser_pub, + usb_redir_cap_device_disconnect_ack))) { + ERROR("error device_disconnect_ack without cap_device_disconnect_ack"); + return 0; + } + break; + case usb_redir_start_bulk_receiving: { + struct usb_redir_start_bulk_receiving_header *start_bulk = header; + + if (!usbredirparser_verify_bulk_recv_cap(parser_pub, send)) { + return 0; + } + if (start_bulk->bytes_per_transfer > MAX_BULK_TRANSFER_SIZE) { + ERROR("start bulk receiving length exceeds limits %u > %u", + start_bulk->bytes_per_transfer, MAX_BULK_TRANSFER_SIZE); + return 0; + } + if (!(start_bulk->endpoint & 0x80)) { + ERROR("start bulk receiving on non input ep %02x", + start_bulk->endpoint); + return 0; + } + break; + } + case usb_redir_stop_bulk_receiving: { + struct usb_redir_stop_bulk_receiving_header *stop_bulk = header; + + if (!usbredirparser_verify_bulk_recv_cap(parser_pub, send)) { + return 0; + } + if (!(stop_bulk->endpoint & 0x80)) { + ERROR("stop bulk receiving on non input ep %02x", + stop_bulk->endpoint); + return 0; + } + break; + } + case usb_redir_bulk_receiving_status: { + struct usb_redir_bulk_receiving_status_header *bulk_status = header; + + if (!usbredirparser_verify_bulk_recv_cap(parser_pub, send)) { + return 0; + } + if (!(bulk_status->endpoint & 0x80)) { + ERROR("bulk receiving status for non input ep %02x", + bulk_status->endpoint); + return 0; + } + break; + } + case usb_redir_control_packet: + length = ((struct usb_redir_control_packet_header *)header)->length; + ep = ((struct usb_redir_control_packet_header *)header)->endpoint; + break; + case usb_redir_bulk_packet: { + struct usb_redir_bulk_packet_header *bulk_packet = header; + if (usbredirparser_have_cap(parser_pub, + usb_redir_cap_32bits_bulk_length) && + usbredirparser_peer_has_cap(parser_pub, + usb_redir_cap_32bits_bulk_length)) { + length = (bulk_packet->length_high << 16) | bulk_packet->length; + } else { + length = bulk_packet->length; + if (!send) + bulk_packet->length_high = 0; + } + if ((uint32_t)length > MAX_BULK_TRANSFER_SIZE) { + ERROR("bulk transfer length exceeds limits %u > %u", + (uint32_t)length, MAX_BULK_TRANSFER_SIZE); + return 0; + } + ep = bulk_packet->endpoint; + break; + } + case usb_redir_iso_packet: + length = ((struct usb_redir_iso_packet_header *)header)->length; + ep = ((struct usb_redir_iso_packet_header *)header)->endpoint; + break; + case usb_redir_interrupt_packet: + length = ((struct usb_redir_interrupt_packet_header *)header)->length; + ep = ((struct usb_redir_interrupt_packet_header *)header)->endpoint; + break; + case usb_redir_buffered_bulk_packet: { + struct usb_redir_buffered_bulk_packet_header *buf_bulk_pkt = header; + length = buf_bulk_pkt->length; + if (!usbredirparser_verify_bulk_recv_cap(parser_pub, send)) { + return 0; + } + if ((uint32_t)length > MAX_BULK_TRANSFER_SIZE) { + ERROR("buffered bulk transfer length exceeds limits %u > %u", + (uint32_t)length, MAX_BULK_TRANSFER_SIZE); + return 0; + } + ep = buf_bulk_pkt->endpoint; + break; + } + } + + if (ep != -1) { + if (((ep & 0x80) && !command_for_host) || + (!(ep & 0x80) && command_for_host)) { + expect_extra_data = 1; + } + if (expect_extra_data) { + if (data_len != length) { + ERROR("error data len %d != header len %d ep %02X", + data_len, length, ep); + return 0; + } + } else { + if (data || data_len) { + ERROR("error unexpected extra data ep %02X", ep); + return 0; + } + switch (type) { + case usb_redir_iso_packet: + ERROR("error iso packet send in wrong direction"); + return 0; + case usb_redir_interrupt_packet: + if (command_for_host) { + ERROR("error interrupt packet send in wrong direction"); + return 0; + } + break; + case usb_redir_buffered_bulk_packet: + ERROR("error buffered bulk packet send in wrong direction"); + return 0; + } + } + } + + return 1; /* Verify ok */ +} + +static void usbredirparser_call_type_func(struct usbredirparser *parser_pub) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + uint64_t id; + + if (usbredirparser_using_32bits_ids(parser_pub)) + id = parser->header_32bit_id.id; + else + id = parser->header.id; + + switch (parser->header.type) { + case usb_redir_hello: + usbredirparser_handle_hello(parser_pub, + (struct usb_redir_hello_header *)parser->type_header, + parser->data, parser->data_len); + break; + case usb_redir_device_connect: + parser->callb.device_connect_func(parser->callb.priv, + (struct usb_redir_device_connect_header *)parser->type_header); + break; + case usb_redir_device_disconnect: + parser->callb.device_disconnect_func(parser->callb.priv); + if (usbredirparser_peer_has_cap(parser_pub, + usb_redir_cap_device_disconnect_ack)) + usbredirparser_queue(parser_pub, usb_redir_device_disconnect_ack, + 0, NULL, NULL, 0); + break; + case usb_redir_reset: + parser->callb.reset_func(parser->callb.priv); + break; + case usb_redir_interface_info: + parser->callb.interface_info_func(parser->callb.priv, + (struct usb_redir_interface_info_header *)parser->type_header); + break; + case usb_redir_ep_info: + parser->callb.ep_info_func(parser->callb.priv, + (struct usb_redir_ep_info_header *)parser->type_header); + break; + case usb_redir_set_configuration: + parser->callb.set_configuration_func(parser->callb.priv, id, + (struct usb_redir_set_configuration_header *)parser->type_header); + break; + case usb_redir_get_configuration: + parser->callb.get_configuration_func(parser->callb.priv, id); + break; + case usb_redir_configuration_status: + parser->callb.configuration_status_func(parser->callb.priv, id, + (struct usb_redir_configuration_status_header *)parser->type_header); + break; + case usb_redir_set_alt_setting: + parser->callb.set_alt_setting_func(parser->callb.priv, id, + (struct usb_redir_set_alt_setting_header *)parser->type_header); + break; + case usb_redir_get_alt_setting: + parser->callb.get_alt_setting_func(parser->callb.priv, id, + (struct usb_redir_get_alt_setting_header *)parser->type_header); + break; + case usb_redir_alt_setting_status: + parser->callb.alt_setting_status_func(parser->callb.priv, id, + (struct usb_redir_alt_setting_status_header *)parser->type_header); + break; + case usb_redir_start_iso_stream: + parser->callb.start_iso_stream_func(parser->callb.priv, id, + (struct usb_redir_start_iso_stream_header *)parser->type_header); + break; + case usb_redir_stop_iso_stream: + parser->callb.stop_iso_stream_func(parser->callb.priv, id, + (struct usb_redir_stop_iso_stream_header *)parser->type_header); + break; + case usb_redir_iso_stream_status: + parser->callb.iso_stream_status_func(parser->callb.priv, id, + (struct usb_redir_iso_stream_status_header *)parser->type_header); + break; + case usb_redir_start_interrupt_receiving: + parser->callb.start_interrupt_receiving_func(parser->callb.priv, id, + (struct usb_redir_start_interrupt_receiving_header *) + parser->type_header); + break; + case usb_redir_stop_interrupt_receiving: + parser->callb.stop_interrupt_receiving_func(parser->callb.priv, id, + (struct usb_redir_stop_interrupt_receiving_header *) + parser->type_header); + break; + case usb_redir_interrupt_receiving_status: + parser->callb.interrupt_receiving_status_func(parser->callb.priv, id, + (struct usb_redir_interrupt_receiving_status_header *) + parser->type_header); + break; + case usb_redir_alloc_bulk_streams: + parser->callb.alloc_bulk_streams_func(parser->callb.priv, id, + (struct usb_redir_alloc_bulk_streams_header *)parser->type_header); + break; + case usb_redir_free_bulk_streams: + parser->callb.free_bulk_streams_func(parser->callb.priv, id, + (struct usb_redir_free_bulk_streams_header *)parser->type_header); + break; + case usb_redir_bulk_streams_status: + parser->callb.bulk_streams_status_func(parser->callb.priv, id, + (struct usb_redir_bulk_streams_status_header *)parser->type_header); + break; + case usb_redir_cancel_data_packet: + parser->callb.cancel_data_packet_func(parser->callb.priv, id); + break; + case usb_redir_filter_reject: + parser->callb.filter_reject_func(parser->callb.priv); + break; + case usb_redir_filter_filter: { + struct usbredirfilter_rule *rules; + int r, count; + + r = usbredirfilter_string_to_rules((char *)parser->data, ",", "|", + &rules, &count); + if (r) { + ERROR("error parsing filter (%d), ignoring filter message", r); + break; + } + parser->callb.filter_filter_func(parser->callb.priv, rules, count); + break; + } + case usb_redir_device_disconnect_ack: + parser->callb.device_disconnect_ack_func(parser->callb.priv); + break; + case usb_redir_start_bulk_receiving: + parser->callb.start_bulk_receiving_func(parser->callb.priv, id, + (struct usb_redir_start_bulk_receiving_header *) + parser->type_header); + break; + case usb_redir_stop_bulk_receiving: + parser->callb.stop_bulk_receiving_func(parser->callb.priv, id, + (struct usb_redir_stop_bulk_receiving_header *) + parser->type_header); + break; + case usb_redir_bulk_receiving_status: + parser->callb.bulk_receiving_status_func(parser->callb.priv, id, + (struct usb_redir_bulk_receiving_status_header *) + parser->type_header); + break; + case usb_redir_control_packet: + parser->callb.control_packet_func(parser->callb.priv, id, + (struct usb_redir_control_packet_header *)parser->type_header, + parser->data, parser->data_len); + break; + case usb_redir_bulk_packet: + parser->callb.bulk_packet_func(parser->callb.priv, id, + (struct usb_redir_bulk_packet_header *)parser->type_header, + parser->data, parser->data_len); + break; + case usb_redir_iso_packet: + parser->callb.iso_packet_func(parser->callb.priv, id, + (struct usb_redir_iso_packet_header *)parser->type_header, + parser->data, parser->data_len); + break; + case usb_redir_interrupt_packet: + parser->callb.interrupt_packet_func(parser->callb.priv, id, + (struct usb_redir_interrupt_packet_header *)parser->type_header, + parser->data, parser->data_len); + break; + case usb_redir_buffered_bulk_packet: + parser->callb.buffered_bulk_packet_func(parser->callb.priv, id, + (struct usb_redir_buffered_bulk_packet_header *)parser->type_header, + parser->data, parser->data_len); + break; + } +} + +int usbredirparser_do_read(struct usbredirparser *parser_pub) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + int r, header_len, type_header_len, data_len; + uint8_t *dest; + + header_len = usbredirparser_get_header_len(parser_pub); + + /* Skip forward to next packet (only used in error conditions) */ + while (parser->to_skip > 0) { + uint8_t buf[1024]; + r = (parser->to_skip > sizeof(buf)) ? sizeof(buf) : parser->to_skip; + r = parser->callb.read_func(parser->callb.priv, buf, r); + if (r <= 0) + return r; + parser->to_skip -= r; + } + + /* Consume data until read would block or returns an error */ + while (1) { + if (parser->header_read < header_len) { + r = header_len - parser->header_read; + dest = (uint8_t *)&parser->header + parser->header_read; + } else if (parser->type_header_read < parser->type_header_len) { + r = parser->type_header_len - parser->type_header_read; + dest = parser->type_header + parser->type_header_read; + } else { + r = parser->data_len - parser->data_read; + dest = parser->data + parser->data_read; + } + + if (r > 0) { + r = parser->callb.read_func(parser->callb.priv, dest, r); + if (r <= 0) { + return r; + } + } + + if (parser->header_read < header_len) { + parser->header_read += r; + if (parser->header_read == header_len) { + type_header_len = + usbredirparser_get_type_header_len(parser_pub, + parser->header.type, 0); + if (type_header_len < 0) { + ERROR("error invalid usb-redir packet type: %u", + parser->header.type); + parser->to_skip = parser->header.length; + parser->header_read = 0; + return -2; + } + /* This should never happen */ + if (type_header_len > sizeof(parser->type_header)) { + ERROR("error type specific header buffer too small, please report!!"); + parser->to_skip = parser->header.length; + parser->header_read = 0; + return -2; + } + if ((int)parser->header.length < type_header_len || + ((int)parser->header.length > type_header_len && + !usbredirparser_expect_extra_data(parser))) { + ERROR("error invalid packet type %u length: %u", + parser->header.type, parser->header.length); + parser->to_skip = parser->header.length; + parser->header_read = 0; + return -2; + } + data_len = parser->header.length - type_header_len; + if (data_len) { + parser->data = MALLOC(data_len); + if (!parser->data) { + ERROR("Out of memory allocating data buffer"); + parser->to_skip = parser->header.length; + parser->header_read = 0; + return -2; + } + } + parser->type_header_len = type_header_len; + parser->data_len = data_len; + } + } else if (parser->type_header_read < parser->type_header_len) { + parser->type_header_read += r; + } else { + parser->data_read += r; + if (parser->data_read == parser->data_len) { + r = usbredirparser_verify_type_header(parser_pub, + parser->header.type, parser->type_header, + parser->data, parser->data_len, 0); + if (r) + usbredirparser_call_type_func(parser_pub); + parser->header_read = 0; + parser->type_header_len = 0; + parser->type_header_read = 0; + parser->data_len = 0; + parser->data_read = 0; + parser->data = NULL; + if (!r) + return -2; + /* header len may change if this was an hello packet */ + header_len = usbredirparser_get_header_len(parser_pub); + } + } + } +} + +int usbredirparser_has_data_to_write(struct usbredirparser *parser_pub) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + return parser->write_buf_count; +} + +int usbredirparser_do_write(struct usbredirparser *parser_pub) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + struct usbredirparser_buf* wbuf; + int w, ret = 0; + + LOCK(parser); + for (;;) { + wbuf = parser->write_buf; + if (!wbuf) + break; + + w = wbuf->len - wbuf->pos; + w = parser->callb.write_func(parser->callb.priv, + wbuf->buf + wbuf->pos, w); + if (w <= 0) { + ret = w; + break; + } + + /* See usbredirparser_write documentation */ + if ((parser->flags & usbredirparser_fl_write_cb_owns_buffer) && + w != wbuf->len) { +#if ! defined(__KERNEL__) + abort(); +#else + ret = -1; + break; +#endif + } + + wbuf->pos += w; + if (wbuf->pos == wbuf->len) { + parser->write_buf = wbuf->next; + if (!(parser->flags & usbredirparser_fl_write_cb_owns_buffer)) + FREE(wbuf->buf); + FREE(wbuf); + parser->write_buf_count--; + } + } + UNLOCK(parser); + return ret; +} + +void usbredirparser_free_write_buffer(struct usbredirparser *parser, + uint8_t *data) +{ + FREE(data); +} + +void usbredirparser_free_packet_data(struct usbredirparser *parser, + uint8_t *data) +{ + FREE(data); +} + +static void usbredirparser_queue(struct usbredirparser *parser_pub, + uint32_t type, uint64_t id, void *type_header_in, + uint8_t *data_in, int data_len) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + uint8_t *buf, *type_header_out, *data_out; + struct usb_redir_header *header; + struct usbredirparser_buf *wbuf, *new_wbuf; + int header_len, type_header_len; + + header_len = usbredirparser_get_header_len(parser_pub); + type_header_len = usbredirparser_get_type_header_len(parser_pub, type, 1); + if (type_header_len < 0) { /* This should never happen */ + ERROR("error packet type unknown with internal call, please report!!"); + return; + } + + if (!usbredirparser_verify_type_header(parser_pub, type, type_header_in, + data_in, data_len, 1)) { + ERROR("error usbredirparser_send_* call invalid params, please report!!"); + return; + } + + new_wbuf = CALLOC(1, sizeof(*new_wbuf)); + buf = MALLOC(header_len + type_header_len + data_len); + if (!new_wbuf || !buf) { + ERROR("Out of memory allocating buffer to send packet, dropping!"); + FREE(new_wbuf); FREE(buf); + return; + } + + new_wbuf->buf = buf; + new_wbuf->len = header_len + type_header_len + data_len; + + header = (struct usb_redir_header *)buf; + type_header_out = buf + header_len; + data_out = type_header_out + type_header_len; + + header->type = type; + header->length = type_header_len + data_len; + if (usbredirparser_using_32bits_ids(parser_pub)) + ((struct usb_redir_header_32bit_id *)header)->id = id; + else + header->id = id; + memcpy(type_header_out, type_header_in, type_header_len); + memcpy(data_out, data_in, data_len); + + LOCK(parser); + if (!parser->write_buf) { + parser->write_buf = new_wbuf; + } else { + /* limiting the write_buf's stack depth is our users responsibility */ + wbuf = parser->write_buf; + while (wbuf->next) + wbuf = wbuf->next; + + wbuf->next = new_wbuf; + } + parser->write_buf_count++; + UNLOCK(parser); +} + +void usbredirparser_send_device_connect(struct usbredirparser *parser, + struct usb_redir_device_connect_header *device_connect) +{ + usbredirparser_queue(parser, usb_redir_device_connect, 0, device_connect, + NULL, 0); +} + +void usbredirparser_send_device_disconnect(struct usbredirparser *parser) +{ + usbredirparser_queue(parser, usb_redir_device_disconnect, 0, NULL, + NULL, 0); +} + +void usbredirparser_send_reset(struct usbredirparser *parser) +{ + usbredirparser_queue(parser, usb_redir_reset, 0, NULL, NULL, 0); +} + +void usbredirparser_send_interface_info(struct usbredirparser *parser, + struct usb_redir_interface_info_header *interface_info) +{ + usbredirparser_queue(parser, usb_redir_interface_info, 0, interface_info, + NULL, 0); +} + +void usbredirparser_send_ep_info(struct usbredirparser *parser, + struct usb_redir_ep_info_header *ep_info) +{ + usbredirparser_queue(parser, usb_redir_ep_info, 0, ep_info, NULL, 0); +} + +void usbredirparser_send_set_configuration(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_set_configuration_header *set_configuration) +{ + usbredirparser_queue(parser, usb_redir_set_configuration, id, + set_configuration, NULL, 0); +} + +void usbredirparser_send_get_configuration(struct usbredirparser *parser, + uint64_t id) +{ + usbredirparser_queue(parser, usb_redir_get_configuration, id, + NULL, NULL, 0); +} + +void usbredirparser_send_configuration_status(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_configuration_status_header *configuration_status) +{ + usbredirparser_queue(parser, usb_redir_configuration_status, id, + configuration_status, NULL, 0); +} + +void usbredirparser_send_set_alt_setting(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_set_alt_setting_header *set_alt_setting) +{ + usbredirparser_queue(parser, usb_redir_set_alt_setting, id, + set_alt_setting, NULL, 0); +} + +void usbredirparser_send_get_alt_setting(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_get_alt_setting_header *get_alt_setting) +{ + usbredirparser_queue(parser, usb_redir_get_alt_setting, id, + get_alt_setting, NULL, 0); +} + +void usbredirparser_send_alt_setting_status(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_alt_setting_status_header *alt_setting_status) +{ + usbredirparser_queue(parser, usb_redir_alt_setting_status, id, + alt_setting_status, NULL, 0); +} + +void usbredirparser_send_start_iso_stream(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_start_iso_stream_header *start_iso_stream) +{ + usbredirparser_queue(parser, usb_redir_start_iso_stream, id, + start_iso_stream, NULL, 0); +} + +void usbredirparser_send_stop_iso_stream(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_stop_iso_stream_header *stop_iso_stream) +{ + usbredirparser_queue(parser, usb_redir_stop_iso_stream, id, + stop_iso_stream, NULL, 0); +} + +void usbredirparser_send_iso_stream_status(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_iso_stream_status_header *iso_stream_status) +{ + usbredirparser_queue(parser, usb_redir_iso_stream_status, id, + iso_stream_status, NULL, 0); +} + +void usbredirparser_send_start_interrupt_receiving(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_start_interrupt_receiving_header *start_interrupt_receiving) +{ + usbredirparser_queue(parser, usb_redir_start_interrupt_receiving, id, + start_interrupt_receiving, NULL, 0); +} + +void usbredirparser_send_stop_interrupt_receiving(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_stop_interrupt_receiving_header *stop_interrupt_receiving) +{ + usbredirparser_queue(parser, usb_redir_stop_interrupt_receiving, id, + stop_interrupt_receiving, NULL, 0); +} + +void usbredirparser_send_interrupt_receiving_status(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_interrupt_receiving_status_header *interrupt_receiving_status) +{ + usbredirparser_queue(parser, usb_redir_interrupt_receiving_status, id, + interrupt_receiving_status, NULL, 0); +} + +void usbredirparser_send_alloc_bulk_streams(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_alloc_bulk_streams_header *alloc_bulk_streams) +{ + usbredirparser_queue(parser, usb_redir_alloc_bulk_streams, id, + alloc_bulk_streams, NULL, 0); +} + +void usbredirparser_send_free_bulk_streams(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_free_bulk_streams_header *free_bulk_streams) +{ + usbredirparser_queue(parser, usb_redir_free_bulk_streams, id, + free_bulk_streams, NULL, 0); +} + +void usbredirparser_send_bulk_streams_status(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_bulk_streams_status_header *bulk_streams_status) +{ + usbredirparser_queue(parser, usb_redir_bulk_streams_status, id, + bulk_streams_status, NULL, 0); +} + +void usbredirparser_send_cancel_data_packet(struct usbredirparser *parser, + uint64_t id) +{ + usbredirparser_queue(parser, usb_redir_cancel_data_packet, id, + NULL, NULL, 0); +} + +void usbredirparser_send_filter_reject(struct usbredirparser *parser) +{ + if (!usbredirparser_peer_has_cap(parser, usb_redir_cap_filter)) + return; + + usbredirparser_queue(parser, usb_redir_filter_reject, 0, NULL, NULL, 0); +} + +void usbredirparser_send_filter_filter(struct usbredirparser *parser_pub, + const struct usbredirfilter_rule *rules, int rules_count) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + char *str; + + if (!usbredirparser_peer_has_cap(parser_pub, usb_redir_cap_filter)) + return; + + str = usbredirfilter_rules_to_string(rules, rules_count, ",", "|"); + if (!str) { + ERROR("error creating filter string, not sending filter"); + return; + } + usbredirparser_queue(parser_pub, usb_redir_filter_filter, 0, NULL, + (uint8_t *)str, strlen(str) + 1); + FREE(str); +} + +void usbredirparser_send_start_bulk_receiving(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_start_bulk_receiving_header *start_bulk_receiving) +{ + usbredirparser_queue(parser, usb_redir_start_bulk_receiving, id, + start_bulk_receiving, NULL, 0); +} + +void usbredirparser_send_stop_bulk_receiving(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_stop_bulk_receiving_header *stop_bulk_receiving) +{ + usbredirparser_queue(parser, usb_redir_stop_bulk_receiving, id, + stop_bulk_receiving, NULL, 0); +} + +void usbredirparser_send_bulk_receiving_status(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_bulk_receiving_status_header *bulk_receiving_status) +{ + usbredirparser_queue(parser, usb_redir_bulk_receiving_status, id, + bulk_receiving_status, NULL, 0); +} + +/* Data packets: */ +void usbredirparser_send_control_packet(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_control_packet_header *control_header, + uint8_t *data, int data_len) +{ + usbredirparser_queue(parser, usb_redir_control_packet, id, control_header, + data, data_len); +} + +void usbredirparser_send_bulk_packet(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_bulk_packet_header *bulk_header, + uint8_t *data, int data_len) +{ + usbredirparser_queue(parser, usb_redir_bulk_packet, id, bulk_header, + data, data_len); +} + +void usbredirparser_send_iso_packet(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_iso_packet_header *iso_header, + uint8_t *data, int data_len) +{ + usbredirparser_queue(parser, usb_redir_iso_packet, id, iso_header, + data, data_len); +} + +void usbredirparser_send_interrupt_packet(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_interrupt_packet_header *interrupt_header, + uint8_t *data, int data_len) +{ + usbredirparser_queue(parser, usb_redir_interrupt_packet, id, + interrupt_header, data, data_len); +} + +void usbredirparser_send_buffered_bulk_packet(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_buffered_bulk_packet_header *buffered_bulk_header, + uint8_t *data, int data_len) +{ + usbredirparser_queue(parser, usb_redir_buffered_bulk_packet, id, + buffered_bulk_header, data, data_len); +} + +/****** Serialization support ******/ + +#define USBREDIRPARSER_SERIALIZE_MAGIC 0x55525031 +#define USBREDIRPARSER_SERIALIZE_BUF_SIZE 65536 + +/* Serialization format, send and receiving endian are expected to be the same! + uint32 MAGIC: 0x55525031 ascii: URP1 (UsbRedirParser version 1) + uint32 len: length of the entire serialized state, including MAGIC + uint32 our_caps_len + uint32 our_caps[our_caps_len] + uint32 peer_caps_len + uint32 peer_caps[peer_caps_len] + uint32 to_skip + uint32 header_read + uint8 header[header_read] + uint32 type_header_read + uint8 type_header[type_header_read] + uint32 data_read + uint8 data[data_read] + uint32 write_buf_count: followed by write_buf_count times: + uint32 write_buf_len + uint8 write_buf_data[write_buf_len] +*/ + +static int serialize_alloc(struct usbredirparser_priv *parser, + uint8_t **state, uint8_t **pos, + uint32_t *remain, uint32_t needed) +{ + uint8_t *old_state = *state; + uint32_t used, size; + + if (*remain >= needed) + return 0; + + used = *pos - *state; + size = (used + needed + USBREDIRPARSER_SERIALIZE_BUF_SIZE - 1) & + ~(USBREDIRPARSER_SERIALIZE_BUF_SIZE - 1); + + *state = REALLOC(*state, size); + if (!*state) { + FREE(old_state); + ERROR("Out of memory allocating serialization buffer"); + return -1; + } + + *pos = *state + used; + *remain = size - used; + + return 0; +} + +static int serialize_int(struct usbredirparser_priv *parser, + uint8_t **state, uint8_t **pos, uint32_t *remain, + uint32_t val, const char *desc) +{ + DEBUGP("serializing int %08x : %s", val, desc); + + if (serialize_alloc(parser, state, pos, remain, sizeof(uint32_t))) + return -1; + + memcpy(*pos, &val, sizeof(uint32_t)); + *pos += sizeof(uint32_t); + *remain -= sizeof(uint32_t); + + return 0; +} + +static int unserialize_int(struct usbredirparser_priv *parser, + uint8_t **pos, uint32_t *remain, uint32_t *val, + const char *desc) +{ + if (*remain < sizeof(uint32_t)) { + ERROR("error buffer underrun while unserializing state"); + return -1; + } + memcpy(val, *pos, sizeof(uint32_t)); + *pos += sizeof(uint32_t); + *remain -= sizeof(uint32_t); + + DEBUGP("unserialized int %08x : %s", *val, desc); + + return 0; +} + +static int serialize_data(struct usbredirparser_priv *parser, + uint8_t **state, uint8_t **pos, uint32_t *remain, + uint8_t *data, uint32_t len, const char *desc) +{ + DEBUGP("serializing %d bytes of %s data", len, desc); + if (len >= 8) + DEBUGP("First 8 bytes of %s: %02x %02x %02x %02x %02x %02x %02x %02x", + desc, data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7]); + + if (serialize_alloc(parser, state, pos, remain, sizeof(uint32_t) + len)) + return -1; + + memcpy(*pos, &len, sizeof(uint32_t)); + *pos += sizeof(uint32_t); + *remain -= sizeof(uint32_t); + + memcpy(*pos, data, len); + *pos += len; + *remain -= len; + + return 0; +} + +/* If *data == NULL, allocs buffer dynamically, else len_in_out must contain + the length of the passed in buffer. */ +static int unserialize_data(struct usbredirparser_priv *parser, + uint8_t **pos, uint32_t *remain, + uint8_t **data, uint32_t *len_in_out, + const char *desc) +{ + uint32_t len; + + if (*remain < sizeof(uint32_t)) { + ERROR("error buffer underrun while unserializing state"); + return -1; + } + memcpy(&len, *pos, sizeof(uint32_t)); + *pos += sizeof(uint32_t); + *remain -= sizeof(uint32_t); + + if (*remain < len) { + ERROR("error buffer underrun while unserializing state"); + return -1; + } + if (*data == NULL && len > 0) { + *data = MALLOC(len); + if (!*data) { + ERROR("Out of memory allocating unserialize buffer"); + return -1; + } + } else { + if (*len_in_out < len) { + ERROR("error buffer overrun while unserializing state"); + return -1; + } + } + + memcpy(*data, *pos, len); + *pos += len; + *remain -= len; + *len_in_out = len; + + DEBUGP("unserialized %d bytes of %s data", len, desc); + if (len >= 8) + DEBUGP("First 8 bytes of %s: %02x %02x %02x %02x %02x %02x %02x %02x", + desc, (*data)[0], (*data)[1], (*data)[2], (*data)[3], + (*data)[4], (*data)[5], (*data)[6], (*data)[7]); + + return 0; +} + +int usbredirparser_serialize(struct usbredirparser *parser_pub, + uint8_t **state_dest, int *state_len) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + struct usbredirparser_buf *wbuf; + uint8_t *write_buf_count_pos, *state = NULL, *pos = NULL; + uint32_t write_buf_count = 0, len, remain = 0; + + *state_dest = NULL; + *state_len = 0; + + if (serialize_int(parser, &state, &pos, &remain, + USBREDIRPARSER_SERIALIZE_MAGIC, "magic")) + return -1; + + /* To be replaced with length later */ + if (serialize_int(parser, &state, &pos, &remain, 0, "length")) + return -1; + + if (serialize_data(parser, &state, &pos, &remain, + (uint8_t *)parser->our_caps, + USB_REDIR_CAPS_SIZE * sizeof(int32_t), "our_caps")) + return -1; + + if (parser->have_peer_caps) { + if (serialize_data(parser, &state, &pos, &remain, + (uint8_t *)parser->peer_caps, + USB_REDIR_CAPS_SIZE * sizeof(int32_t), "peer_caps")) + return -1; + } else { + if (serialize_int(parser, &state, &pos, &remain, 0, "peer_caps_len")) + return -1; + } + + if (serialize_int(parser, &state, &pos, &remain, parser->to_skip, "skip")) + return -1; + + if (serialize_data(parser, &state, &pos, &remain, + (uint8_t *)&parser->header, parser->header_read, + "header")) + return -1; + + if (serialize_data(parser, &state, &pos, &remain, + parser->type_header, parser->type_header_read, + "type_header")) + return -1; + + if (serialize_data(parser, &state, &pos, &remain, + parser->data, parser->data_read, "packet-data")) + return -1; + + write_buf_count_pos = pos; + /* To be replaced with write_buf_count later */ + if (serialize_int(parser, &state, &pos, &remain, 0, "write_buf_count")) + return -1; + + wbuf = parser->write_buf; + while (wbuf) { + if (serialize_data(parser, &state, &pos, &remain, + wbuf->buf + wbuf->pos, wbuf->len - wbuf->pos, + "write-buf")) + return -1; + write_buf_count++; + wbuf = wbuf->next; + } + /* Patch in write_buf_count */ + memcpy(write_buf_count_pos, &write_buf_count, sizeof(int32_t)); + + /* Patch in length */ + len = pos - state; + memcpy(state + sizeof(int32_t), &len, sizeof(int32_t)); + + *state_dest = state; + *state_len = len; + + return 0; +} + +int usbredirparser_unserialize(struct usbredirparser *parser_pub, + uint8_t *state, int len) +{ + struct usbredirparser_priv *parser = + (struct usbredirparser_priv *)parser_pub; + struct usbredirparser_buf *wbuf, **next; + uint32_t orig_caps[USB_REDIR_CAPS_SIZE]; + uint8_t *data; + uint32_t i, l, header_len, remain = len; + + if (unserialize_int(parser, &state, &remain, &i, "magic")) + return -1; + if (i != USBREDIRPARSER_SERIALIZE_MAGIC) { + ERROR("error unserialize magic mismatch"); + return -1; + } + + if (unserialize_int(parser, &state, &remain, &i, "length")) + return -1; + if (i != len) { + ERROR("error unserialize length mismatch"); + return -1; + } + + data = (uint8_t *)parser->our_caps; + i = USB_REDIR_CAPS_SIZE * sizeof(int32_t); + memcpy(orig_caps, parser->our_caps, i); + if (unserialize_data(parser, &state, &remain, &data, &i, "our_caps")) + return -1; + if (memcmp(parser->our_caps, orig_caps, + USB_REDIR_CAPS_SIZE * sizeof(int32_t)) != 0) { + ERROR("error unserialize caps mismatch"); + return -1; + } + + data = (uint8_t *)parser->peer_caps; + i = USB_REDIR_CAPS_SIZE * sizeof(int32_t); + if (unserialize_data(parser, &state, &remain, &data, &i, "peer_caps")) + return -1; + if (i) + parser->have_peer_caps = 1; + + if (unserialize_int(parser, &state, &remain, &i, "skip")) + return -1; + parser->to_skip = i; + + header_len = usbredirparser_get_header_len(parser_pub); + data = (uint8_t *)&parser->header; + i = header_len; + if (unserialize_data(parser, &state, &remain, &data, &i, "header")) + return -1; + parser->header_read = i; + + /* Set various length field froms the header (if we've a header) */ + if (parser->header_read == header_len) { + int type_header_len = + usbredirparser_get_type_header_len(parser_pub, + parser->header.type, 0); + if (type_header_len < 0 || + type_header_len > sizeof(parser->type_header) || + parser->header.length < type_header_len || + (parser->header.length > type_header_len && + !usbredirparser_expect_extra_data(parser))) { + ERROR("error unserialize packet header invalid"); + return -1; + } + parser->type_header_len = type_header_len; + parser->data_len = parser->header.length - type_header_len; + } + + data = parser->type_header; + i = parser->type_header_len; + if (unserialize_data(parser, &state, &remain, &data, &i, "type_header")) + return -1; + parser->type_header_read = i; + + if (parser->data_len) { + parser->data = MALLOC(parser->data_len); + if (!parser->data) { + ERROR("Out of memory allocating unserialize buffer"); + return -1; + } + } + i = parser->data_len; + if (unserialize_data(parser, &state, &remain, &parser->data, &i, "data")) + return -1; + parser->data_read = i; + + /* Get the write buffer count and the write buffers */ + if (unserialize_int(parser, &state, &remain, &i, "write_buf_count")) + return -1; + next = &parser->write_buf; + while (i) { + wbuf = CALLOC(1, sizeof(*wbuf)); + if (!wbuf) { + ERROR("Out of memory allocating unserialize buffer"); + return -1; + } + *next = wbuf; + l = 0; + if (unserialize_data(parser, &state, &remain, &wbuf->buf, &l, "wbuf")) + return -1; + wbuf->len = l; + next = &wbuf->next; + i--; + } + + if (remain) { + ERROR("error unserialize %d bytes of extraneous state data", remain); + return -1; + } + + return 0; +} diff --git a/drivers/usb/usbredir/usbredirparser.h b/drivers/usb/usbredir/usbredirparser.h new file mode 100644 index 0000000..ffeb1d7 --- /dev/null +++ b/drivers/usb/usbredir/usbredirparser.h @@ -0,0 +1,386 @@ +/* usbredirparser.h usb redirection protocol parser header + + Copyright 2010-2012 Red Hat, Inc. + + Red Hat Authors: + Hans de Goede + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . +*/ +#ifndef __USBREDIRPARSER_H +#define __USBREDIRPARSER_H + +#include "usbredirproto.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct usbredirparser; +struct usbredirfilter_rule; + +/* Called by a usbredirparser to log various messages */ +enum { usbredirparser_none, usbredirparser_error, usbredirparser_warning, + usbredirparser_info, usbredirparser_debug, usbredirparser_debug_data }; +typedef void (*usbredirparser_log)(void *priv, int level, const char *msg); + +/* Called by a usbredirparser to read/write data to its peer. + Must return the amount of bytes read/written, 0 when the read/write would + block (and this is undesirable) and -1 on error. + + If the usbredirparser_fl_write_cb_owns_buffer flag is passed to + usbredirparser_init, then the usbredirparser_write callback becomes + the owner of the buffer pointed to by data and should call + usbredirparser_free_write_buffer() when it is done with the buffer. + In this case the callback is not allowed to return any amount of bytes + written, it must either accept the entire buffer (return count), + or signal blocking (return 0) or error (return -1). Returning any other + value will result in a call to abort(). +*/ +typedef int (*usbredirparser_read)(void *priv, uint8_t *data, int count); +typedef int (*usbredirparser_write)(void *priv, uint8_t *data, int count); + +/* Locking functions for use by multithread apps */ +typedef void *(*usbredirparser_alloc_lock)(void); +typedef void (*usbredirparser_lock)(void *lock); +typedef void (*usbredirparser_unlock)(void *lock); +typedef void (*usbredirparser_free_lock)(void *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. + + Control packets: */ +typedef void (*usbredirparser_hello)(void *priv, + struct usb_redir_hello_header *hello); +typedef void (*usbredirparser_device_connect)(void *priv, + struct usb_redir_device_connect_header *device_connect); +typedef void (*usbredirparser_device_disconnect)(void *priv); +typedef void (*usbredirparser_reset)(void *priv); +typedef void (*usbredirparser_interface_info)(void *priv, + struct usb_redir_interface_info_header *interface_info); +typedef void (*usbredirparser_ep_info)(void *priv, + struct usb_redir_ep_info_header *ep_info); +typedef void (*usbredirparser_set_configuration)(void *priv, + uint64_t id, struct usb_redir_set_configuration_header *set_configuration); +typedef void (*usbredirparser_get_configuration)(void *priv, uint64_t id); +typedef void (*usbredirparser_configuration_status)(void *priv, + uint64_t id, struct usb_redir_configuration_status_header *configuration_status); +typedef void (*usbredirparser_set_alt_setting)(void *priv, + uint64_t id, struct usb_redir_set_alt_setting_header *set_alt_setting); +typedef void (*usbredirparser_get_alt_setting)(void *priv, + uint64_t id, struct usb_redir_get_alt_setting_header *get_alt_setting); +typedef void (*usbredirparser_alt_setting_status)(void *priv, + uint64_t id, struct usb_redir_alt_setting_status_header *alt_setting_status); +typedef void (*usbredirparser_start_iso_stream)(void *priv, + uint64_t id, struct usb_redir_start_iso_stream_header *start_iso_stream); +typedef void (*usbredirparser_stop_iso_stream)(void *priv, + uint64_t id, struct usb_redir_stop_iso_stream_header *stop_iso_stream); +typedef void (*usbredirparser_iso_stream_status)(void *priv, + uint64_t id, struct usb_redir_iso_stream_status_header *iso_stream_status); +typedef void (*usbredirparser_start_interrupt_receiving)(void *priv, + uint64_t id, struct usb_redir_start_interrupt_receiving_header *start_interrupt_receiving); +typedef void (*usbredirparser_stop_interrupt_receiving)(void *priv, + uint64_t id, struct usb_redir_stop_interrupt_receiving_header *stop_interrupt_receiving); +typedef void (*usbredirparser_interrupt_receiving_status)(void *priv, + uint64_t id, struct usb_redir_interrupt_receiving_status_header *interrupt_receiving_status); +typedef void (*usbredirparser_alloc_bulk_streams)(void *priv, + uint64_t id, struct usb_redir_alloc_bulk_streams_header *alloc_bulk_streams); +typedef void (*usbredirparser_free_bulk_streams)(void *priv, + uint64_t id, struct usb_redir_free_bulk_streams_header *free_bulk_streams); +typedef void (*usbredirparser_bulk_streams_status)(void *priv, + uint64_t id, struct usb_redir_bulk_streams_status_header *bulk_streams_status); +typedef void (*usbredirparser_cancel_data_packet)(void *priv, uint64_t id); +typedef void (*usbredirparser_filter_reject)(void *priv); +/* Note that the ownership of the rules array is passed on to the callback. */ +typedef void (*usbredirparser_filter_filter)(void *priv, + struct usbredirfilter_rule *rules, int rules_count); +typedef void (*usbredirparser_device_disconnect_ack)(void *priv); +typedef void (*usbredirparser_start_bulk_receiving)(void *priv, + uint64_t id, struct usb_redir_start_bulk_receiving_header *start_bulk_receiving); +typedef void (*usbredirparser_stop_bulk_receiving)(void *priv, + uint64_t id, struct usb_redir_stop_bulk_receiving_header *stop_bulk_receiving); +typedef void (*usbredirparser_bulk_receiving_status)(void *priv, + uint64_t id, struct usb_redir_bulk_receiving_status_header *bulk_receiving_status); + +/* Data packets: + + Note that ownership of the the data buffer (if not NULL) is passed on to + the callback. The callback should free it by calling + usbredirparser_free_packet_data when it is done with it. */ +typedef void (*usbredirparser_control_packet)(void *priv, + uint64_t id, struct usb_redir_control_packet_header *control_header, + uint8_t *data, int data_len); +typedef void (*usbredirparser_bulk_packet)(void *priv, + uint64_t id, struct usb_redir_bulk_packet_header *bulk_header, + uint8_t *data, int data_len); +typedef void (*usbredirparser_iso_packet)(void *priv, + uint64_t id, struct usb_redir_iso_packet_header *iso_header, + uint8_t *data, int data_len); +typedef void (*usbredirparser_interrupt_packet)(void *priv, + uint64_t id, struct usb_redir_interrupt_packet_header *interrupt_header, + uint8_t *data, int data_len); +typedef void (*usbredirparser_buffered_bulk_packet)(void *priv, uint64_t id, + struct usb_redir_buffered_bulk_packet_header *buffered_bulk_header, + uint8_t *data, int data_len); + + +/* Public part of the data allocated by usbredirparser_alloc, *never* allocate + a usbredirparser struct yourself, it may be extended in the future to add + callbacks for new packet types (which will then get added at the end), + *and* usbredirparser_alloc will also alloc some space behind it for + private data */ +struct usbredirparser { + /* app private data passed into all callbacks as the priv argument */ + void *priv; + /* non packet callbacks */ + usbredirparser_log log_func; + usbredirparser_read read_func; + usbredirparser_write write_func; + /* usb-redir-protocol v0.3 control packet complete callbacks */ + usbredirparser_device_connect device_connect_func; + usbredirparser_device_disconnect device_disconnect_func; + usbredirparser_reset reset_func; + usbredirparser_interface_info interface_info_func; + usbredirparser_ep_info ep_info_func; + usbredirparser_set_configuration set_configuration_func; + usbredirparser_get_configuration get_configuration_func; + usbredirparser_configuration_status configuration_status_func; + usbredirparser_set_alt_setting set_alt_setting_func; + usbredirparser_get_alt_setting get_alt_setting_func; + usbredirparser_alt_setting_status alt_setting_status_func; + usbredirparser_start_iso_stream start_iso_stream_func; + usbredirparser_stop_iso_stream stop_iso_stream_func; + usbredirparser_iso_stream_status iso_stream_status_func; + usbredirparser_start_interrupt_receiving start_interrupt_receiving_func; + usbredirparser_stop_interrupt_receiving stop_interrupt_receiving_func; + usbredirparser_interrupt_receiving_status interrupt_receiving_status_func; + usbredirparser_alloc_bulk_streams alloc_bulk_streams_func; + usbredirparser_free_bulk_streams free_bulk_streams_func; + usbredirparser_bulk_streams_status bulk_streams_status_func; + usbredirparser_cancel_data_packet cancel_data_packet_func; + /* usb-redir-protocol v0.3 data packet complete callbacks */ + usbredirparser_control_packet control_packet_func; + usbredirparser_bulk_packet bulk_packet_func; + usbredirparser_iso_packet iso_packet_func; + usbredirparser_interrupt_packet interrupt_packet_func; + /* usbredir 0.3.2 new non packet callbacks (for multi-thread locking) */ + usbredirparser_alloc_lock alloc_lock_func; + usbredirparser_lock lock_func; + usbredirparser_unlock unlock_func; + usbredirparser_free_lock free_lock_func; + /* usbredir 0.3.2 new control packet complete callbacks */ + usbredirparser_hello hello_func; + /* usbredir 0.4 new control packet complete callbacks */ + usbredirparser_filter_reject filter_reject_func; + usbredirparser_filter_filter filter_filter_func; + usbredirparser_device_disconnect_ack device_disconnect_ack_func; + /* usbredir 0.6 new control packet complete callbacks */ + usbredirparser_start_bulk_receiving start_bulk_receiving_func; + usbredirparser_stop_bulk_receiving stop_bulk_receiving_func; + usbredirparser_bulk_receiving_status bulk_receiving_status_func; + /* usbredir 0.6 new data packet complete callbacks */ + usbredirparser_buffered_bulk_packet buffered_bulk_packet_func; +}; + +/* Allocate a usbredirparser, after this the app should set the callback app + private data and all the callbacks it needs, before calling + usbredirparser_init */ +struct usbredirparser *usbredirparser_create(void); + +/* Set capability cap in the USB_REDIR_CAPS_SIZE sized caps array, + this is a helper function to set capabilities in the caps array + passed to usbredirparser_init(). */ +void usbredirparser_caps_set_cap(uint32_t *caps, int cap); + +/* Init the parser, this will queue an initial usb_redir_hello packet, + sending the version and caps to the peer, as well as configure the parsing + according to the passed in flags. */ +enum { + usbredirparser_fl_usb_host = 0x01, + usbredirparser_fl_write_cb_owns_buffer = 0x02, + usbredirparser_fl_no_hello = 0x04, +}; + +void usbredirparser_init(struct usbredirparser *parser, + const char *version, uint32_t *caps, int caps_len, int flags); + +void usbredirparser_destroy(struct usbredirparser *parser); + +/* See if our side has a certain cap (checks the caps passed into _init) */ +int usbredirparser_have_cap(struct usbredirparser *parser, int cap); + +/* Check if we've received the caps from the peer */ +int usbredirparser_have_peer_caps(struct usbredirparser *parser); + +/* Check if our peer has a certain capability. Note this function + should not be used before the hello_func callback has been called. */ +int usbredirparser_peer_has_cap(struct usbredirparser *parser, int cap); + +/* Call this whenever there is data ready from the otherside to parse + On an usbredirparser_read_io_error this function will continue where it + left of the last time on the next call. On an + usbredirparser_read_parse_error it will skip to the next packet (*). + *) As determined by the faulty's package headers length field */ +enum { + usbredirparser_read_io_error = -1, + usbredirparser_read_parse_error = -2, +}; +int usbredirparser_do_read(struct usbredirparser *parser); + +/* This returns the number of usbredir packets queued up for writing */ +int usbredirparser_has_data_to_write(struct usbredirparser *parser); + +/* Call this when usbredirparser_has_data_to_write returns > 0 + returns 0 on success, -1 if a write error happened. + If a write error happened, this function will retry writing any queued data + on the next call, and will continue doing so until it has succeeded! */ +enum { + usbredirparser_write_io_error = -1, +}; +int usbredirparser_do_write(struct usbredirparser *parser); + +/* See usbredirparser_write documentation */ +void usbredirparser_free_write_buffer(struct usbredirparser *parser, + uint8_t *data); + +/* See the data packet callbacks documentation */ +void usbredirparser_free_packet_data(struct usbredirparser *parser, + uint8_t *data); + +/* Functions to marshall and queue a packet for sending to its peer. Note: + 1) it will not be actually send until usbredirparser_do_write is called + 2) if their is not enough memory for buffers the packet will be dropped + (and an error message will be logged */ +/* Control packets: */ + +/* Note this function should not be used before the hello_func callback has + been called (as it checks the usb_redir_cap_connect_device_version cap). */ +void usbredirparser_send_device_connect(struct usbredirparser *parser, + struct usb_redir_device_connect_header *device_connect); +void usbredirparser_send_device_disconnect(struct usbredirparser *parser); +void usbredirparser_send_reset(struct usbredirparser *parser); +void usbredirparser_send_interface_info(struct usbredirparser *parser, + struct usb_redir_interface_info_header *interface_info); +void usbredirparser_send_ep_info(struct usbredirparser *parser, + struct usb_redir_ep_info_header *ep_info); +void usbredirparser_send_set_configuration(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_set_configuration_header *set_configuration); +void usbredirparser_send_get_configuration(struct usbredirparser *parser, + uint64_t id); +void usbredirparser_send_configuration_status(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_configuration_status_header *configuration_status); +void usbredirparser_send_set_alt_setting(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_set_alt_setting_header *set_alt_setting); +void usbredirparser_send_get_alt_setting(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_get_alt_setting_header *get_alt_setting); +void usbredirparser_send_alt_setting_status(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_alt_setting_status_header *alt_setting_status); +void usbredirparser_send_start_iso_stream(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_start_iso_stream_header *start_iso_stream); +void usbredirparser_send_stop_iso_stream(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_stop_iso_stream_header *stop_iso_stream); +void usbredirparser_send_iso_stream_status(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_iso_stream_status_header *iso_stream_status); +void usbredirparser_send_start_interrupt_receiving(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_start_interrupt_receiving_header *start_interrupt_receiving); +void usbredirparser_send_stop_interrupt_receiving(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_stop_interrupt_receiving_header *stop_interrupt_receiving); +void usbredirparser_send_interrupt_receiving_status(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_interrupt_receiving_status_header *interrupt_receiving_status); +void usbredirparser_send_alloc_bulk_streams(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_alloc_bulk_streams_header *alloc_bulk_streams); +void usbredirparser_send_free_bulk_streams(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_free_bulk_streams_header *free_bulk_streams); +void usbredirparser_send_bulk_streams_status(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_bulk_streams_status_header *bulk_streams_status); +void usbredirparser_send_cancel_data_packet(struct usbredirparser *parser, + uint64_t id); +void usbredirparser_send_filter_reject(struct usbredirparser *parser); +void usbredirparser_send_filter_filter(struct usbredirparser *parser, + const struct usbredirfilter_rule *rules, int rules_count); +void usbredirparser_send_start_bulk_receiving(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_start_bulk_receiving_header *start_bulk_receiving); +void usbredirparser_send_stop_bulk_receiving(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_stop_bulk_receiving_header *stop_bulk_receiving); +void usbredirparser_send_bulk_receiving_status(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_bulk_receiving_status_header *bulk_receiving_status); +/* Data packets: */ +void usbredirparser_send_control_packet(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_control_packet_header *control_header, + uint8_t *data, int data_len); +void usbredirparser_send_bulk_packet(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_bulk_packet_header *bulk_header, + uint8_t *data, int data_len); +void usbredirparser_send_iso_packet(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_iso_packet_header *iso_header, + uint8_t *data, int data_len); +void usbredirparser_send_interrupt_packet(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_interrupt_packet_header *interrupt_header, + uint8_t *data, int data_len); +void usbredirparser_send_buffered_bulk_packet(struct usbredirparser *parser, + uint64_t id, + struct usb_redir_buffered_bulk_packet_header *buffered_bulk_header, + uint8_t *data, int data_len); + + +/* Serialization */ + +/* This function serializes the current usbredirparser state. It will allocate + a large enough buffer for this itself and store this in state_dest, it will + store the size of this buffer in state_len. + + Return value: 0 on success, -1 on error (out of memory). + + The buffer should be free-ed by the caller using free(). */ +int usbredirparser_serialize(struct usbredirparser *parser, + uint8_t **state_dest, int *state_len); + +/* This function sets the current usbredirparser state from a serialized state. + This function assumes that the parser has just been initialized with the + usbredirparser_fl_no_hello flag. + + Return value: 0 on success, -1 on error (out of memory, or + invalid state data). */ +int usbredirparser_unserialize(struct usbredirparser *parser_pub, + uint8_t *state, int len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/usb/usbredir/usbredirproto-compat.h b/drivers/usb/usbredir/usbredirproto-compat.h new file mode 100644 index 0000000..55d2ccf --- /dev/null +++ b/drivers/usb/usbredir/usbredirproto-compat.h @@ -0,0 +1,88 @@ +/* usbredirproto-compat.h usb redirection compatibility protocol definitions + + Copyright 2011 Red Hat, Inc. + + Red Hat Authors: + Hans de Goede + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . +*/ +#ifndef __USBREDIRPROTO_COMPAT_H +#define __USBREDIRPROTO_COMPAT_H + +/* PACK macros borrowed from spice-protocol */ +#ifdef __GNUC__ + +#define ATTR_PACKED __attribute__ ((__packed__)) + +#ifdef __MINGW32__ +#pragma pack(push,1) +#endif + +#else + +#pragma pack(push) +#pragma pack(1) +#define ATTR_PACKED +#pragma warning(disable:4200) +#pragma warning(disable:4103) + +#endif + +#if ! defined(__KERNEL__) +#include +#endif + +struct usb_redir_device_connect_header_no_device_version { + uint8_t speed; + uint8_t device_class; + uint8_t device_subclass; + uint8_t device_protocol; + uint16_t vendor_id; + uint16_t product_id; +} ATTR_PACKED; + +struct usb_redir_ep_info_header_no_max_pktsz { + uint8_t type[32]; + uint8_t interval[32]; + uint8_t interface[32]; +} ATTR_PACKED; + +struct usb_redir_ep_info_header_no_max_streams { + uint8_t type[32]; + uint8_t interval[32]; + uint8_t interface[32]; + uint16_t max_packet_size[32]; +} ATTR_PACKED; + +struct usb_redir_header_32bit_id { + uint32_t type; + uint32_t length; + uint32_t id; +} ATTR_PACKED; + +struct usb_redir_bulk_packet_header_16bit_length { + uint8_t endpoint; + uint8_t status; + uint16_t length; + uint32_t stream_id; +} ATTR_PACKED; + +#undef ATTR_PACKED + +#if defined(__MINGW32__) || !defined(__GNUC__) +#pragma pack(pop) +#endif + +#endif diff --git a/drivers/usb/usbredir/usbredirproto.h b/drivers/usb/usbredir/usbredirproto.h new file mode 100644 index 0000000..46b82e5 --- /dev/null +++ b/drivers/usb/usbredir/usbredirproto.h @@ -0,0 +1,309 @@ +/* usbredirproto.h usb redirection protocol definitions + + Copyright 2010-2011 Red Hat, Inc. + + Red Hat Authors: + Hans de Goede + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . +*/ +#ifndef __USBREDIRPROTO_H +#define __USBREDIRPROTO_H + +/* PACK macros borrowed from spice-protocol */ +#ifdef __GNUC__ + +#define ATTR_PACKED __attribute__ ((__packed__)) + +#ifdef __MINGW32__ +#pragma pack(push,1) +#endif + +#else + +#pragma pack(push) +#pragma pack(1) +#define ATTR_PACKED +#pragma warning(disable:4200) +#pragma warning(disable:4103) + +#endif + +#if ! defined(__KERNEL__) +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define USBREDIR_VERSION 0x000700 /* 0.7 [.0] */ + +enum { + usb_redir_success, + usb_redir_cancelled, /* The transfer was cancelled */ + usb_redir_inval, /* Invalid packet type / length / ep, etc. */ + usb_redir_ioerror, /* IO error */ + usb_redir_stall, /* Stalled */ + usb_redir_timeout, /* Request timed out */ + usb_redir_babble, /* The device has "babbled" (since 0.4.2) */ +}; + +enum { + /* Note these 4 match the usb spec! */ + usb_redir_type_control, + usb_redir_type_iso, + usb_redir_type_bulk, + usb_redir_type_interrupt, + usb_redir_type_invalid = 255 +}; + +enum { + usb_redir_speed_low, + usb_redir_speed_full, + usb_redir_speed_high, + usb_redir_speed_super, + usb_redir_speed_unknown = 255 +}; + +enum { + /* Control packets */ + usb_redir_hello, + usb_redir_device_connect, + usb_redir_device_disconnect, + usb_redir_reset, + usb_redir_interface_info, + usb_redir_ep_info, + usb_redir_set_configuration, + usb_redir_get_configuration, + usb_redir_configuration_status, + usb_redir_set_alt_setting, + usb_redir_get_alt_setting, + usb_redir_alt_setting_status, + usb_redir_start_iso_stream, + usb_redir_stop_iso_stream, + usb_redir_iso_stream_status, + usb_redir_start_interrupt_receiving, + usb_redir_stop_interrupt_receiving, + usb_redir_interrupt_receiving_status, + usb_redir_alloc_bulk_streams, + usb_redir_free_bulk_streams, + usb_redir_bulk_streams_status, + usb_redir_cancel_data_packet, + usb_redir_filter_reject, + usb_redir_filter_filter, + usb_redir_device_disconnect_ack, + usb_redir_start_bulk_receiving, + usb_redir_stop_bulk_receiving, + usb_redir_bulk_receiving_status, + + /* Data packets */ + usb_redir_control_packet = 100, + usb_redir_bulk_packet, + usb_redir_iso_packet, + usb_redir_interrupt_packet, + usb_redir_buffered_bulk_packet, +}; + +enum { + /* Supports USB 3 bulk streams */ + usb_redir_cap_bulk_streams, + /* The device_connect packet has the device_version_bcd field */ + usb_redir_cap_connect_device_version, + /* Supports usb_redir_filter_reject and usb_redir_filter_filter pkts */ + usb_redir_cap_filter, + /* Supports the usb_redir_device_disconnect_ack packet */ + usb_redir_cap_device_disconnect_ack, + /* The ep_info packet has the max_packet_size field */ + usb_redir_cap_ep_info_max_packet_size, + /* Supports 64 bits ids in usb_redir_header */ + usb_redir_cap_64bits_ids, + /* Supports 32 bits length in usb_redir_bulk_packet_header */ + usb_redir_cap_32bits_bulk_length, + /* Supports bulk receiving / buffered bulk input */ + usb_redir_cap_bulk_receiving, +}; +/* Number of uint32_t-s needed to hold all (known) capabilities */ +#define USB_REDIR_CAPS_SIZE 1 + +struct usb_redir_header { + uint32_t type; + uint32_t length; + uint64_t id; +} ATTR_PACKED; + +struct usb_redir_hello_header { + char version[64]; + uint32_t capabilities[0]; +} ATTR_PACKED; + +struct usb_redir_device_connect_header { + uint8_t speed; + uint8_t device_class; + uint8_t device_subclass; + uint8_t device_protocol; + uint16_t vendor_id; + uint16_t product_id; + uint16_t device_version_bcd; +} ATTR_PACKED; + +struct usb_redir_interface_info_header { + uint32_t interface_count; + uint8_t interface[32]; + uint8_t interface_class[32]; + uint8_t interface_subclass[32]; + uint8_t interface_protocol[32]; +} ATTR_PACKED; + +struct usb_redir_ep_info_header { + uint8_t type[32]; + uint8_t interval[32]; + uint8_t interface[32]; + uint16_t max_packet_size[32]; + uint32_t max_streams[32]; +} ATTR_PACKED; + +struct usb_redir_set_configuration_header { + uint8_t configuration; +} ATTR_PACKED; + +struct usb_redir_configuration_status_header { + uint8_t status; + uint8_t configuration; +} ATTR_PACKED; + +struct usb_redir_set_alt_setting_header { + uint8_t interface; + uint8_t alt; +} ATTR_PACKED; + +struct usb_redir_get_alt_setting_header { + uint8_t interface; +} ATTR_PACKED; + +struct usb_redir_alt_setting_status_header { + uint8_t status; + uint8_t interface; + uint8_t alt; +} ATTR_PACKED; + +struct usb_redir_start_iso_stream_header { + uint8_t endpoint; + uint8_t pkts_per_urb; + uint8_t no_urbs; +} ATTR_PACKED; + +struct usb_redir_stop_iso_stream_header { + uint8_t endpoint; +} ATTR_PACKED; + +struct usb_redir_iso_stream_status_header { + uint8_t status; + uint8_t endpoint; +} ATTR_PACKED; + +struct usb_redir_start_interrupt_receiving_header { + uint8_t endpoint; +} ATTR_PACKED; + +struct usb_redir_stop_interrupt_receiving_header { + uint8_t endpoint; +} ATTR_PACKED; + +struct usb_redir_interrupt_receiving_status_header { + uint8_t status; + uint8_t endpoint; +} ATTR_PACKED; + +struct usb_redir_alloc_bulk_streams_header { + uint32_t endpoints; /* bitmask indicating on which eps to alloc streams */ + uint32_t no_streams; +} ATTR_PACKED; + +struct usb_redir_free_bulk_streams_header { + uint32_t endpoints; /* bitmask indicating on which eps to free streams */ +} ATTR_PACKED; + +struct usb_redir_bulk_streams_status_header { + uint32_t endpoints; /* bitmask indicating eps this status message is for */ + uint32_t no_streams; + uint8_t status; +} ATTR_PACKED; + +struct usb_redir_start_bulk_receiving_header { + uint32_t stream_id; + uint32_t bytes_per_transfer; + uint8_t endpoint; + uint8_t no_transfers; +} ATTR_PACKED; + +struct usb_redir_stop_bulk_receiving_header { + uint32_t stream_id; + uint8_t endpoint; +} ATTR_PACKED; + +struct usb_redir_bulk_receiving_status_header { + uint32_t stream_id; + uint8_t endpoint; + uint8_t status; +} ATTR_PACKED; + +struct usb_redir_control_packet_header { + uint8_t endpoint; + uint8_t request; + uint8_t requesttype; + uint8_t status; + uint16_t value; + uint16_t index; + uint16_t length; +} ATTR_PACKED; + +struct usb_redir_bulk_packet_header { + uint8_t endpoint; + uint8_t status; + uint16_t length; + uint32_t stream_id; + uint16_t length_high; /* High 16 bits of the packet length */ +} ATTR_PACKED; + +struct usb_redir_iso_packet_header { + uint8_t endpoint; + uint8_t status; + uint16_t length; +} ATTR_PACKED; + +struct usb_redir_interrupt_packet_header { + uint8_t endpoint; + uint8_t status; + uint16_t length; +} ATTR_PACKED; + +struct usb_redir_buffered_bulk_packet_header { + uint32_t stream_id; + uint32_t length; + uint8_t endpoint; + uint8_t status; +} ATTR_PACKED; + +#undef ATTR_PACKED + +#if defined(__MINGW32__) || !defined(__GNUC__) +#pragma pack(pop) +#endif + +#ifdef __cplusplus +} +#endif + +#endif -- 2.1.4 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/