2015-02-26 13:35:25

by Juergen Gross

[permalink] [raw]
Subject: [PATCH 0/4] xen, usb: support pvUSB drivers

This series adds XEN pvUSB support. With pvUSB it is possible to use physical
USB devices from a XEN domain.

The support consists of a backend in the privileged Domain-0 doing the real
I/O and a frontend in the unprivileged domU passing I/O-requests to the backend.

The code is taken (and adapted) from the original pvUSB implementation done
for Linux 2.6 in 2008 by Fujitsu.

Normal operation of USB devices by adding and removing them dynamically to/from
a domain has been tested using various USB devices (USB 1.1, 2.0 and 3.0).
Assignment of a USB device at domain creation via config file has been tested
as well.

Tests have been performed with the xm toolset and with xl using patches
from Chun Yan Liu which are not upstream yet.

Juergen Gross (4):
usb: Add Xen pvUSB protocol description
usb: Introduce Xen pvUSB frontend
usb: Introduce Xen pvUSB backend
xen: add Xen pvUSB maintainer

MAINTAINERS | 8 +
drivers/usb/Kconfig | 2 +
drivers/usb/Makefile | 3 +
drivers/usb/xen/Kconfig | 20 +
drivers/usb/xen/Makefile | 6 +
drivers/usb/xen/xen-usbback.c | 1845 ++++++++++++++++++++++++++++++++++++++
drivers/usb/xen/xen-usbfront.c | 1634 +++++++++++++++++++++++++++++++++
include/xen/interface/io/usbif.h | 220 +++++
8 files changed, 3738 insertions(+)
create mode 100644 drivers/usb/xen/Kconfig
create mode 100644 drivers/usb/xen/Makefile
create mode 100644 drivers/usb/xen/xen-usbback.c
create mode 100644 drivers/usb/xen/xen-usbfront.c
create mode 100644 include/xen/interface/io/usbif.h

--
2.1.4


2015-02-26 13:35:27

by Juergen Gross

[permalink] [raw]
Subject: [PATCH 1/4] usb: Add Xen pvUSB protocol description

Add the definition of pvUSB protocol used between the pvUSB frontend in
a Xen domU and the pvUSB backend in a Xen driver domain (usually Dom0).

This header was originally provided by Fujitsu for Xen based on Linux
2.6.18.

Changes are:
- adapt to Linux style guide

Signed-off-by: Juergen Gross <[email protected]>
---
include/xen/interface/io/usbif.h | 220 +++++++++++++++++++++++++++++++++++++++
1 file changed, 220 insertions(+)
create mode 100644 include/xen/interface/io/usbif.h

diff --git a/include/xen/interface/io/usbif.h b/include/xen/interface/io/usbif.h
new file mode 100644
index 0000000..29815e2
--- /dev/null
+++ b/include/xen/interface/io/usbif.h
@@ -0,0 +1,220 @@
+/*
+ * usbif.h
+ *
+ * USB I/O interface for Xen guest OSes.
+ *
+ * Copyright (C) 2009, FUJITSU LABORATORIES LTD.
+ * Author: Noboru Iwamatsu <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __XEN_PUBLIC_IO_USBIF_H__
+#define __XEN_PUBLIC_IO_USBIF_H__
+
+#include "ring.h"
+#include "../grant_table.h"
+
+/*
+ * Feature and Parameter Negotiation
+ * =================================
+ * The two halves of a Xen pvUSB driver utilize nodes within the XenStore to
+ * communicate capabilities and to negotiate operating parameters. This
+ * section enumerates these nodes which reside in the respective front and
+ * backend portions of the XenStore, following the XenBus convention.
+ *
+ * Any specified default value is in effect if the corresponding XenBus node
+ * is not present in the XenStore.
+ *
+ * XenStore nodes in sections marked "PRIVATE" are solely for use by the
+ * driver side whose XenBus tree contains them.
+ *
+ *****************************************************************************
+ * Backend XenBus Nodes
+ *****************************************************************************
+ *
+ *------------------ Backend Device Identification (PRIVATE) ------------------
+ *
+ * num-ports
+ * Values: unsigned [1...31]
+ *
+ * Number of ports for this (virtual) USB host connector.
+ *
+ * usb-ver
+ * Values: unsigned [1...2]
+ *
+ * USB version of this host connector: 1 = USB 1.1, 2 = USB 2.0.
+ *
+ * port/[1...31]
+ * Values: string
+ *
+ * Physical USB device connected to the given port, e.g. "3-1.5".
+ *
+ *****************************************************************************
+ * Frontend XenBus Nodes
+ *****************************************************************************
+ *
+ *----------------------- Request Transport Parameters -----------------------
+ *
+ * event-channel
+ * Values: unsigned
+ *
+ * The identifier of the Xen event channel used to signal activity
+ * in the ring buffer.
+ *
+ * urb-ring-ref
+ * Values: unsigned
+ *
+ * The Xen grant reference granting permission for the backend to map
+ * the sole page in a single page sized ring buffer. This is the ring
+ * buffer for urb requests.
+ *
+ * conn-ring-ref
+ * Values: unsigned
+ *
+ * The Xen grant reference granting permission for the backend to map
+ * the sole page in a single page sized ring buffer. This is the ring
+ * buffer for connection/disconnection requests.
+ *
+ * protocol
+ * Values: string (XEN_IO_PROTO_ABI_*)
+ * Default Value: XEN_IO_PROTO_ABI_NATIVE
+ *
+ * The machine ABI rules governing the format of all ring request and
+ * response structures.
+ *
+ */
+
+enum usb_spec_version {
+ USB_VER_UNKNOWN = 0,
+ USB_VER_USB11,
+ USB_VER_USB20,
+ USB_VER_USB30, /* not supported yet */
+};
+
+/*
+ * USB pipe in usbif_request
+ *
+ * bits 0-5 are specific bits for virtual USB driver.
+ * bits 7-31 are standard urb pipe.
+ *
+ * - port number(NEW): bits 0-4
+ * (USB_MAXCHILDREN is 31)
+ *
+ * - operation flag(NEW): bit 5
+ * (0 = submit urb,
+ * 1 = unlink urb)
+ *
+ * - direction: bit 7
+ * (0 = Host-to-Device [Out]
+ * 1 = Device-to-Host [In])
+ *
+ * - device address: bits 8-14
+ *
+ * - endpoint: bits 15-18
+ *
+ * - pipe type: bits 30-31
+ * (00 = isochronous, 01 = interrupt,
+ * 10 = control, 11 = bulk)
+ */
+#define usbif_pipeportnum(pipe) ((pipe) & 0x1f)
+#define usbif_setportnum_pipe(pipe, portnum) \
+ ((pipe)|(portnum))
+
+#define usbif_pipeunlink(pipe) ((pipe) & 0x20)
+#define usbif_pipesubmit(pipe) (!usbif_pipeunlink(pipe))
+#define usbif_setunlink_pipe(pipe) ((pipe) | 0x20)
+
+#define USBIF_MAX_SEGMENTS_PER_REQUEST 16
+
+/*
+ * RING for transferring urbs.
+ */
+struct usbif_request_segment {
+ grant_ref_t gref;
+ uint16_t offset;
+ uint16_t length;
+};
+
+struct usbif_urb_request {
+ uint16_t id; /* request id */
+ uint16_t nr_buffer_segs; /* # of urb->transfer_buffer segments */
+
+ /* basic urb parameter */
+ uint32_t pipe;
+ uint16_t transfer_flags;
+ uint16_t buffer_length;
+ union {
+ uint8_t ctrl[8]; /* pipe type control */
+ /* setup packet */
+
+ struct {
+ uint16_t interval; /* max (1024*8) in usb core */
+ uint16_t start_frame; /* start frame */
+ uint16_t number_of_packets; /* # of ISO packets */
+ uint16_t nr_frame_desc_segs;
+ /* # of iso_frame_desc segments */
+ } isoc; /* pipe type isochronous */
+
+ struct {
+ uint16_t interval; /* max (1024*8) in usb core */
+ uint16_t pad[3];
+ } intr; /* pipe type interrupt */
+
+ struct {
+ uint16_t unlink_id; /* unlink request id */
+ uint16_t pad[3];
+ } unlink; /* pipe unlink */
+
+ } u;
+
+ /* urb data segments */
+ struct usbif_request_segment seg[USBIF_MAX_SEGMENTS_PER_REQUEST];
+};
+
+struct usbif_urb_response {
+ uint16_t id; /* request id */
+ uint16_t start_frame; /* start frame (ISO) */
+ int32_t status; /* status (non-ISO) */
+ int32_t actual_length; /* actual transfer length */
+ int32_t error_count; /* number of ISO errors */
+};
+
+DEFINE_RING_TYPES(usbif_urb, struct usbif_urb_request,
+ struct usbif_urb_response);
+#define USB_URB_RING_SIZE __CONST_RING_SIZE(usbif_urb, PAGE_SIZE)
+
+/*
+ * RING for notifying connect/disconnect events to frontend
+ */
+struct usbif_conn_request {
+ uint16_t id;
+};
+
+struct usbif_conn_response {
+ uint16_t id; /* request id */
+ uint8_t portnum; /* port number */
+ uint8_t speed; /* usb_device_speed */
+};
+
+DEFINE_RING_TYPES(usbif_conn, struct usbif_conn_request,
+ struct usbif_conn_response);
+#define USB_CONN_RING_SIZE __CONST_RING_SIZE(usbif_conn, PAGE_SIZE)
+
+#endif /* __XEN_PUBLIC_IO_USBIF_H__ */
--
2.1.4

2015-02-26 13:35:59

by Juergen Gross

[permalink] [raw]
Subject: [PATCH 2/4] usb: Introduce Xen pvUSB frontend

Introduces the Xen pvUSB frontend. With pvUSB it is possible for a Xen
domU to communicate with a USB device assigned to that domU. The
communication is all done via the pvUSB backend in a driver domain
(usually Dom0) which is owner of the physical device.

The code is taken from the pvUSB implementation in Xen done by Fujitsu
based on Linux kernel 2.6.18.

Changes from the original version are:
- port to upstream kernel
- put all code in just one source file
- move module to appropriate location in kernel tree
- adapt to Linux style guide
- minor code modifications to increase readability

Signed-off-by: Juergen Gross <[email protected]>
---
drivers/usb/Kconfig | 2 +
drivers/usb/Makefile | 2 +
drivers/usb/xen/Kconfig | 10 +
drivers/usb/xen/Makefile | 5 +
drivers/usb/xen/xen-usbfront.c | 1634 ++++++++++++++++++++++++++++++++++++++++
5 files changed, 1653 insertions(+)
create mode 100644 drivers/usb/xen/Kconfig
create mode 100644 drivers/usb/xen/Makefile
create mode 100644 drivers/usb/xen/xen-usbfront.c

diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index 8ed451d..de998f1 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/xen/Kconfig"
+
endif

source "drivers/usb/musb/Kconfig"
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 2f1e2aa..2676ef6 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -62,3 +62,5 @@ obj-$(CONFIG_USB_GADGET) += gadget/
obj-$(CONFIG_USB_COMMON) += common/

obj-$(CONFIG_USBIP_CORE) += usbip/
+
+obj-$(CONFIG_XEN_USB_FRONTEND) += xen/
diff --git a/drivers/usb/xen/Kconfig b/drivers/usb/xen/Kconfig
new file mode 100644
index 0000000..5d995477
--- /dev/null
+++ b/drivers/usb/xen/Kconfig
@@ -0,0 +1,10 @@
+config XEN_USB_FRONTEND
+ tristate "Xen USB frontend driver"
+ depends on XEN
+ default m
+ select XEN_XENBUS_FRONTEND
+ help
+ The Xen USB frontend driver allows the kernel to access USB Devices
+ within another guest OS (usually Dom0).
+ Only needed if the kernel is running in a Xen guest and generic
+ access to a USB device is needed.
diff --git a/drivers/usb/xen/Makefile b/drivers/usb/xen/Makefile
new file mode 100644
index 0000000..4568c26
--- /dev/null
+++ b/drivers/usb/xen/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for Xen pvUSB drivers
+#
+
+obj-$(CONFIG_XEN_USB_FRONTEND) += xen-usbfront.o
diff --git a/drivers/usb/xen/xen-usbfront.c b/drivers/usb/xen/xen-usbfront.c
new file mode 100644
index 0000000..f49e8e9
--- /dev/null
+++ b/drivers/usb/xen/xen-usbfront.c
@@ -0,0 +1,1634 @@
+/*
+ * xen-usbfront.c
+ *
+ * Xen USB Virtual Host Controller driver
+ *
+ * Copyright (C) 2009, FUJITSU LABORATORIES LTD.
+ * Author: Noboru Iwamatsu <[email protected]>
+ *
+ * This program 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 program 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * or, by your choice,
+ *
+ * When distributed separately from the Linux kernel or incorporated into
+ * other software packages, subject to the following license:
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/list.h>
+#include <linux/usb/hcd.h>
+#include <linux/io.h>
+
+#include <xen/xen.h>
+#include <xen/xenbus.h>
+#include <xen/grant_table.h>
+#include <xen/events.h>
+#include <xen/page.h>
+
+#include <xen/interface/io/usbif.h>
+
+/* Private per-URB data */
+struct urb_priv {
+ struct list_head list;
+ struct urb *urb;
+ int req_id; /* RING_REQUEST id for submitting */
+ int unlink_req_id; /* RING_REQUEST id for unlinking */
+ int status;
+ unsigned unlinked:1; /* dequeued marker */
+};
+
+/* virtual roothub port status */
+struct rhport_status {
+ u32 status;
+ unsigned resuming:1; /* in resuming */
+ unsigned c_connection:1; /* connection changed */
+ unsigned long timeout;
+};
+
+/* status of attached device */
+struct vdevice_status {
+ int devnum;
+ enum usb_device_state status;
+ enum usb_device_speed speed;
+};
+
+/* RING request shadow */
+struct usb_shadow {
+ struct usbif_urb_request req;
+ struct urb *urb;
+};
+
+struct usbfront_info {
+ /* Virtual Host Controller has 4 urb queues */
+ struct list_head pending_submit_list;
+ struct list_head pending_unlink_list;
+ struct list_head in_progress_list;
+ struct list_head giveback_waiting_list;
+
+ spinlock_t lock;
+
+ /* timer that kick pending and giveback waiting urbs */
+ struct timer_list watchdog;
+ unsigned long actions;
+
+ /* virtual root hub */
+ int rh_numports;
+ struct rhport_status ports[USB_MAXCHILDREN];
+ struct vdevice_status devices[USB_MAXCHILDREN];
+
+ /* Xen related staff */
+ struct xenbus_device *xbdev;
+ int urb_ring_ref;
+ int conn_ring_ref;
+ struct usbif_urb_front_ring urb_ring;
+ struct usbif_conn_front_ring conn_ring;
+
+ unsigned int evtchn;
+ unsigned int irq;
+ struct usb_shadow shadow[USB_URB_RING_SIZE];
+ unsigned long shadow_free;
+};
+
+#define GRANT_INVALID_REF 0
+
+#define XENHCD_RING_JIFFIES (HZ/200)
+#define XENHCD_SCAN_JIFFIES 1
+
+enum xenhcd_timer_action {
+ TIMER_RING_WATCHDOG,
+ TIMER_SCAN_PENDING_URBS,
+};
+
+static struct kmem_cache *usbfront_urbp_cachep;
+
+static inline struct usbfront_info *usbfront_hcd_to_info(struct usb_hcd *hcd)
+{
+ return (struct usbfront_info *)hcd->hcd_priv;
+}
+
+static inline struct usb_hcd *usbfront_info_to_hcd(struct usbfront_info *info)
+{
+ return container_of((void *)info, struct usb_hcd, hcd_priv);
+}
+
+static inline void usbfront_timer_action_done(struct usbfront_info *info,
+ enum xenhcd_timer_action action)
+{
+ clear_bit(action, &info->actions);
+}
+
+static void usbfront_timer_action(struct usbfront_info *info,
+ enum xenhcd_timer_action action)
+{
+ if (timer_pending(&info->watchdog) &&
+ test_bit(TIMER_SCAN_PENDING_URBS, &info->actions))
+ return;
+
+ if (!test_and_set_bit(action, &info->actions)) {
+ unsigned long t;
+
+ switch (action) {
+ case TIMER_RING_WATCHDOG:
+ t = XENHCD_RING_JIFFIES;
+ break;
+ default:
+ t = XENHCD_SCAN_JIFFIES;
+ break;
+ }
+ mod_timer(&info->watchdog, t + jiffies);
+ }
+}
+
+static ssize_t usbfront_show_statistics(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_hcd *hcd;
+ struct usbfront_info *info;
+ unsigned long flags;
+ unsigned size;
+
+ hcd = dev_get_drvdata(dev);
+ info = usbfront_hcd_to_info(hcd);
+
+ spin_lock_irqsave(&info->lock, flags);
+
+ size = scnprintf(buf, PAGE_SIZE, "bus %s, device %s\n%s\n"
+ "xenhcd, hcd state %d\n",
+ hcd->self.controller->bus->name,
+ dev_name(hcd->self.controller), hcd->product_desc,
+ hcd->state);
+
+ spin_unlock_irqrestore(&info->lock, flags);
+
+ return size;
+}
+
+static DEVICE_ATTR(statistics, S_IRUGO, usbfront_show_statistics, NULL);
+
+static void usbfront_create_debug_file(struct usbfront_info *info)
+{
+ struct device *dev = usbfront_info_to_hcd(info)->self.controller;
+
+ if (device_create_file(dev, &dev_attr_statistics))
+ pr_warn("statistics file not created for %s\n",
+ usbfront_info_to_hcd(info)->self.bus_name);
+}
+
+static void usbfront_remove_debug_file(struct usbfront_info *info)
+{
+ struct device *dev = usbfront_info_to_hcd(info)->self.controller;
+
+ device_remove_file(dev, &dev_attr_statistics);
+}
+
+/*
+ * set virtual port connection status
+ */
+static void usbfront_set_connect_state(struct usbfront_info *info, int portnum)
+{
+ int port;
+
+ port = portnum - 1;
+ if (info->ports[port].status & USB_PORT_STAT_POWER) {
+ switch (info->devices[port].speed) {
+ case USB_SPEED_UNKNOWN:
+ info->ports[port].status &=
+ ~(USB_PORT_STAT_CONNECTION |
+ USB_PORT_STAT_ENABLE |
+ USB_PORT_STAT_LOW_SPEED |
+ USB_PORT_STAT_HIGH_SPEED |
+ USB_PORT_STAT_SUSPEND);
+ break;
+ case USB_SPEED_LOW:
+ info->ports[port].status |= USB_PORT_STAT_CONNECTION;
+ info->ports[port].status |= USB_PORT_STAT_LOW_SPEED;
+ break;
+ case USB_SPEED_FULL:
+ info->ports[port].status |= USB_PORT_STAT_CONNECTION;
+ break;
+ case USB_SPEED_HIGH:
+ info->ports[port].status |= USB_PORT_STAT_CONNECTION;
+ info->ports[port].status |= USB_PORT_STAT_HIGH_SPEED;
+ break;
+ default: /* error */
+ return;
+ }
+ info->ports[port].status |= (USB_PORT_STAT_C_CONNECTION << 16);
+ }
+}
+
+/*
+ * set virtual device connection status
+ */
+static void usbfront_rhport_connect(struct usbfront_info *info, int portnum,
+ enum usb_device_speed speed)
+{
+ int port;
+
+ if (portnum < 1 || portnum > info->rh_numports)
+ return; /* invalid port number */
+
+ port = portnum - 1;
+ if (info->devices[port].speed != speed) {
+ switch (speed) {
+ case USB_SPEED_UNKNOWN: /* disconnect */
+ info->devices[port].status = USB_STATE_NOTATTACHED;
+ break;
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ case USB_SPEED_HIGH:
+ info->devices[port].status = USB_STATE_ATTACHED;
+ break;
+ default: /* error */
+ return;
+ }
+ info->devices[port].speed = speed;
+ info->ports[port].c_connection = 1;
+
+ usbfront_set_connect_state(info, portnum);
+ }
+}
+
+/*
+ * SetPortFeature(PORT_SUSPENDED)
+ */
+static void usbfront_rhport_suspend(struct usbfront_info *info, int portnum)
+{
+ int port;
+
+ port = portnum - 1;
+ info->ports[port].status |= USB_PORT_STAT_SUSPEND;
+ info->devices[port].status = USB_STATE_SUSPENDED;
+}
+
+/*
+ * ClearPortFeature(PORT_SUSPENDED)
+ */
+static void usbfront_rhport_resume(struct usbfront_info *info, int portnum)
+{
+ int port;
+
+ port = portnum - 1;
+ if (info->ports[port].status & USB_PORT_STAT_SUSPEND) {
+ info->ports[port].resuming = 1;
+ info->ports[port].timeout = jiffies + msecs_to_jiffies(20);
+ }
+}
+
+/*
+ * SetPortFeature(PORT_POWER)
+ */
+static void usbfront_rhport_power_on(struct usbfront_info *info, int portnum)
+{
+ int port;
+
+ port = portnum - 1;
+ if ((info->ports[port].status & USB_PORT_STAT_POWER) == 0) {
+ info->ports[port].status |= USB_PORT_STAT_POWER;
+ if (info->devices[port].status != USB_STATE_NOTATTACHED)
+ info->devices[port].status = USB_STATE_POWERED;
+ if (info->ports[port].c_connection)
+ usbfront_set_connect_state(info, portnum);
+ }
+}
+
+/*
+ * ClearPortFeature(PORT_POWER)
+ * SetConfiguration(non-zero)
+ * Power_Source_Off
+ * Over-current
+ */
+static void usbfront_rhport_power_off(struct usbfront_info *info, int portnum)
+{
+ int port;
+
+ port = portnum - 1;
+ if (info->ports[port].status & USB_PORT_STAT_POWER) {
+ info->ports[port].status = 0;
+ if (info->devices[port].status != USB_STATE_NOTATTACHED)
+ info->devices[port].status = USB_STATE_ATTACHED;
+ }
+}
+
+/*
+ * ClearPortFeature(PORT_ENABLE)
+ */
+static void usbfront_rhport_disable(struct usbfront_info *info, int portnum)
+{
+ int port;
+
+ port = portnum - 1;
+ info->ports[port].status &= ~USB_PORT_STAT_ENABLE;
+ info->ports[port].status &= ~USB_PORT_STAT_SUSPEND;
+ info->ports[port].resuming = 0;
+ if (info->devices[port].status != USB_STATE_NOTATTACHED)
+ info->devices[port].status = USB_STATE_POWERED;
+}
+
+/*
+ * SetPortFeature(PORT_RESET)
+ */
+static void usbfront_rhport_reset(struct usbfront_info *info, int portnum)
+{
+ int port;
+
+ port = portnum - 1;
+ info->ports[port].status &= ~(USB_PORT_STAT_ENABLE |
+ USB_PORT_STAT_LOW_SPEED |
+ USB_PORT_STAT_HIGH_SPEED);
+ info->ports[port].status |= USB_PORT_STAT_RESET;
+
+ if (info->devices[port].status != USB_STATE_NOTATTACHED)
+ info->devices[port].status = USB_STATE_ATTACHED;
+
+ /* 10msec reset signaling */
+ info->ports[port].timeout = jiffies + msecs_to_jiffies(10);
+}
+
+#ifdef CONFIG_PM
+static int usbfront_hcd_bus_suspend(struct usb_hcd *hcd)
+{
+ struct usbfront_info *info = usbfront_hcd_to_info(hcd);
+ int ret = 0;
+ int i, ports;
+
+ ports = info->rh_numports;
+
+ spin_lock_irq(&info->lock);
+ if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) {
+ ret = -ESHUTDOWN;
+ } else {
+ /* suspend any active ports*/
+ for (i = 1; i <= ports; i++)
+ usbfront_rhport_suspend(info, i);
+ }
+ spin_unlock_irq(&info->lock);
+
+ del_timer_sync(&info->watchdog);
+
+ return ret;
+}
+
+static int usbfront_hcd_bus_resume(struct usb_hcd *hcd)
+{
+ struct usbfront_info *info = usbfront_hcd_to_info(hcd);
+ int ret = 0;
+ int i, ports;
+
+ ports = info->rh_numports;
+
+ spin_lock_irq(&info->lock);
+ if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) {
+ ret = -ESHUTDOWN;
+ } else {
+ /* resume any suspended ports*/
+ for (i = 1; i <= ports; i++)
+ usbfront_rhport_resume(info, i);
+ }
+ spin_unlock_irq(&info->lock);
+
+ return ret;
+}
+#endif
+
+static void usbfront_hcd_hub_descriptor(struct usbfront_info *info,
+ struct usb_hub_descriptor *desc)
+{
+ u16 temp;
+ int ports = info->rh_numports;
+
+ desc->bDescriptorType = 0x29;
+ desc->bPwrOn2PwrGood = 10; /* EHCI says 20ms max */
+ desc->bHubContrCurrent = 0;
+ desc->bNbrPorts = ports;
+
+ /* size of DeviceRemovable and PortPwrCtrlMask fields */
+ temp = 1 + (ports / 8);
+ desc->bDescLength = 7 + 2 * temp;
+
+ /* bitmaps for DeviceRemovable and PortPwrCtrlMask */
+ memset(&desc->u.hs.DeviceRemovable[0], 0, temp);
+ memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp);
+
+ /* per-port over current reporting and no power switching */
+ temp = 0x000a;
+ desc->wHubCharacteristics = cpu_to_le16(temp);
+}
+
+/* port status change mask for hub_status_data */
+#define PORT_C_MASK ((USB_PORT_STAT_C_CONNECTION | \
+ USB_PORT_STAT_C_ENABLE | \
+ USB_PORT_STAT_C_SUSPEND | \
+ USB_PORT_STAT_C_OVERCURRENT | \
+ USB_PORT_STAT_C_RESET) << 16)
+
+/*
+ * See USB 2.0 Spec, 11.12.4 Hub and Port Status Change Bitmap.
+ * If port status changed, writes the bitmap to buf and return
+ * that length(number of bytes).
+ * If Nothing changed, return 0.
+ */
+static int usbfront_hcd_hub_status_data(struct usb_hcd *hcd, char *buf)
+{
+ struct usbfront_info *info = usbfront_hcd_to_info(hcd);
+ int ports;
+ int i;
+ int length;
+ unsigned long flags;
+ int ret = 0;
+ int changed = 0;
+
+ if (!HC_IS_RUNNING(hcd->state))
+ return 0;
+
+ /* initialize the status to no-changes */
+ ports = info->rh_numports;
+ length = 1 + (ports / 8);
+ for (i = 0; i < length; i++) {
+ buf[i] = 0;
+ ret++;
+ }
+
+ spin_lock_irqsave(&info->lock, flags);
+
+ for (i = 0; i < ports; i++) {
+ /* check status for each port */
+ if (info->ports[i].status & PORT_C_MASK) {
+ if (i < 7)
+ buf[0] |= 1 << (i + 1);
+ else if (i < 15)
+ buf[1] |= 1 << (i - 7);
+ else if (i < 23)
+ buf[2] |= 1 << (i - 15);
+ else
+ buf[3] |= 1 << (i - 23);
+ changed = 1;
+ }
+ }
+
+ if (!changed)
+ ret = 0;
+
+ spin_unlock_irqrestore(&info->lock, flags);
+
+ return ret;
+}
+
+static int usbfront_hcd_hub_control(struct usb_hcd *hcd, u16 typeReq,
+ u16 wValue, u16 wIndex, char *buf, u16 wLength)
+{
+ struct usbfront_info *info = usbfront_hcd_to_info(hcd);
+ int ports = info->rh_numports;
+ unsigned long flags;
+ int ret = 0;
+ int i;
+ int changed = 0;
+
+ spin_lock_irqsave(&info->lock, flags);
+ switch (typeReq) {
+ case ClearHubFeature:
+ /* ignore this request */
+ break;
+ case ClearPortFeature:
+ if (!wIndex || wIndex > ports)
+ goto error;
+
+ switch (wValue) {
+ case USB_PORT_FEAT_SUSPEND:
+ usbfront_rhport_resume(info, wIndex);
+ break;
+ case USB_PORT_FEAT_POWER:
+ usbfront_rhport_power_off(info, wIndex);
+ break;
+ case USB_PORT_FEAT_ENABLE:
+ usbfront_rhport_disable(info, wIndex);
+ break;
+ case USB_PORT_FEAT_C_CONNECTION:
+ info->ports[wIndex-1].c_connection = 0;
+ /* falling through */
+ default:
+ info->ports[wIndex-1].status &= ~(1 << wValue);
+ break;
+ }
+ break;
+ case GetHubDescriptor:
+ usbfront_hcd_hub_descriptor(info,
+ (struct usb_hub_descriptor *)buf);
+ break;
+ case GetHubStatus:
+ /* always local power supply good and no over-current exists. */
+ *(__le32 *)buf = cpu_to_le32(0);
+ break;
+ case GetPortStatus:
+ if (!wIndex || wIndex > ports)
+ goto error;
+
+ wIndex--;
+
+ /* resume completion */
+ if (info->ports[wIndex].resuming &&
+ time_after_eq(jiffies, info->ports[wIndex].timeout)) {
+ info->ports[wIndex].status |=
+ USB_PORT_STAT_C_SUSPEND << 16;
+ info->ports[wIndex].status &= ~USB_PORT_STAT_SUSPEND;
+ }
+
+ /* reset completion */
+ if ((info->ports[wIndex].status & USB_PORT_STAT_RESET) != 0 &&
+ time_after_eq(jiffies, info->ports[wIndex].timeout)) {
+ info->ports[wIndex].status |=
+ USB_PORT_STAT_C_RESET << 16;
+ info->ports[wIndex].status &= ~USB_PORT_STAT_RESET;
+
+ if (info->devices[wIndex].status !=
+ USB_STATE_NOTATTACHED) {
+ info->ports[wIndex].status |=
+ USB_PORT_STAT_ENABLE;
+ info->devices[wIndex].status =
+ USB_STATE_DEFAULT;
+ }
+
+ switch (info->devices[wIndex].speed) {
+ case USB_SPEED_LOW:
+ info->ports[wIndex].status |=
+ USB_PORT_STAT_LOW_SPEED;
+ break;
+ case USB_SPEED_HIGH:
+ info->ports[wIndex].status |=
+ USB_PORT_STAT_HIGH_SPEED;
+ break;
+ default:
+ break;
+ }
+ }
+
+ ((u16 *)buf)[0] = cpu_to_le16(info->ports[wIndex].status);
+ ((u16 *)buf)[1] = cpu_to_le16(info->ports[wIndex].status >> 16);
+ break;
+ case SetPortFeature:
+ if (!wIndex || wIndex > ports)
+ goto error;
+
+ switch (wValue) {
+ case USB_PORT_FEAT_POWER:
+ usbfront_rhport_power_on(info, wIndex);
+ break;
+ case USB_PORT_FEAT_RESET:
+ usbfront_rhport_reset(info, wIndex);
+ break;
+ case USB_PORT_FEAT_SUSPEND:
+ usbfront_rhport_suspend(info, wIndex);
+ break;
+ default:
+ if (info->ports[wIndex-1].status & USB_PORT_STAT_POWER)
+ info->ports[wIndex-1].status |= (1 << wValue);
+ }
+ break;
+
+ case SetHubFeature:
+ /* not supported */
+ default:
+error:
+ ret = -EPIPE;
+ }
+ spin_unlock_irqrestore(&info->lock, flags);
+
+ /* check status for each port */
+ for (i = 0; i < ports; i++) {
+ if (info->ports[i].status & PORT_C_MASK)
+ changed = 1;
+ }
+ if (changed)
+ usb_hcd_poll_rh_status(hcd);
+
+ return ret;
+}
+
+static void usbfront_free_urb_priv(struct urb_priv *urbp)
+{
+ urbp->urb->hcpriv = NULL;
+ kmem_cache_free(usbfront_urbp_cachep, urbp);
+}
+
+static inline int usbfront_get_id_from_freelist(struct usbfront_info *info)
+{
+ unsigned long free;
+
+ free = info->shadow_free;
+ BUG_ON(free >= USB_URB_RING_SIZE);
+ info->shadow_free = info->shadow[free].req.id;
+ info->shadow[free].req.id = (unsigned int)0x0fff; /* debug */
+ return free;
+}
+
+static inline void usbfront_add_id_to_freelist(struct usbfront_info *info,
+ unsigned long id)
+{
+ info->shadow[id].req.id = info->shadow_free;
+ info->shadow[id].urb = NULL;
+ info->shadow_free = id;
+}
+
+static inline int usbfront_count_pages(void *addr, int length)
+{
+ unsigned long vaddr = (unsigned long)addr;
+
+ return PFN_UP(vaddr + length) - PFN_DOWN(vaddr);
+}
+
+static void usbfront_hcd_gnttab_map(struct usbfront_info *info, void *addr,
+ int length, grant_ref_t *gref_head,
+ struct usbif_request_segment *seg,
+ int nr_pages, int flags)
+{
+ grant_ref_t ref;
+ unsigned long buffer_mfn;
+ unsigned int offset;
+ unsigned int len = length;
+ unsigned int bytes;
+ int i;
+
+ for (i = 0; i < nr_pages; i++) {
+ BUG_ON(!len);
+
+ buffer_mfn = PFN_DOWN(arbitrary_virt_to_machine(addr).maddr);
+ offset = offset_in_page(addr);
+
+ bytes = PAGE_SIZE - offset;
+ if (bytes > len)
+ bytes = len;
+
+ ref = gnttab_claim_grant_reference(gref_head);
+ BUG_ON(ref == -ENOSPC);
+ gnttab_grant_foreign_access_ref(ref, info->xbdev->otherend_id,
+ buffer_mfn, flags);
+ seg[i].gref = ref;
+ seg[i].offset = (uint16_t)offset;
+ seg[i].length = (uint16_t)bytes;
+
+ addr += bytes;
+ len -= bytes;
+ }
+}
+
+static int usbfront_map_urb_for_request(struct usbfront_info *info,
+ struct urb *urb, struct usbif_urb_request *req)
+{
+ grant_ref_t gref_head;
+ int nr_buff_pages = 0;
+ int nr_isodesc_pages = 0;
+ int nr_grants = 0;
+
+ if (urb->transfer_buffer_length) {
+ nr_buff_pages = usbfront_count_pages(urb->transfer_buffer,
+ urb->transfer_buffer_length);
+
+ if (usb_pipeisoc(urb->pipe))
+ nr_isodesc_pages = usbfront_count_pages(
+ &urb->iso_frame_desc[0],
+ sizeof(struct usb_iso_packet_descriptor) *
+ urb->number_of_packets);
+
+ nr_grants = nr_buff_pages + nr_isodesc_pages;
+ if (nr_grants > USBIF_MAX_SEGMENTS_PER_REQUEST) {
+ pr_err("usbfront: error: %d grants\n", nr_grants);
+ return -E2BIG;
+ }
+
+ if (gnttab_alloc_grant_references(nr_grants, &gref_head)) {
+ pr_err("usbfront: gnttab_alloc_grant_references() error\n");
+ return -ENOMEM;
+ }
+
+ usbfront_hcd_gnttab_map(info, urb->transfer_buffer,
+ urb->transfer_buffer_length, &gref_head,
+ &req->seg[0], nr_buff_pages,
+ usb_pipein(urb->pipe) ? 0 : GTF_readonly);
+ }
+
+ req->pipe = usbif_setportnum_pipe(urb->pipe, urb->dev->portnum);
+ req->transfer_flags = urb->transfer_flags;
+ req->buffer_length = urb->transfer_buffer_length;
+ req->nr_buffer_segs = nr_buff_pages;
+
+ switch (usb_pipetype(urb->pipe)) {
+ case PIPE_ISOCHRONOUS:
+ req->u.isoc.interval = urb->interval;
+ req->u.isoc.start_frame = urb->start_frame;
+ req->u.isoc.number_of_packets = urb->number_of_packets;
+ req->u.isoc.nr_frame_desc_segs = nr_isodesc_pages;
+
+ /* urb->number_of_packets must be > 0 */
+ BUG_ON(urb->number_of_packets <= 0);
+
+ usbfront_hcd_gnttab_map(info, &urb->iso_frame_desc[0],
+ sizeof(struct usb_iso_packet_descriptor) *
+ urb->number_of_packets,
+ &gref_head, &req->seg[nr_buff_pages],
+ nr_isodesc_pages, 0);
+ break;
+ case PIPE_INTERRUPT:
+ req->u.intr.interval = urb->interval;
+ break;
+ case PIPE_CONTROL:
+ if (urb->setup_packet)
+ memcpy(req->u.ctrl, urb->setup_packet, 8);
+ break;
+ case PIPE_BULK:
+ break;
+ default:
+ BUG();
+ }
+
+ if (nr_grants)
+ gnttab_free_grant_references(gref_head);
+
+ return 0;
+}
+
+static void usbfront_hcd_gnttab_done(struct usb_shadow *shadow)
+{
+ int nr_segs = 0;
+ int i;
+
+ nr_segs = shadow->req.nr_buffer_segs;
+
+ if (usb_pipeisoc(shadow->req.pipe))
+ nr_segs += shadow->req.u.isoc.nr_frame_desc_segs;
+
+ for (i = 0; i < nr_segs; i++)
+ gnttab_end_foreign_access(shadow->req.seg[i].gref, 0, 0UL);
+
+ shadow->req.nr_buffer_segs = 0;
+ shadow->req.u.isoc.nr_frame_desc_segs = 0;
+}
+
+static void usbfront_hcd_giveback_urb(struct usbfront_info *info,
+ struct urb *urb, int status)
+{
+ struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv;
+ int priv_status = urbp->status;
+
+ list_del_init(&urbp->list);
+ usbfront_free_urb_priv(urbp);
+
+ if (urb->status == -EINPROGRESS)
+ urb->status = status;
+
+ spin_unlock(&info->lock);
+ usb_hcd_giveback_urb(usbfront_info_to_hcd(info), urb,
+ priv_status <= 0 ? priv_status : urb->status);
+ spin_lock(&info->lock);
+}
+
+static int usbfront_hcd_do_request(struct usbfront_info *info,
+ struct urb_priv *urbp)
+{
+ struct usbif_urb_request *req;
+ struct urb *urb = urbp->urb;
+ uint16_t id;
+ int notify;
+ int ret;
+
+ req = RING_GET_REQUEST(&info->urb_ring, info->urb_ring.req_prod_pvt);
+ id = usbfront_get_id_from_freelist(info);
+ req->id = id;
+
+ if (unlikely(urbp->unlinked)) {
+ req->u.unlink.unlink_id = urbp->req_id;
+ req->pipe = usbif_setunlink_pipe(usbif_setportnum_pipe(
+ urb->pipe, urb->dev->portnum));
+ urbp->unlink_req_id = id;
+ } else {
+ ret = usbfront_map_urb_for_request(info, urb, req);
+ if (ret) {
+ usbfront_add_id_to_freelist(info, id);
+ return ret;
+ }
+ urbp->req_id = id;
+ }
+
+ info->urb_ring.req_prod_pvt++;
+ info->shadow[id].urb = urb;
+ info->shadow[id].req = *req;
+
+ RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&info->urb_ring, notify);
+ if (notify)
+ notify_remote_via_irq(info->irq);
+
+ return 0;
+}
+
+static void usbfront_hcd_kick_pending_urbs(struct usbfront_info *info)
+{
+ struct urb_priv *urbp;
+
+ while (!list_empty(&info->pending_submit_list)) {
+ if (RING_FULL(&info->urb_ring)) {
+ usbfront_timer_action(info, TIMER_RING_WATCHDOG);
+ return;
+ }
+
+ urbp = list_entry(info->pending_submit_list.next,
+ struct urb_priv, list);
+ if (!usbfront_hcd_do_request(info, urbp))
+ list_move_tail(&urbp->list, &info->in_progress_list);
+ else
+ usbfront_hcd_giveback_urb(info, urbp->urb, -ESHUTDOWN);
+ }
+ usbfront_timer_action_done(info, TIMER_SCAN_PENDING_URBS);
+}
+
+/*
+ * caller must lock info->lock
+ */
+static void usbfront_hcd_cancel_all_enqueued_urbs(struct usbfront_info *info)
+{
+ struct urb_priv *urbp, *tmp;
+ int req_id;
+
+ list_for_each_entry_safe(urbp, tmp, &info->in_progress_list, list) {
+ req_id = urbp->req_id;
+ if (!urbp->unlinked) {
+ usbfront_hcd_gnttab_done(&info->shadow[req_id]);
+ if (urbp->urb->status == -EINPROGRESS)
+ /* not dequeued */
+ usbfront_hcd_giveback_urb(info, urbp->urb,
+ -ESHUTDOWN);
+ else /* dequeued */
+ usbfront_hcd_giveback_urb(info, urbp->urb,
+ urbp->urb->status);
+ }
+ info->shadow[req_id].urb = NULL;
+ }
+
+ list_for_each_entry_safe(urbp, tmp, &info->pending_submit_list, list)
+ usbfront_hcd_giveback_urb(info, urbp->urb, -ESHUTDOWN);
+}
+
+/*
+ * caller must lock info->lock
+ */
+static void usbfront_hcd_giveback_unlinked_urbs(struct usbfront_info *info)
+{
+ struct urb_priv *urbp, *tmp;
+
+ list_for_each_entry_safe(urbp, tmp, &info->giveback_waiting_list, list)
+ usbfront_hcd_giveback_urb(info, urbp->urb, urbp->urb->status);
+}
+
+static int usbfront_hcd_submit_urb(struct usbfront_info *info,
+ struct urb_priv *urbp)
+{
+ int ret;
+
+ if (RING_FULL(&info->urb_ring)) {
+ list_add_tail(&urbp->list, &info->pending_submit_list);
+ usbfront_timer_action(info, TIMER_RING_WATCHDOG);
+ return 0;
+ }
+
+ if (!list_empty(&info->pending_submit_list)) {
+ list_add_tail(&urbp->list, &info->pending_submit_list);
+ usbfront_timer_action(info, TIMER_SCAN_PENDING_URBS);
+ return 0;
+ }
+
+ ret = usbfront_hcd_do_request(info, urbp);
+ if (ret == 0)
+ list_add_tail(&urbp->list, &info->in_progress_list);
+
+ return ret;
+}
+
+static int usbfront_hcd_unlink_urb(struct usbfront_info *info,
+ struct urb_priv *urbp)
+{
+ int ret;
+
+ /* already unlinked? */
+ if (urbp->unlinked)
+ return -EBUSY;
+
+ urbp->unlinked = 1;
+
+ /* the urb is still in pending_submit queue */
+ if (urbp->req_id == ~0) {
+ list_move_tail(&urbp->list, &info->giveback_waiting_list);
+ usbfront_timer_action(info, TIMER_SCAN_PENDING_URBS);
+ return 0;
+ }
+
+ /* send unlink request to backend */
+ if (RING_FULL(&info->urb_ring)) {
+ list_move_tail(&urbp->list, &info->pending_unlink_list);
+ usbfront_timer_action(info, TIMER_RING_WATCHDOG);
+ return 0;
+ }
+
+ if (!list_empty(&info->pending_unlink_list)) {
+ list_move_tail(&urbp->list, &info->pending_unlink_list);
+ usbfront_timer_action(info, TIMER_SCAN_PENDING_URBS);
+ return 0;
+ }
+
+ ret = usbfront_hcd_do_request(info, urbp);
+ if (ret == 0)
+ list_move_tail(&urbp->list, &info->in_progress_list);
+
+ return ret;
+}
+
+static int usbfront_hcd_urb_request_done(struct usbfront_info *info)
+{
+ struct usbif_urb_response *res;
+ struct urb *urb;
+ RING_IDX i, rp;
+ uint16_t id;
+ int more_to_do = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->lock, flags);
+
+ rp = info->urb_ring.sring->rsp_prod;
+ rmb(); /* ensure we see queued responses up to "rp" */
+
+ for (i = info->urb_ring.rsp_cons; i != rp; i++) {
+ res = RING_GET_RESPONSE(&info->urb_ring, i);
+ id = res->id;
+
+ if (likely(usbif_pipesubmit(info->shadow[id].req.pipe))) {
+ usbfront_hcd_gnttab_done(&info->shadow[id]);
+ urb = info->shadow[id].urb;
+ if (likely(urb)) {
+ urb->actual_length = res->actual_length;
+ urb->error_count = res->error_count;
+ urb->start_frame = res->start_frame;
+ usbfront_hcd_giveback_urb(info, urb,
+ res->status);
+ }
+ }
+
+ usbfront_add_id_to_freelist(info, id);
+ }
+ info->urb_ring.rsp_cons = i;
+
+ if (i != info->urb_ring.req_prod_pvt)
+ RING_FINAL_CHECK_FOR_RESPONSES(&info->urb_ring, more_to_do);
+ else
+ info->urb_ring.sring->rsp_event = i + 1;
+
+ spin_unlock_irqrestore(&info->lock, flags);
+
+ return more_to_do;
+}
+
+static int usbfront_hcd_conn_notify(struct usbfront_info *info)
+{
+ struct usbif_conn_response *res;
+ struct usbif_conn_request *req;
+ RING_IDX rc, rp;
+ uint16_t id;
+ uint8_t portnum, speed;
+ int more_to_do = 0;
+ int notify;
+ int port_changed = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->lock, flags);
+
+ rc = info->conn_ring.rsp_cons;
+ rp = info->conn_ring.sring->rsp_prod;
+ rmb(); /* ensure we see queued responses up to "rp" */
+
+ while (rc != rp) {
+ res = RING_GET_RESPONSE(&info->conn_ring, rc);
+ id = res->id;
+ portnum = res->portnum;
+ speed = res->speed;
+ info->conn_ring.rsp_cons = ++rc;
+
+ usbfront_rhport_connect(info, portnum, speed);
+ if (info->ports[portnum - 1].c_connection)
+ port_changed = 1;
+
+ barrier();
+
+ req = RING_GET_REQUEST(&info->conn_ring,
+ info->conn_ring.req_prod_pvt);
+ req->id = id;
+ info->conn_ring.req_prod_pvt++;
+ }
+
+ if (rc != info->conn_ring.req_prod_pvt)
+ RING_FINAL_CHECK_FOR_RESPONSES(&info->conn_ring, more_to_do);
+ else
+ info->conn_ring.sring->rsp_event = rc + 1;
+
+ RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&info->conn_ring, notify);
+ if (notify)
+ notify_remote_via_irq(info->irq);
+
+ spin_unlock_irqrestore(&info->lock, flags);
+
+ if (port_changed)
+ usb_hcd_poll_rh_status(usbfront_info_to_hcd(info));
+
+ return more_to_do;
+}
+
+static irqreturn_t usbfront_hcd_int(int irq, void *dev_id)
+{
+ struct usbfront_info *info = (struct usbfront_info *)dev_id;
+
+ while (usbfront_hcd_urb_request_done(info) |
+ usbfront_hcd_conn_notify(info))
+ /* Yield point for this unbounded loop. */
+ cond_resched();
+
+ return IRQ_HANDLED;
+}
+
+static void usbfront_destroy_rings(struct usbfront_info *info)
+{
+ if (info->irq)
+ unbind_from_irqhandler(info->irq, info);
+ info->irq = 0;
+
+ if (info->urb_ring_ref != GRANT_INVALID_REF) {
+ gnttab_end_foreign_access(info->urb_ring_ref, 0,
+ (unsigned long)info->urb_ring.sring);
+ info->urb_ring_ref = GRANT_INVALID_REF;
+ }
+ info->urb_ring.sring = NULL;
+
+ if (info->conn_ring_ref != GRANT_INVALID_REF) {
+ gnttab_end_foreign_access(info->conn_ring_ref, 0,
+ (unsigned long)info->conn_ring.sring);
+ info->conn_ring_ref = GRANT_INVALID_REF;
+ }
+ info->conn_ring.sring = NULL;
+}
+
+static int usbfront_setup_rings(struct xenbus_device *dev,
+ struct usbfront_info *info)
+{
+ struct usbif_urb_sring *urb_sring;
+ struct usbif_conn_sring *conn_sring;
+ int err;
+
+ info->urb_ring_ref = GRANT_INVALID_REF;
+ info->conn_ring_ref = GRANT_INVALID_REF;
+
+ urb_sring = (struct usbif_urb_sring *)get_zeroed_page(
+ GFP_NOIO | __GFP_HIGH);
+ if (!urb_sring) {
+ xenbus_dev_fatal(dev, -ENOMEM, "allocating urb ring");
+ return -ENOMEM;
+ }
+ SHARED_RING_INIT(urb_sring);
+ FRONT_RING_INIT(&info->urb_ring, urb_sring, PAGE_SIZE);
+
+ err = xenbus_grant_ring(dev, virt_to_mfn(info->urb_ring.sring));
+ if (err < 0) {
+ free_page((unsigned long)urb_sring);
+ info->urb_ring.sring = NULL;
+ goto fail;
+ }
+ info->urb_ring_ref = err;
+
+ conn_sring = (struct usbif_conn_sring *)get_zeroed_page(
+ GFP_NOIO | __GFP_HIGH);
+ if (!conn_sring) {
+ xenbus_dev_fatal(dev, -ENOMEM, "allocating conn ring");
+ err = -ENOMEM;
+ goto fail;
+ }
+ SHARED_RING_INIT(conn_sring);
+ FRONT_RING_INIT(&info->conn_ring, conn_sring, PAGE_SIZE);
+
+ err = xenbus_grant_ring(dev, virt_to_mfn(info->conn_ring.sring));
+ if (err < 0) {
+ free_page((unsigned long)conn_sring);
+ info->conn_ring.sring = NULL;
+ goto fail;
+ }
+ info->conn_ring_ref = err;
+
+ err = xenbus_alloc_evtchn(dev, &info->evtchn);
+ if (err) {
+ xenbus_dev_fatal(dev, err, "xenbus_alloc_evtchn");
+ goto fail;
+ }
+
+ err = bind_evtchn_to_irq(info->evtchn);
+ if (err <= 0) {
+ xenbus_dev_fatal(dev, err, "bind_evtchn_to_irq");
+ goto fail;
+ }
+
+ info->irq = err;
+
+ err = request_threaded_irq(info->irq, NULL, usbfront_hcd_int,
+ IRQF_ONESHOT, "usbfront", info);
+ if (err) {
+ xenbus_dev_fatal(dev, err, "request_threaded_irq");
+ goto free_irq;
+ }
+
+ return 0;
+
+free_irq:
+ unbind_from_irqhandler(info->irq, info);
+fail:
+ usbfront_destroy_rings(info);
+ return err;
+}
+
+static int usbfront_talk_to_backend(struct xenbus_device *dev,
+ struct usbfront_info *info)
+{
+ const char *message;
+ struct xenbus_transaction xbt;
+ int err;
+
+ err = usbfront_setup_rings(dev, info);
+ if (err)
+ return err;
+
+again:
+ err = xenbus_transaction_start(&xbt);
+ if (err) {
+ xenbus_dev_fatal(dev, err, "starting transaction");
+ goto destroy_ring;
+ }
+
+ err = xenbus_printf(xbt, dev->nodename, "urb-ring-ref", "%u",
+ info->urb_ring_ref);
+ if (err) {
+ message = "writing urb-ring-ref";
+ goto abort_transaction;
+ }
+
+ err = xenbus_printf(xbt, dev->nodename, "conn-ring-ref", "%u",
+ info->conn_ring_ref);
+ if (err) {
+ message = "writing conn-ring-ref";
+ goto abort_transaction;
+ }
+
+ err = xenbus_printf(xbt, dev->nodename, "event-channel", "%u",
+ info->evtchn);
+ if (err) {
+ message = "writing event-channel";
+ goto abort_transaction;
+ }
+
+ err = xenbus_transaction_end(xbt, 0);
+ if (err) {
+ if (err == -EAGAIN)
+ goto again;
+ xenbus_dev_fatal(dev, err, "completing transaction");
+ goto destroy_ring;
+ }
+
+ return 0;
+
+abort_transaction:
+ xenbus_transaction_end(xbt, 1);
+ xenbus_dev_fatal(dev, err, "%s", message);
+
+destroy_ring:
+ usbfront_destroy_rings(info);
+
+ return err;
+}
+
+static int usbfront_connect(struct xenbus_device *dev)
+{
+ struct usbfront_info *info = dev_get_drvdata(&dev->dev);
+ struct usbif_conn_request *req;
+ int idx, err;
+ int notify;
+ char name[TASK_COMM_LEN];
+ struct usb_hcd *hcd;
+
+ hcd = usbfront_info_to_hcd(info);
+ snprintf(name, TASK_COMM_LEN, "xenhcd.%d", hcd->self.busnum);
+
+ err = usbfront_talk_to_backend(dev, info);
+ if (err)
+ return err;
+
+ /* prepare ring for hotplug notification */
+ for (idx = 0; idx < USB_CONN_RING_SIZE; idx++) {
+ req = RING_GET_REQUEST(&info->conn_ring, idx);
+ req->id = idx;
+ }
+ info->conn_ring.req_prod_pvt = idx;
+
+ RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&info->conn_ring, notify);
+ if (notify)
+ notify_remote_via_irq(info->irq);
+
+ return 0;
+}
+
+static void usbfront_disconnect(struct xenbus_device *dev)
+{
+ struct usbfront_info *info = dev_get_drvdata(&dev->dev);
+ struct usb_hcd *hcd = usbfront_info_to_hcd(info);
+
+ usb_remove_hcd(hcd);
+ xenbus_frontend_closed(dev);
+}
+
+static void usbfront_hcd_watchdog(unsigned long param)
+{
+ struct usbfront_info *info = (struct usbfront_info *)param;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->lock, flags);
+ if (likely(HC_IS_RUNNING(usbfront_info_to_hcd(info)->state))) {
+ usbfront_timer_action_done(info, TIMER_RING_WATCHDOG);
+ usbfront_hcd_giveback_unlinked_urbs(info);
+ usbfront_hcd_kick_pending_urbs(info);
+ }
+ spin_unlock_irqrestore(&info->lock, flags);
+}
+
+/*
+ * one-time HC init
+ */
+static int usbfront_hcd_setup(struct usb_hcd *hcd)
+{
+ struct usbfront_info *info = usbfront_hcd_to_info(hcd);
+
+ spin_lock_init(&info->lock);
+ INIT_LIST_HEAD(&info->pending_submit_list);
+ INIT_LIST_HEAD(&info->pending_unlink_list);
+ INIT_LIST_HEAD(&info->in_progress_list);
+ INIT_LIST_HEAD(&info->giveback_waiting_list);
+ init_timer(&info->watchdog);
+ info->watchdog.function = usbfront_hcd_watchdog;
+ info->watchdog.data = (unsigned long)info;
+
+ hcd->has_tt = (hcd->driver->flags & HCD_MASK) != HCD_USB11;
+
+ return 0;
+}
+
+/*
+ * start HC running
+ */
+static int usbfront_hcd_run(struct usb_hcd *hcd)
+{
+ hcd->uses_new_polling = 1;
+ clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
+ hcd->state = HC_STATE_RUNNING;
+ usbfront_create_debug_file(usbfront_hcd_to_info(hcd));
+ return 0;
+}
+
+/*
+ * stop running HC
+ */
+static void usbfront_hcd_stop(struct usb_hcd *hcd)
+{
+ struct usbfront_info *info = usbfront_hcd_to_info(hcd);
+
+ del_timer_sync(&info->watchdog);
+ usbfront_remove_debug_file(info);
+ spin_lock_irq(&info->lock);
+ /* cancel all urbs */
+ hcd->state = HC_STATE_HALT;
+ usbfront_hcd_cancel_all_enqueued_urbs(info);
+ usbfront_hcd_giveback_unlinked_urbs(info);
+ spin_unlock_irq(&info->lock);
+}
+
+/*
+ * called as .urb_enqueue()
+ * non-error returns are promise to giveback the urb later
+ */
+static int usbfront_hcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
+ gfp_t mem_flags)
+{
+ struct usbfront_info *info = usbfront_hcd_to_info(hcd);
+ struct urb_priv *urbp;
+ unsigned long flags;
+ int ret;
+
+
+ urbp = kmem_cache_zalloc(usbfront_urbp_cachep, mem_flags);
+ if (!urbp)
+ return -ENOMEM;
+
+ spin_lock_irqsave(&info->lock, flags);
+
+ urbp->urb = urb;
+ urb->hcpriv = urbp;
+ urbp->req_id = ~0;
+ urbp->unlink_req_id = ~0;
+ INIT_LIST_HEAD(&urbp->list);
+ urbp->status = 1;
+ urb->unlinked = 0;
+
+ ret = usbfront_hcd_submit_urb(info, urbp);
+
+ if (ret)
+ usbfront_free_urb_priv(urbp);
+
+ spin_unlock_irqrestore(&info->lock, flags);
+
+ return ret;
+}
+
+/*
+ * called as .urb_dequeue()
+ */
+static int usbfront_hcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
+ int status)
+{
+ struct usbfront_info *info = usbfront_hcd_to_info(hcd);
+ struct urb_priv *urbp;
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&info->lock, flags);
+
+ urbp = urb->hcpriv;
+ if (urbp) {
+ urbp->status = status;
+ ret = usbfront_hcd_unlink_urb(info, urbp);
+ }
+
+ spin_unlock_irqrestore(&info->lock, flags);
+
+ return ret;
+}
+
+/*
+ * called from usb_get_current_frame_number(),
+ * but, almost all drivers not use such function.
+ */
+static int usbfront_hcd_get_frame(struct usb_hcd *hcd)
+{
+ /* it means error, but probably no problem :-) */
+ return 0;
+}
+
+static const char usbfront_hcd_name[] = "xen_hcd";
+
+static struct hc_driver usbfront_usb20_hc_driver = {
+ .description = usbfront_hcd_name,
+ .product_desc = "Xen USB2.0 Virtual Host Controller",
+ .hcd_priv_size = sizeof(struct usbfront_info),
+ .flags = HCD_USB2,
+
+ /* basic HC lifecycle operations */
+ .reset = usbfront_hcd_setup,
+ .start = usbfront_hcd_run,
+ .stop = usbfront_hcd_stop,
+
+ /* managing urb I/O */
+ .urb_enqueue = usbfront_hcd_urb_enqueue,
+ .urb_dequeue = usbfront_hcd_urb_dequeue,
+ .get_frame_number = usbfront_hcd_get_frame,
+
+ /* root hub operations */
+ .hub_status_data = usbfront_hcd_hub_status_data,
+ .hub_control = usbfront_hcd_hub_control,
+#ifdef CONFIG_PM
+ .bus_suspend = usbfront_hcd_bus_suspend,
+ .bus_resume = usbfront_hcd_bus_resume,
+#endif
+};
+
+static struct hc_driver usbfront_usb11_hc_driver = {
+ .description = usbfront_hcd_name,
+ .product_desc = "Xen USB1.1 Virtual Host Controller",
+ .hcd_priv_size = sizeof(struct usbfront_info),
+ .flags = HCD_USB11,
+
+ /* basic HC lifecycle operations */
+ .reset = usbfront_hcd_setup,
+ .start = usbfront_hcd_run,
+ .stop = usbfront_hcd_stop,
+
+ /* managing urb I/O */
+ .urb_enqueue = usbfront_hcd_urb_enqueue,
+ .urb_dequeue = usbfront_hcd_urb_dequeue,
+ .get_frame_number = usbfront_hcd_get_frame,
+
+ /* root hub operations */
+ .hub_status_data = usbfront_hcd_hub_status_data,
+ .hub_control = usbfront_hcd_hub_control,
+#ifdef CONFIG_PM
+ .bus_suspend = usbfront_hcd_bus_suspend,
+ .bus_resume = usbfront_hcd_bus_resume,
+#endif
+};
+
+static struct usb_hcd *usbfront_create_hcd(struct xenbus_device *dev)
+{
+ int i;
+ int err = 0;
+ int num_ports;
+ int usb_ver;
+ struct usb_hcd *hcd = NULL;
+ struct usbfront_info *info;
+
+ err = xenbus_scanf(XBT_NIL, dev->otherend, "num-ports", "%d",
+ &num_ports);
+ if (err != 1) {
+ xenbus_dev_fatal(dev, err, "reading num-ports");
+ return ERR_PTR(-EINVAL);
+ }
+ if (num_ports < 1 || num_ports > USB_MAXCHILDREN) {
+ xenbus_dev_fatal(dev, err, "invalid num-ports");
+ return ERR_PTR(-EINVAL);
+ }
+
+ err = xenbus_scanf(XBT_NIL, dev->otherend, "usb-ver", "%d", &usb_ver);
+ if (err != 1) {
+ xenbus_dev_fatal(dev, err, "reading usb-ver");
+ return ERR_PTR(-EINVAL);
+ }
+ switch (usb_ver) {
+ case USB_VER_USB11:
+ hcd = usb_create_hcd(&usbfront_usb11_hc_driver, &dev->dev,
+ dev_name(&dev->dev));
+ break;
+ case USB_VER_USB20:
+ hcd = usb_create_hcd(&usbfront_usb20_hc_driver, &dev->dev,
+ dev_name(&dev->dev));
+ break;
+ default:
+ xenbus_dev_fatal(dev, err, "invalid usb-ver");
+ return ERR_PTR(-EINVAL);
+ }
+ if (!hcd) {
+ xenbus_dev_fatal(dev, err,
+ "fail to allocate USB host controller");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ info = usbfront_hcd_to_info(hcd);
+ info->xbdev = dev;
+ info->rh_numports = num_ports;
+
+ for (i = 0; i < USB_URB_RING_SIZE; i++) {
+ info->shadow[i].req.id = i + 1;
+ info->shadow[i].urb = NULL;
+ }
+ info->shadow[USB_URB_RING_SIZE - 1].req.id = 0x0fff;
+
+ return hcd;
+}
+
+static void usbfront_backend_changed(struct xenbus_device *dev,
+ enum xenbus_state backend_state)
+{
+ switch (backend_state) {
+ case XenbusStateInitialising:
+ case XenbusStateReconfiguring:
+ case XenbusStateReconfigured:
+ case XenbusStateUnknown:
+ break;
+
+ case XenbusStateInitWait:
+ case XenbusStateInitialised:
+ case XenbusStateConnected:
+ if (dev->state != XenbusStateInitialising)
+ break;
+ if (!usbfront_connect(dev))
+ xenbus_switch_state(dev, XenbusStateConnected);
+ break;
+
+ case XenbusStateClosed:
+ if (dev->state == XenbusStateClosed)
+ break;
+ /* Missed the backend's Closing state -- fallthrough */
+ case XenbusStateClosing:
+ usbfront_disconnect(dev);
+ break;
+
+ default:
+ xenbus_dev_fatal(dev, -EINVAL, "saw state %d at frontend",
+ backend_state);
+ break;
+ }
+}
+
+static int usbfront_remove(struct xenbus_device *dev)
+{
+ struct usbfront_info *info = dev_get_drvdata(&dev->dev);
+ struct usb_hcd *hcd = usbfront_info_to_hcd(info);
+
+ usbfront_destroy_rings(info);
+ usb_put_hcd(hcd);
+
+ return 0;
+}
+
+static int usbfront_probe(struct xenbus_device *dev,
+ const struct xenbus_device_id *id)
+{
+ int err;
+ struct usb_hcd *hcd;
+ struct usbfront_info *info;
+
+ if (usb_disabled())
+ return -ENODEV;
+
+ hcd = usbfront_create_hcd(dev);
+ if (IS_ERR(hcd)) {
+ err = PTR_ERR(hcd);
+ xenbus_dev_fatal(dev, err,
+ "fail to create usb host controller");
+ return err;
+ }
+
+ info = usbfront_hcd_to_info(hcd);
+ dev_set_drvdata(&dev->dev, info);
+
+ err = usb_add_hcd(hcd, 0, 0);
+ if (err) {
+ xenbus_dev_fatal(dev, err, "fail to add USB host controller");
+ usb_put_hcd(hcd);
+ dev_set_drvdata(&dev->dev, NULL);
+ }
+
+ return err;
+}
+
+static const struct xenbus_device_id usbfront_ids[] = {
+ { "vusb" },
+ { "" },
+};
+
+static struct xenbus_driver usbfront_driver = {
+ .ids = usbfront_ids,
+ .probe = usbfront_probe,
+ .otherend_changed = usbfront_backend_changed,
+ .remove = usbfront_remove,
+};
+
+static int __init usbfront_init(void)
+{
+ if (!xen_domain())
+ return -ENODEV;
+
+ usbfront_urbp_cachep = kmem_cache_create("xenhcd_urb_priv",
+ sizeof(struct urb_priv), 0, 0, NULL);
+ if (!usbfront_urbp_cachep) {
+ pr_err("usbfront failed to create kmem cache\n");
+ return -ENOMEM;
+ }
+
+ return xenbus_register_frontend(&usbfront_driver);
+}
+module_init(usbfront_init);
+
+static void __exit usbfront_exit(void)
+{
+ kmem_cache_destroy(usbfront_urbp_cachep);
+ xenbus_unregister_driver(&usbfront_driver);
+}
+module_exit(usbfront_exit);
+
+MODULE_ALIAS("xen:vusb");
+MODULE_AUTHOR("Juergen Gross <[email protected]>");
+MODULE_DESCRIPTION("Xen USB Virtual Host Controller driver (usbfront)");
+MODULE_LICENSE("Dual BSD/GPL");
--
2.1.4

2015-02-26 13:35:57

by Juergen Gross

[permalink] [raw]
Subject: [PATCH 3/4] usb: Introduce Xen pvUSB backend

Introduces the Xen pvUSB backend. With pvUSB it is possible for a Xen
domU to communicate with a USB device assigned to that domU. The
communication is all done via the pvUSB backend in a driver domain
(usually Dom0) which is owner of the physical device.

The code is taken from the pvUSB implementation in Xen done by Fujitsu
based on Linux kernel 2.6.18.

Changes from the original version are:
- port to upstream kernel
- put all code in just one source file
- move module to appropriate location in kernel tree
- adapt to Linux style guide
- allocate resources dynamically
- use threaded irq
- correct sequence of state changes when assigning a device

Signed-off-by: Juergen Gross <[email protected]>
---
drivers/usb/Makefile | 1 +
drivers/usb/xen/Kconfig | 10 +
drivers/usb/xen/Makefile | 1 +
drivers/usb/xen/xen-usbback.c | 1845 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 1857 insertions(+)
create mode 100644 drivers/usb/xen/xen-usbback.c

diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 2676ef6..41f7398 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -64,3 +64,4 @@ obj-$(CONFIG_USB_COMMON) += common/
obj-$(CONFIG_USBIP_CORE) += usbip/

obj-$(CONFIG_XEN_USB_FRONTEND) += xen/
+obj-$(CONFIG_XEN_USB_BACKEND) += xen/
diff --git a/drivers/usb/xen/Kconfig b/drivers/usb/xen/Kconfig
index 5d995477..3414617 100644
--- a/drivers/usb/xen/Kconfig
+++ b/drivers/usb/xen/Kconfig
@@ -8,3 +8,13 @@ config XEN_USB_FRONTEND
within another guest OS (usually Dom0).
Only needed if the kernel is running in a Xen guest and generic
access to a USB device is needed.
+
+config XEN_USB_BACKEND
+ tristate "Xen USB backend driver"
+ depends on XEN_BACKEND
+ default m
+ help
+ The USB backend driver allows the kernel to export its USB Devices
+ to other guests via a high-performance shared-memory interface.
+ Only needed for systems running as Xen driver domains (e.g. Dom0) and
+ if guests need generic access to USB devices.
diff --git a/drivers/usb/xen/Makefile b/drivers/usb/xen/Makefile
index 4568c26..c1a571065 100644
--- a/drivers/usb/xen/Makefile
+++ b/drivers/usb/xen/Makefile
@@ -3,3 +3,4 @@
#

obj-$(CONFIG_XEN_USB_FRONTEND) += xen-usbfront.o
+obj-$(CONFIG_XEN_USB_BACKEND) += xen-usbback.o
diff --git a/drivers/usb/xen/xen-usbback.c b/drivers/usb/xen/xen-usbback.c
new file mode 100644
index 0000000..56a600e
--- /dev/null
+++ b/drivers/usb/xen/xen-usbback.c
@@ -0,0 +1,1845 @@
+/*
+ * xen-usbback.c
+ *
+ * Xen USB backend driver.
+ *
+ * Copyright (C) 2009, FUJITSU LABORATORIES LTD.
+ * Author: Noboru Iwamatsu <[email protected]>
+ *
+ * This program 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 program 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * or, by your choice,
+ *
+ * When distributed separately from the Linux kernel or incorporated into
+ * other software packages, subject to the following license:
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/wait.h>
+#include <linux/list.h>
+#include <linux/kref.h>
+
+#include <linux/usb/ch11.h>
+
+#include <xen/xen.h>
+#include <xen/balloon.h>
+#include <xen/events.h>
+#include <xen/xenbus.h>
+#include <xen/grant_table.h>
+#include <xen/page.h>
+
+#include <xen/interface/grant_table.h>
+#include <xen/interface/io/usbif.h>
+
+struct usbstub;
+
+#define USBBACK_BUS_ID_SIZE 20
+#define USB_DEV_ADDR_SIZE 128
+
+struct usbback_info {
+ domid_t domid;
+ unsigned handle;
+ int num_ports;
+ enum usb_spec_version usb_ver;
+
+ struct xenbus_device *xbdev;
+ struct list_head usbif_list;
+
+ unsigned irq;
+
+ struct usbif_urb_back_ring urb_ring;
+ struct usbif_conn_back_ring conn_ring;
+
+ spinlock_t urb_ring_lock;
+ spinlock_t conn_ring_lock;
+ atomic_t refcnt;
+ int is_connected;
+
+ int ring_error;
+
+ struct xenbus_watch backend_watch;
+
+ /* device address lookup table */
+ struct usbstub *addr_table[USB_DEV_ADDR_SIZE];
+ spinlock_t addr_lock;
+
+ /* connected device list */
+ struct list_head stub_list;
+ spinlock_t stub_lock;
+
+ /* deferred hotplug list, guarded by conn_ring_lock */
+ struct list_head hotplug_list;
+
+ /* request schedule */
+ wait_queue_head_t waiting_to_free;
+};
+
+struct vusb_port_id {
+ struct list_head id_list;
+ struct list_head hotplug_list;
+ char phys_bus[USBBACK_BUS_ID_SIZE];
+ domid_t domid;
+ unsigned handle;
+ int portnum;
+ int speed;
+};
+
+struct usbstub {
+ struct kref kref;
+ struct list_head dev_list;
+
+ struct vusb_port_id *portid;
+ struct usb_device *udev;
+ struct usbback_info *usbif;
+ int addr;
+
+ struct list_head submitting_list;
+ spinlock_t submitting_lock;
+};
+
+struct pending_req_segment {
+ uint16_t offset;
+ uint16_t length;
+};
+
+struct pending_req {
+ struct usbback_info *usbif;
+
+ uint16_t id; /* request id */
+
+ struct usbstub *stub;
+ struct list_head urb_list;
+
+ /* urb */
+ struct urb *urb;
+ void *buffer;
+ dma_addr_t transfer_dma;
+ struct usb_ctrlrequest *setup;
+
+ /* request segments */
+ uint16_t nr_buffer_segs; /* number of urb->transfer_buffer segments */
+ uint16_t nr_extra_segs; /* number of iso_frame_desc segments (ISO) */
+ struct pending_req_segment seg[USBIF_MAX_SEGMENTS_PER_REQUEST];
+ grant_handle_t grant_handles[USBIF_MAX_SEGMENTS_PER_REQUEST];
+ struct page *pages[USBIF_MAX_SEGMENTS_PER_REQUEST];
+};
+
+struct work_request_data {
+ int (*work_func)(struct usb_device *, struct work_request_data *);
+ union {
+ struct {
+ int pipe;
+ } clear_halt;
+ struct {
+ int interface;
+ int alternate;
+ } set_interface;
+ };
+};
+
+struct work_request {
+ struct pending_req *pending_req;
+ struct work_request_data data;
+ struct work_struct work;
+};
+
+static int usbback_max_buffer_pages = 1024;
+module_param_named(max_buffer_pages, usbback_max_buffer_pages, int, 0644);
+MODULE_PARM_DESC(max_buffer_pages,
+"Maximum number of free pages to keep in backend buffer");
+
+static struct kmem_cache *usbback_cachep;
+static DEFINE_SPINLOCK(usbback_free_pages_lock);
+static int usbback_free_pages_num;
+static LIST_HEAD(usbback_free_pages);
+static LIST_HEAD(usbback_pending_urb_free);
+static DEFINE_SPINLOCK(usbback_urb_free_lock);
+static LIST_HEAD(usbback_port_list);
+static DEFINE_SPINLOCK(usbback_port_list_lock);
+static LIST_HEAD(usbback_usbif_list);
+static DEFINE_SPINLOCK(usbback_usbif_list_lock);
+
+#define USBBACK_INVALID_HANDLE (~0)
+
+static void usbback_get(struct usbback_info *info)
+{
+ atomic_inc(&info->refcnt);
+}
+
+static void usbback_put(struct usbback_info *info)
+{
+ if (atomic_dec_and_test(&info->refcnt))
+ wake_up(&info->waiting_to_free);
+}
+
+static void usbback_put_free_pages(struct page **page, int num)
+{
+ unsigned long flags;
+ int i = usbback_free_pages_num + num, n = num;
+
+ if (num == 0)
+ return;
+ if (i > usbback_max_buffer_pages) {
+ n = min(num, i - usbback_max_buffer_pages);
+ free_xenballooned_pages(n, page + num - n);
+ n = num - n;
+ }
+ spin_lock_irqsave(&usbback_free_pages_lock, flags);
+ for (i = 0; i < n; i++)
+ list_add(&page[i]->lru, &usbback_free_pages);
+ usbback_free_pages_num += n;
+ spin_unlock_irqrestore(&usbback_free_pages_lock, flags);
+}
+
+static int usbback_get_free_page(struct page **page)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&usbback_free_pages_lock, flags);
+ if (list_empty(&usbback_free_pages)) {
+ spin_unlock_irqrestore(&usbback_free_pages_lock, flags);
+ return alloc_xenballooned_pages(1, page, false);
+ }
+ page[0] = list_first_entry(&usbback_free_pages, struct page, lru);
+ list_del(&page[0]->lru);
+ usbback_free_pages_num--;
+ spin_unlock_irqrestore(&usbback_free_pages_lock, flags);
+ return 0;
+}
+
+static unsigned long usbback_vaddr(struct pending_req *req, int seg)
+{
+ unsigned long pfn = page_to_pfn(req->pages[seg]);
+
+ return (unsigned long)pfn_to_kaddr(pfn);
+}
+
+static void usbback_add_req_to_submitting_list(struct pending_req *pending_req)
+{
+ unsigned long flags;
+ struct usbstub *stub = pending_req->stub;
+
+ spin_lock_irqsave(&stub->submitting_lock, flags);
+ list_add_tail(&pending_req->urb_list, &stub->submitting_list);
+ spin_unlock_irqrestore(&stub->submitting_lock, flags);
+}
+
+static void usbback_rm_req_from_submitting_list(struct pending_req *pending_req)
+{
+ unsigned long flags;
+ struct usbstub *stub = pending_req->stub;
+
+ spin_lock_irqsave(&stub->submitting_lock, flags);
+ list_del_init(&pending_req->urb_list);
+ spin_unlock_irqrestore(&stub->submitting_lock, flags);
+}
+
+static void usbback_unlink_urbs(struct usbstub *stub)
+{
+ struct pending_req *req, *tmp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&stub->submitting_lock, flags);
+ list_for_each_entry_safe(req, tmp, &stub->submitting_list, urb_list) {
+ usb_unlink_urb(req->urb);
+ }
+ spin_unlock_irqrestore(&stub->submitting_lock, flags);
+}
+
+static void usbback_fast_flush_area(struct pending_req *pending_req)
+{
+ struct gnttab_unmap_grant_ref unmap[USBIF_MAX_SEGMENTS_PER_REQUEST];
+ struct page *pages[USBIF_MAX_SEGMENTS_PER_REQUEST];
+ unsigned i, nr_segs, invcount = 0;
+ grant_handle_t handle;
+ int ret;
+
+ nr_segs = pending_req->nr_buffer_segs + pending_req->nr_extra_segs;
+ if (!nr_segs)
+ return;
+
+ for (i = 0; i < nr_segs; i++) {
+ handle = pending_req->grant_handles[i];
+ if (handle == USBBACK_INVALID_HANDLE)
+ continue;
+ gnttab_set_unmap_op(&unmap[invcount],
+ usbback_vaddr(pending_req, i),
+ GNTMAP_host_map, handle);
+ pages[invcount] = pending_req->pages[i];
+ invcount++;
+ }
+
+ ret = gnttab_unmap_refs(unmap, NULL, pages, invcount);
+ BUG_ON(ret);
+
+ usbback_put_free_pages(pending_req->pages, nr_segs);
+}
+
+static void usbback_copy_buff_to_pages(void *buf,
+ struct pending_req *pending_req,
+ int start, int nr_pages, unsigned offset,
+ unsigned length)
+{
+ int i;
+ struct pending_req_segment *seg;
+ unsigned buf_off = 0;
+ unsigned off, len;
+
+ seg = pending_req->seg + start;
+ for (i = start; i < start + nr_pages; i++) {
+ len = seg->length;
+ off = seg->offset;
+ if (buf_off + len > offset) {
+ if (buf_off < offset) {
+ len -= offset - buf_off;
+ off += offset - buf_off;
+ buf_off += offset - buf_off;
+ }
+ if (buf_off + len > offset + length)
+ len -= offset + length - buf_off;
+ memcpy((void *)usbback_vaddr(pending_req, i) + off,
+ buf + buf_off, len);
+ }
+ buf_off += len;
+ if (buf_off >= offset + length)
+ return;
+ seg++;
+ }
+}
+
+static void usbback_copy_pages_to_buff(void *buf,
+ struct pending_req *pending_req,
+ int start, int nr_pages)
+{
+ int i;
+ struct pending_req_segment *seg;
+
+ seg = pending_req->seg + start;
+ for (i = start; i < start + nr_pages; i++) {
+ memcpy(buf, (void *)usbback_vaddr(pending_req, i) + seg->offset,
+ seg->length);
+ buf += seg->length;
+ seg++;
+ }
+}
+
+static int usbback_alloc_urb(struct usbif_urb_request *req,
+ struct pending_req *pending_req)
+{
+ int ret;
+
+ if (usb_pipeisoc(req->pipe))
+ pending_req->urb = usb_alloc_urb(req->u.isoc.number_of_packets,
+ GFP_KERNEL);
+ else
+ pending_req->urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!pending_req->urb)
+ return -ENOMEM;
+
+ if (req->buffer_length) {
+ pending_req->buffer =
+ usb_alloc_coherent(pending_req->stub->udev,
+ req->buffer_length, GFP_KERNEL,
+ &pending_req->transfer_dma);
+ if (!pending_req->buffer) {
+ ret = -ENOMEM;
+ goto fail_free_urb;
+ }
+ }
+
+ if (usb_pipecontrol(req->pipe)) {
+ pending_req->setup = kmalloc(sizeof(struct usb_ctrlrequest),
+ GFP_KERNEL);
+ if (!pending_req->setup) {
+ ret = -ENOMEM;
+ goto fail_free_buffer;
+ }
+ }
+
+ return 0;
+
+fail_free_buffer:
+ if (req->buffer_length)
+ usb_free_coherent(pending_req->stub->udev, req->buffer_length,
+ pending_req->buffer,
+ pending_req->transfer_dma);
+fail_free_urb:
+ usb_free_urb(pending_req->urb);
+ return ret;
+}
+
+static void usbback_free_urb(struct urb *urb)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&usbback_urb_free_lock, flags);
+ list_add(&urb->urb_list, &usbback_pending_urb_free);
+ spin_unlock_irqrestore(&usbback_urb_free_lock, flags);
+}
+
+static void _usbback_free_urb(struct urb *urb)
+{
+ if (usb_pipecontrol(urb->pipe))
+ kfree(urb->setup_packet);
+ if (urb->transfer_buffer_length)
+ usb_free_coherent(urb->dev, urb->transfer_buffer_length,
+ urb->transfer_buffer, urb->transfer_dma);
+ usb_free_urb(urb);
+}
+
+static void usbback__free_urbs(void)
+{
+ unsigned long flags;
+ struct list_head tmp_list;
+ struct urb *next_urb;
+
+ if (list_empty(&usbback_pending_urb_free))
+ return;
+
+ INIT_LIST_HEAD(&tmp_list);
+
+ spin_lock_irqsave(&usbback_urb_free_lock, flags);
+ list_splice_init(&usbback_pending_urb_free, &tmp_list);
+ spin_unlock_irqrestore(&usbback_urb_free_lock, flags);
+
+ while (!list_empty(&tmp_list)) {
+ next_urb = list_first_entry(&tmp_list, struct urb, urb_list);
+ list_del(&next_urb->urb_list);
+ _usbback_free_urb(next_urb);
+ }
+}
+
+static void usbback_do_response(struct pending_req *pending_req,
+ int32_t status, int32_t actual_length,
+ int32_t error_count, uint16_t start_frame)
+{
+ struct usbback_info *usbif = pending_req->usbif;
+ struct usbif_urb_response *res;
+ unsigned long flags;
+ int notify;
+
+ spin_lock_irqsave(&usbif->urb_ring_lock, flags);
+ res = RING_GET_RESPONSE(&usbif->urb_ring, usbif->urb_ring.rsp_prod_pvt);
+ res->id = pending_req->id;
+ res->status = status;
+ res->actual_length = actual_length;
+ res->error_count = error_count;
+ res->start_frame = start_frame;
+ usbif->urb_ring.rsp_prod_pvt++;
+ barrier();
+ RING_PUSH_RESPONSES_AND_CHECK_NOTIFY(&usbif->urb_ring, notify);
+ spin_unlock_irqrestore(&usbif->urb_ring_lock, flags);
+
+ if (notify)
+ notify_remote_via_irq(usbif->irq);
+}
+
+static void usbback_do_response_ret(struct pending_req *pending_req,
+ int32_t status)
+{
+ usbback_do_response(pending_req, status, 0, 0, 0);
+ usbback_put(pending_req->usbif);
+ kmem_cache_free(usbback_cachep, pending_req);
+}
+
+static void usbback_copy_isoc_to_pages(struct pending_req *pending_req)
+{
+ unsigned sz, n_isoc, i;
+ struct urb *urb = pending_req->urb;
+ struct usb_iso_packet_descriptor *isoc;
+
+ isoc = &urb->iso_frame_desc[0];
+ n_isoc = urb->number_of_packets;
+ sz = n_isoc * sizeof(struct usb_iso_packet_descriptor);
+
+ usbback_copy_buff_to_pages(isoc, pending_req,
+ pending_req->nr_buffer_segs,
+ pending_req->nr_extra_segs, 0, sz);
+
+ if (!usb_pipein(urb->pipe))
+ return;
+
+ for (i = 0; i < n_isoc; i++) {
+ usbback_copy_buff_to_pages(pending_req->buffer,
+ pending_req, 0,
+ pending_req->nr_buffer_segs,
+ isoc->offset, isoc->actual_length);
+ isoc++;
+ }
+}
+
+static void usbback_urb_complete(struct urb *urb)
+{
+ struct pending_req *pending_req = (struct pending_req *)urb->context;
+
+ if (usb_pipeisoc(urb->pipe))
+ usbback_copy_isoc_to_pages(pending_req);
+ else if (usb_pipein(urb->pipe) && urb->actual_length > 0)
+ usbback_copy_buff_to_pages(pending_req->buffer, pending_req,
+ 0, pending_req->nr_buffer_segs,
+ 0, urb->actual_length);
+
+ usbback_fast_flush_area(pending_req);
+
+ usbback_do_response(pending_req, urb->status, urb->actual_length,
+ urb->error_count, urb->start_frame);
+
+ usbback_rm_req_from_submitting_list(pending_req);
+
+ usbback_free_urb(urb);
+ usbback_put(pending_req->usbif);
+ kmem_cache_free(usbback_cachep, pending_req);
+}
+
+static int usbback_gnttab_map(struct usbback_info *usbif,
+ struct usbif_urb_request *req,
+ struct pending_req *pending_req)
+{
+ int i, ret;
+ unsigned nr_segs;
+ uint32_t flags;
+ struct gnttab_map_grant_ref map[USBIF_MAX_SEGMENTS_PER_REQUEST];
+
+ nr_segs = pending_req->nr_buffer_segs + pending_req->nr_extra_segs;
+ if (!nr_segs)
+ return 0;
+
+ if (nr_segs > USBIF_MAX_SEGMENTS_PER_REQUEST) {
+ pr_err("xen-pvusb: Bad number of segments in request\n");
+ return -EINVAL;
+ }
+
+ if (pending_req->nr_buffer_segs) {
+ flags = GNTMAP_host_map;
+ if (usb_pipeout(req->pipe))
+ flags |= GNTMAP_readonly;
+ for (i = 0; i < pending_req->nr_buffer_segs; i++) {
+ if (usbback_get_free_page(pending_req->pages + i)) {
+ usbback_put_free_pages(pending_req->pages, i);
+ pr_err("xen-pvusb: no grant page\n");
+ return -ENOMEM;
+ }
+ gnttab_set_map_op(&map[i],
+ usbback_vaddr(pending_req, i), flags,
+ req->seg[i].gref, usbif->domid);
+ }
+ }
+
+ if (pending_req->nr_extra_segs) {
+ flags = GNTMAP_host_map;
+ for (i = req->nr_buffer_segs; i < nr_segs; i++) {
+ if (usbback_get_free_page(pending_req->pages + i)) {
+ usbback_put_free_pages(pending_req->pages, i);
+ pr_err("xen-pvusb: no grant page\n");
+ return -ENOMEM;
+ }
+ gnttab_set_map_op(&map[i],
+ usbback_vaddr(pending_req, i), flags,
+ req->seg[i].gref, usbif->domid);
+ }
+ }
+
+ ret = gnttab_map_refs(map, NULL, pending_req->pages, nr_segs);
+ BUG_ON(ret);
+
+ for (i = 0; i < nr_segs; i++) {
+ if (unlikely(map[i].status != GNTST_okay)) {
+ pr_err("xen-pvusb: invalid buffer, could not map it\n");
+ map[i].handle = USBBACK_INVALID_HANDLE;
+ ret = 1;
+ }
+
+ pending_req->grant_handles[i] = map[i].handle;
+
+ if (ret)
+ continue;
+
+ pending_req->seg[i].offset = req->seg[i].offset;
+ pending_req->seg[i].length = req->seg[i].length;
+
+ if ((unsigned)pending_req->seg[i].offset +
+ (unsigned)pending_req->seg[i].length > PAGE_SIZE)
+ ret = 1;
+ }
+
+ if (ret) {
+ usbback_fast_flush_area(pending_req);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void usbback_init_urb(struct usbif_urb_request *req,
+ struct pending_req *pending_req)
+{
+ unsigned pipe;
+ struct usb_device *udev = pending_req->stub->udev;
+ struct urb *urb = pending_req->urb;
+
+ switch (usb_pipetype(req->pipe)) {
+ case PIPE_ISOCHRONOUS:
+ pipe = usb_pipein(req->pipe) ?
+ usb_rcvisocpipe(udev, usb_pipeendpoint(req->pipe)) :
+ usb_sndisocpipe(udev, usb_pipeendpoint(req->pipe));
+ urb->dev = udev;
+ urb->pipe = pipe;
+ urb->transfer_flags = req->transfer_flags;
+ urb->transfer_flags |= URB_ISO_ASAP;
+ urb->transfer_buffer = pending_req->buffer;
+ urb->transfer_buffer_length = req->buffer_length;
+ urb->complete = usbback_urb_complete;
+ urb->context = pending_req;
+ urb->interval = req->u.isoc.interval;
+ urb->start_frame = req->u.isoc.start_frame;
+ urb->number_of_packets = req->u.isoc.number_of_packets;
+ break;
+
+ case PIPE_INTERRUPT:
+ pipe = usb_pipein(req->pipe) ?
+ usb_rcvintpipe(udev, usb_pipeendpoint(req->pipe)) :
+ usb_sndintpipe(udev, usb_pipeendpoint(req->pipe));
+ usb_fill_int_urb(urb, udev, pipe,
+ pending_req->buffer, req->buffer_length,
+ usbback_urb_complete,
+ pending_req, req->u.intr.interval);
+ /*
+ * High speed interrupt endpoints use a logarithmic encoding of
+ * the endpoint interval, and usb_fill_int_urb() initializes a
+ * interrupt urb with the encoded interval value.
+ *
+ * req->u.intr.interval is the interval value that already
+ * encoded in the frontend part, and the above
+ * usb_fill_int_urb() initializes the urb->interval with double
+ * encoded value.
+ *
+ * So, simply overwrite the urb->interval with original value.
+ */
+ urb->interval = req->u.intr.interval;
+ urb->transfer_flags = req->transfer_flags;
+ break;
+
+ case PIPE_CONTROL:
+ pipe = usb_pipein(req->pipe) ? usb_rcvctrlpipe(udev, 0) :
+ usb_sndctrlpipe(udev, 0);
+ usb_fill_control_urb(urb, udev, pipe,
+ (unsigned char *)pending_req->setup,
+ pending_req->buffer, req->buffer_length,
+ usbback_urb_complete, pending_req);
+ memcpy(pending_req->setup, req->u.ctrl, 8);
+ urb->transfer_flags = req->transfer_flags;
+ break;
+
+ case PIPE_BULK:
+ pipe = usb_pipein(req->pipe) ?
+ usb_rcvbulkpipe(udev, usb_pipeendpoint(req->pipe)) :
+ usb_sndbulkpipe(udev, usb_pipeendpoint(req->pipe));
+ usb_fill_bulk_urb(urb, udev, pipe,
+ pending_req->buffer, req->buffer_length,
+ usbback_urb_complete, pending_req);
+ urb->transfer_flags = req->transfer_flags;
+ break;
+
+ default:
+ break;
+ }
+
+ if (req->buffer_length) {
+ urb->transfer_dma = pending_req->transfer_dma;
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ }
+}
+
+static int usbback_set_interface_work(struct usb_device *udev,
+ struct work_request_data *data)
+{
+ int ret;
+
+ usb_lock_device(udev);
+ ret = usb_set_interface(udev, data->set_interface.interface,
+ data->set_interface.alternate);
+ usb_unlock_device(udev);
+
+ return ret;
+}
+
+static int usbback_clear_halt_work(struct usb_device *udev,
+ struct work_request_data *data)
+{
+ int ret;
+
+ usb_lock_device(udev);
+ ret = usb_clear_halt(udev, data->clear_halt.pipe);
+ usb_unlock_device(udev);
+
+ return ret;
+}
+
+static void usbback_work(struct work_struct *arg)
+{
+ struct work_request *req;
+ struct pending_req *pending_req;
+ struct usb_device *udev;
+ int ret;
+
+ req = container_of(arg, struct work_request, work);
+ pending_req = req->pending_req;
+ udev = pending_req->stub->udev;
+
+ ret = req->data.work_func(udev, &req->data);
+ usb_put_dev(udev);
+
+ usbback_do_response_ret(pending_req, ret);
+ kfree(req);
+}
+
+static void usbback_sched_work(struct pending_req *pending_req,
+ struct work_request_data *data)
+{
+ struct work_request *req;
+ struct usb_device *udev = pending_req->stub->udev;
+
+ req = kmalloc(sizeof(*req), GFP_KERNEL);
+ if (!req) {
+ usbback_do_response_ret(pending_req, -ESHUTDOWN);
+ return;
+ }
+
+ req->pending_req = pending_req;
+ req->data = *data;
+ INIT_WORK(&req->work, usbback_work);
+
+ usb_get_dev(udev);
+ schedule_work(&req->work);
+}
+
+static void usbback_set_interface(struct pending_req *pending_req,
+ int interface, int alternate)
+{
+ struct work_request_data data;
+
+ data.work_func = usbback_set_interface_work;
+ data.set_interface.interface = interface;
+ data.set_interface.alternate = alternate;
+ usbback_sched_work(pending_req, &data);
+}
+
+static void usbback_clear_halt(struct pending_req *pending_req, int pipe)
+{
+ struct work_request_data data;
+
+ data.work_func = usbback_clear_halt_work;
+ data.clear_halt.pipe = pipe;
+ usbback_sched_work(pending_req, &data);
+}
+
+static void usbback_set_address(struct usbback_info *usbif,
+ struct usbstub *stub, int cur_addr,
+ int new_addr)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&usbif->addr_lock, flags);
+ if (cur_addr)
+ usbif->addr_table[cur_addr] = NULL;
+ if (new_addr)
+ usbif->addr_table[new_addr] = stub;
+ stub->addr = new_addr;
+ spin_unlock_irqrestore(&usbif->addr_lock, flags);
+}
+
+static struct usbstub *usbback_find_attached_device(struct usbback_info *usbif,
+ int portnum)
+{
+ struct usbstub *stub;
+ int found = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&usbif->stub_lock, flags);
+ list_for_each_entry(stub, &usbif->stub_list, dev_list)
+ if (stub->portid->portnum == portnum) {
+ found = 1;
+ break;
+ }
+ spin_unlock_irqrestore(&usbif->stub_lock, flags);
+
+ return found ? stub : NULL;
+}
+
+static void usbback_process_unlink_req(struct usbif_urb_request *req,
+ struct pending_req *pending_req)
+{
+ struct usbback_info *usbif = pending_req->usbif;
+ struct pending_req *unlink_req;
+ int devnum;
+ int ret = 0;
+ unsigned long flags;
+
+ devnum = usb_pipedevice(req->pipe);
+ if (unlikely(devnum == 0)) {
+ pending_req->stub = usbback_find_attached_device(usbif,
+ usbif_pipeportnum(req->pipe));
+ if (unlikely(!pending_req->stub)) {
+ ret = -ENODEV;
+ goto fail_response;
+ }
+ } else {
+ if (unlikely(!usbif->addr_table[devnum])) {
+ ret = -ENODEV;
+ goto fail_response;
+ }
+ pending_req->stub = usbif->addr_table[devnum];
+ }
+
+ spin_lock_irqsave(&pending_req->stub->submitting_lock, flags);
+ list_for_each_entry(unlink_req, &pending_req->stub->submitting_list,
+ urb_list)
+ if (unlink_req->id == req->u.unlink.unlink_id) {
+ ret = usb_unlink_urb(unlink_req->urb);
+ break;
+ }
+ spin_unlock_irqrestore(&pending_req->stub->submitting_lock, flags);
+
+fail_response:
+ usbback_do_response_ret(pending_req, ret);
+}
+
+static int usbback_check_and_submit(struct usbif_urb_request *req,
+ struct pending_req *pending_req)
+{
+ struct usbback_info *usbif = pending_req->usbif;
+ int devnum;
+ struct usbstub *stub = NULL;
+ struct usb_ctrlrequest *ctrl = (struct usb_ctrlrequest *)req->u.ctrl;
+ int ret;
+ int done = 0;
+ __u16 wValue = le16_to_cpu(ctrl->wValue);
+ __u16 wIndex = le16_to_cpu(ctrl->wIndex);
+
+ devnum = usb_pipedevice(req->pipe);
+
+ /*
+ * When the device is first connected or resetted, USB device has no
+ * address. In this initial state, following requests are sent to device
+ * address (#0),
+ *
+ * 1. GET_DESCRIPTOR (with Descriptor Type is "DEVICE") is sent,
+ * and OS knows what device is connected to.
+ *
+ * 2. SET_ADDRESS is sent, and then device has its address.
+ *
+ * In the next step, SET_CONFIGURATION is sent to addressed device, and
+ * then the device is finally ready to use.
+ */
+ if (unlikely(devnum == 0)) {
+ stub = usbback_find_attached_device(usbif,
+ usbif_pipeportnum(req->pipe));
+ if (unlikely(!stub)) {
+ ret = -ENODEV;
+ goto do_response;
+ }
+
+ switch (ctrl->bRequest) {
+ case USB_REQ_GET_DESCRIPTOR:
+ /*
+ * GET_DESCRIPTOR request to device #0.
+ * through to normal urb transfer.
+ */
+ pending_req->stub = stub;
+ return 0;
+ case USB_REQ_SET_ADDRESS:
+ /*
+ * SET_ADDRESS request to device #0.
+ * add attached device to addr_table.
+ */
+ usbback_set_address(usbif, stub, 0, wValue);
+ ret = 0;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ goto do_response;
+ }
+
+ if (unlikely(!usbif->addr_table[devnum])) {
+ ret = -ENODEV;
+ goto do_response;
+ }
+ pending_req->stub = usbif->addr_table[devnum];
+
+ /*
+ * Check special request
+ */
+ switch (ctrl->bRequest) {
+ case USB_REQ_SET_ADDRESS:
+ /*
+ * SET_ADDRESS request to addressed device.
+ * change addr or remove from addr_table.
+ */
+ usbback_set_address(usbif, pending_req->stub, devnum, wValue);
+ ret = 0;
+ goto do_response;
+ case USB_REQ_SET_INTERFACE:
+ if (ctrl->bRequestType == USB_RECIP_INTERFACE) {
+ usbback_set_interface(pending_req, wIndex, wValue);
+ done = 1;
+ }
+ break;
+ case USB_REQ_CLEAR_FEATURE:
+ if (ctrl->bRequestType == USB_RECIP_ENDPOINT &&
+ wValue == USB_ENDPOINT_HALT) {
+ int pipe;
+ int ep = wIndex & 0x0f;
+
+ pipe = wIndex & USB_DIR_IN ?
+ usb_rcvctrlpipe(pending_req->stub->udev, ep) :
+ usb_sndctrlpipe(pending_req->stub->udev, ep);
+ usbback_clear_halt(pending_req, pipe);
+ done = 1;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return done;
+
+do_response:
+ usbback_do_response_ret(pending_req, ret);
+ return 1;
+}
+
+static void usbback_dispatch(struct usbif_urb_request *req,
+ struct pending_req *pending_req)
+{
+ int ret;
+ struct usbback_info *usbif = pending_req->usbif;
+
+ usbback_get(usbif);
+
+ /* unlink request */
+ if (unlikely(usbif_pipeunlink(req->pipe))) {
+ usbback_process_unlink_req(req, pending_req);
+ return;
+ }
+
+ if (usb_pipecontrol(req->pipe)) {
+ if (usbback_check_and_submit(req, pending_req))
+ return;
+ } else {
+ int devnum = usb_pipedevice(req->pipe);
+
+ if (unlikely(!usbif->addr_table[devnum])) {
+ ret = -ENODEV;
+ goto fail_response;
+ }
+ pending_req->stub = usbif->addr_table[devnum];
+ }
+
+ ret = usbback_alloc_urb(req, pending_req);
+ if (ret) {
+ ret = -ESHUTDOWN;
+ goto fail_response;
+ }
+
+ usbback_add_req_to_submitting_list(pending_req);
+
+ usbback_init_urb(req, pending_req);
+
+ pending_req->nr_buffer_segs = req->nr_buffer_segs;
+ if (usb_pipeisoc(req->pipe))
+ pending_req->nr_extra_segs = req->u.isoc.nr_frame_desc_segs;
+ else
+ pending_req->nr_extra_segs = 0;
+
+ ret = usbback_gnttab_map(usbif, req, pending_req);
+ if (ret) {
+ pr_err("xen-pvusb: invalid buffer\n");
+ ret = -ESHUTDOWN;
+ goto fail_free_urb;
+ }
+
+ if (usb_pipeout(req->pipe) && req->buffer_length)
+ usbback_copy_pages_to_buff(pending_req->buffer, pending_req,
+ 0, pending_req->nr_buffer_segs);
+ if (usb_pipeisoc(req->pipe))
+ usbback_copy_pages_to_buff(&pending_req->urb->iso_frame_desc[0],
+ pending_req,
+ pending_req->nr_buffer_segs,
+ pending_req->nr_extra_segs);
+
+ ret = usb_submit_urb(pending_req->urb, GFP_KERNEL);
+ if (!ret)
+ return;
+
+ pr_err("xen-pvusb: failed submitting urb, error %d\n", ret);
+ ret = -ESHUTDOWN;
+
+ usbback_fast_flush_area(pending_req);
+fail_free_urb:
+ usbback_rm_req_from_submitting_list(pending_req);
+ usbback_free_urb(pending_req->urb);
+fail_response:
+ usbback_do_response_ret(pending_req, ret);
+}
+
+static int usbback_start_submit_urb(struct usbback_info *usbif)
+{
+ struct usbif_urb_back_ring *urb_ring = &usbif->urb_ring;
+ struct usbif_urb_request req;
+ struct pending_req *pending_req;
+ RING_IDX rc, rp;
+ int more_to_do;
+
+ rc = urb_ring->req_cons;
+ rp = urb_ring->sring->req_prod;
+ rmb(); /* req_cons is written by frontend. */
+
+ if (RING_REQUEST_PROD_OVERFLOW(urb_ring, rp)) {
+ rc = urb_ring->rsp_prod_pvt;
+ pr_warn("xen-pvusb: Dom%d provided bogus ring requests (%#x - %#x = %u). Halting ring processing on dev=%#x\n",
+ usbif->domid, rp, rc, rp - rc, usbif->handle);
+ usbif->ring_error = 1;
+ return 0;
+ }
+
+ while (rc != rp) {
+ if (RING_REQUEST_CONS_OVERFLOW(urb_ring, rc))
+ break;
+
+ pending_req = kmem_cache_alloc(usbback_cachep, GFP_KERNEL);
+ if (!pending_req)
+ return 1;
+
+ req = *RING_GET_REQUEST(urb_ring, rc);
+
+ pending_req->id = req.id;
+ pending_req->usbif = usbif;
+
+ usbback_dispatch(&req, pending_req);
+
+ urb_ring->req_cons = ++rc;
+
+ cond_resched();
+ }
+
+ RING_FINAL_CHECK_FOR_REQUESTS(urb_ring, more_to_do);
+
+ return !!more_to_do;
+}
+
+static void usbback_hotplug_notify(struct usbback_info *usbif,
+ struct vusb_port_id *portid)
+{
+ struct usbif_conn_back_ring *ring = &usbif->conn_ring;
+ struct usbif_conn_request *req;
+ struct usbif_conn_response *res;
+ unsigned long flags;
+ u16 id;
+ int notify;
+
+ spin_lock_irqsave(&usbif->conn_ring_lock, flags);
+
+ if (!usbif->is_connected) {
+ list_add(&portid->hotplug_list, &usbif->hotplug_list);
+ spin_unlock_irqrestore(&usbif->conn_ring_lock, flags);
+ return;
+ }
+
+ req = RING_GET_REQUEST(ring, ring->req_cons);
+ id = req->id;
+ ring->req_cons++;
+ ring->sring->req_event = ring->req_cons + 1;
+
+ res = RING_GET_RESPONSE(ring, ring->rsp_prod_pvt);
+ res->id = id;
+ res->portnum = portid->portnum;
+ res->speed = portid->speed;
+ ring->rsp_prod_pvt++;
+ RING_PUSH_RESPONSES_AND_CHECK_NOTIFY(ring, notify);
+
+ spin_unlock_irqrestore(&usbif->conn_ring_lock, flags);
+
+ if (notify)
+ notify_remote_via_irq(usbif->irq);
+}
+
+static void usbback_do_hotplug(struct usbback_info *usbif)
+{
+ unsigned long flags;
+ struct vusb_port_id *portid;
+
+ spin_lock_irqsave(&usbif->conn_ring_lock, flags);
+ usbif->is_connected = 1;
+ while (!list_empty(&usbif->hotplug_list)) {
+ portid = list_first_entry(&usbif->hotplug_list,
+ struct vusb_port_id, hotplug_list);
+ list_del(&portid->hotplug_list);
+ spin_unlock_irqrestore(&usbif->conn_ring_lock, flags);
+ usbback_hotplug_notify(usbif, portid);
+ spin_lock_irqsave(&usbif->conn_ring_lock, flags);
+ }
+ spin_unlock_irqrestore(&usbif->conn_ring_lock, flags);
+}
+
+static irqreturn_t usbback_be_int(int irq, void *dev_id)
+{
+ struct usbback_info *usbif = dev_id;
+
+ if (usbif->ring_error)
+ return IRQ_HANDLED;
+
+ while (usbback_start_submit_urb(usbif)) {
+ usbback__free_urbs();
+ cond_resched();
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Attach usbstub device to usbif.
+ */
+static void usbback_attach_device(struct usbback_info *usbif,
+ struct usbstub *stub)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&usbif->stub_lock, flags);
+ list_add(&stub->dev_list, &usbif->stub_list);
+ spin_unlock_irqrestore(&usbif->stub_lock, flags);
+ stub->usbif = usbif;
+}
+
+/*
+ * Detach usbstub device from usbif.
+ */
+static void usbback_detach_device_without_lock(struct usbback_info *usbif,
+ struct usbstub *stub)
+{
+ if (stub->addr)
+ usbback_set_address(usbif, stub, stub->addr, 0);
+ list_del(&stub->dev_list);
+ stub->usbif = NULL;
+}
+
+static void usbback_detach_device(struct usbback_info *usbif,
+ struct usbstub *stub)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&usbif->stub_lock, flags);
+ usbback_detach_device_without_lock(usbif, stub);
+ spin_unlock_irqrestore(&usbif->stub_lock, flags);
+}
+
+static struct vusb_port_id *usbback_find_portid_by_busid(const char *busid)
+{
+ struct vusb_port_id *portid;
+ int found = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&usbback_port_list_lock, flags);
+ list_for_each_entry(portid, &usbback_port_list, id_list)
+ if (!(strncmp(portid->phys_bus, busid, USBBACK_BUS_ID_SIZE))) {
+ found = 1;
+ break;
+ }
+ spin_unlock_irqrestore(&usbback_port_list_lock, flags);
+
+ return found ? portid : NULL;
+}
+
+static struct vusb_port_id *usbback_find_portid(const domid_t domid,
+ unsigned handle, int portnum)
+{
+ struct vusb_port_id *portid;
+ int found = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&usbback_port_list_lock, flags);
+ list_for_each_entry(portid, &usbback_port_list, id_list)
+ if (portid->domid == domid && portid->handle == handle &&
+ portid->portnum == portnum) {
+ found = 1;
+ break;
+ }
+ spin_unlock_irqrestore(&usbback_port_list_lock, flags);
+
+ return found ? portid : NULL;
+}
+
+static int usbback_portid_add(const char *busid, domid_t domid, unsigned handle,
+ int portnum)
+{
+ struct vusb_port_id *portid;
+ unsigned long flags;
+
+ portid = kzalloc(sizeof(*portid), GFP_KERNEL);
+ if (!portid)
+ return -ENOMEM;
+
+ portid->domid = domid;
+ portid->handle = handle;
+ portid->portnum = portnum;
+
+ strlcpy(portid->phys_bus, busid, USBBACK_BUS_ID_SIZE);
+
+ spin_lock_irqsave(&usbback_port_list_lock, flags);
+ list_add(&portid->id_list, &usbback_port_list);
+ spin_unlock_irqrestore(&usbback_port_list_lock, flags);
+
+ return 0;
+}
+
+static int usbback_portid_remove(domid_t domid, unsigned handle, int portnum)
+{
+ struct vusb_port_id *portid, *tmp;
+ int err = -ENOENT;
+ unsigned long flags;
+
+ spin_lock_irqsave(&usbback_port_list_lock, flags);
+ list_for_each_entry_safe(portid, tmp, &usbback_port_list, id_list)
+ if (portid->domid == domid && portid->handle == handle &&
+ portid->portnum == portnum) {
+ list_del(&portid->id_list);
+ kfree(portid);
+ err = 0;
+ }
+ spin_unlock_irqrestore(&usbback_port_list_lock, flags);
+
+ return err;
+}
+
+static struct usbstub *usbback_stub_alloc(struct usb_device *udev,
+ struct vusb_port_id *portid)
+{
+ struct usbstub *stub;
+
+ stub = kzalloc(sizeof(*stub), GFP_KERNEL);
+ if (!stub)
+ return NULL;
+
+ kref_init(&stub->kref);
+ stub->udev = usb_get_dev(udev);
+ stub->portid = portid;
+ spin_lock_init(&stub->submitting_lock);
+ INIT_LIST_HEAD(&stub->submitting_list);
+
+ return stub;
+}
+
+static void usbback_stub_release(struct kref *kref)
+{
+ struct usbstub *stub;
+
+ stub = container_of(kref, struct usbstub, kref);
+
+ usb_put_dev(stub->udev);
+ stub->udev = NULL;
+ stub->portid = NULL;
+ kfree(stub);
+}
+
+static struct usbback_info *usbback_find_usbif(domid_t domid, unsigned handle)
+{
+ struct usbback_info *usbif;
+ int found = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&usbback_usbif_list_lock, flags);
+ list_for_each_entry(usbif, &usbback_usbif_list, usbif_list)
+ if (usbif->domid == domid && usbif->handle == handle) {
+ found = 1;
+ break;
+ }
+ spin_unlock_irqrestore(&usbback_usbif_list_lock, flags);
+
+ return found ? usbif : NULL;
+}
+
+static int usbback_stub_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ const char *busid = dev_name(intf->dev.parent);
+ struct vusb_port_id *portid;
+ struct usbstub *stub;
+ struct usbback_info *usbif;
+
+ /* hub currently not supported, so skip. */
+ if (udev->descriptor.bDeviceClass == USB_CLASS_HUB)
+ return -ENODEV;
+
+ portid = usbback_find_portid_by_busid(busid);
+ if (!portid)
+ return -ENODEV;
+
+ usbif = usbback_find_usbif(portid->domid, portid->handle);
+ if (!usbif)
+ return -ENODEV;
+
+ switch (udev->speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ break;
+ case USB_SPEED_HIGH:
+ if (usbif->usb_ver >= USB_VER_USB20)
+ break;
+ /* fall through */
+ default:
+ return -ENODEV;
+ }
+
+ stub = usbback_find_attached_device(usbif, portid->portnum);
+ if (!stub) {
+ /* new connection */
+ stub = usbback_stub_alloc(udev, portid);
+ if (!stub)
+ return -ENOMEM;
+ usbback_attach_device(usbif, stub);
+ portid->speed = udev->speed;
+ usbback_hotplug_notify(usbif, portid);
+ } else {
+ /* maybe already called and connected by other intf */
+ if (strncmp(stub->portid->phys_bus, busid, USBBACK_BUS_ID_SIZE))
+ return -ENODEV;
+ }
+
+ kref_get(&stub->kref);
+ usb_set_intfdata(intf, stub);
+ return 0;
+}
+
+static void usbback_stub_disconnect(struct usb_interface *intf)
+{
+ struct usbstub *stub = (struct usbstub *)usb_get_intfdata(intf);
+
+ usb_set_intfdata(intf, NULL);
+
+ if (!stub)
+ return;
+
+ if (stub->usbif) {
+ stub->portid->speed = 0;
+ usbback_hotplug_notify(stub->usbif, stub->portid);
+ usbback_detach_device(stub->usbif, stub);
+ }
+ usbback_unlink_urbs(stub);
+ kref_put(&stub->kref, usbback_stub_release);
+}
+
+static ssize_t usbback_stub_show_portids(struct device_driver *driver,
+ char *buf)
+{
+ struct vusb_port_id *portid;
+ size_t count = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&usbback_port_list_lock, flags);
+ list_for_each_entry(portid, &usbback_port_list, id_list) {
+ if (count >= PAGE_SIZE)
+ break;
+ count += scnprintf((char *)buf + count, PAGE_SIZE - count,
+ "%s:%d:%d:%d\n", portid->phys_bus,
+ portid->domid, portid->handle, portid->portnum);
+ }
+ spin_unlock_irqrestore(&usbback_port_list_lock, flags);
+
+ return count;
+}
+static DRIVER_ATTR(port_ids, S_IRUSR, usbback_stub_show_portids, NULL);
+
+/* table of devices that matches any usbdevice */
+static const struct usb_device_id usbback_stub_table[] = {
+ { .driver_info = 1 }, /* wildcard, see usb_match_id() */
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, usbback_stub_table);
+
+static struct usb_driver usbback_usb_driver = {
+ .name = "usbback",
+ .probe = usbback_stub_probe,
+ .disconnect = usbback_stub_disconnect,
+ .id_table = usbback_stub_table,
+ .no_dynamic_id = 1,
+};
+
+static int __init usbback_stub_init(void)
+{
+ int err;
+
+ err = usb_register(&usbback_usb_driver);
+ if (err) {
+ pr_err("xen-pvusb: usb_register failed (%d)\n", err);
+ return err;
+ }
+
+ err = driver_create_file(&usbback_usb_driver.drvwrap.driver,
+ &driver_attr_port_ids);
+ if (err)
+ usb_deregister(&usbback_usb_driver);
+
+ return err;
+}
+
+static void usbback_stub_exit(void)
+{
+ driver_remove_file(&usbback_usb_driver.drvwrap.driver,
+ &driver_attr_port_ids);
+ usb_deregister(&usbback_usb_driver);
+}
+
+static int usbback_process_port(struct xenbus_transaction xbt,
+ struct usbback_info *usbif,
+ struct xenbus_device *dev, unsigned port)
+{
+ char node[8];
+ char *busid;
+ int err;
+ struct vusb_port_id *portid;
+
+ snprintf(node, sizeof(node), "port/%d", port);
+ busid = xenbus_read(xbt, dev->nodename, node, NULL);
+ if (IS_ERR(busid)) {
+ err = PTR_ERR(busid);
+ xenbus_dev_fatal(dev, err, "reading %s", node);
+ return err;
+ }
+
+ /* Remove portid, if the port is not connected. */
+ if (strlen(busid) == 0) {
+ portid = usbback_find_portid(usbif->domid, usbif->handle, port);
+ if (portid)
+ usbback_portid_remove(usbif->domid,
+ usbif->handle, port);
+ return 0; /* never configured, ignore */
+ }
+
+ /*
+ * Add portid, if the port isn't configured and unused from other usbif.
+ */
+ portid = usbback_find_portid(usbif->domid, usbif->handle, port);
+ if (portid) {
+ if ((strncmp(portid->phys_bus, busid, USBBACK_BUS_ID_SIZE)))
+ xenbus_dev_fatal(dev, -EEXIST,
+ "can't add %s, remove first", node);
+ } else {
+ if (usbback_find_portid_by_busid(busid))
+ xenbus_dev_fatal(dev, -EBUSY,
+ "can't add %s, busid already used", node);
+ else
+ usbback_portid_add(busid, usbif->domid, usbif->handle,
+ port);
+ }
+
+ return 0;
+}
+
+static void usbback_backend_changed(struct xenbus_watch *watch,
+ const char **vec, unsigned len)
+{
+ struct xenbus_transaction xbt;
+ int err;
+ unsigned i;
+ struct usbback_info *usbif;
+ struct xenbus_device *dev;
+
+ usbif = container_of(watch, struct usbback_info, backend_watch);
+ dev = usbif->xbdev;
+
+ do {
+ err = xenbus_transaction_start(&xbt);
+ if (err) {
+ xenbus_dev_fatal(dev, err, "starting transaction");
+ return;
+ }
+
+ for (i = 1; i <= usbif->num_ports; i++) {
+ if (usbback_process_port(xbt, usbif, dev, i)) {
+ xenbus_transaction_end(xbt, 1);
+ return;
+ }
+ }
+
+ err = xenbus_transaction_end(xbt, 0);
+ } while (err == -EAGAIN);
+
+ if (err)
+ xenbus_dev_fatal(dev, err, "completing transaction");
+}
+
+static void usbback_disconnect(struct usbback_info *usbif)
+{
+ struct usbstub *stub, *tmp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&usbif->stub_lock, flags);
+ list_for_each_entry_safe(stub, tmp, &usbif->stub_list, dev_list) {
+ usbback_unlink_urbs(stub);
+ usbback_detach_device_without_lock(usbif, stub);
+ }
+ spin_unlock_irqrestore(&usbif->stub_lock, flags);
+
+ wait_event(usbif->waiting_to_free, atomic_read(&usbif->refcnt) == 0);
+
+ if (usbif->irq) {
+ unbind_from_irqhandler(usbif->irq, usbif);
+ usbif->irq = 0;
+ }
+
+ if (usbif->urb_ring.sring) {
+ xenbus_unmap_ring_vfree(usbif->xbdev, usbif->urb_ring.sring);
+ xenbus_unmap_ring_vfree(usbif->xbdev, usbif->conn_ring.sring);
+ usbif->urb_ring.sring = NULL;
+ usbif->conn_ring.sring = NULL;
+ }
+}
+
+static void usbback_free(struct usbback_info *usbif)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&usbback_usbif_list_lock, flags);
+ list_del(&usbif->usbif_list);
+ spin_unlock_irqrestore(&usbback_usbif_list_lock, flags);
+ kfree(usbif);
+}
+
+static int usbback_remove(struct xenbus_device *dev)
+{
+ struct usbback_info *usbif = dev_get_drvdata(&dev->dev);
+ int i;
+
+ if (!usbif)
+ return 0;
+
+ if (usbif->backend_watch.node) {
+ unregister_xenbus_watch(&usbif->backend_watch);
+ kfree(usbif->backend_watch.node);
+ usbif->backend_watch.node = NULL;
+ }
+
+ /* remove all ports */
+ for (i = 1; i <= usbif->num_ports; i++)
+ usbback_portid_remove(usbif->domid, usbif->handle, i);
+ usbback_disconnect(usbif);
+ usbback_free(usbif);
+
+ dev_set_drvdata(&dev->dev, NULL);
+
+ return 0;
+}
+
+static struct usbback_info *usbback_alloc(domid_t domid, unsigned handle)
+{
+ struct usbback_info *usbif;
+ unsigned long flags;
+ int i;
+
+ usbif = kzalloc(sizeof(struct usbback_info), GFP_KERNEL);
+ if (!usbif)
+ return NULL;
+
+ usbif->domid = domid;
+ usbif->handle = handle;
+ spin_lock_init(&usbif->urb_ring_lock);
+ spin_lock_init(&usbif->conn_ring_lock);
+ atomic_set(&usbif->refcnt, 0);
+ usbif->ring_error = 0;
+ init_waitqueue_head(&usbif->waiting_to_free);
+ spin_lock_init(&usbif->stub_lock);
+ INIT_LIST_HEAD(&usbif->stub_list);
+ INIT_LIST_HEAD(&usbif->hotplug_list);
+ spin_lock_init(&usbif->addr_lock);
+ for (i = 0; i < USB_DEV_ADDR_SIZE; i++)
+ usbif->addr_table[i] = NULL;
+
+ spin_lock_irqsave(&usbback_usbif_list_lock, flags);
+ list_add(&usbif->usbif_list, &usbback_usbif_list);
+ spin_unlock_irqrestore(&usbback_usbif_list_lock, flags);
+
+ return usbif;
+}
+
+static int usbback_probe(struct xenbus_device *dev,
+ const struct xenbus_device_id *id)
+{
+ struct usbback_info *usbif;
+ unsigned long handle;
+ int num_ports;
+ int usb_ver;
+ int err;
+
+ if (usb_disabled())
+ return -ENODEV;
+
+ if (kstrtoul(strrchr(dev->otherend, '/') + 1, 0, &handle))
+ return -EINVAL;
+
+ usbif = usbback_alloc(dev->otherend_id, handle);
+ if (!usbif) {
+ xenbus_dev_fatal(dev, -ENOMEM, "allocating backend interface");
+ return -ENOMEM;
+ }
+ usbif->xbdev = dev;
+ dev_set_drvdata(&dev->dev, usbif);
+
+ err = xenbus_scanf(XBT_NIL, dev->nodename, "num-ports", "%d",
+ &num_ports);
+ if (err != 1) {
+ xenbus_dev_fatal(dev, err, "reading num-ports");
+ goto fail;
+ }
+ if (num_ports < 1 || num_ports > USB_MAXCHILDREN) {
+ xenbus_dev_fatal(dev, err, "invalid num-ports");
+ goto fail;
+ }
+ usbif->num_ports = num_ports;
+
+ err = xenbus_scanf(XBT_NIL, dev->nodename, "usb-ver", "%d", &usb_ver);
+ if (err != 1) {
+ xenbus_dev_fatal(dev, err, "reading usb-ver");
+ goto fail;
+ }
+ switch (usb_ver) {
+ case USB_VER_USB11:
+ case USB_VER_USB20:
+ usbif->usb_ver = usb_ver;
+ break;
+ default:
+ xenbus_dev_fatal(dev, err, "invalid usb-ver");
+ goto fail;
+ }
+
+ err = xenbus_watch_pathfmt(dev, &usbif->backend_watch,
+ usbback_backend_changed, "%s/port",
+ dev->nodename);
+ if (err)
+ goto fail;
+
+ err = xenbus_switch_state(dev, XenbusStateConnected);
+ if (err)
+ goto fail;
+
+ return 0;
+
+fail:
+ usbback_remove(dev);
+ return err;
+}
+
+static int usbback_map(struct usbback_info *usbif, grant_ref_t urb_ring_ref,
+ grant_ref_t conn_ring_ref, evtchn_port_t evtchn)
+{
+ int err;
+ void *addr;
+ struct usbif_urb_sring *urb_sring;
+ struct usbif_conn_sring *conn_sring;
+
+ if (usbif->irq)
+ return 0;
+
+ err = xenbus_map_ring_valloc(usbif->xbdev, urb_ring_ref, &addr);
+ if (err)
+ return err;
+ urb_sring = addr;
+ err = xenbus_map_ring_valloc(usbif->xbdev, conn_ring_ref, &addr);
+ if (err)
+ goto fail_alloc;
+ conn_sring = addr;
+
+ err = bind_interdomain_evtchn_to_irq(usbif->domid, evtchn);
+ if (err < 0)
+ goto fail_evtchn;
+ usbif->irq = err;
+
+ BACK_RING_INIT(&usbif->urb_ring, urb_sring, PAGE_SIZE);
+ BACK_RING_INIT(&usbif->conn_ring, conn_sring, PAGE_SIZE);
+
+ err = request_threaded_irq(usbif->irq, NULL, usbback_be_int,
+ IRQF_ONESHOT, "xen-usbif", usbif);
+ if (err)
+ goto free_irq;
+
+ return 0;
+
+free_irq:
+ unbind_from_irqhandler(usbif->irq, usbif);
+ usbif->irq = 0;
+ usbif->urb_ring.sring = NULL;
+ usbif->conn_ring.sring = NULL;
+fail_evtchn:
+ xenbus_unmap_ring_vfree(usbif->xbdev, conn_sring);
+fail_alloc:
+ xenbus_unmap_ring_vfree(usbif->xbdev, urb_sring);
+
+ return err;
+}
+
+static int usbback_connect_rings(struct usbback_info *usbif)
+{
+ struct xenbus_device *dev = usbif->xbdev;
+ unsigned urb_ring_ref, conn_ring_ref, evtchn;
+ int err;
+
+ err = xenbus_gather(XBT_NIL, dev->otherend,
+ "urb-ring-ref", "%u", &urb_ring_ref,
+ "conn-ring-ref", "%u", &conn_ring_ref,
+ "event-channel", "%u", &evtchn, NULL);
+ if (err) {
+ xenbus_dev_fatal(dev, err,
+ "reading %s/ring-ref and event-channel",
+ dev->otherend);
+ return err;
+ }
+
+ pr_info("xen-pvusb: urb-ring-ref %u, conn-ring-ref %u, event-channel %u\n",
+ urb_ring_ref, conn_ring_ref, evtchn);
+
+ err = usbback_map(usbif, urb_ring_ref, conn_ring_ref, evtchn);
+ if (err)
+ xenbus_dev_fatal(dev, err,
+ "mapping urb-ring-ref %u conn-ring-ref %u port %u",
+ urb_ring_ref, conn_ring_ref, evtchn);
+
+ return err;
+}
+
+static void usbback_frontend_changed(struct xenbus_device *dev,
+ enum xenbus_state frontend_state)
+{
+ struct usbback_info *usbif = dev_get_drvdata(&dev->dev);
+
+ switch (frontend_state) {
+ case XenbusStateInitialised:
+ case XenbusStateReconfiguring:
+ case XenbusStateReconfigured:
+ break;
+
+ case XenbusStateInitialising:
+ if (dev->state == XenbusStateClosed) {
+ pr_info("xen-pvusb: %s: prepare for reconnect\n",
+ dev->nodename);
+ xenbus_switch_state(dev, XenbusStateInitWait);
+ }
+ break;
+
+ case XenbusStateConnected:
+ if (dev->state != XenbusStateConnected)
+ xenbus_switch_state(dev, XenbusStateConnected);
+
+ if (usbback_connect_rings(usbif))
+ break;
+
+ usbback_do_hotplug(usbif);
+ break;
+
+ case XenbusStateClosing:
+ usbback_disconnect(usbif);
+ xenbus_switch_state(dev, XenbusStateClosing);
+ break;
+
+ case XenbusStateClosed:
+ xenbus_switch_state(dev, XenbusStateClosed);
+ if (xenbus_dev_is_online(dev))
+ break;
+ /* fall through if not online */
+ case XenbusStateUnknown:
+ device_unregister(&dev->dev);
+ break;
+
+ default:
+ xenbus_dev_fatal(dev, -EINVAL, "saw state %d at frontend",
+ frontend_state);
+ break;
+ }
+}
+
+static const struct xenbus_device_id usbback_ids[] = {
+ { "vusb" },
+ { "" },
+};
+
+static struct xenbus_driver usbback_driver = {
+ .ids = usbback_ids,
+ .probe = usbback_probe,
+ .otherend_changed = usbback_frontend_changed,
+ .remove = usbback_remove,
+};
+
+static int __init usbback_init(void)
+{
+ int err;
+
+ if (!xen_domain())
+ return -ENODEV;
+
+ usbback_cachep = kmem_cache_create("xen-usbif_cache",
+ sizeof(struct pending_req), 0, 0, NULL);
+ if (!usbback_cachep)
+ return -ENOMEM;
+
+ err = xenbus_register_backend(&usbback_driver);
+ if (err)
+ goto out_mem;
+
+ err = usbback_stub_init();
+ if (err)
+ goto out_xenbus;
+
+ return 0;
+
+out_xenbus:
+ usbback_stub_exit();
+out_mem:
+ kmem_cache_destroy(usbback_cachep);
+ return err;
+}
+module_init(usbback_init);
+
+static void __exit usbback_exit(void)
+{
+ xenbus_unregister_driver(&usbback_driver);
+ usbback_stub_exit();
+ kmem_cache_destroy(usbback_cachep);
+}
+module_exit(usbback_exit);
+
+MODULE_ALIAS("xen-backend:vusb");
+MODULE_AUTHOR("Juergen Gross <[email protected]>");
+MODULE_DESCRIPTION("Xen USB backend driver (usbback)");
+MODULE_LICENSE("Dual BSD/GPL");
--
2.1.4

2015-02-26 13:35:58

by Juergen Gross

[permalink] [raw]
Subject: [PATCH 4/4] xen: add Xen pvUSB maintainer

Add myself as maintainer for the Xen pvUSB stuff.

Signed-off-by: Juergen Gross <[email protected]>
---
MAINTAINERS | 8 ++++++++
1 file changed, 8 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index ddc5a8c..8ec1e1f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10787,6 +10787,14 @@ F: drivers/scsi/xen-scsifront.c
F: drivers/xen/xen-scsiback.c
F: include/xen/interface/io/vscsiif.h

+XEN PVUSB DRIVERS
+M: Juergen Gross <[email protected]>
+L: [email protected] (moderated for non-subscribers)
+L: [email protected]
+S: Supported
+F: divers/usb/xen/
+F: include/xen/interface/io/usbif.h
+
XEN SWIOTLB SUBSYSTEM
M: Konrad Rzeszutek Wilk <[email protected]>
L: [email protected] (moderated for non-subscribers)
--
2.1.4

2015-02-26 14:09:47

by Oliver Neukum

[permalink] [raw]
Subject: Re: [PATCH 2/4] usb: Introduce Xen pvUSB frontend

On Thu, 2015-02-26 at 14:35 +0100, Juergen Gross wrote:
> +
> + /* reset completion */
> + if ((info->ports[wIndex].status &
> USB_PORT_STAT_RESET) != 0 &&
> + time_after_eq(jiffies,
> info->ports[wIndex].timeout)) {
> + info->ports[wIndex].status |=
> + USB_PORT_STAT_C_RESET << 16;
> + info->ports[wIndex].status &=
> ~USB_PORT_STAT_RESET;
> +
> + if (info->devices[wIndex].status !=
> + USB_STATE_NOTATTACHED) {
> + info->ports[wIndex].status |=
> + USB_PORT_STAT_ENABLE;
> + info->devices[wIndex].status =
> + USB_STATE_DEFAULT;
> + }
> +
> + switch (info->devices[wIndex].speed) {
> + case USB_SPEED_LOW:
> + info->ports[wIndex].status |=
> + USB_PORT_STAT_LOW_SPEED;
> + break;
> + case USB_SPEED_HIGH:
> + info->ports[wIndex].status |=
> + USB_PORT_STAT_HIGH_SPEED;
> + break;
> + default:
> + break;
> + }
> + }
> +
> + ((u16 *)buf)[0] =
> cpu_to_le16(info->ports[wIndex].status);
> + ((u16 *)buf)[1] =
> cpu_to_le16(info->ports[wIndex].status >> 16);

Why in two chunks?
Regards
Oliver
> + break;


--
Oliver Neukum <[email protected]>

2015-02-26 17:25:36

by Konrad Rzeszutek Wilk

[permalink] [raw]
Subject: Re: [PATCH 4/4] xen: add Xen pvUSB maintainer

On February 26, 2015 8:35:17 AM EST, Juergen Gross <[email protected]> wrote:
>Add myself as maintainer for the Xen pvUSB stuff.
>
>Signed-off-by: Juergen Gross <[email protected]>
>---
> MAINTAINERS | 8 ++++++++
> 1 file changed, 8 insertions(+)
>
>diff --git a/MAINTAINERS b/MAINTAINERS
>index ddc5a8c..8ec1e1f 100644
>--- a/MAINTAINERS
>+++ b/MAINTAINERS
>@@ -10787,6 +10787,14 @@ F: drivers/scsi/xen-scsifront.c
> F: drivers/xen/xen-scsiback.c
> F: include/xen/interface/io/vscsiif.h
>
>+XEN PVUSB DRIVERS
>+M: Juergen Gross <[email protected]>
>+L: [email protected] (moderated for non-subscribers)
>+L: [email protected]
>+S: Supported
>+F: divers/usb/xen/
>+F: include/xen/interface/io/usbif.h

Acked-by: Konrad Rzeszutek Wilk <[email protected]>

On the include/Xen/... part.
>+
> XEN SWIOTLB SUBSYSTEM
> M: Konrad Rzeszutek Wilk <[email protected]>
> L: [email protected] (moderated for non-subscribers)