4256 lines
110 KiB
Diff
4256 lines
110 KiB
Diff
From 10b675fc21702ff5a9b94fc13e2b504ca09073fd Mon Sep 17 00:00:00 2001
|
|
From: Nathanael Rensen <nathanael@polymorpheus.com>
|
|
Date: Tue, 7 Feb 2012 13:50:24 +0800
|
|
Subject: [PATCH] usb: xen pvusb driver
|
|
|
|
Port the original Xen PV USB drivers developed by Noboru Iwamatsu
|
|
<n_iwamatsu@jp.fujitsu.com> to the Linux pvops kernel. The backend driver
|
|
resides in dom0 with access to the physical USB device. The frontend driver
|
|
resides in a domU to provide paravirtualised access to physical USB devices.
|
|
|
|
For usage, see http://wiki.xensource.com/xenwiki/XenUSBPassthrough.
|
|
|
|
Signed-off-by: Nathanael Rensen <nathanael@polymorpheus.com>.
|
|
Signed-off-by: Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>
|
|
---
|
|
drivers/usb/host/Kconfig | 23 +
|
|
drivers/usb/host/Makefile | 2 +
|
|
drivers/usb/host/xen-usbback/Makefile | 3 +
|
|
drivers/usb/host/xen-usbback/common.h | 170 ++++
|
|
drivers/usb/host/xen-usbback/usbback.c | 1272 +++++++++++++++++++++++
|
|
drivers/usb/host/xen-usbback/usbdev.c | 319 ++++++
|
|
drivers/usb/host/xen-usbback/xenbus.c | 482 +++++++++
|
|
drivers/usb/host/xen-usbfront.c | 1739 ++++++++++++++++++++++++++++++++
|
|
include/xen/interface/io/usbif.h | 150 +++
|
|
9 files changed, 4160 insertions(+), 0 deletions(-)
|
|
create mode 100644 drivers/usb/host/xen-usbback/Makefile
|
|
create mode 100644 drivers/usb/host/xen-usbback/common.h
|
|
create mode 100644 drivers/usb/host/xen-usbback/usbback.c
|
|
create mode 100644 drivers/usb/host/xen-usbback/usbdev.c
|
|
create mode 100644 drivers/usb/host/xen-usbback/xenbus.c
|
|
create mode 100644 drivers/usb/host/xen-usbfront.c
|
|
create mode 100644 include/xen/interface/io/usbif.h
|
|
|
|
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
|
|
index f788eb8..cbb0961 100644
|
|
--- a/drivers/usb/host/Kconfig
|
|
+++ b/drivers/usb/host/Kconfig
|
|
@@ -745,3 +745,26 @@
|
|
This option is of interest only to developers who need to validate
|
|
their USB hardware designs. It is not needed for normal use. If
|
|
unsure, say N.
|
|
+
|
|
+config XEN_USBDEV_FRONTEND
|
|
+ tristate "Xen pvusb device frontend driver"
|
|
+ depends on XEN && USB
|
|
+ select XEN_XENBUS_FRONTEND
|
|
+ default m
|
|
+ help
|
|
+ The pvusb device frontend driver allows the kernel to
|
|
+ access usb devices exported exported by a virtual
|
|
+ machine containing a physical usb device driver. The
|
|
+ frontend driver is intended for unprivileged guest domains;
|
|
+ if you are compiling a kernel for a Xen guest, you almost
|
|
+ certainly want to enable this.
|
|
+
|
|
+config XEN_USBDEV_BACKEND
|
|
+ tristate "PVUSB device backend driver"
|
|
+ depends on XEN_BACKEND && USB
|
|
+ default m
|
|
+ help
|
|
+ The pvusb backend driver allows the kernel to export its usb
|
|
+ devices to other guests via a high-performance shared-memory
|
|
+ interface. This requires the guest to have the pvusb frontend
|
|
+ available.
|
|
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
|
|
index 0982bcc..d62fe38 100644
|
|
--- a/drivers/usb/host/Makefile
|
|
+++ b/drivers/usb/host/Makefile
|
|
@@ -40,6 +40,8 @@ obj-$(CONFIG_USB_HWA_HCD) += hwa-hc.o
|
|
obj-$(CONFIG_USB_IMX21_HCD) += imx21-hcd.o
|
|
obj-$(CONFIG_USB_FSL_MPH_DR_OF) += fsl-mph-dr-of.o
|
|
obj-$(CONFIG_USB_OCTEON2_COMMON) += octeon2-common.o
|
|
+obj-$(CONFIG_XEN_USBDEV_FRONTEND) += xen-usbfront.o
|
|
+obj-$(CONFIG_XEN_USBDEV_BACKEND) += xen-usbback/
|
|
obj-$(CONFIG_USB_HCD_BCMA) += bcma-hcd.o
|
|
obj-$(CONFIG_USB_HCD_SSB) += ssb-hcd.o
|
|
obj-$(CONFIG_USB_FUSBH200_HCD) += fusbh200-hcd.o
|
|
diff --git a/drivers/usb/host/xen-usbback/Makefile b/drivers/usb/host/xen-usbback/Makefile
|
|
new file mode 100644
|
|
index 0000000..9f3628c
|
|
--- /dev/null
|
|
+++ b/drivers/usb/host/xen-usbback/Makefile
|
|
@@ -0,0 +1,3 @@
|
|
+obj-$(CONFIG_XEN_USBDEV_BACKEND) := xen-usbback.o
|
|
+
|
|
+xen-usbback-y := usbdev.o xenbus.o usbback.o
|
|
diff --git a/drivers/usb/host/xen-usbback/common.h b/drivers/usb/host/xen-usbback/common.h
|
|
new file mode 100644
|
|
index 0000000..d9671ec
|
|
--- /dev/null
|
|
+++ b/drivers/usb/host/xen-usbback/common.h
|
|
@@ -0,0 +1,171 @@
|
|
+/*
|
|
+ * This file is part of Xen USB backend driver.
|
|
+ *
|
|
+ * Copyright (C) 2009, FUJITSU LABORATORIES LTD.
|
|
+ * Author: Noboru Iwamatsu <n_iwamatsu@jp.fujitsu.com>
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef __XEN_USBBACK__COMMON_H__
|
|
+#define __XEN_USBBACK__COMMON_H__
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/usb.h>
|
|
+#include <linux/usb/ch11.h>
|
|
+#include <linux/vmalloc.h>
|
|
+#include <linux/kthread.h>
|
|
+#include <linux/wait.h>
|
|
+#include <linux/list.h>
|
|
+#include <linux/kref.h>
|
|
+#include <asm/hypervisor.h>
|
|
+#include <xen/xen.h>
|
|
+#include <xen/events.h>
|
|
+#include <xen/interface/xen.h>
|
|
+#include <xen/xenbus.h>
|
|
+#include <xen/page.h>
|
|
+#include <xen/grant_table.h>
|
|
+#include <xen/interface/io/usbif.h>
|
|
+
|
|
+#define DRV_PFX "xen-usbback:"
|
|
+
|
|
+struct xen_usbdev;
|
|
+
|
|
+#ifndef BUS_ID_SIZE
|
|
+#define XEN_USB_BUS_ID_SIZE 20
|
|
+#else
|
|
+#define XEN_USB_BUS_ID_SIZE BUS_ID_SIZE
|
|
+#endif
|
|
+
|
|
+#define XEN_USB_DEV_ADDR_SIZE 128
|
|
+
|
|
+struct xen_usbif {
|
|
+ domid_t domid;
|
|
+ unsigned int handle;
|
|
+ int num_ports;
|
|
+ enum usb_spec_version usb_ver;
|
|
+
|
|
+ struct list_head usbif_list;
|
|
+
|
|
+ struct xenbus_device *xbdev;
|
|
+
|
|
+ unsigned int irq;
|
|
+
|
|
+ void *urb_sring;
|
|
+ void *conn_sring;
|
|
+ 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;
|
|
+
|
|
+ struct xenbus_watch backend_watch;
|
|
+
|
|
+ /* device address lookup table */
|
|
+ struct xen_usbdev *addr_table[XEN_USB_DEV_ADDR_SIZE];
|
|
+ spinlock_t addr_lock;
|
|
+
|
|
+ /* connected device list */
|
|
+ struct list_head dev_list;
|
|
+ spinlock_t dev_lock;
|
|
+
|
|
+ /* request schedule */
|
|
+ struct task_struct *xenusbd;
|
|
+ unsigned int waiting_reqs;
|
|
+ wait_queue_head_t waiting_to_free;
|
|
+ wait_queue_head_t wq;
|
|
+};
|
|
+
|
|
+struct xen_usbport {
|
|
+ struct list_head port_list;
|
|
+
|
|
+ char phys_bus[XEN_USB_BUS_ID_SIZE];
|
|
+ domid_t domid;
|
|
+ unsigned int handle;
|
|
+ int portnum;
|
|
+ unsigned is_connected:1;
|
|
+};
|
|
+
|
|
+struct xen_usbdev {
|
|
+ struct kref kref;
|
|
+ struct list_head dev_list;
|
|
+
|
|
+ struct xen_usbport *port;
|
|
+ struct usb_device *udev;
|
|
+ struct xen_usbif *usbif;
|
|
+ int addr;
|
|
+
|
|
+ struct list_head submitting_list;
|
|
+ spinlock_t submitting_lock;
|
|
+};
|
|
+
|
|
+#define usbif_get(_b) (atomic_inc(&(_b)->refcnt))
|
|
+#define usbif_put(_b) \
|
|
+ do { \
|
|
+ if (atomic_dec_and_test(&(_b)->refcnt)) \
|
|
+ wake_up(&(_b)->waiting_to_free); \
|
|
+ } while (0)
|
|
+
|
|
+int xen_usbif_xenbus_init(void);
|
|
+void xen_usbif_xenbus_exit(void);
|
|
+struct xen_usbif *xen_usbif_find(domid_t domid, unsigned int handle);
|
|
+
|
|
+int xen_usbdev_init(void);
|
|
+void xen_usbdev_exit(void);
|
|
+
|
|
+void xen_usbif_attach_device(struct xen_usbif *usbif, struct xen_usbdev *dev);
|
|
+void xen_usbif_detach_device(struct xen_usbif *usbif, struct xen_usbdev *dev);
|
|
+void xen_usbif_detach_device_without_lock(struct xen_usbif *usbif,
|
|
+ struct xen_usbdev *dev);
|
|
+void xen_usbif_hotplug_notify(struct xen_usbif *usbif, int portnum, int speed);
|
|
+struct xen_usbdev *xen_usbif_find_attached_device(struct xen_usbif *usbif,
|
|
+ int port);
|
|
+irqreturn_t xen_usbif_be_int(int irq, void *dev_id);
|
|
+int xen_usbif_schedule(void *arg);
|
|
+void xen_usbif_unlink_urbs(struct xen_usbdev *dev);
|
|
+
|
|
+struct xen_usbport *xen_usbport_find_by_busid(const char *busid);
|
|
+struct xen_usbport *xen_usbport_find(const domid_t domid,
|
|
+ const unsigned int handle, const int portnum);
|
|
+int xen_usbport_add(const char *busid, const domid_t domid,
|
|
+ const unsigned int handle, const int portnum);
|
|
+int xen_usbport_remove(const domid_t domid, const unsigned int handle,
|
|
+ const int portnum);
|
|
+#endif /* __XEN_USBBACK__COMMON_H__ */
|
|
diff --git a/drivers/usb/host/xen-usbback/usbback.c b/drivers/usb/host/xen-usbback/usbback.c
|
|
new file mode 100644
|
|
index 0000000..df1afa9
|
|
--- /dev/null
|
|
+++ b/drivers/usb/host/xen-usbback/usbback.c
|
|
@@ -0,0 +1,1272 @@
|
|
+/*
|
|
+ * Xen USB backend driver
|
|
+ *
|
|
+ * Copyright (C) 2009, FUJITSU LABORATORIES LTD.
|
|
+ * Author: Noboru Iwamatsu <n_iwamatsu@jp.fujitsu.com>
|
|
+ *
|
|
+ * 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/mm.h>
|
|
+#include "common.h"
|
|
+
|
|
+static int xen_usbif_reqs = USBIF_BACK_MAX_PENDING_REQS;
|
|
+module_param_named(reqs, xen_usbif_reqs, int, 0);
|
|
+MODULE_PARM_DESC(reqs, "Number of usbback requests to allocate");
|
|
+
|
|
+struct pending_req_segment {
|
|
+ uint16_t offset;
|
|
+ uint16_t length;
|
|
+};
|
|
+
|
|
+struct pending_req {
|
|
+ struct xen_usbif *usbif;
|
|
+
|
|
+ uint16_t id; /* request id */
|
|
+
|
|
+ struct xen_usbdev *dev;
|
|
+ struct list_head urb_list;
|
|
+
|
|
+ /* urb */
|
|
+ struct urb *urb;
|
|
+ void *buffer;
|
|
+ dma_addr_t transfer_dma;
|
|
+ struct usb_ctrlrequest *setup;
|
|
+ dma_addr_t setup_dma;
|
|
+
|
|
+ /* 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;
|
|
+
|
|
+ struct list_head free_list;
|
|
+};
|
|
+
|
|
+#define USBBACK_INVALID_HANDLE (~0)
|
|
+
|
|
+struct xen_usbbk {
|
|
+ struct pending_req *pending_reqs;
|
|
+ struct list_head pending_free;
|
|
+ spinlock_t pending_free_lock;
|
|
+ wait_queue_head_t pending_free_wq;
|
|
+ struct list_head urb_free;
|
|
+ spinlock_t urb_free_lock;
|
|
+ struct page **pending_pages;
|
|
+ grant_handle_t *pending_grant_handles;
|
|
+};
|
|
+
|
|
+static struct xen_usbbk *usbbk;
|
|
+
|
|
+static inline int vaddr_pagenr(struct pending_req *req, int seg)
|
|
+{
|
|
+ return (req - usbbk->pending_reqs) *
|
|
+ USBIF_MAX_SEGMENTS_PER_REQUEST + seg;
|
|
+}
|
|
+
|
|
+#define pending_page(req, seg) pending_pages[vaddr_pagenr(req, seg)]
|
|
+
|
|
+static inline unsigned long vaddr(struct pending_req *req, int seg)
|
|
+{
|
|
+ unsigned long pfn = page_to_pfn(usbbk->pending_page(req, seg));
|
|
+ return (unsigned long)pfn_to_kaddr(pfn);
|
|
+}
|
|
+
|
|
+#define pending_handle(_req, _seg) \
|
|
+ (usbbk->pending_grant_handles[vaddr_pagenr(_req, _seg)])
|
|
+
|
|
+static struct pending_req *alloc_req(void)
|
|
+{
|
|
+ struct pending_req *req = NULL;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&usbbk->pending_free_lock, flags);
|
|
+ if (!list_empty(&usbbk->pending_free)) {
|
|
+ req = list_entry(usbbk->pending_free.next, struct pending_req,
|
|
+ free_list);
|
|
+ list_del(&req->free_list);
|
|
+ }
|
|
+ spin_unlock_irqrestore(&usbbk->pending_free_lock, flags);
|
|
+ return req;
|
|
+}
|
|
+
|
|
+static void free_req(struct pending_req *req)
|
|
+{
|
|
+ unsigned long flags;
|
|
+ int was_empty;
|
|
+
|
|
+ spin_lock_irqsave(&usbbk->pending_free_lock, flags);
|
|
+ was_empty = list_empty(&usbbk->pending_free);
|
|
+ list_add(&req->free_list, &usbbk->pending_free);
|
|
+ spin_unlock_irqrestore(&usbbk->pending_free_lock, flags);
|
|
+ if (was_empty)
|
|
+ wake_up(&usbbk->pending_free_wq);
|
|
+}
|
|
+
|
|
+static inline void add_req_to_submitting_list(struct xen_usbdev *dev,
|
|
+ struct pending_req *pending_req)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&dev->submitting_lock, flags);
|
|
+ list_add_tail(&pending_req->urb_list, &dev->submitting_list);
|
|
+ spin_unlock_irqrestore(&dev->submitting_lock, flags);
|
|
+}
|
|
+
|
|
+static inline void remove_req_from_submitting_list(struct xen_usbdev *dev,
|
|
+ struct pending_req *pending_req)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&dev->submitting_lock, flags);
|
|
+ list_del_init(&pending_req->urb_list);
|
|
+ spin_unlock_irqrestore(&dev->submitting_lock, flags);
|
|
+}
|
|
+
|
|
+void xen_usbif_unlink_urbs(struct xen_usbdev *dev)
|
|
+{
|
|
+ struct pending_req *req, *tmp;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&dev->submitting_lock, flags);
|
|
+ list_for_each_entry_safe(req, tmp, &dev->submitting_list, urb_list) {
|
|
+ usb_unlink_urb(req->urb);
|
|
+ }
|
|
+ spin_unlock_irqrestore(&dev->submitting_lock, flags);
|
|
+}
|
|
+
|
|
+static void copy_buff_to_pages(void *buff, struct pending_req *pending_req,
|
|
+ int start, int nr_pages)
|
|
+{
|
|
+ unsigned long copied = 0;
|
|
+ int i;
|
|
+
|
|
+ for (i = start; i < start + nr_pages; i++) {
|
|
+ memcpy((void *) vaddr(pending_req, i) +
|
|
+ pending_req->seg[i].offset,
|
|
+ buff + copied, pending_req->seg[i].length);
|
|
+ copied += pending_req->seg[i].length;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void copy_pages_to_buff(void *buff, struct pending_req *pending_req,
|
|
+ int start, int nr_pages)
|
|
+{
|
|
+ unsigned long copied = 0;
|
|
+ int i;
|
|
+
|
|
+ for (i = start; i < start + nr_pages; i++) {
|
|
+ void *src = (void *) vaddr(pending_req, i) +
|
|
+ pending_req->seg[i].offset;
|
|
+ memcpy(buff + copied, src, pending_req->seg[i].length);
|
|
+ copied += pending_req->seg[i].length;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int usbbk_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) {
|
|
+ pr_alert(DRV_PFX "can't alloc urb\n");
|
|
+ ret = -ENOMEM;
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ if (req->buffer_length) {
|
|
+ pending_req->buffer =
|
|
+ usb_alloc_coherent(pending_req->dev->udev,
|
|
+ req->buffer_length, GFP_KERNEL,
|
|
+ &pending_req->transfer_dma);
|
|
+ if (!pending_req->buffer) {
|
|
+ pr_alert(DRV_PFX "can't alloc urb buffer\n");
|
|
+ ret = -ENOMEM;
|
|
+ goto fail_free_urb;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (usb_pipecontrol(req->pipe)) {
|
|
+ pending_req->setup = usb_alloc_coherent(pending_req->dev->udev,
|
|
+ sizeof(struct usb_ctrlrequest),
|
|
+ GFP_KERNEL, &pending_req->setup_dma);
|
|
+ if (!pending_req->setup) {
|
|
+ pr_alert(DRV_PFX "can't alloc usb_ctrlrequest\n");
|
|
+ ret = -ENOMEM;
|
|
+ goto fail_free_buffer;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+fail_free_buffer:
|
|
+ if (req->buffer_length)
|
|
+ usb_free_coherent(pending_req->dev->udev, req->buffer_length,
|
|
+ pending_req->buffer, pending_req->transfer_dma);
|
|
+fail_free_urb:
|
|
+ usb_free_urb(pending_req->urb);
|
|
+fail:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void usbbk_release_urb(struct urb *urb)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&usbbk->urb_free_lock, flags);
|
|
+ list_add(&urb->urb_list, &usbbk->urb_free);
|
|
+ spin_unlock_irqrestore(&usbbk->urb_free_lock, flags);
|
|
+}
|
|
+
|
|
+static void usbbk_free_urb(struct urb *urb)
|
|
+{
|
|
+ if (usb_pipecontrol(urb->pipe))
|
|
+ usb_free_coherent(urb->dev, sizeof(struct usb_ctrlrequest),
|
|
+ urb->setup_packet, urb->setup_dma);
|
|
+ if (urb->transfer_buffer_length)
|
|
+ usb_free_coherent(urb->dev, urb->transfer_buffer_length,
|
|
+ urb->transfer_buffer, urb->transfer_dma);
|
|
+ barrier();
|
|
+ usb_free_urb(urb);
|
|
+}
|
|
+
|
|
+static void usbbk_free_urbs(void)
|
|
+{
|
|
+ unsigned long flags;
|
|
+ struct list_head tmp_list;
|
|
+
|
|
+ if (list_empty(&usbbk->urb_free))
|
|
+ return;
|
|
+
|
|
+ INIT_LIST_HEAD(&tmp_list);
|
|
+
|
|
+ spin_lock_irqsave(&usbbk->urb_free_lock, flags);
|
|
+ list_splice_init(&usbbk->urb_free, &tmp_list);
|
|
+ spin_unlock_irqrestore(&usbbk->urb_free_lock, flags);
|
|
+
|
|
+ while (!list_empty(&tmp_list)) {
|
|
+ struct urb *next_urb =
|
|
+ list_first_entry(&tmp_list, struct urb, urb_list);
|
|
+ list_del(&next_urb->urb_list);
|
|
+ usbbk_free_urb(next_urb);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void usbif_notify_work(struct xen_usbif *usbif)
|
|
+{
|
|
+ usbif->waiting_reqs = 1;
|
|
+ wake_up(&usbif->wq);
|
|
+}
|
|
+
|
|
+irqreturn_t xen_usbif_be_int(int irq, void *dev_id)
|
|
+{
|
|
+ usbif_notify_work(dev_id);
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static void xen_usbbk_unmap(struct pending_req *req)
|
|
+{
|
|
+ struct gnttab_unmap_grant_ref unmap[USBIF_MAX_SEGMENTS_PER_REQUEST];
|
|
+ unsigned int i, nr_segs, invcount = 0;
|
|
+ grant_handle_t handle;
|
|
+ int ret;
|
|
+
|
|
+ nr_segs = req->nr_buffer_segs + req->nr_extra_segs;
|
|
+
|
|
+ if (nr_segs == 0)
|
|
+ return;
|
|
+
|
|
+ for (i = 0; i < nr_segs; i++) {
|
|
+ handle = pending_handle(req, i);
|
|
+ if (handle == USBBACK_INVALID_HANDLE)
|
|
+ continue;
|
|
+ gnttab_set_unmap_op(&unmap[invcount], vaddr(req, i),
|
|
+ GNTMAP_host_map, handle);
|
|
+ pending_handle(req, i) = USBBACK_INVALID_HANDLE;
|
|
+ invcount++;
|
|
+ }
|
|
+
|
|
+ ret = HYPERVISOR_grant_table_op(
|
|
+ GNTTABOP_unmap_grant_ref, unmap, invcount);
|
|
+ BUG_ON(ret);
|
|
+ /*
|
|
+ * Note, we use invcount, not nr_segs, so we can't index
|
|
+ * using vaddr(req, i).
|
|
+ */
|
|
+ for (i = 0; i < invcount; i++) {
|
|
+ ret = m2p_remove_override(
|
|
+ virt_to_page(unmap[i].host_addr), false);
|
|
+ if (ret) {
|
|
+ pr_alert(DRV_PFX "Failed to remove M2P override for "
|
|
+ "%lx\n", (unsigned long)unmap[i].host_addr);
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ kfree(req->seg);
|
|
+}
|
|
+
|
|
+static int xen_usbbk_map(struct xen_usbif *usbif,
|
|
+ struct usbif_urb_request *req,
|
|
+ struct pending_req *pending_req)
|
|
+{
|
|
+ int i, ret;
|
|
+ unsigned int 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 == 0)
|
|
+ return 0;
|
|
+
|
|
+ if (nr_segs > USBIF_MAX_SEGMENTS_PER_REQUEST) {
|
|
+ pr_alert(DRV_PFX "Bad number of segments in request\n");
|
|
+ ret = -EINVAL;
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ pending_req->seg = kmalloc(sizeof(struct pending_req_segment) *
|
|
+ nr_segs, GFP_KERNEL);
|
|
+ if (!pending_req->seg) {
|
|
+ ret = -ENOMEM;
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ flags = GNTMAP_host_map;
|
|
+ if (usb_pipeout(req->pipe))
|
|
+ flags |= GNTMAP_readonly;
|
|
+ for (i = 0; i < pending_req->nr_buffer_segs; i++) {
|
|
+ gnttab_set_map_op(&map[i], vaddr(pending_req, i), flags,
|
|
+ req->seg[i].gref, usbif->domid);
|
|
+ }
|
|
+
|
|
+ flags = GNTMAP_host_map;
|
|
+ for (i = pending_req->nr_buffer_segs; i < nr_segs; i++) {
|
|
+ gnttab_set_map_op(&map[i], vaddr(pending_req, i), flags,
|
|
+ req->seg[i].gref, usbif->domid);
|
|
+ }
|
|
+
|
|
+ ret = HYPERVISOR_grant_table_op(GNTTABOP_map_grant_ref, map, nr_segs);
|
|
+ BUG_ON(ret);
|
|
+
|
|
+ for (i = 0; i < nr_segs; i++) {
|
|
+ if (unlikely(map[i].status != 0)) {
|
|
+ pr_alert(DRV_PFX "invalid buffer "
|
|
+ "-- could not remap it (error %d)\n",
|
|
+ map[i].status);
|
|
+ map[i].handle = USBBACK_INVALID_HANDLE;
|
|
+ ret |= 1;
|
|
+ }
|
|
+
|
|
+ pending_handle(pending_req, i) = map[i].handle;
|
|
+
|
|
+ if (ret)
|
|
+ continue;
|
|
+
|
|
+ ret = m2p_add_override(PFN_DOWN(map[i].dev_bus_addr),
|
|
+ usbbk->pending_page(pending_req, i), NULL);
|
|
+ if (ret) {
|
|
+ pr_alert(DRV_PFX "Failed to install M2P override for "
|
|
+ "%lx (ret: %d)\n",
|
|
+ (unsigned long)map[i].dev_bus_addr, ret);
|
|
+ /* We could switch over to GNTTABOP_copy */
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ pending_req->seg[i].offset = req->seg[i].offset;
|
|
+ pending_req->seg[i].length = req->seg[i].length;
|
|
+
|
|
+ barrier();
|
|
+
|
|
+ if (pending_req->seg[i].offset >= PAGE_SIZE ||
|
|
+ pending_req->seg[i].length > PAGE_SIZE ||
|
|
+ pending_req->seg[i].offset +
|
|
+ pending_req->seg[i].length > PAGE_SIZE)
|
|
+ ret |= 1;
|
|
+ }
|
|
+
|
|
+ if (ret)
|
|
+ goto fail_flush;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+fail_flush:
|
|
+ xen_usbbk_unmap(pending_req);
|
|
+ ret = -ENOMEM;
|
|
+
|
|
+fail:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void usbbk_do_response(struct pending_req *pending_req, int32_t status,
|
|
+ int32_t actual_length, int32_t error_count,
|
|
+ uint16_t start_frame)
|
|
+{
|
|
+ struct xen_usbif *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 usbbk_urb_complete(struct urb *urb)
|
|
+{
|
|
+ struct pending_req *pending_req = (struct pending_req *)urb->context;
|
|
+
|
|
+ if (usb_pipein(urb->pipe) && urb->status == 0 && urb->actual_length > 0)
|
|
+ copy_buff_to_pages(pending_req->buffer, pending_req, 0,
|
|
+ pending_req->nr_buffer_segs);
|
|
+
|
|
+ if (usb_pipeisoc(urb->pipe))
|
|
+ copy_buff_to_pages(&urb->iso_frame_desc[0], pending_req,
|
|
+ pending_req->nr_buffer_segs,
|
|
+ pending_req->nr_extra_segs);
|
|
+
|
|
+ barrier();
|
|
+
|
|
+ xen_usbbk_unmap(pending_req);
|
|
+
|
|
+ usbbk_do_response(pending_req, urb->status, urb->actual_length,
|
|
+ urb->error_count, urb->start_frame);
|
|
+
|
|
+ remove_req_from_submitting_list(pending_req->dev, pending_req);
|
|
+
|
|
+ barrier();
|
|
+ usbbk_release_urb(urb);
|
|
+ usbif_put(pending_req->usbif);
|
|
+ free_req(pending_req);
|
|
+}
|
|
+
|
|
+static void usbbk_init_urb(struct usbif_urb_request *req,
|
|
+ struct pending_req *pending_req)
|
|
+{
|
|
+ unsigned int pipe;
|
|
+ struct usb_device *udev = pending_req->dev->udev;
|
|
+ struct urb *urb = pending_req->urb;
|
|
+
|
|
+ switch (usb_pipetype(req->pipe)) {
|
|
+ case PIPE_ISOCHRONOUS:
|
|
+ if (usb_pipein(req->pipe))
|
|
+ pipe = usb_rcvisocpipe(udev,
|
|
+ usb_pipeendpoint(req->pipe));
|
|
+ else
|
|
+ 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 = usbbk_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:
|
|
+ if (usb_pipein(req->pipe))
|
|
+ pipe = usb_rcvintpipe(udev,
|
|
+ usb_pipeendpoint(req->pipe));
|
|
+ else
|
|
+ pipe = usb_sndintpipe(udev,
|
|
+ usb_pipeendpoint(req->pipe));
|
|
+
|
|
+ usb_fill_int_urb(urb, udev, pipe,
|
|
+ pending_req->buffer, req->buffer_length,
|
|
+ usbbk_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:
|
|
+ if (usb_pipein(req->pipe))
|
|
+ pipe = usb_rcvctrlpipe(udev, 0);
|
|
+ else
|
|
+ pipe = usb_sndctrlpipe(udev, 0);
|
|
+
|
|
+ usb_fill_control_urb(urb, udev, pipe,
|
|
+ (unsigned char *) pending_req->setup,
|
|
+ pending_req->buffer, req->buffer_length,
|
|
+ usbbk_urb_complete, pending_req);
|
|
+ memcpy(pending_req->setup, req->u.ctrl, 8);
|
|
+ urb->setup_dma = pending_req->setup_dma;
|
|
+ urb->transfer_flags = req->transfer_flags;
|
|
+
|
|
+ break;
|
|
+ case PIPE_BULK:
|
|
+ if (usb_pipein(req->pipe))
|
|
+ pipe = usb_rcvbulkpipe(udev,
|
|
+ usb_pipeendpoint(req->pipe));
|
|
+ else
|
|
+ pipe = usb_sndbulkpipe(udev,
|
|
+ usb_pipeendpoint(req->pipe));
|
|
+
|
|
+ usb_fill_bulk_urb(urb, udev, pipe, pending_req->buffer,
|
|
+ req->buffer_length, usbbk_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;
|
|
+ }
|
|
+}
|
|
+
|
|
+struct set_interface_request {
|
|
+ struct pending_req *pending_req;
|
|
+ int interface;
|
|
+ int alternate;
|
|
+ struct work_struct work;
|
|
+};
|
|
+
|
|
+static void usbbk_set_interface_work(struct work_struct *arg)
|
|
+{
|
|
+ struct set_interface_request *req
|
|
+ = container_of(arg, struct set_interface_request, work);
|
|
+ struct pending_req *pending_req = req->pending_req;
|
|
+ struct usb_device *udev = req->pending_req->dev->udev;
|
|
+
|
|
+ int ret;
|
|
+
|
|
+ usb_lock_device(udev);
|
|
+ ret = usb_set_interface(udev, req->interface, req->alternate);
|
|
+ usb_unlock_device(udev);
|
|
+ usb_put_dev(udev);
|
|
+
|
|
+ usbbk_do_response(pending_req, ret, 0, 0, 0);
|
|
+ usbif_put(pending_req->usbif);
|
|
+ free_req(pending_req);
|
|
+ kfree(req);
|
|
+}
|
|
+
|
|
+static int usbbk_set_interface(struct pending_req *pending_req, int interface,
|
|
+ int alternate)
|
|
+{
|
|
+ struct set_interface_request *req;
|
|
+ struct usb_device *udev = pending_req->dev->udev;
|
|
+
|
|
+ req = kmalloc(sizeof(*req), GFP_KERNEL);
|
|
+ if (!req)
|
|
+ return -ENOMEM;
|
|
+ req->pending_req = pending_req;
|
|
+ req->interface = interface;
|
|
+ req->alternate = alternate;
|
|
+ INIT_WORK(&req->work, usbbk_set_interface_work);
|
|
+ usb_get_dev(udev);
|
|
+ schedule_work(&req->work);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+struct clear_halt_request {
|
|
+ struct pending_req *pending_req;
|
|
+ int pipe;
|
|
+ struct work_struct work;
|
|
+};
|
|
+
|
|
+static void usbbk_clear_halt_work(struct work_struct *arg)
|
|
+{
|
|
+ struct clear_halt_request *req = container_of(arg,
|
|
+ struct clear_halt_request, work);
|
|
+ struct pending_req *pending_req = req->pending_req;
|
|
+ struct usb_device *udev = req->pending_req->dev->udev;
|
|
+ int ret;
|
|
+
|
|
+ usb_lock_device(udev);
|
|
+ ret = usb_clear_halt(req->pending_req->dev->udev, req->pipe);
|
|
+ usb_unlock_device(udev);
|
|
+ usb_put_dev(udev);
|
|
+
|
|
+ usbbk_do_response(pending_req, ret, 0, 0, 0);
|
|
+ usbif_put(pending_req->usbif);
|
|
+ free_req(pending_req);
|
|
+ kfree(req);
|
|
+}
|
|
+
|
|
+static int usbbk_clear_halt(struct pending_req *pending_req, int pipe)
|
|
+{
|
|
+ struct clear_halt_request *req;
|
|
+ struct usb_device *udev = pending_req->dev->udev;
|
|
+
|
|
+ req = kmalloc(sizeof(*req), GFP_KERNEL);
|
|
+ if (!req)
|
|
+ return -ENOMEM;
|
|
+ req->pending_req = pending_req;
|
|
+ req->pipe = pipe;
|
|
+ INIT_WORK(&req->work, usbbk_clear_halt_work);
|
|
+
|
|
+ usb_get_dev(udev);
|
|
+ schedule_work(&req->work);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#if 0
|
|
+struct port_reset_request {
|
|
+ struct pending_req *pending_req;
|
|
+ struct work_struct work;
|
|
+};
|
|
+
|
|
+static void usbbk_port_reset_work(struct work_struct *arg)
|
|
+{
|
|
+ struct port_reset_request *req = container_of(arg,
|
|
+ struct port_reset_request, work);
|
|
+ struct pending_req *pending_req = req->pending_req;
|
|
+ struct usb_device *udev = pending_req->dev->udev;
|
|
+ int ret, ret_lock;
|
|
+
|
|
+ ret = ret_lock = usb_lock_device_for_reset(udev, NULL);
|
|
+ if (ret_lock >= 0) {
|
|
+ ret = usb_reset_device(udev);
|
|
+ if (ret_lock)
|
|
+ usb_unlock_device(udev);
|
|
+ }
|
|
+ usb_put_dev(udev);
|
|
+
|
|
+ usbbk_do_response(pending_req, ret, 0, 0, 0);
|
|
+ usbif_put(pending_req->usbif);
|
|
+ free_req(pending_req);
|
|
+ kfree(req);
|
|
+}
|
|
+
|
|
+static int usbbk_port_reset(struct pending_req *pending_req)
|
|
+{
|
|
+ struct port_reset_request *req;
|
|
+ struct usb_device *udev = pending_req->dev->udev;
|
|
+
|
|
+ req = kmalloc(sizeof(*req), GFP_KERNEL);
|
|
+ if (!req)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ req->pending_req = pending_req;
|
|
+ INIT_WORK(&req->work, usbbk_port_reset_work);
|
|
+
|
|
+ usb_get_dev(udev);
|
|
+ schedule_work(&req->work);
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static void usbbk_set_address(struct xen_usbif *usbif, struct xen_usbdev *dev,
|
|
+ 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] = dev;
|
|
+ dev->addr = new_addr;
|
|
+ spin_unlock_irqrestore(&usbif->addr_lock, flags);
|
|
+}
|
|
+
|
|
+static void process_unlink_req(struct xen_usbif *usbif,
|
|
+ struct usbif_urb_request *req,
|
|
+ struct pending_req *pending_req)
|
|
+{
|
|
+ struct pending_req *unlink_req = NULL;
|
|
+ int devnum;
|
|
+ int ret = 0;
|
|
+ unsigned long flags;
|
|
+
|
|
+ devnum = usb_pipedevice(req->pipe);
|
|
+ if (unlikely(devnum == 0)) {
|
|
+ pending_req->dev = xen_usbif_find_attached_device(usbif,
|
|
+ usbif_pipeportnum(req->pipe));
|
|
+ if (unlikely(!pending_req->dev)) {
|
|
+ ret = -ENODEV;
|
|
+ goto fail_response;
|
|
+ }
|
|
+ } else {
|
|
+ if (unlikely(!usbif->addr_table[devnum])) {
|
|
+ ret = -ENODEV;
|
|
+ goto fail_response;
|
|
+ }
|
|
+ pending_req->dev = usbif->addr_table[devnum];
|
|
+ }
|
|
+
|
|
+ spin_lock_irqsave(&pending_req->dev->submitting_lock, flags);
|
|
+ list_for_each_entry(unlink_req, &pending_req->dev->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->dev->submitting_lock, flags);
|
|
+
|
|
+fail_response:
|
|
+ usbbk_do_response(pending_req, ret, 0, 0, 0);
|
|
+ usbif_put(usbif);
|
|
+ free_req(pending_req);
|
|
+ return;
|
|
+}
|
|
+
|
|
+static int check_and_submit_special_ctrlreq(struct xen_usbif *usbif,
|
|
+ struct usbif_urb_request *req,
|
|
+ struct pending_req *pending_req)
|
|
+{
|
|
+ int devnum;
|
|
+ struct xen_usbdev *dev = NULL;
|
|
+ struct usb_ctrlrequest *ctrl = (struct usb_ctrlrequest *) req->u.ctrl;
|
|
+ int ret;
|
|
+ int done = 0;
|
|
+
|
|
+ devnum = usb_pipedevice(req->pipe);
|
|
+
|
|
+ /*
|
|
+ * When the device is first connected or reseted, USB device has no
|
|
+ * address. In this initial state, following requests are send to
|
|
+ * device address (#0),
|
|
+ *
|
|
+ * 1. GET_DESCRIPTOR (with Descriptor Type is "DEVICE") is send, and
|
|
+ * OS knows what device is connected to.
|
|
+ *
|
|
+ * 2. SET_ADDRESS is send, and then, device has its address.
|
|
+ *
|
|
+ * In the next step, SET_CONFIGURATION is send to addressed device, and
|
|
+ * then, the device is finally ready to use.
|
|
+ */
|
|
+ if (unlikely(devnum == 0)) {
|
|
+ dev = xen_usbif_find_attached_device(usbif,
|
|
+ usbif_pipeportnum(req->pipe));
|
|
+ if (unlikely(!dev)) {
|
|
+ ret = -ENODEV;
|
|
+ goto fail_response;
|
|
+ }
|
|
+
|
|
+ switch (ctrl->bRequest) {
|
|
+ case USB_REQ_GET_DESCRIPTOR:
|
|
+ /*
|
|
+ * GET_DESCRIPTOR request to device #0.
|
|
+ * through to normal urb transfer.
|
|
+ */
|
|
+ pending_req->dev = dev;
|
|
+ return 0;
|
|
+ break;
|
|
+ case USB_REQ_SET_ADDRESS:
|
|
+ /*
|
|
+ * SET_ADDRESS request to device #0.
|
|
+ * add attached device to addr_table.
|
|
+ */
|
|
+ {
|
|
+ __u16 addr = le16_to_cpu(ctrl->wValue);
|
|
+ usbbk_set_address(usbif, dev, 0, addr);
|
|
+ }
|
|
+ ret = 0;
|
|
+ goto fail_response;
|
|
+ break;
|
|
+ default:
|
|
+ ret = -EINVAL;
|
|
+ goto fail_response;
|
|
+ }
|
|
+ } else {
|
|
+ if (unlikely(!usbif->addr_table[devnum])) {
|
|
+ ret = -ENODEV;
|
|
+ goto fail_response;
|
|
+ }
|
|
+ pending_req->dev = 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.
|
|
+ */
|
|
+ {
|
|
+ __u16 addr = le16_to_cpu(ctrl->wValue);
|
|
+ usbbk_set_address(usbif, dev, devnum, addr);
|
|
+ }
|
|
+ ret = 0;
|
|
+ goto fail_response;
|
|
+ break;
|
|
+#if 0
|
|
+ case USB_REQ_SET_CONFIGURATION:
|
|
+ /*
|
|
+ * linux 2.6.27 or later version only!
|
|
+ */
|
|
+ if (ctrl->RequestType == USB_RECIP_DEVICE) {
|
|
+ __u16 config = le16_to_cpu(ctrl->wValue);
|
|
+ usb_driver_set_configuration(pending_req->dev->udev,
|
|
+ config);
|
|
+ done = 1;
|
|
+ }
|
|
+ break;
|
|
+#endif
|
|
+ case USB_REQ_SET_INTERFACE:
|
|
+ if (ctrl->bRequestType == USB_RECIP_INTERFACE) {
|
|
+ __u16 alt = le16_to_cpu(ctrl->wValue);
|
|
+ __u16 intf = le16_to_cpu(ctrl->wIndex);
|
|
+ usbbk_set_interface(pending_req, intf, alt);
|
|
+ done = 1;
|
|
+ }
|
|
+ break;
|
|
+ case USB_REQ_CLEAR_FEATURE:
|
|
+ if (ctrl->bRequestType == USB_RECIP_ENDPOINT
|
|
+ && ctrl->wValue == USB_ENDPOINT_HALT) {
|
|
+ int pipe;
|
|
+ int ep = le16_to_cpu(ctrl->wIndex) & 0x0f;
|
|
+ int dir = le16_to_cpu(ctrl->wIndex) & USB_DIR_IN;
|
|
+ if (dir)
|
|
+ pipe = usb_rcvctrlpipe(pending_req->dev->udev,
|
|
+ ep);
|
|
+ else
|
|
+ pipe = usb_sndctrlpipe(pending_req->dev->udev,
|
|
+ ep);
|
|
+ usbbk_clear_halt(pending_req, pipe);
|
|
+ done = 1;
|
|
+ }
|
|
+ break;
|
|
+#if 0 /* not tested yet */
|
|
+ case USB_REQ_SET_FEATURE:
|
|
+ if (ctrl->bRequestType == USB_RT_PORT) {
|
|
+ __u16 feat = le16_to_cpu(ctrl->wValue);
|
|
+ if (feat == USB_PORT_FEAT_RESET) {
|
|
+ usbbk_port_reset(pending_req);
|
|
+ done = 1;
|
|
+ }
|
|
+ }
|
|
+ break;
|
|
+#endif
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return done;
|
|
+
|
|
+fail_response:
|
|
+ usbbk_do_response(pending_req, ret, 0, 0, 0);
|
|
+ usbif_put(usbif);
|
|
+ free_req(pending_req);
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static void dispatch_request_to_pending_reqs(struct xen_usbif *usbif,
|
|
+ struct usbif_urb_request *req,
|
|
+ struct pending_req *pending_req)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ pending_req->id = req->id;
|
|
+ pending_req->usbif = usbif;
|
|
+
|
|
+ barrier();
|
|
+
|
|
+ usbif_get(usbif);
|
|
+
|
|
+ /* unlink request */
|
|
+ if (unlikely(usbif_pipeunlink(req->pipe))) {
|
|
+ process_unlink_req(usbif, req, pending_req);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (usb_pipecontrol(req->pipe)) {
|
|
+ if (check_and_submit_special_ctrlreq(usbif, req, pending_req))
|
|
+ return;
|
|
+ } else {
|
|
+ int devnum = usb_pipedevice(req->pipe);
|
|
+ if (unlikely(!usbif->addr_table[devnum])) {
|
|
+ ret = -ENODEV;
|
|
+ goto fail_response;
|
|
+ }
|
|
+ pending_req->dev = usbif->addr_table[devnum];
|
|
+ }
|
|
+
|
|
+ barrier();
|
|
+
|
|
+ ret = usbbk_alloc_urb(req, pending_req);
|
|
+ if (ret) {
|
|
+ ret = -ESHUTDOWN;
|
|
+ goto fail_response;
|
|
+ }
|
|
+
|
|
+ add_req_to_submitting_list(pending_req->dev, pending_req);
|
|
+
|
|
+ barrier();
|
|
+
|
|
+ usbbk_init_urb(req, pending_req);
|
|
+
|
|
+ barrier();
|
|
+
|
|
+ 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;
|
|
+
|
|
+ barrier();
|
|
+
|
|
+ ret = xen_usbbk_map(usbif, req, pending_req);
|
|
+ if (ret) {
|
|
+ pr_alert(DRV_PFX "invalid buffer\n");
|
|
+ ret = -ESHUTDOWN;
|
|
+ goto fail_free_urb;
|
|
+ }
|
|
+
|
|
+ barrier();
|
|
+
|
|
+ if (usb_pipeout(req->pipe) && req->buffer_length)
|
|
+ copy_pages_to_buff(pending_req->buffer, pending_req, 0,
|
|
+ pending_req->nr_buffer_segs);
|
|
+ if (usb_pipeisoc(req->pipe)) {
|
|
+ copy_pages_to_buff(&pending_req->urb->iso_frame_desc[0],
|
|
+ pending_req, pending_req->nr_buffer_segs,
|
|
+ pending_req->nr_extra_segs);
|
|
+ }
|
|
+
|
|
+ barrier();
|
|
+
|
|
+ ret = usb_submit_urb(pending_req->urb, GFP_KERNEL);
|
|
+ if (ret) {
|
|
+ pr_alert(DRV_PFX "failed submitting urb, error %d\n", ret);
|
|
+ ret = -ESHUTDOWN;
|
|
+ goto fail_flush_area;
|
|
+ }
|
|
+ return;
|
|
+
|
|
+fail_flush_area:
|
|
+ xen_usbbk_unmap(pending_req);
|
|
+fail_free_urb:
|
|
+ remove_req_from_submitting_list(pending_req->dev, pending_req);
|
|
+ barrier();
|
|
+ usbbk_release_urb(pending_req->urb);
|
|
+fail_response:
|
|
+ usbbk_do_response(pending_req, ret, 0, 0, 0);
|
|
+ usbif_put(usbif);
|
|
+ free_req(pending_req);
|
|
+}
|
|
+
|
|
+static int usbbk_start_submit_urb(struct xen_usbif *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 = 0;
|
|
+
|
|
+ rc = urb_ring->req_cons;
|
|
+ rp = urb_ring->sring->req_prod;
|
|
+ rmb();
|
|
+
|
|
+ while (rc != rp) {
|
|
+ if (RING_REQUEST_CONS_OVERFLOW(urb_ring, rc)) {
|
|
+ pr_warn(DRV_PFX "RING_REQUEST_CONS_OVERFLOW\n");
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ pending_req = alloc_req();
|
|
+ if (NULL == pending_req) {
|
|
+ more_to_do = 1;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ req = RING_GET_REQUEST(urb_ring, rc);
|
|
+ urb_ring->req_cons = ++rc;
|
|
+
|
|
+ dispatch_request_to_pending_reqs(usbif, req, pending_req);
|
|
+ }
|
|
+
|
|
+ RING_FINAL_CHECK_FOR_REQUESTS(&usbif->urb_ring, more_to_do);
|
|
+
|
|
+ cond_resched();
|
|
+
|
|
+ return more_to_do;
|
|
+}
|
|
+
|
|
+void xen_usbif_hotplug_notify(struct xen_usbif *usbif, int portnum, int speed)
|
|
+{
|
|
+ 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);
|
|
+
|
|
+ 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 = portnum;
|
|
+ res->speed = 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);
|
|
+}
|
|
+
|
|
+int xen_usbif_schedule(void *arg)
|
|
+{
|
|
+ struct xen_usbif *usbif = (struct xen_usbif *) arg;
|
|
+
|
|
+ usbif_get(usbif);
|
|
+
|
|
+ while (!kthread_should_stop()) {
|
|
+ wait_event_interruptible(usbif->wq,
|
|
+ usbif->waiting_reqs || kthread_should_stop());
|
|
+ wait_event_interruptible(usbbk->pending_free_wq,
|
|
+ !list_empty(&usbbk->pending_free) || kthread_should_stop());
|
|
+ usbif->waiting_reqs = 0;
|
|
+ smp_mb();
|
|
+
|
|
+ if (usbbk_start_submit_urb(usbif))
|
|
+ usbif->waiting_reqs = 1;
|
|
+
|
|
+ usbbk_free_urbs();
|
|
+ }
|
|
+
|
|
+ usbbk_free_urbs();
|
|
+ usbif->xenusbd = NULL;
|
|
+ usbif_put(usbif);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * attach xen_usbdev device to usbif.
|
|
+ */
|
|
+void xen_usbif_attach_device(struct xen_usbif *usbif, struct xen_usbdev *dev)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&usbif->dev_lock, flags);
|
|
+ list_add(&dev->dev_list, &usbif->dev_list);
|
|
+ spin_unlock_irqrestore(&usbif->dev_lock, flags);
|
|
+ dev->usbif = usbif;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * detach usbdev device from usbif.
|
|
+ */
|
|
+void xen_usbif_detach_device(struct xen_usbif *usbif, struct xen_usbdev *dev)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ if (dev->addr)
|
|
+ usbbk_set_address(usbif, dev, dev->addr, 0);
|
|
+ spin_lock_irqsave(&usbif->dev_lock, flags);
|
|
+ list_del(&dev->dev_list);
|
|
+ spin_unlock_irqrestore(&usbif->dev_lock, flags);
|
|
+ dev->usbif = NULL;
|
|
+}
|
|
+
|
|
+void xen_usbif_detach_device_without_lock(struct xen_usbif *usbif,
|
|
+ struct xen_usbdev *dev)
|
|
+{
|
|
+ if (dev->addr)
|
|
+ usbbk_set_address(usbif, dev, dev->addr, 0);
|
|
+ list_del(&dev->dev_list);
|
|
+ dev->usbif = NULL;
|
|
+}
|
|
+
|
|
+static int __init xen_usbif_init(void)
|
|
+{
|
|
+ int i, mmap_pages;
|
|
+ int rc = 0;
|
|
+
|
|
+ if (!xen_pv_domain())
|
|
+ return -ENODEV;
|
|
+
|
|
+ usbbk = kzalloc(sizeof(struct xen_usbbk), GFP_KERNEL);
|
|
+ if (!usbbk) {
|
|
+ pr_alert(DRV_PFX "%s: out of memory!\n", __func__);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ mmap_pages = xen_usbif_reqs * USBIF_MAX_SEGMENTS_PER_REQUEST;
|
|
+ usbbk->pending_reqs =
|
|
+ kzalloc(sizeof(usbbk->pending_reqs[0]) * xen_usbif_reqs,
|
|
+ GFP_KERNEL);
|
|
+ usbbk->pending_grant_handles =
|
|
+ kmalloc(sizeof(usbbk->pending_grant_handles[0]) * mmap_pages,
|
|
+ GFP_KERNEL);
|
|
+ usbbk->pending_pages =
|
|
+ kzalloc(sizeof(usbbk->pending_pages[0]) * mmap_pages,
|
|
+ GFP_KERNEL);
|
|
+
|
|
+ if (!usbbk->pending_reqs || !usbbk->pending_grant_handles ||
|
|
+ !usbbk->pending_pages) {
|
|
+ rc = -ENOMEM;
|
|
+ pr_alert(DRV_PFX "%s: out of memory\n", __func__);
|
|
+ goto failed_init;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < mmap_pages; i++) {
|
|
+ usbbk->pending_grant_handles[i] = USBBACK_INVALID_HANDLE;
|
|
+ usbbk->pending_pages[i] = alloc_page(GFP_KERNEL);
|
|
+ if (usbbk->pending_pages[i] == NULL) {
|
|
+ rc = -ENOMEM;
|
|
+ pr_alert(DRV_PFX "%s: out of memory\n", __func__);
|
|
+ goto failed_init;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ INIT_LIST_HEAD(&usbbk->pending_free);
|
|
+ spin_lock_init(&usbbk->pending_free_lock);
|
|
+ init_waitqueue_head(&usbbk->pending_free_wq);
|
|
+
|
|
+ INIT_LIST_HEAD(&usbbk->urb_free);
|
|
+ spin_lock_init(&usbbk->urb_free_lock);
|
|
+
|
|
+ for (i = 0; i < xen_usbif_reqs; i++)
|
|
+ list_add_tail(&usbbk->pending_reqs[i].free_list,
|
|
+ &usbbk->pending_free);
|
|
+
|
|
+ rc = xen_usbdev_init();
|
|
+ if (rc)
|
|
+ goto failed_init;
|
|
+
|
|
+ rc = xen_usbif_xenbus_init();
|
|
+ if (rc)
|
|
+ goto usb_exit;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+ usb_exit:
|
|
+ xen_usbdev_exit();
|
|
+ failed_init:
|
|
+ kfree(usbbk->pending_reqs);
|
|
+ kfree(usbbk->pending_grant_handles);
|
|
+ if (usbbk->pending_pages) {
|
|
+ for (i = 0; i < mmap_pages; i++) {
|
|
+ if (usbbk->pending_pages[i])
|
|
+ __free_page(usbbk->pending_pages[i]);
|
|
+ }
|
|
+ kfree(usbbk->pending_pages);
|
|
+ }
|
|
+ kfree(usbbk);
|
|
+ usbbk = NULL;
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+struct xen_usbdev *xen_usbif_find_attached_device(struct xen_usbif *usbif,
|
|
+ int portnum)
|
|
+{
|
|
+ struct xen_usbdev *dev;
|
|
+ int found = 0;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&usbif->dev_lock, flags);
|
|
+ list_for_each_entry(dev, &usbif->dev_list, dev_list) {
|
|
+ if (dev->port->portnum == portnum) {
|
|
+ found = 1;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ spin_unlock_irqrestore(&usbif->dev_lock, flags);
|
|
+
|
|
+ if (found)
|
|
+ return dev;
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static void __exit xen_usbif_exit(void)
|
|
+{
|
|
+ int i;
|
|
+ int mmap_pages = xen_usbif_reqs * USBIF_MAX_SEGMENTS_PER_REQUEST;
|
|
+
|
|
+ xen_usbif_xenbus_exit();
|
|
+ xen_usbdev_exit();
|
|
+ kfree(usbbk->pending_reqs);
|
|
+ kfree(usbbk->pending_grant_handles);
|
|
+ for (i = 0; i < mmap_pages; i++) {
|
|
+ if (usbbk->pending_pages[i])
|
|
+ __free_page(usbbk->pending_pages[i]);
|
|
+ }
|
|
+ kfree(usbbk->pending_pages);
|
|
+ usbbk = NULL;
|
|
+}
|
|
+
|
|
+module_init(xen_usbif_init);
|
|
+module_exit(xen_usbif_exit);
|
|
+
|
|
+MODULE_AUTHOR("");
|
|
+MODULE_DESCRIPTION("Xen USB backend driver (xen_usbback)");
|
|
+MODULE_LICENSE("Dual BSD/GPL");
|
|
diff --git a/drivers/usb/host/xen-usbback/usbdev.c b/drivers/usb/host/xen-usbback/usbdev.c
|
|
new file mode 100644
|
|
index 0000000..53a14b4
|
|
--- /dev/null
|
|
+++ b/drivers/usb/host/xen-usbback/usbdev.c
|
|
@@ -0,0 +1,319 @@
|
|
+/*
|
|
+ * USB stub device driver - grabbing and managing USB devices.
|
|
+ *
|
|
+ * Copyright (C) 2009, FUJITSU LABORATORIES LTD.
|
|
+ * Author: Noboru Iwamatsu <n_iwamatsu@jp.fujitsu.com>
|
|
+ *
|
|
+ * 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 "common.h"
|
|
+
|
|
+static LIST_HEAD(port_list);
|
|
+static DEFINE_SPINLOCK(port_list_lock);
|
|
+
|
|
+struct xen_usbport *xen_usbport_find_by_busid(const char *busid)
|
|
+{
|
|
+ struct xen_usbport *port;
|
|
+ int found = 0;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&port_list_lock, flags);
|
|
+ list_for_each_entry(port, &port_list, port_list) {
|
|
+ if (!(strncmp(port->phys_bus, busid, XEN_USB_BUS_ID_SIZE))) {
|
|
+ found = 1;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ spin_unlock_irqrestore(&port_list_lock, flags);
|
|
+
|
|
+ if (found)
|
|
+ return port;
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+struct xen_usbport *xen_usbport_find(const domid_t domid,
|
|
+ const unsigned int handle, const int portnum)
|
|
+{
|
|
+ struct xen_usbport *port;
|
|
+ int found = 0;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&port_list_lock, flags);
|
|
+ list_for_each_entry(port, &port_list, port_list) {
|
|
+ if ((port->domid == domid) &&
|
|
+ (port->handle == handle) &&
|
|
+ (port->portnum == portnum)) {
|
|
+ found = 1;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ spin_unlock_irqrestore(&port_list_lock, flags);
|
|
+
|
|
+ if (found)
|
|
+ return port;
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+int xen_usbport_add(const char *busid, const domid_t domid,
|
|
+ const unsigned int handle, const int portnum)
|
|
+{
|
|
+ struct xen_usbport *port;
|
|
+ unsigned long flags;
|
|
+
|
|
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
|
|
+ if (!port)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ port->domid = domid;
|
|
+ port->handle = handle;
|
|
+ port->portnum = portnum;
|
|
+
|
|
+ strncpy(port->phys_bus, busid, XEN_USB_BUS_ID_SIZE);
|
|
+
|
|
+ spin_lock_irqsave(&port_list_lock, flags);
|
|
+ list_add(&port->port_list, &port_list);
|
|
+ spin_unlock_irqrestore(&port_list_lock, flags);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int xen_usbport_remove(const domid_t domid, const unsigned int handle,
|
|
+ const int portnum)
|
|
+{
|
|
+ struct xen_usbport *port, *tmp;
|
|
+ int err = -ENOENT;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&port_list_lock, flags);
|
|
+ list_for_each_entry_safe(port, tmp, &port_list, port_list) {
|
|
+ if (port->domid == domid &&
|
|
+ port->handle == handle &&
|
|
+ port->portnum == portnum) {
|
|
+ list_del(&port->port_list);
|
|
+ kfree(port);
|
|
+
|
|
+ err = 0;
|
|
+ }
|
|
+ }
|
|
+ spin_unlock_irqrestore(&port_list_lock, flags);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static struct xen_usbdev *xen_usbdev_alloc(struct usb_device *udev,
|
|
+ struct xen_usbport *port)
|
|
+{
|
|
+ struct xen_usbdev *dev;
|
|
+
|
|
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
|
+ if (!dev) {
|
|
+ pr_alert(DRV_PFX "no memory for alloc xen_usbdev\n");
|
|
+ return NULL;
|
|
+ }
|
|
+ kref_init(&dev->kref);
|
|
+ dev->udev = usb_get_dev(udev);
|
|
+ dev->port = port;
|
|
+ spin_lock_init(&dev->submitting_lock);
|
|
+ INIT_LIST_HEAD(&dev->submitting_list);
|
|
+
|
|
+ return dev;
|
|
+}
|
|
+
|
|
+static void usbdev_release(struct kref *kref)
|
|
+{
|
|
+ struct xen_usbdev *dev;
|
|
+
|
|
+ dev = container_of(kref, struct xen_usbdev, kref);
|
|
+
|
|
+ usb_put_dev(dev->udev);
|
|
+ dev->udev = NULL;
|
|
+ dev->port = NULL;
|
|
+ kfree(dev);
|
|
+}
|
|
+
|
|
+static inline void usbdev_get(struct xen_usbdev *dev)
|
|
+{
|
|
+ kref_get(&dev->kref);
|
|
+}
|
|
+
|
|
+static inline void usbdev_put(struct xen_usbdev *dev)
|
|
+{
|
|
+ kref_put(&dev->kref, usbdev_release);
|
|
+}
|
|
+
|
|
+static int usbdev_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 xen_usbport *port = NULL;
|
|
+ struct xen_usbdev *dev = NULL;
|
|
+ struct xen_usbif *usbif = NULL;
|
|
+ int retval = -ENODEV;
|
|
+
|
|
+ /* hub currently not supported, so skip. */
|
|
+ if (udev->descriptor.bDeviceClass == USB_CLASS_HUB)
|
|
+ goto out;
|
|
+
|
|
+ port = xen_usbport_find_by_busid(busid);
|
|
+ if (!port)
|
|
+ goto out;
|
|
+
|
|
+ usbif = xen_usbif_find(port->domid, port->handle);
|
|
+ if (!usbif)
|
|
+ goto out;
|
|
+
|
|
+ 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:
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ dev = xen_usbif_find_attached_device(usbif, port->portnum);
|
|
+ if (!dev) {
|
|
+ /* new connection */
|
|
+ dev = xen_usbdev_alloc(udev, port);
|
|
+ if (!dev)
|
|
+ return -ENOMEM;
|
|
+ xen_usbif_attach_device(usbif, dev);
|
|
+ xen_usbif_hotplug_notify(usbif, port->portnum, udev->speed);
|
|
+ } else {
|
|
+ /* maybe already called and connected by other intf */
|
|
+ if (strncmp(dev->port->phys_bus, busid, XEN_USB_BUS_ID_SIZE))
|
|
+ goto out; /* invalid call */
|
|
+ }
|
|
+
|
|
+ usbdev_get(dev);
|
|
+ usb_set_intfdata(intf, dev);
|
|
+ retval = 0;
|
|
+
|
|
+out:
|
|
+ return retval;
|
|
+}
|
|
+
|
|
+static void usbdev_disconnect(struct usb_interface *intf)
|
|
+{
|
|
+ struct xen_usbdev *dev
|
|
+ = (struct xen_usbdev *) usb_get_intfdata(intf);
|
|
+
|
|
+ usb_set_intfdata(intf, NULL);
|
|
+
|
|
+ if (!dev)
|
|
+ return;
|
|
+
|
|
+ if (dev->usbif) {
|
|
+ xen_usbif_hotplug_notify(dev->usbif, dev->port->portnum, 0);
|
|
+ xen_usbif_detach_device(dev->usbif, dev);
|
|
+ }
|
|
+ xen_usbif_unlink_urbs(dev);
|
|
+ usbdev_put(dev);
|
|
+}
|
|
+
|
|
+static ssize_t usbdev_show_ports(struct device_driver *driver, char *buf)
|
|
+{
|
|
+ struct xen_usbport *port;
|
|
+ size_t count = 0;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&port_list_lock, flags);
|
|
+ list_for_each_entry(port, &port_list, port_list) {
|
|
+ if (count >= PAGE_SIZE)
|
|
+ break;
|
|
+ count += scnprintf((char *)buf + count, PAGE_SIZE - count,
|
|
+ "%s:%d:%d:%d\n",
|
|
+ &port->phys_bus[0],
|
|
+ port->domid,
|
|
+ port->handle,
|
|
+ port->portnum);
|
|
+ }
|
|
+ spin_unlock_irqrestore(&port_list_lock, flags);
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+DRIVER_ATTR(port_ids, S_IRUSR, usbdev_show_ports, NULL);
|
|
+
|
|
+/* table of devices that matches any usbdevice */
|
|
+static const struct usb_device_id usbdev_table[] = {
|
|
+ { .driver_info = 1 }, /* wildcard, see usb_match_id() */
|
|
+ { } /* Terminating entry */
|
|
+};
|
|
+MODULE_DEVICE_TABLE(usb, usbdev_table);
|
|
+
|
|
+static struct usb_driver xen_usbdev_driver = {
|
|
+ .name = "usbback",
|
|
+ .probe = usbdev_probe,
|
|
+ .disconnect = usbdev_disconnect,
|
|
+ .id_table = usbdev_table,
|
|
+ .no_dynamic_id = 1,
|
|
+};
|
|
+
|
|
+int __init xen_usbdev_init(void)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ err = usb_register(&xen_usbdev_driver);
|
|
+ if (err < 0) {
|
|
+ pr_alert(DRV_PFX "usb_register failed (error %d)\n",
|
|
+ err);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ err = driver_create_file(&xen_usbdev_driver.drvwrap.driver,
|
|
+ &driver_attr_port_ids);
|
|
+ if (err)
|
|
+ usb_deregister(&xen_usbdev_driver);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void xen_usbdev_exit(void)
|
|
+{
|
|
+ driver_remove_file(&xen_usbdev_driver.drvwrap.driver,
|
|
+ &driver_attr_port_ids);
|
|
+ usb_deregister(&xen_usbdev_driver);
|
|
+}
|
|
diff --git a/drivers/usb/host/xen-usbback/xenbus.c b/drivers/usb/host/xen-usbback/xenbus.c
|
|
new file mode 100644
|
|
index 0000000..5eae4ec
|
|
--- /dev/null
|
|
+++ b/drivers/usb/host/xen-usbback/xenbus.c
|
|
@@ -0,0 +1,482 @@
|
|
+/*
|
|
+ * Xenbus interface for USB backend driver.
|
|
+ *
|
|
+ * Copyright (C) 2009, FUJITSU LABORATORIES LTD.
|
|
+ * Author: Noboru Iwamatsu <n_iwamatsu@jp.fujitsu.com>
|
|
+ *
|
|
+ * 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/delay.h>
|
|
+#include "common.h"
|
|
+
|
|
+static LIST_HEAD(usbif_list);
|
|
+static DEFINE_SPINLOCK(usbif_list_lock);
|
|
+
|
|
+struct xen_usbif *xen_usbif_find(domid_t domid, unsigned int handle)
|
|
+{
|
|
+ struct xen_usbif *usbif;
|
|
+ int found = 0;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&usbif_list_lock, flags);
|
|
+ list_for_each_entry(usbif, &usbif_list, usbif_list) {
|
|
+ if (usbif->domid == domid && usbif->handle == handle) {
|
|
+ found = 1;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ spin_unlock_irqrestore(&usbif_list_lock, flags);
|
|
+
|
|
+ if (found)
|
|
+ return usbif;
|
|
+
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+struct xen_usbif *xen_usbif_alloc(domid_t domid, unsigned int handle)
|
|
+{
|
|
+ struct xen_usbif *usbif;
|
|
+ unsigned long flags;
|
|
+ int i;
|
|
+
|
|
+ usbif = kzalloc(sizeof(struct xen_usbif), GFP_KERNEL);
|
|
+ if (!usbif)
|
|
+ return NULL;
|
|
+
|
|
+ usbif->domid = domid;
|
|
+ usbif->handle = handle;
|
|
+ INIT_LIST_HEAD(&usbif->usbif_list);
|
|
+ spin_lock_init(&usbif->urb_ring_lock);
|
|
+ spin_lock_init(&usbif->conn_ring_lock);
|
|
+ atomic_set(&usbif->refcnt, 0);
|
|
+ init_waitqueue_head(&usbif->wq);
|
|
+ init_waitqueue_head(&usbif->waiting_to_free);
|
|
+ spin_lock_init(&usbif->dev_lock);
|
|
+ INIT_LIST_HEAD(&usbif->dev_list);
|
|
+ spin_lock_init(&usbif->addr_lock);
|
|
+ for (i = 0; i < XEN_USB_DEV_ADDR_SIZE; i++)
|
|
+ usbif->addr_table[i] = NULL;
|
|
+
|
|
+ spin_lock_irqsave(&usbif_list_lock, flags);
|
|
+ list_add(&usbif->usbif_list, &usbif_list);
|
|
+ spin_unlock_irqrestore(&usbif_list_lock, flags);
|
|
+
|
|
+ return usbif;
|
|
+}
|
|
+
|
|
+static int xen_usbif_map(struct xen_usbif *usbif, unsigned long urb_ring_ref,
|
|
+ unsigned long conn_ring_ref, unsigned int evtchn)
|
|
+{
|
|
+ int err = -ENOMEM;
|
|
+
|
|
+ if (usbif->irq)
|
|
+ return 0;
|
|
+
|
|
+ err = xenbus_map_ring_valloc(usbif->xbdev, urb_ring_ref,
|
|
+ &usbif->urb_sring);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ err = xenbus_map_ring_valloc(usbif->xbdev, conn_ring_ref,
|
|
+ &usbif->conn_sring);
|
|
+ if (err < 0)
|
|
+ goto fail_alloc;
|
|
+
|
|
+ err = bind_interdomain_evtchn_to_irqhandler(usbif->domid, evtchn,
|
|
+ xen_usbif_be_int, 0, "usbif-backend", usbif);
|
|
+ if (err < 0)
|
|
+ goto fail_evtchn;
|
|
+ usbif->irq = err;
|
|
+
|
|
+ BACK_RING_INIT(&usbif->urb_ring,
|
|
+ (struct usbif_urb_sring *)usbif->urb_sring, PAGE_SIZE);
|
|
+ BACK_RING_INIT(&usbif->conn_ring,
|
|
+ (struct usbif_conn_sring *)usbif->conn_sring, PAGE_SIZE);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+fail_evtchn:
|
|
+ xenbus_unmap_ring_vfree(usbif->xbdev, usbif->conn_sring);
|
|
+fail_alloc:
|
|
+ xenbus_unmap_ring_vfree(usbif->xbdev, usbif->urb_sring);
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void xen_usbif_disconnect(struct xen_usbif *usbif)
|
|
+{
|
|
+ struct xen_usbdev *dev, *tmp;
|
|
+ unsigned long flags;
|
|
+
|
|
+ if (usbif->xenusbd) {
|
|
+ kthread_stop(usbif->xenusbd);
|
|
+ usbif->xenusbd = NULL;
|
|
+ }
|
|
+
|
|
+ spin_lock_irqsave(&usbif->dev_lock, flags);
|
|
+ list_for_each_entry_safe(dev, tmp, &usbif->dev_list, dev_list) {
|
|
+ xen_usbif_unlink_urbs(dev);
|
|
+ xen_usbif_detach_device_without_lock(usbif, dev);
|
|
+ }
|
|
+ spin_unlock_irqrestore(&usbif->dev_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_sring);
|
|
+ usbif->urb_ring.sring = NULL;
|
|
+ xenbus_unmap_ring_vfree(usbif->xbdev, usbif->conn_sring);
|
|
+ usbif->conn_ring.sring = NULL;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void xen_usbif_free(struct xen_usbif *usbif)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&usbif_list_lock, flags);
|
|
+ list_del(&usbif->usbif_list);
|
|
+ spin_unlock_irqrestore(&usbif_list_lock, flags);
|
|
+ kfree(usbif);
|
|
+}
|
|
+
|
|
+static void usbbk_changed(struct xenbus_watch *watch, const char **vec,
|
|
+ unsigned int len)
|
|
+{
|
|
+ struct xenbus_transaction xbt;
|
|
+ int err;
|
|
+ int i;
|
|
+ char node[8];
|
|
+ char *busid;
|
|
+ struct xen_usbport *port = NULL;
|
|
+
|
|
+ struct xen_usbif *usbif = container_of(watch, struct xen_usbif,
|
|
+ backend_watch);
|
|
+ struct xenbus_device *dev = usbif->xbdev;
|
|
+
|
|
+again:
|
|
+ err = xenbus_transaction_start(&xbt);
|
|
+ if (err) {
|
|
+ xenbus_dev_fatal(dev, err, "starting transaction");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ for (i = 1; i <= usbif->num_ports; i++) {
|
|
+ sprintf(node, "port/%d", i);
|
|
+ busid = xenbus_read(xbt, dev->nodename, node, NULL);
|
|
+ if (IS_ERR(busid)) {
|
|
+ err = PTR_ERR(busid);
|
|
+ xenbus_dev_fatal(dev, err, "reading port/%d", i);
|
|
+ goto abort;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * remove port, if the port is not connected,
|
|
+ */
|
|
+ if (strlen(busid) == 0) {
|
|
+ port = xen_usbport_find(usbif->domid, usbif->handle, i);
|
|
+ if (port) {
|
|
+ if (port->is_connected)
|
|
+ xenbus_dev_fatal(dev, err,
|
|
+ "can't remove port/%d, "
|
|
+ "unbind first", i);
|
|
+ else
|
|
+ xen_usbport_remove(usbif->domid,
|
|
+ usbif->handle, i);
|
|
+ }
|
|
+ continue; /* never configured, ignore */
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * add port,
|
|
+ * if the port is not configured and not used from other usbif.
|
|
+ */
|
|
+ port = xen_usbport_find(usbif->domid, usbif->handle, i);
|
|
+ if (port) {
|
|
+ if ((strncmp(port->phys_bus, busid,
|
|
+ XEN_USB_BUS_ID_SIZE)))
|
|
+ xenbus_dev_fatal(dev, err, "can't add port/%d, "
|
|
+ "remove first", i);
|
|
+ else
|
|
+ continue; /* already configured, ignore */
|
|
+ } else {
|
|
+ if (xen_usbport_find_by_busid(busid))
|
|
+ xenbus_dev_fatal(dev, err, "can't add port/%d, "
|
|
+ "busid already used", i);
|
|
+ else
|
|
+ xen_usbport_add(busid, usbif->domid,
|
|
+ usbif->handle, i);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ err = xenbus_transaction_end(xbt, 0);
|
|
+ if (err == -EAGAIN)
|
|
+ goto again;
|
|
+ if (err)
|
|
+ xenbus_dev_fatal(dev, err, "completing transaction");
|
|
+
|
|
+ return;
|
|
+
|
|
+abort:
|
|
+ xenbus_transaction_end(xbt, 1);
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
+static int usbbk_remove(struct xenbus_device *dev)
|
|
+{
|
|
+ struct xen_usbif *usbif = dev_get_drvdata(&dev->dev);
|
|
+ int i;
|
|
+
|
|
+ if (usbif->backend_watch.node) {
|
|
+ unregister_xenbus_watch(&usbif->backend_watch);
|
|
+ kfree(usbif->backend_watch.node);
|
|
+ usbif->backend_watch.node = NULL;
|
|
+ }
|
|
+
|
|
+ if (usbif) {
|
|
+ /* remove all ports */
|
|
+ for (i = 1; i <= usbif->num_ports; i++)
|
|
+ xen_usbport_remove(usbif->domid, usbif->handle, i);
|
|
+ xen_usbif_disconnect(usbif);
|
|
+ xen_usbif_free(usbif);
|
|
+ }
|
|
+ dev_set_drvdata(&dev->dev, NULL);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int usbbk_probe(struct xenbus_device *dev,
|
|
+ const struct xenbus_device_id *id)
|
|
+{
|
|
+ struct xen_usbif *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 -ENOENT;
|
|
+
|
|
+ usbif = xen_usbif_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_switch_state(dev, XenbusStateInitWait);
|
|
+ if (err)
|
|
+ goto fail;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+fail:
|
|
+ usbbk_remove(dev);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int connect_rings(struct xen_usbif *usbif)
|
|
+{
|
|
+ struct xenbus_device *dev = usbif->xbdev;
|
|
+ unsigned long urb_ring_ref;
|
|
+ unsigned long conn_ring_ref;
|
|
+ unsigned int evtchn;
|
|
+ int err;
|
|
+
|
|
+ err = xenbus_gather(XBT_NIL, dev->otherend,
|
|
+ "urb-ring-ref", "%lu", &urb_ring_ref,
|
|
+ "conn-ring-ref", "%lu", &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(DRV_PFX "urb-ring-ref %ld, conn-ring-ref %ld, "
|
|
+ "event-channel %d\n", urb_ring_ref, conn_ring_ref, evtchn);
|
|
+
|
|
+ err = xen_usbif_map(usbif, urb_ring_ref, conn_ring_ref, evtchn);
|
|
+ if (err) {
|
|
+ xenbus_dev_fatal(dev, err, "mapping urb-ring-ref %lu "
|
|
+ "conn-ring-ref %lu port %u",
|
|
+ urb_ring_ref, conn_ring_ref, evtchn);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int start_xenusbd(struct xen_usbif *usbif)
|
|
+{
|
|
+ int err = 0;
|
|
+ char name[TASK_COMM_LEN];
|
|
+
|
|
+ snprintf(name, TASK_COMM_LEN, "usbback.%d.%d", usbif->domid,
|
|
+ usbif->handle);
|
|
+ usbif->xenusbd = kthread_run(xen_usbif_schedule, usbif, name);
|
|
+ if (IS_ERR(usbif->xenusbd)) {
|
|
+ err = PTR_ERR(usbif->xenusbd);
|
|
+ usbif->xenusbd = NULL;
|
|
+ xenbus_dev_error(usbif->xbdev, err, "start xenusbd");
|
|
+ }
|
|
+
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void frontend_changed(struct xenbus_device *dev,
|
|
+ enum xenbus_state frontend_state)
|
|
+{
|
|
+ struct xen_usbif *usbif = dev_get_drvdata(&dev->dev);
|
|
+ int err;
|
|
+
|
|
+ switch (frontend_state) {
|
|
+ case XenbusStateReconfiguring:
|
|
+ case XenbusStateReconfigured:
|
|
+ break;
|
|
+
|
|
+ case XenbusStateInitialising:
|
|
+ if (dev->state == XenbusStateClosed) {
|
|
+ pr_info(DRV_PFX "%s: %s: prepare for reconnect\n",
|
|
+ __func__, dev->nodename);
|
|
+ xenbus_switch_state(dev, XenbusStateInitWait);
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ case XenbusStateInitialised:
|
|
+ case XenbusStateConnected:
|
|
+ if (dev->state == XenbusStateConnected)
|
|
+ break;
|
|
+
|
|
+ xen_usbif_disconnect(usbif);
|
|
+
|
|
+ err = connect_rings(usbif);
|
|
+ if (err)
|
|
+ break;
|
|
+ err = start_xenusbd(usbif);
|
|
+ if (err)
|
|
+ break;
|
|
+ err = xenbus_watch_pathfmt(dev, &usbif->backend_watch,
|
|
+ usbbk_changed, "%s/%s", dev->nodename, "port");
|
|
+ if (err)
|
|
+ break;
|
|
+ xenbus_switch_state(dev, XenbusStateConnected);
|
|
+ break;
|
|
+
|
|
+ case XenbusStateClosing:
|
|
+ xenbus_switch_state(dev, XenbusStateClosing);
|
|
+ break;
|
|
+
|
|
+ case XenbusStateClosed:
|
|
+ xen_usbif_disconnect(usbif);
|
|
+ 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;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/* ** Driver Registration ** */
|
|
+
|
|
+static const struct xenbus_device_id usbback_ids[] = {
|
|
+ { "vusb" },
|
|
+ { "" },
|
|
+};
|
|
+
|
|
+static DEFINE_XENBUS_DRIVER(usbback, ,
|
|
+ .probe = usbbk_probe,
|
|
+ .remove = usbbk_remove,
|
|
+ .otherend_changed = frontend_changed,
|
|
+);
|
|
+
|
|
+int __init xen_usbif_xenbus_init(void)
|
|
+{
|
|
+ return xenbus_register_backend(&usbback_driver);
|
|
+}
|
|
+
|
|
+void __exit xen_usbif_xenbus_exit(void)
|
|
+{
|
|
+ xenbus_unregister_driver(&usbback_driver);
|
|
+}
|
|
diff --git a/drivers/usb/host/xen-usbfront.c b/drivers/usb/host/xen-usbfront.c
|
|
new file mode 100644
|
|
index 0000000..e632de3
|
|
--- /dev/null
|
|
+++ b/drivers/usb/host/xen-usbfront.c
|
|
@@ -0,0 +1,1739 @@
|
|
+/*
|
|
+ * xen-usbfront.c
|
|
+ *
|
|
+ * This file is part of Xen USB Virtual Host Controller driver.
|
|
+ *
|
|
+ * Copyright (C) 2009, FUJITSU LABORATORIES LTD.
|
|
+ * Author: Noboru Iwamatsu <n_iwamatsu@jp.fujitsu.com>
|
|
+ *
|
|
+ * 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/usb/hcd.h>
|
|
+#include <linux/list.h>
|
|
+#include <linux/kthread.h>
|
|
+#include <linux/wait.h>
|
|
+#include <linux/io.h>
|
|
+#include <xen/xenbus.h>
|
|
+#include <xen/events.h>
|
|
+#include <xen/page.h>
|
|
+#include <xen/grant_table.h>
|
|
+#include <xen/interface/xen.h>
|
|
+#include <xen/interface/io/usbif.h>
|
|
+
|
|
+static inline struct usbfront_info *hcd_to_info(struct usb_hcd *hcd)
|
|
+{
|
|
+ return (struct usbfront_info *) (hcd->hcd_priv);
|
|
+}
|
|
+
|
|
+static inline struct usb_hcd *info_to_hcd(struct usbfront_info *info)
|
|
+{
|
|
+ return container_of((void *) info, struct usb_hcd, hcd_priv);
|
|
+}
|
|
+
|
|
+/* 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;
|
|
+};
|
|
+
|
|
+/* statistics for tuning, monitoring, ... */
|
|
+struct xenhcd_stats {
|
|
+ unsigned long ring_full; /* RING_FULL conditions */
|
|
+ unsigned long complete; /* normal giveback urbs */
|
|
+ unsigned long unlink; /* unlinked urbs */
|
|
+};
|
|
+
|
|
+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, irq; /* event channel */
|
|
+ struct usb_shadow shadow[USB_URB_RING_SIZE];
|
|
+ unsigned long shadow_free;
|
|
+
|
|
+ /* RING_RESPONSE thread */
|
|
+ struct task_struct *kthread;
|
|
+ wait_queue_head_t wq;
|
|
+ unsigned int waiting_resp;
|
|
+
|
|
+ /* xmit statistics */
|
|
+#ifdef XENHCD_STATS
|
|
+ struct xenhcd_stats stats;
|
|
+#define COUNT(x) do { (x)++; } while (0)
|
|
+#else
|
|
+#define COUNT(x) do {} while (0)
|
|
+#endif
|
|
+};
|
|
+
|
|
+#define XENHCD_RING_JIFFIES (HZ/200)
|
|
+#define XENHCD_SCAN_JIFFIES 1
|
|
+
|
|
+enum xenhcd_timer_action {
|
|
+ TIMER_RING_WATCHDOG,
|
|
+ TIMER_SCAN_PENDING_URBS,
|
|
+};
|
|
+
|
|
+static inline void
|
|
+timer_action_done(struct usbfront_info *info, enum xenhcd_timer_action action)
|
|
+{
|
|
+ clear_bit(action, &info->actions);
|
|
+}
|
|
+
|
|
+static inline void
|
|
+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);
|
|
+ }
|
|
+}
|
|
+
|
|
+struct kmem_cache *xenhcd_urbp_cachep;
|
|
+struct hc_driver xen_usb20_hc_driver;
|
|
+struct hc_driver xen_usb11_hc_driver;
|
|
+
|
|
+static ssize_t show_statistics(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct usb_hcd *hcd;
|
|
+ struct usbfront_info *info;
|
|
+ unsigned long flags;
|
|
+ unsigned temp, size;
|
|
+ char *next;
|
|
+
|
|
+ hcd = dev_get_drvdata(dev);
|
|
+ info = hcd_to_info(hcd);
|
|
+ next = buf;
|
|
+ size = PAGE_SIZE;
|
|
+
|
|
+ spin_lock_irqsave(&info->lock, flags);
|
|
+
|
|
+ temp = scnprintf(next, 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);
|
|
+ size -= temp;
|
|
+ next += temp;
|
|
+
|
|
+#ifdef XENHCD_STATS
|
|
+ temp = scnprintf(next, size,
|
|
+ "complete %ld unlink %ld ring_full %ld\n",
|
|
+ info->stats.complete, info->stats.unlink,
|
|
+ info->stats.ring_full);
|
|
+ size -= temp;
|
|
+ next += temp;
|
|
+#endif
|
|
+
|
|
+ spin_unlock_irqrestore(&info->lock, flags);
|
|
+
|
|
+ return PAGE_SIZE - size;
|
|
+}
|
|
+
|
|
+static DEVICE_ATTR(statistics, S_IRUGO, show_statistics, NULL);
|
|
+
|
|
+static inline void create_debug_file(struct usbfront_info *info)
|
|
+{
|
|
+ struct device *dev = info_to_hcd(info)->self.controller;
|
|
+ if (device_create_file(dev, &dev_attr_statistics))
|
|
+ printk(KERN_WARNING "statistics file not created for %s\n",
|
|
+ info_to_hcd(info)->self.bus_name);
|
|
+}
|
|
+
|
|
+static inline void remove_debug_file(struct usbfront_info *info)
|
|
+{
|
|
+ struct device *dev = info_to_hcd(info)->self.controller;
|
|
+ device_remove_file(dev, &dev_attr_statistics);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * set virtual port connection status
|
|
+ */
|
|
+void 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
|
|
+ */
|
|
+void 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;
|
|
+
|
|
+ set_connect_state(info, portnum);
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+ * SetPortFeature(PORT_SUSPENDED)
|
|
+ */
|
|
+void 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)
|
|
+ */
|
|
+void 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)
|
|
+ */
|
|
+void 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)
|
|
+ set_connect_state(info, portnum);
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+ * ClearPortFeature(PORT_POWER)
|
|
+ * SetConfiguration(non-zero)
|
|
+ * Power_Source_Off
|
|
+ * Over-current
|
|
+ */
|
|
+void 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)
|
|
+ */
|
|
+void 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)
|
|
+ */
|
|
+void 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 XENHCD_PM
|
|
+#ifdef CONFIG_PM
|
|
+static int xenhcd_bus_suspend(struct usb_hcd *hcd)
|
|
+{
|
|
+ struct usbfront_info *info = 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++)
|
|
+ rhport_suspend(info, i);
|
|
+ }
|
|
+ spin_unlock_irq(&info->lock);
|
|
+
|
|
+ del_timer_sync(&info->watchdog);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int xenhcd_bus_resume(struct usb_hcd *hcd)
|
|
+{
|
|
+ struct usbfront_info *info = 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++)
|
|
+ rhport_resume(info, i);
|
|
+ }
|
|
+ spin_unlock_irq(&info->lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+#endif
|
|
+#endif
|
|
+
|
|
+static void xenhcd_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 xenhcd_hub_status_data(struct usb_hcd *hcd, char *buf)
|
|
+{
|
|
+ struct usbfront_info *info = 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 xenhcd_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|
+ u16 wIndex, char *buf, u16 wLength)
|
|
+{
|
|
+ struct usbfront_info *info = 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:
|
|
+ rhport_resume(info, wIndex);
|
|
+ break;
|
|
+ case USB_PORT_FEAT_POWER:
|
|
+ rhport_power_off(info, wIndex);
|
|
+ break;
|
|
+ case USB_PORT_FEAT_ENABLE:
|
|
+ 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:
|
|
+ xenhcd_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 SetHubFeature:
|
|
+ /* not supported */
|
|
+ goto error;
|
|
+ case SetPortFeature:
|
|
+ if (!wIndex || wIndex > ports)
|
|
+ goto error;
|
|
+
|
|
+ switch (wValue) {
|
|
+ case USB_PORT_FEAT_POWER:
|
|
+ rhport_power_on(info, wIndex);
|
|
+ break;
|
|
+ case USB_PORT_FEAT_RESET:
|
|
+ rhport_reset(info, wIndex);
|
|
+ break;
|
|
+ case USB_PORT_FEAT_SUSPEND:
|
|
+ rhport_suspend(info, wIndex);
|
|
+ break;
|
|
+ default:
|
|
+ if ((info->ports[wIndex-1].status &
|
|
+ USB_PORT_STAT_POWER) != 0)
|
|
+ info->ports[wIndex-1].status |= (1 << wValue);
|
|
+ }
|
|
+ break;
|
|
+
|
|
+ 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;
|
|
+}
|
|
+
|
|
+struct kmem_cache *xenhcd_urbp_cachep;
|
|
+
|
|
+static struct urb_priv *alloc_urb_priv(struct urb *urb)
|
|
+{
|
|
+ struct urb_priv *urbp;
|
|
+
|
|
+ urbp = kmem_cache_zalloc(xenhcd_urbp_cachep, GFP_ATOMIC);
|
|
+ if (!urbp)
|
|
+ return NULL;
|
|
+
|
|
+ urbp->urb = urb;
|
|
+ urb->hcpriv = urbp;
|
|
+ urbp->req_id = ~0;
|
|
+ urbp->unlink_req_id = ~0;
|
|
+ INIT_LIST_HEAD(&urbp->list);
|
|
+
|
|
+ return urbp;
|
|
+}
|
|
+
|
|
+static void free_urb_priv(struct urb_priv *urbp)
|
|
+{
|
|
+ urbp->urb->hcpriv = NULL;
|
|
+ kmem_cache_free(xenhcd_urbp_cachep, urbp);
|
|
+}
|
|
+
|
|
+static inline int 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 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 count_pages(void *addr, int length)
|
|
+{
|
|
+ unsigned long start = (unsigned long) addr >> PAGE_SHIFT;
|
|
+ unsigned long end = (unsigned long)
|
|
+ (addr + length + PAGE_SIZE - 1) >> PAGE_SHIFT;
|
|
+ return end - start;
|
|
+}
|
|
+
|
|
+static inline void xenhcd_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 mfn;
|
|
+ unsigned int offset;
|
|
+ unsigned int len;
|
|
+ unsigned int bytes;
|
|
+ int i;
|
|
+
|
|
+ len = length;
|
|
+
|
|
+ for (i = 0; i < nr_pages; i++) {
|
|
+ BUG_ON(!len);
|
|
+
|
|
+ mfn = virt_to_mfn(addr);
|
|
+ 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,
|
|
+ mfn, flags);
|
|
+ seg[i].gref = ref;
|
|
+ seg[i].offset = (uint16_t)offset;
|
|
+ seg[i].length = (uint16_t)bytes;
|
|
+
|
|
+ addr += bytes;
|
|
+ len -= bytes;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int 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 ret = 0;
|
|
+
|
|
+ if (urb->transfer_buffer_length) {
|
|
+ nr_buff_pages = count_pages(urb->transfer_buffer,
|
|
+ urb->transfer_buffer_length);
|
|
+
|
|
+ if (usb_pipeisoc(urb->pipe))
|
|
+ nr_isodesc_pages = count_pages(&urb->iso_frame_desc[0],
|
|
+ sizeof(struct usb_iso_packet_descriptor) *
|
|
+ urb->number_of_packets);
|
|
+
|
|
+ if (nr_buff_pages + nr_isodesc_pages >
|
|
+ USBIF_MAX_SEGMENTS_PER_REQUEST)
|
|
+ return -E2BIG;
|
|
+
|
|
+ ret = gnttab_alloc_grant_references(
|
|
+ USBIF_MAX_SEGMENTS_PER_REQUEST, &gref_head);
|
|
+ if (ret) {
|
|
+ printk(KERN_ERR "usbfront: "
|
|
+ "gnttab_alloc_grant_references() error\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ xenhcd_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);
|
|
+
|
|
+ if (!usb_pipeisoc(urb->pipe))
|
|
+ gnttab_free_grant_references(gref_head);
|
|
+ }
|
|
+
|
|
+ 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 */
|
|
+ if (unlikely(urb->number_of_packets <= 0))
|
|
+ BUG();
|
|
+ xenhcd_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);
|
|
+ gnttab_free_grant_references(gref_head);
|
|
+ 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:
|
|
+ ret = -EINVAL;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void xenhcd_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 xenhcd_giveback_urb(struct usbfront_info *info, struct urb *urb,
|
|
+ int status)
|
|
+__releases(info->lock)
|
|
+__acquires(info->lock)
|
|
+{
|
|
+ struct urb_priv *urbp = (struct urb_priv *) urb->hcpriv;
|
|
+
|
|
+ list_del_init(&urbp->list);
|
|
+ free_urb_priv(urbp);
|
|
+ switch (urb->status) {
|
|
+ case -ECONNRESET:
|
|
+ case -ENOENT:
|
|
+ COUNT(info->stats.unlink);
|
|
+ break;
|
|
+ case -EINPROGRESS:
|
|
+ urb->status = status;
|
|
+ /* falling through */
|
|
+ default:
|
|
+ COUNT(info->stats.complete);
|
|
+ }
|
|
+ spin_unlock(&info->lock);
|
|
+ usb_hcd_giveback_urb(info_to_hcd(info), urb,
|
|
+ urbp->status <= 0 ? urbp->status : urb->status);
|
|
+ spin_lock(&info->lock);
|
|
+}
|
|
+
|
|
+static inline int xenhcd_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 = 0;
|
|
+
|
|
+ req = RING_GET_REQUEST(&info->urb_ring, info->urb_ring.req_prod_pvt);
|
|
+ id = 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 = map_urb_for_request(info, urb, req);
|
|
+ if (ret < 0) {
|
|
+ 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 ret;
|
|
+}
|
|
+
|
|
+static void xenhcd_kick_pending_urbs(struct usbfront_info *info)
|
|
+{
|
|
+ struct urb_priv *urbp;
|
|
+ int ret;
|
|
+
|
|
+ while (!list_empty(&info->pending_submit_list)) {
|
|
+ if (RING_FULL(&info->urb_ring)) {
|
|
+ COUNT(info->stats.ring_full);
|
|
+ timer_action(info, TIMER_RING_WATCHDOG);
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ urbp = list_entry(info->pending_submit_list.next,
|
|
+ struct urb_priv, list);
|
|
+ ret = xenhcd_do_request(info, urbp);
|
|
+ if (ret == 0)
|
|
+ list_move_tail(&urbp->list, &info->in_progress_list);
|
|
+ else
|
|
+ xenhcd_giveback_urb(info, urbp->urb, -ESHUTDOWN);
|
|
+ }
|
|
+ timer_action_done(info, TIMER_SCAN_PENDING_URBS);
|
|
+
|
|
+done:
|
|
+ return;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * caller must lock info->lock
|
|
+ */
|
|
+static void xenhcd_cancel_all_enqueued_urbs(struct usbfront_info *info)
|
|
+{
|
|
+ struct urb_priv *urbp, *tmp;
|
|
+
|
|
+ list_for_each_entry_safe(urbp, tmp, &info->in_progress_list, list) {
|
|
+ if (!urbp->unlinked) {
|
|
+ xenhcd_gnttab_done(&info->shadow[urbp->req_id]);
|
|
+ barrier();
|
|
+ if (urbp->urb->status == -EINPROGRESS)/* not dequeued */
|
|
+ xenhcd_giveback_urb(info, urbp->urb,
|
|
+ -ESHUTDOWN);
|
|
+ else /* dequeued */
|
|
+ xenhcd_giveback_urb(info, urbp->urb,
|
|
+ urbp->urb->status);
|
|
+ }
|
|
+ info->shadow[urbp->req_id].urb = NULL;
|
|
+ }
|
|
+
|
|
+ list_for_each_entry_safe(urbp, tmp, &info->pending_submit_list, list) {
|
|
+ xenhcd_giveback_urb(info, urbp->urb, -ESHUTDOWN);
|
|
+ }
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * caller must lock info->lock
|
|
+ */
|
|
+static void xenhcd_giveback_unlinked_urbs(struct usbfront_info *info)
|
|
+{
|
|
+ struct urb_priv *urbp, *tmp;
|
|
+
|
|
+ list_for_each_entry_safe(urbp, tmp,
|
|
+ &info->giveback_waiting_list, list) {
|
|
+ xenhcd_giveback_urb(info, urbp->urb, urbp->urb->status);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int xenhcd_submit_urb(struct usbfront_info *info, struct urb_priv *urbp)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ if (RING_FULL(&info->urb_ring)) {
|
|
+ list_add_tail(&urbp->list, &info->pending_submit_list);
|
|
+ COUNT(info->stats.ring_full);
|
|
+ timer_action(info, TIMER_RING_WATCHDOG);
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ if (!list_empty(&info->pending_submit_list)) {
|
|
+ list_add_tail(&urbp->list, &info->pending_submit_list);
|
|
+ timer_action(info, TIMER_SCAN_PENDING_URBS);
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ ret = xenhcd_do_request(info, urbp);
|
|
+ if (ret == 0)
|
|
+ list_add_tail(&urbp->list, &info->in_progress_list);
|
|
+
|
|
+done:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int xenhcd_unlink_urb(struct usbfront_info *info, struct urb_priv *urbp)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ /* 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);
|
|
+ timer_action(info, TIMER_SCAN_PENDING_URBS);
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* send unlink request to backend */
|
|
+ if (RING_FULL(&info->urb_ring)) {
|
|
+ list_move_tail(&urbp->list, &info->pending_unlink_list);
|
|
+ COUNT(info->stats.ring_full);
|
|
+ timer_action(info, TIMER_RING_WATCHDOG);
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ if (!list_empty(&info->pending_unlink_list)) {
|
|
+ list_move_tail(&urbp->list, &info->pending_unlink_list);
|
|
+ timer_action(info, TIMER_SCAN_PENDING_URBS);
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ ret = xenhcd_do_request(info, urbp);
|
|
+ if (ret == 0)
|
|
+ list_move_tail(&urbp->list, &info->in_progress_list);
|
|
+
|
|
+done:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int xenhcd_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))) {
|
|
+ xenhcd_gnttab_done(&info->shadow[id]);
|
|
+ urb = info->shadow[id].urb;
|
|
+ barrier();
|
|
+ if (likely(urb)) {
|
|
+ urb->actual_length = res->actual_length;
|
|
+ urb->error_count = res->error_count;
|
|
+ urb->start_frame = res->start_frame;
|
|
+ barrier();
|
|
+ xenhcd_giveback_urb(info, urb, res->status);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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);
|
|
+
|
|
+ cond_resched();
|
|
+
|
|
+ return more_to_do;
|
|
+}
|
|
+
|
|
+static int xenhcd_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;
|
|
+
|
|
+ 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(info_to_hcd(info));
|
|
+
|
|
+ cond_resched();
|
|
+
|
|
+ return more_to_do;
|
|
+}
|
|
+
|
|
+int xenhcd_schedule(void *arg)
|
|
+{
|
|
+ struct usbfront_info *info = (struct usbfront_info *) arg;
|
|
+
|
|
+ while (!kthread_should_stop()) {
|
|
+ wait_event_interruptible(info->wq,
|
|
+ info->waiting_resp || kthread_should_stop());
|
|
+ info->waiting_resp = 0;
|
|
+ smp_mb();
|
|
+
|
|
+ if (xenhcd_urb_request_done(info))
|
|
+ info->waiting_resp = 1;
|
|
+
|
|
+ if (xenhcd_conn_notify(info))
|
|
+ info->waiting_resp = 1;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void xenhcd_notify_work(struct usbfront_info *info)
|
|
+{
|
|
+ info->waiting_resp = 1;
|
|
+ wake_up(&info->wq);
|
|
+}
|
|
+
|
|
+irqreturn_t xenhcd_int(int irq, void *dev_id)
|
|
+{
|
|
+ xenhcd_notify_work((struct usbfront_info *) dev_id);
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static void xenhcd_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(info_to_hcd(info)->state))) {
|
|
+ timer_action_done(info, TIMER_RING_WATCHDOG);
|
|
+ xenhcd_giveback_unlinked_urbs(info);
|
|
+ xenhcd_kick_pending_urbs(info);
|
|
+ }
|
|
+ spin_unlock_irqrestore(&info->lock, flags);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * one-time HC init
|
|
+ */
|
|
+static int xenhcd_setup(struct usb_hcd *hcd)
|
|
+{
|
|
+ struct usbfront_info *info = 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 = xenhcd_watchdog;
|
|
+ info->watchdog.data = (unsigned long) info;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * start HC running
|
|
+ */
|
|
+static int xenhcd_run(struct usb_hcd *hcd)
|
|
+{
|
|
+ hcd->uses_new_polling = 1;
|
|
+ hcd->state = HC_STATE_RUNNING;
|
|
+ create_debug_file(hcd_to_info(hcd));
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * stop running HC
|
|
+ */
|
|
+static void xenhcd_stop(struct usb_hcd *hcd)
|
|
+{
|
|
+ struct usbfront_info *info = hcd_to_info(hcd);
|
|
+
|
|
+ del_timer_sync(&info->watchdog);
|
|
+ remove_debug_file(info);
|
|
+ spin_lock_irq(&info->lock);
|
|
+ /* cancel all urbs */
|
|
+ hcd->state = HC_STATE_HALT;
|
|
+ xenhcd_cancel_all_enqueued_urbs(info);
|
|
+ xenhcd_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 xenhcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
|
|
+ gfp_t mem_flags)
|
|
+{
|
|
+ struct usbfront_info *info = hcd_to_info(hcd);
|
|
+ struct urb_priv *urbp;
|
|
+ unsigned long flags;
|
|
+ int ret = 0;
|
|
+
|
|
+ spin_lock_irqsave(&info->lock, flags);
|
|
+
|
|
+ urbp = alloc_urb_priv(urb);
|
|
+ if (!urbp) {
|
|
+ ret = -ENOMEM;
|
|
+ goto done;
|
|
+ }
|
|
+ urbp->status = 1;
|
|
+
|
|
+ ret = xenhcd_submit_urb(info, urbp);
|
|
+ if (ret != 0)
|
|
+ free_urb_priv(urbp);
|
|
+
|
|
+done:
|
|
+ spin_unlock_irqrestore(&info->lock, flags);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * called as .urb_dequeue()
|
|
+ */
|
|
+static int xenhcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
|
|
+{
|
|
+ struct usbfront_info *info = 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)
|
|
+ goto done;
|
|
+
|
|
+ urbp->status = status;
|
|
+ ret = xenhcd_unlink_urb(info, urbp);
|
|
+
|
|
+done:
|
|
+ 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 xenhcd_get_frame(struct usb_hcd *hcd)
|
|
+{
|
|
+ /* it means error, but probably no problem :-) */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const char hcd_name[] = "xen_hcd";
|
|
+
|
|
+struct hc_driver xen_usb20_hc_driver = {
|
|
+ .description = 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 = xenhcd_setup,
|
|
+ .start = xenhcd_run,
|
|
+ .stop = xenhcd_stop,
|
|
+
|
|
+ /* managing urb I/O */
|
|
+ .urb_enqueue = xenhcd_urb_enqueue,
|
|
+ .urb_dequeue = xenhcd_urb_dequeue,
|
|
+ .get_frame_number = xenhcd_get_frame,
|
|
+
|
|
+ /* root hub operations */
|
|
+ .hub_status_data = xenhcd_hub_status_data,
|
|
+ .hub_control = xenhcd_hub_control,
|
|
+#ifdef XENHCD_PM
|
|
+#ifdef CONFIG_PM
|
|
+ .bus_suspend = xenhcd_bus_suspend,
|
|
+ .bus_resume = xenhcd_bus_resume,
|
|
+#endif
|
|
+#endif
|
|
+};
|
|
+
|
|
+struct hc_driver xen_usb11_hc_driver = {
|
|
+ .description = 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 = xenhcd_setup,
|
|
+ .start = xenhcd_run,
|
|
+ .stop = xenhcd_stop,
|
|
+
|
|
+ /* managing urb I/O */
|
|
+ .urb_enqueue = xenhcd_urb_enqueue,
|
|
+ .urb_dequeue = xenhcd_urb_dequeue,
|
|
+ .get_frame_number = xenhcd_get_frame,
|
|
+
|
|
+ /* root hub operations */
|
|
+ .hub_status_data = xenhcd_hub_status_data,
|
|
+ .hub_control = xenhcd_hub_control,
|
|
+#ifdef XENHCD_PM
|
|
+#ifdef CONFIG_PM
|
|
+ .bus_suspend = xenhcd_bus_suspend,
|
|
+ .bus_resume = xenhcd_bus_resume,
|
|
+#endif
|
|
+#endif
|
|
+};
|
|
+
|
|
+#define GRANT_INVALID_REF 0
|
|
+
|
|
+static void destroy_rings(struct usbfront_info *info)
|
|
+{
|
|
+ if (info->irq)
|
|
+ unbind_from_irqhandler(info->irq, info);
|
|
+ info->evtchn = 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 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");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ 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)
|
|
+ goto fail;
|
|
+
|
|
+ err = bind_evtchn_to_irqhandler(info->evtchn, xenhcd_int, 0,
|
|
+ "usbif", info);
|
|
+ if (err <= 0) {
|
|
+ xenbus_dev_fatal(dev, err, "bind_listening_port_to_irqhandler");
|
|
+ goto fail;
|
|
+ }
|
|
+ info->irq = err;
|
|
+
|
|
+ return 0;
|
|
+fail:
|
|
+ destroy_rings(info);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int talk_to_usbback(struct xenbus_device *dev,
|
|
+ struct usbfront_info *info)
|
|
+{
|
|
+ const char *message;
|
|
+ struct xenbus_transaction xbt;
|
|
+ int err;
|
|
+
|
|
+ err = setup_rings(dev, info);
|
|
+ if (err)
|
|
+ goto out;
|
|
+
|
|
+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:
|
|
+ destroy_rings(info);
|
|
+
|
|
+out:
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int connect(struct xenbus_device *dev)
|
|
+{
|
|
+ struct usbfront_info *info = dev_get_drvdata(&dev->dev);
|
|
+
|
|
+ struct usbif_conn_request *req;
|
|
+ int i, idx, err;
|
|
+ int notify;
|
|
+ char name[TASK_COMM_LEN];
|
|
+ struct usb_hcd *hcd;
|
|
+
|
|
+ hcd = info_to_hcd(info);
|
|
+ snprintf(name, TASK_COMM_LEN, "xenhcd.%d", hcd->self.busnum);
|
|
+
|
|
+ err = talk_to_usbback(dev, info);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
+ info->kthread = kthread_run(xenhcd_schedule, info, name);
|
|
+ if (IS_ERR(info->kthread)) {
|
|
+ err = PTR_ERR(info->kthread);
|
|
+ info->kthread = NULL;
|
|
+ xenbus_dev_fatal(dev, err, "Error creating thread");
|
|
+ return err;
|
|
+ }
|
|
+ /* prepare ring for hotplug notification */
|
|
+ for (idx = 0, i = 0; i < USB_CONN_RING_SIZE; i++) {
|
|
+ req = RING_GET_REQUEST(&info->conn_ring, idx);
|
|
+ req->id = idx;
|
|
+ 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 struct usb_hcd *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 = NULL;
|
|
+
|
|
+ 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(&xen_usb11_hc_driver,
|
|
+ &dev->dev, dev_name(&dev->dev));
|
|
+ break;
|
|
+ case USB_VER_USB20:
|
|
+ hcd = usb_create_hcd(&xen_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 = 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 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 = create_hcd(dev);
|
|
+ if (IS_ERR(hcd)) {
|
|
+ err = PTR_ERR(hcd);
|
|
+ xenbus_dev_fatal(dev, err,
|
|
+ "failed to create usb host controller");
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ info = hcd_to_info(hcd);
|
|
+ dev_set_drvdata(&dev->dev, info);
|
|
+
|
|
+ err = usb_add_hcd(hcd, 0, 0);
|
|
+ if (err != 0) {
|
|
+ xenbus_dev_fatal(dev, err, "fail to add USB host controller");
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ init_waitqueue_head(&info->wq);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+fail:
|
|
+ usb_put_hcd(hcd);
|
|
+ dev_set_drvdata(&dev->dev, NULL);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static void usbfront_disconnect(struct xenbus_device *dev)
|
|
+{
|
|
+ struct usbfront_info *info = dev_get_drvdata(&dev->dev);
|
|
+ struct usb_hcd *hcd = info_to_hcd(info);
|
|
+
|
|
+ usb_remove_hcd(hcd);
|
|
+ if (info->kthread) {
|
|
+ kthread_stop(info->kthread);
|
|
+ info->kthread = NULL;
|
|
+ }
|
|
+ xenbus_frontend_closed(dev);
|
|
+}
|
|
+
|
|
+static void usbback_changed(struct xenbus_device *dev,
|
|
+ enum xenbus_state backend_state)
|
|
+{
|
|
+ switch (backend_state) {
|
|
+ case XenbusStateInitialising:
|
|
+ case XenbusStateInitialised:
|
|
+ case XenbusStateConnected:
|
|
+ case XenbusStateReconfiguring:
|
|
+ case XenbusStateReconfigured:
|
|
+ case XenbusStateUnknown:
|
|
+ case XenbusStateClosed:
|
|
+ break;
|
|
+
|
|
+ case XenbusStateInitWait:
|
|
+ if (dev->state != XenbusStateInitialising)
|
|
+ break;
|
|
+ if (!connect(dev))
|
|
+ xenbus_switch_state(dev, XenbusStateConnected);
|
|
+ break;
|
|
+
|
|
+ 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 = info_to_hcd(info);
|
|
+
|
|
+ destroy_rings(info);
|
|
+ usb_put_hcd(hcd);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct xenbus_device_id usbfront_ids[] = {
|
|
+ { "vusb" },
|
|
+ { "" },
|
|
+};
|
|
+MODULE_ALIAS("xen:vusb");
|
|
+
|
|
+static DEFINE_XENBUS_DRIVER(usbfront, ,
|
|
+ .probe = usbfront_probe,
|
|
+ .remove = usbfront_remove,
|
|
+ .otherend_changed = usbback_changed,
|
|
+);
|
|
+
|
|
+static int __init usbfront_init(void)
|
|
+{
|
|
+ if (!xen_domain())
|
|
+ return -ENODEV;
|
|
+
|
|
+ xenhcd_urbp_cachep = kmem_cache_create("xenhcd_urb_priv",
|
|
+ sizeof(struct urb_priv), 0, 0, NULL);
|
|
+ if (!xenhcd_urbp_cachep) {
|
|
+ printk(KERN_ERR "usbfront failed to create kmem cache\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ return xenbus_register_frontend(&usbfront_driver);
|
|
+}
|
|
+
|
|
+static void __exit usbfront_exit(void)
|
|
+{
|
|
+ kmem_cache_destroy(xenhcd_urbp_cachep);
|
|
+ xenbus_unregister_driver(&usbfront_driver);
|
|
+}
|
|
+
|
|
+module_init(usbfront_init);
|
|
+module_exit(usbfront_exit);
|
|
+
|
|
+MODULE_AUTHOR("");
|
|
+MODULE_DESCRIPTION("Xen USB Virtual Host Controller driver (usbfront)");
|
|
+MODULE_LICENSE("Dual BSD/GPL");
|
|
diff --git a/include/xen/interface/io/usbif.h b/include/xen/interface/io/usbif.h
|
|
new file mode 100644
|
|
index 0000000..f3bb1b2
|
|
--- /dev/null
|
|
+++ b/include/xen/interface/io/usbif.h
|
|
@@ -0,0 +1,150 @@
|
|
+/*
|
|
+ * usbif.h
|
|
+ *
|
|
+ * USB I/O interface for Xen guest OSes.
|
|
+ *
|
|
+ * Copyright (C) 2009, FUJITSU LABORATORIES LTD.
|
|
+ * Author: Noboru Iwamatsu <n_iwamatsu@jp.fujitsu.com>
|
|
+ *
|
|
+ * 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"
|
|
+
|
|
+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_BACK_MAX_PENDING_REQS (128)
|
|
+#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; /* number of urb->transfer_buffer segments */
|
|
+
|
|
+ /* basic urb parameter */
|
|
+ uint32_t pipe;
|
|
+ uint16_t transfer_flags;
|
|
+ uint16_t buffer_length;
|
|
+ union {
|
|
+ uint8_t ctrl[8]; /* setup_packet (Ctrl) */
|
|
+
|
|
+ struct {
|
|
+ uint16_t interval; /* maximum (1024*8) in usb core */
|
|
+ uint16_t start_frame; /* start frame */
|
|
+ uint16_t number_of_packets; /* number of ISO packet */
|
|
+ uint16_t nr_frame_desc_segs; /* number of iso_frame_desc
|
|
+ segments */
|
|
+ } isoc;
|
|
+
|
|
+ struct {
|
|
+ uint16_t interval; /* maximum (1024*8) in usb core */
|
|
+ uint16_t pad[3];
|
|
+ } intr;
|
|
+
|
|
+ struct {
|
|
+ uint16_t unlink_id; /* unlink request id */
|
|
+ uint16_t pad[3];
|
|
+ } 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__ */
|