[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-devel] [RFC][PATCH 2/4] PVUSB: frontend driver
This patch adds the PVUSB frontend driver. Signed-off-by: Noboru Iwamatsu <n_iwamatsu@xxxxxxxxxxxxxx> diff -r 51decc39e5e7 -r 9ef2e8c6cf3d drivers/xen/usbfront/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drivers/xen/usbfront/Makefile Mon Mar 16 18:41:12 2009 +0900 @@ -0,0 +1,7 @@ +obj-$(CONFIG_XEN_USB_FRONTEND) := xen-hcd.o + +xen-hcd-y := usbfront-hcd.o xenbus.o + +ifeq ($(CONFIG_XEN_USB_FRONTEND_HCD_STATS),y) +EXTRA_CFLAGS += -DXENHCD_STATS +endif diff -r 51decc39e5e7 -r 9ef2e8c6cf3d drivers/xen/usbfront/usbfront-dbg.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drivers/xen/usbfront/usbfront-dbg.c Mon Mar 16 18:41:12 2009 +0900 @@ -0,0 +1,99 @@ +/* + * usbfront-dbg.c + * + * Xen USB Virtual Host Controller - debugging + * + * Copyright (C) 2009, FUJITSU LABORATORIES LTD. + * Author: Noboru Iwamatsu <n_iwamatsu@xxxxxxxxxxxxxx> + * + * 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, + * + * 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. + */ + +static ssize_t show_statistics(struct class_device *class_dev, char *buf) +{ + struct usb_bus *bus; + struct usb_hcd *hcd; + struct usbfront_info *info; + unsigned long flags; + unsigned temp, size; + char *next; + + bus = class_get_devdata(class_dev); + hcd = bus->hcpriv; + 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, + hcd->self.controller->bus_id, + 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 CLASS_DEVICE_ATTR(statistics, S_IRUGO, show_statistics, NULL); + +static inline void create_debug_file(struct usbfront_info *info) +{ + struct class_device *cldev = info_to_hcd(info)->self.class_dev; + class_device_create_file(cldev, &class_device_attr_statistics); +} + +static inline void remove_debug_file(struct usbfront_info *info) +{ + struct class_device *cldev = info_to_hcd(info)->self.class_dev; + class_device_remove_file(cldev, &class_device_attr_statistics); +} diff -r 51decc39e5e7 -r 9ef2e8c6cf3d drivers/xen/usbfront/usbfront-hcd.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drivers/xen/usbfront/usbfront-hcd.c Mon Mar 16 18:41:12 2009 +0900 @@ -0,0 +1,279 @@ +/* + * usbfront-hcd.c + * + * Xen USB Virtual Host Controller driver + * + * Copyright (C) 2009, FUJITSU LABORATORIES LTD. + * Author: Noboru Iwamatsu <n_iwamatsu@xxxxxxxxxxxxxx> + * + * 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, + * + * 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 "usbfront.h" +#include "usbfront-dbg.c" +#include "usbfront-hub.c" +#include "usbfront-q.c" + +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 (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_urbs); + INIT_LIST_HEAD(&info->inprogress_urbs); + INIT_LIST_HEAD(&info->unlinked_urbs); + 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->poll_rh = 0; + 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); + /* + * TODO: port power off, cancel all urbs. + */ + + if (HC_IS_RUNNING(hcd->state)) + hcd->state = HC_STATE_HALT; + spin_unlock_irq(&info->lock); +} + +/* + * TODO: incomplete suspend/resume functions! + */ +#if 0 +#ifdef CONFIG_PM +/* + * suspend running HC + */ +static int xenhcd_suspend(struct usb_hcd *hcd, pm_message_t message) +{ + struct usbfront_info *info = hcd_to_info(hcd); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&info->lock, flags); + if (hcd->state != HC_STATE_SUSPENDED) { + ret = -EINVAL; + goto done; + } + + /* + * TODO: + * canceling all transfer, clear all hc queue, + * stop kthread, + */ + + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); +done: + spin_unlock_irqrestore(&info->lock, flags); + + return ret; +} + +/* + * resume HC + */ +static int xenhcd_resume(struct usb_hcd *hcd) +{ + struct usbfront_info *info = hcd_to_info(hcd); + int ret = -EINVAL; + + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + + /* + * TODO: + * re-init HC. + * resume all roothub ports. + */ + + return ret; +} +#endif +#endif + +/* + * called as .urb_enqueue() + * non-error returns are promise to giveback the urb later + */ +static int xenhcd_urb_enqueue(struct usb_hcd *hcd, + struct usb_host_endpoint *ep, + 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; + } + + 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() + * + * just mark the urb as unlinked + * if the urb is in pending_urbs, move to unlinked_urbs + * TODO: + * canceling the urb transfer in backend + */ +static int xenhcd_urb_dequeue(struct usb_hcd *hcd, + struct urb *urb) +{ + 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; + + 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; +} + +/* + * TODO: + * suspend/resume whole hcd and roothub + */ +static const char hcd_name[] = "xen_hcd"; + +struct hc_driver usbfront_hc_driver = { + .description = hcd_name, + .product_desc = DRIVER_DESC, + .hcd_priv_size = sizeof(struct usbfront_info), + .flags = HCD_USB2, + + /* + * basic HC lifecycle operations + */ + .reset = xenhcd_setup, + .start = xenhcd_run, + .stop = xenhcd_stop, +#if 0 +#ifdef CONFIG_PM + .suspend = xenhcd_suspend, + .resume = xenhcd_resume, +#endif +#endif + /* + * 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, +#if 0 +#ifdef CONFIG_PM + .bus_suspend = xenhcd_bus_suspend, + .bus_resume = xenhcd_bus_resume, +#endif +#endif +}; diff -r 51decc39e5e7 -r 9ef2e8c6cf3d drivers/xen/usbfront/usbfront-hub.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drivers/xen/usbfront/usbfront-hub.c Mon Mar 16 18:41:12 2009 +0900 @@ -0,0 +1,479 @@ +/* + * usbfront-hub.c + * + * Xen USB Virtual Host Controller - Root Hub Emulations + * + * Copyright (C) 2009, FUJITSU LABORATORIES LTD. + * Author: Noboru Iwamatsu <n_iwamatsu@xxxxxxxxxxxxxx> + * + * 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/>. + */ + +/* + * 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; + + 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); + } +} + +void rhport_disconnect(struct usbfront_info *info, int portnum) +{ + rhport_connect(info, portnum, USB_SPEED_UNKNOWN); +} + +void xenhcd_rhport_state_change(struct usbfront_info *info, + int portnum, enum usb_device_speed speed) +{ + int changed = 0; + unsigned long flags; + + if (portnum < 1 || portnum > info->rh_numports) + return; /* invalid port number */ + + spin_lock_irqsave(&info->lock, flags); + rhport_connect(info, portnum, speed); + if (info->ports[portnum-1].c_connection) + changed = 1; + spin_unlock_irqrestore(&info->lock, flags); + + if (changed) + usb_hcd_poll_rh_status(info_to_hcd(info)); +} + +/* + * 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); +} + +#if 0 +#ifdef CONFIG_PM +static int xenhcd_bus_suspend(struct usb_hcd *hcd) +{ + struct usbfront_info *info = hcd_to_info(hcd); + int i, ports; + + ports = info->rh_numports; + + spin_lock_irq(&info->lock); + + if (HC_IS_RUNNING(hcd->state)) { + /* + * TODO: + * clean queue, + * stop all transfers, + * ... + */ + hcd->state = HC_STATE_QUIESCING; + } + + /* suspend any active ports*/ + for (i = 1; i <= ports; i++) { + rhport_suspend(info, i); + } + + del_timer_sync(&info->watchdog); + + spin_unlock_irq(&info->lock); + + return 0; +} + +static int xenhcd_bus_resume(struct usb_hcd *hcd) +{ + struct usbfront_info *info = hcd_to_info(hcd); + int i, ports; + + ports = info->rh_numports; + + spin_lock_irq(&info->lock); + /* resume any suspended ports*/ + for (i = 1; i <= ports; i++) { + rhport_resume(info, i); + } + hcd->state = HC_STATE_RUNNING; + spin_unlock_irq(&info->lock); + return 0; +} +#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->bitmap[0], 0, temp); + memset (&desc->bitmap[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; + +#ifdef USBFRONT_DEBUG + WPRINTK("xenusb_hub_control(typeReq %x wValue %x wIndex %x)\n", + typeReq, wValue, wIndex); +#endif + + 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; +} diff -r 51decc39e5e7 -r 9ef2e8c6cf3d drivers/xen/usbfront/usbfront-q.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drivers/xen/usbfront/usbfront-q.c Mon Mar 16 18:41:12 2009 +0900 @@ -0,0 +1,418 @@ +/* + * usbfront-q.c + * + * Xen USB Virtual Host Controller - RING operations. + * + * Copyright (C) 2009, FUJITSU LABORATORIES LTD. + * Author: Noboru Iwamatsu <n_iwamatsu@xxxxxxxxxxxxxx> + * + * 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, + * + * 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. + */ + +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; + 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_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; + struct page *page; + unsigned long buffer_pfn; + unsigned int offset; + unsigned int len; + unsigned int bytes; + int i; + + page = virt_to_page(addr); + buffer_pfn = page_to_phys(page) >> PAGE_SHIFT; + offset = offset_in_page(addr); + len = length; + + for(i = 0;i < nr_pages;i++){ + bytes = PAGE_SIZE - offset; + if(bytes > len) + bytes = len; + + ref = gnttab_claim_grant_reference(gref_head); + BUG_ON(ref == -ENOSPC); + gnttab_grant_foreign_access_ref(ref, info->xbdev->otherend_id, buffer_pfn, flags); + seg[i].gref = ref; + seg[i].offset = (uint16_t)offset; + seg[i].length = (uint16_t)bytes; + + buffer_pfn++; + len -= bytes; + offset = 0; + } +} + +static int map_urb_for_request(struct usbfront_info *info, struct urb *urb, + usbif_request_t *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, 0UL); +} + +static void xenhcd_giveback_urb(struct usbfront_info *info, struct urb *urb) +__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; + default: + COUNT(info->stats.complete); + } + spin_unlock(&info->lock); + usb_hcd_giveback_urb(info_to_hcd(info), urb, NULL); + spin_lock(&info->lock); +} + +static inline int xenhcd_do_request(struct usbfront_info *info, struct urb_priv *urbp) +{ + usbif_request_t *ring_req; + struct urb *urb = urbp->urb; + uint16_t id; + int notify; + int ret = 0; + + ring_req = RING_GET_REQUEST(&info->ring, info->ring.req_prod_pvt); + id = get_id_from_freelist(info); + ring_req->id = id; + + ret = map_urb_for_request(info, urb, ring_req); + if (ret < 0) { + add_id_to_freelist(info, id); + return ret; + } + + info->ring.req_prod_pvt++; + info->shadow[id].urb = urb; + info->shadow[id].req = *ring_req; + urbp->req_id = id; + + RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&info->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_urbs)) { + if (RING_FULL(&info->ring)) { + COUNT(info->stats.ring_full); + timer_action(info, TIMER_RING_WATCHDOG); + goto done; + } + + urbp = list_entry(info->pending_urbs.next, struct urb_priv, list); + ret = xenhcd_do_request(info, urbp); + if (ret == 0) + list_move_tail(&urbp->list, &info->inprogress_urbs); + else + xenhcd_giveback_urb(info, urbp->urb); + } + timer_action_done(info, TIMER_SCAN_PENDING_URBS); + +done: + return; +} + +static void xenhcd_giveback_unlinked_urbs(struct usbfront_info *info) +{ + struct urb_priv *urbp, *tmp; + + list_for_each_entry_safe(urbp, tmp, &info->unlinked_urbs, list) { + xenhcd_giveback_urb(info, urbp->urb); + } +} + +static int xenhcd_submit_urb(struct usbfront_info *info, struct urb_priv *urbp) +{ + int ret = 0; + + if (RING_FULL(&info->ring)) { + list_add_tail(&urbp->list, &info->pending_urbs); + COUNT(info->stats.ring_full); + timer_action(info, TIMER_RING_WATCHDOG); + goto done; + } + + if (!list_empty(&info->pending_urbs)) { + list_add_tail(&urbp->list, &info->pending_urbs); + timer_action(info, TIMER_SCAN_PENDING_URBS); + goto done; + } + + ret = xenhcd_do_request(info, urbp); + if (ret == 0) + list_add_tail(&urbp->list, &info->inprogress_urbs); + +done: + return ret; +} + +static int xenhcd_unlink_urb(struct usbfront_info *info, struct urb_priv *urbp) +{ + if (urbp->unlinked) + return -EBUSY; + urbp->unlinked = 1; + + /* if the urb is in pending_urbs */ + if (urbp->req_id == ~0) { + list_move_tail(&urbp->list, &info->unlinked_urbs); + timer_action(info, TIMER_SCAN_PENDING_URBS); + } + + /* TODO: send cancel request to backend */ + + return 0; +} + +static int xenhcd_end_submit_urb(struct usbfront_info *info) +{ + usbif_response_t *ring_res; + struct urb *urb; + struct urb_priv *urbp; + + RING_IDX i, rp; + uint16_t id; + int more_to_do = 0; + unsigned long flags; + + spin_lock_irqsave(&info->lock, flags); + rp = info->ring.sring->rsp_prod; + rmb(); /* ensure we see queued responses up to "rp" */ + + for (i = info->ring.rsp_cons; i != rp; i++) { + ring_res = RING_GET_RESPONSE(&info->ring, i); + id = ring_res->id; + xenhcd_gnttab_done(&info->shadow[id]); + urb = info->shadow[id].urb; + barrier(); + add_id_to_freelist(info, id); + + urbp = (struct urb_priv *)urb->hcpriv; + if (likely(!urbp->unlinked)) { + urb->status = ring_res->status; + urb->actual_length = ring_res->actual_length; + urb->error_count = ring_res->error_count; + urb->start_frame = ring_res->start_frame; + } + barrier(); + xenhcd_giveback_urb(info, urb); + } + info->ring.rsp_cons = i; + + if (i != info->ring.req_prod_pvt) + RING_FINAL_CHECK_FOR_RESPONSES(&info->ring, more_to_do); + else + info->ring.sring->rsp_event = i + 1; + + spin_unlock_irqrestore(&info->lock, flags); + + 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_end_submit_urb(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, struct pt_regs *ptregs) +{ + xenhcd_notify_work((struct usbfront_info *) dev_id); + return IRQ_HANDLED; +} diff -r 51decc39e5e7 -r 9ef2e8c6cf3d drivers/xen/usbfront/usbfront.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drivers/xen/usbfront/usbfront.h Mon Mar 16 18:41:12 2009 +0900 @@ -0,0 +1,216 @@ +/* + * usbfront.h + * + * This file is part of Xen USB Virtual Host Controller driver. + * + * Copyright (C) 2009, FUJITSU LABORATORIES LTD. + * Author: Noboru Iwamatsu <n_iwamatsu@xxxxxxxxxxxxxx> + * + * 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, + * + * 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_USBFRONT_H__ +#define __XEN_USBFRONT_H__ + +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/list.h> +#include <linux/kthread.h> +#include <linux/wait.h> +#include <asm/io.h> +#include <xen/xenbus.h> +#include <xen/evtchn.h> +#include <xen/gnttab.h> +#include <xen/interface/xen.h> +#include <xen/interface/io/usbif.h> + +/* + * usbfront needs USB HCD headers, + * drivers/usb/core/hcd.h and drivers/usb/core/hub.h, + * but, they are not in public include path. + */ +#include "../../usb/core/hcd.h" +#include "../../usb/core/hub.h" + +#define DRIVER_DESC "Xen USB2.0 Virtual Host Controller driver (usbfront)" + +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 */ + unsigned unlinked:1; /* dequeued urb just marked */ +}; + +/* 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 { + usbif_request_t req; + struct urb *urb; +}; + +/* statistics for tuning, monitoring, ... */ +struct xenhcd_stats { + unsigned long ring_full; /* RING_FULL conditions */ + unsigned long complete; /* normal givebacked urbs */ + unsigned long unlink; /* unlinked urbs */ +}; + +struct usbfront_info { + /* + * Virtual Host Controller has 3 queues. + * + * pending_urbs: + * If xenhcd_urb_enqueue() called in RING_FULL state, + * the enqueued urbs are added to this queue, and waits + * to be sent to the backend. + * + * inprogress_urbs: + * After xenhcd_urb_enqueue() called and RING_REQUEST sent, + * the urbs are added to this queue and waits for RING_RESPONSE. + * + * unlinked_urbs: + * When xenhcd_urb_dequeue() called, if the dequeued urb is + * listed in pending_urbs, that urb is moved to this queue + * and waits to be given back to the USB core. + */ + struct list_head pending_urbs; + struct list_head inprogress_urbs; + struct list_head unlinked_urbs; + spinlock_t lock; + + /* + * timer function that kick pending_urbs and unlink_urbs. + */ + unsigned long actions; + struct timer_list watchdog; + + /* + * Virtual roothub: + * Emulates the hub ports and the attached devices status. + * USB_MAXCHILDREN is defined (16) in include/linux/usb.h + */ + int rh_numports; + struct rhport_status ports[USB_MAXCHILDREN]; + struct vdevice_status devices[USB_MAXCHILDREN]; + +#ifdef XENHCD_STATS + struct xenhcd_stats stats; +#define COUNT(x) do { (x)++; } while (0) +#else +#define COUNT(x) do {} while (0) +#endif + + /* Xen related staff */ + struct xenbus_device *xbdev; + int ring_ref; + usbif_front_ring_t ring; + unsigned int irq; + struct usb_shadow shadow[USB_RING_SIZE]; + unsigned long shadow_free; + + /* RING_RESPONSE thread */ + struct task_struct *kthread; + wait_queue_head_t wq; + unsigned int waiting_resp; +}; + +#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); + } +} + +irqreturn_t xenhcd_int(int irq, void *dev_id, struct pt_regs *ptregs); + +#endif /* __XEN_USBFRONT_H__ */ diff -r 51decc39e5e7 -r 9ef2e8c6cf3d drivers/xen/usbfront/xenbus.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drivers/xen/usbfront/xenbus.c Mon Mar 16 18:41:12 2009 +0900 @@ -0,0 +1,365 @@ +/* + * xenbus.c + * + * Xenbus interface for Xen USB Virtual Host Controller + * + * Copyright (C) 2009, FUJITSU LABORATORIES LTD. + * Author: Noboru Iwamatsu <n_iwamatsu@xxxxxxxxxxxxxx> + * + * 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, + * + * 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 "usbfront.h" + +extern struct hc_driver usbfront_hc_driver; +extern struct kmem_cache *xenhcd_urbp_cachep; +extern void xenhcd_rhport_state_change(struct usbfront_info *info, + int port, enum usb_device_speed speed); +extern int xenhcd_schedule(void *arg); + +#define GRANT_INVALID_REF 0 + +static void usbif_free(struct usbfront_info *info) +{ + if (info->ring_ref != GRANT_INVALID_REF) { + gnttab_end_foreign_access(info->ring_ref, + (unsigned long)info->ring.sring); + info->ring_ref = GRANT_INVALID_REF; + info->ring.sring = NULL; + } + if (info->irq) + unbind_from_irqhandler(info->irq, info); + info->irq = 0; +} + +static int setup_usbring(struct xenbus_device *dev, + struct usbfront_info *info) +{ + usbif_sring_t *sring; + int err; + + info->ring_ref= GRANT_INVALID_REF; + + sring = (usbif_sring_t *)get_zeroed_page(GFP_NOIO|__GFP_HIGH); + if (!sring) { + xenbus_dev_fatal(dev, -ENOMEM, "allocating shared ring"); + return -ENOMEM; + } + SHARED_RING_INIT(sring); + FRONT_RING_INIT(&info->ring, sring, PAGE_SIZE); + + err = xenbus_grant_ring(dev, virt_to_mfn(info->ring.sring)); + if (err < 0) { + free_page((unsigned long)sring); + info->ring.sring = NULL; + goto fail; + } + info->ring_ref = err; + + err = bind_listening_port_to_irqhandler( + dev->otherend_id, xenhcd_int, SA_SAMPLE_RANDOM, "usbif", info); + if (err <= 0) { + xenbus_dev_fatal(dev, err, + "bind_listening_port_to_irqhandler"); + goto fail; + } + info->irq = err; + + return 0; +fail: + usbif_free(info); + return err; +} + +static int talk_to_backend(struct xenbus_device *dev, + struct usbfront_info *info) +{ + const char *message; + struct xenbus_transaction xbt; + int err; + + err = setup_usbring(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, "ring-ref", "%u", + info->ring_ref); + if (err) { + message = "writing ring-ref"; + goto abort_transaction; + } + + err = xenbus_printf(xbt, dev->nodename, "event-channel", "%u", + irq_to_evtchn_port(info->irq)); + 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; + } + + xenbus_switch_state(dev, XenbusStateInitialised); + + return 0; + +abort_transaction: + xenbus_transaction_end(xbt, 1); + xenbus_dev_fatal(dev, err, "%s", message); + +destroy_ring: + usbif_free(info); + +out: + return err; +} + +static struct usb_hcd *create_hcd(struct xenbus_device *dev) +{ + int i; + int err = 0; + int num_ports; + 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); + } + + hcd = usb_create_hcd(&usbfront_hc_driver, &dev->dev, dev->dev.bus_id); + 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_RING_SIZE; i++) { + info->shadow[i].req.id = i+1; + info->shadow[i].urb = NULL; + } + info->shadow[USB_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; + char name[TASK_COMM_LEN]; + + if (usb_disabled()) + return -ENODEV; + + hcd = create_hcd(dev); + if (IS_ERR(hcd)) { + err = PTR_ERR(hcd); + xenbus_dev_fatal(dev, err, "fail to create usb host controller"); + goto fail; + } + + info = hcd_to_info(hcd); + dev->dev.driver_data = info; + + err = usb_add_hcd(hcd, 0, 0); + if (err != 0) { + xenbus_dev_fatal(dev, err, "fail to adding USB host controller"); + goto fail; + } + + init_waitqueue_head(&info->wq); + snprintf(name, TASK_COMM_LEN, "xenhcd.%d", hcd->self.busnum); + info->kthread = kthread_run(xenhcd_schedule, info, name); + if (IS_ERR(info->kthread)) { + err = PTR_ERR(info->kthread); + info->kthread = NULL; + goto fail; + } + + err = talk_to_backend(dev, info); + if (err) + goto fail; + + return 0; + +fail: + usb_put_hcd(hcd); + dev->dev.driver_data = NULL; + return err; +} + +/* + * 0=disconnected, 1=low_speed, 2=full_speed, 3=high_speed + */ +static void usbfront_do_hotplug(struct usbfront_info *info) +{ + char port_str[8]; + int i; + int err; + int state; + + for (i = 1; i <= info->rh_numports; i++) { + sprintf(port_str, "port-%d", i); + err = xenbus_scanf(XBT_NIL, info->xbdev->otherend, + port_str, "%d", &state); + if (err == 1) + xenhcd_rhport_state_change(info, i, state); + } +} + +static void backend_changed(struct xenbus_device *dev, + enum xenbus_state backend_state) +{ + struct usbfront_info *info = dev->dev.driver_data; + + switch (backend_state) { + case XenbusStateInitialising: + case XenbusStateInitWait: + case XenbusStateInitialised: + case XenbusStateUnknown: + case XenbusStateClosed: + break; + + case XenbusStateConnected: + if (dev->state == XenbusStateConnected) + break; + if (dev->state == XenbusStateInitialised) + usbfront_do_hotplug(info); + xenbus_switch_state(dev, XenbusStateConnected); + break; + + case XenbusStateClosing: + xenbus_frontend_closed(dev); + break; + + case XenbusStateReconfiguring: + if (dev->state == XenbusStateConnected) + xenbus_switch_state(dev, XenbusStateReconfiguring); + break; + + case XenbusStateReconfigured: + usbfront_do_hotplug(info); + xenbus_switch_state(dev, XenbusStateConnected); + 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->dev.driver_data; + struct usb_hcd *hcd = info_to_hcd(info); + + usb_remove_hcd(hcd); + if (info->kthread) { + kthread_stop(info->kthread); + info->kthread = NULL; + } + usbif_free(info); + usb_put_hcd(hcd); + + return 0; +} + +static const struct xenbus_device_id usbfront_ids[] = { + { "vusb" }, + { "" }, +}; + +static struct xenbus_driver usbfront_driver = { + .name = "vusb", + .owner = THIS_MODULE, + .ids = usbfront_ids, + .probe = usbfront_probe, + .otherend_changed = backend_changed, + .remove = usbfront_remove, +}; + +static int __init usbfront_init(void) +{ + if (!is_running_on_xen()) + return -ENODEV; + + xenhcd_urbp_cachep = kmem_cache_create("xenhcd_urb_priv", + sizeof(struct urb_priv), 0, 0, NULL, 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(DRIVER_DESC); +MODULE_LICENSE("Dual BSD/GPL"); _______________________________________________ Xen-devel mailing list Xen-devel@xxxxxxxxxxxxxxxxxxx http://lists.xensource.com/xen-devel
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |