|  | // SPDX-License-Identifier: GPL-2.0-or-later | 
|  | /* | 
|  | * xen-hcd.c | 
|  | * | 
|  | * Xen USB Virtual Host Controller driver | 
|  | * | 
|  | * Copyright (C) 2009, FUJITSU LABORATORIES LTD. | 
|  | * Author: Noboru Iwamatsu <n_iwamatsu@jp.fujitsu.com> | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/usb.h> | 
|  | #include <linux/list.h> | 
|  | #include <linux/usb/hcd.h> | 
|  | #include <linux/io.h> | 
|  |  | 
|  | #include <xen/xen.h> | 
|  | #include <xen/xenbus.h> | 
|  | #include <xen/grant_table.h> | 
|  | #include <xen/events.h> | 
|  | #include <xen/page.h> | 
|  |  | 
|  | #include <xen/interface/io/usbif.h> | 
|  |  | 
|  | /* Private per-URB data */ | 
|  | struct urb_priv { | 
|  | struct list_head list; | 
|  | struct urb *urb; | 
|  | int req_id;		/* RING_REQUEST id for submitting */ | 
|  | int unlink_req_id;	/* RING_REQUEST id for unlinking */ | 
|  | int status; | 
|  | bool unlinked;		/* dequeued marker */ | 
|  | }; | 
|  |  | 
|  | /* virtual roothub port status */ | 
|  | struct rhport_status { | 
|  | __u32 status; | 
|  | bool resuming;		/* in resuming */ | 
|  | bool c_connection;	/* 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 xenusb_urb_request req; | 
|  | struct urb *urb; | 
|  | bool in_flight; | 
|  | }; | 
|  |  | 
|  | struct xenhcd_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[XENUSB_MAX_PORTNR]; | 
|  | struct vdevice_status devices[XENUSB_MAX_PORTNR]; | 
|  |  | 
|  | /* Xen related staff */ | 
|  | struct xenbus_device *xbdev; | 
|  | int urb_ring_ref; | 
|  | int conn_ring_ref; | 
|  | struct xenusb_urb_front_ring urb_ring; | 
|  | struct xenusb_conn_front_ring conn_ring; | 
|  |  | 
|  | unsigned int evtchn; | 
|  | unsigned int irq; | 
|  | struct usb_shadow shadow[XENUSB_URB_RING_SIZE]; | 
|  | unsigned int shadow_free; | 
|  |  | 
|  | bool error; | 
|  | }; | 
|  |  | 
|  | #define XENHCD_RING_JIFFIES (HZ/200) | 
|  | #define XENHCD_SCAN_JIFFIES 1 | 
|  |  | 
|  | enum xenhcd_timer_action { | 
|  | TIMER_RING_WATCHDOG, | 
|  | TIMER_SCAN_PENDING_URBS, | 
|  | }; | 
|  |  | 
|  | static struct kmem_cache *xenhcd_urbp_cachep; | 
|  |  | 
|  | static inline struct xenhcd_info *xenhcd_hcd_to_info(struct usb_hcd *hcd) | 
|  | { | 
|  | return (struct xenhcd_info *)hcd->hcd_priv; | 
|  | } | 
|  |  | 
|  | static inline struct usb_hcd *xenhcd_info_to_hcd(struct xenhcd_info *info) | 
|  | { | 
|  | return container_of((void *)info, struct usb_hcd, hcd_priv); | 
|  | } | 
|  |  | 
|  | static void xenhcd_set_error(struct xenhcd_info *info, const char *msg) | 
|  | { | 
|  | info->error = true; | 
|  |  | 
|  | pr_alert("xen-hcd: protocol error: %s!\n", msg); | 
|  | } | 
|  |  | 
|  | static inline void xenhcd_timer_action_done(struct xenhcd_info *info, | 
|  | enum xenhcd_timer_action action) | 
|  | { | 
|  | clear_bit(action, &info->actions); | 
|  | } | 
|  |  | 
|  | static void xenhcd_timer_action(struct xenhcd_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); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * set virtual port connection status | 
|  | */ | 
|  | static void xenhcd_set_connect_state(struct xenhcd_info *info, int portnum) | 
|  | { | 
|  | int port; | 
|  |  | 
|  | port = portnum - 1; | 
|  | if (info->ports[port].status & USB_PORT_STAT_POWER) { | 
|  | switch (info->devices[port].speed) { | 
|  | case XENUSB_SPEED_NONE: | 
|  | 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 XENUSB_SPEED_LOW: | 
|  | info->ports[port].status |= USB_PORT_STAT_CONNECTION; | 
|  | info->ports[port].status |= USB_PORT_STAT_LOW_SPEED; | 
|  | break; | 
|  | case XENUSB_SPEED_FULL: | 
|  | info->ports[port].status |= USB_PORT_STAT_CONNECTION; | 
|  | break; | 
|  | case XENUSB_SPEED_HIGH: | 
|  | info->ports[port].status |= USB_PORT_STAT_CONNECTION; | 
|  | info->ports[port].status |= USB_PORT_STAT_HIGH_SPEED; | 
|  | break; | 
|  | default: /* error */ | 
|  | return; | 
|  | } | 
|  | info->ports[port].status |= (USB_PORT_STAT_C_CONNECTION << 16); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * set virtual device connection status | 
|  | */ | 
|  | static int xenhcd_rhport_connect(struct xenhcd_info *info, __u8 portnum, | 
|  | __u8 speed) | 
|  | { | 
|  | int port; | 
|  |  | 
|  | if (portnum < 1 || portnum > info->rh_numports) | 
|  | return -EINVAL; /* invalid port number */ | 
|  |  | 
|  | port = portnum - 1; | 
|  | if (info->devices[port].speed != speed) { | 
|  | switch (speed) { | 
|  | case XENUSB_SPEED_NONE: /* disconnect */ | 
|  | info->devices[port].status = USB_STATE_NOTATTACHED; | 
|  | break; | 
|  | case XENUSB_SPEED_LOW: | 
|  | case XENUSB_SPEED_FULL: | 
|  | case XENUSB_SPEED_HIGH: | 
|  | info->devices[port].status = USB_STATE_ATTACHED; | 
|  | break; | 
|  | default: /* error */ | 
|  | return -EINVAL; | 
|  | } | 
|  | info->devices[port].speed = speed; | 
|  | info->ports[port].c_connection = true; | 
|  |  | 
|  | xenhcd_set_connect_state(info, portnum); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * SetPortFeature(PORT_SUSPENDED) | 
|  | */ | 
|  | static void xenhcd_rhport_suspend(struct xenhcd_info *info, int portnum) | 
|  | { | 
|  | int port; | 
|  |  | 
|  | port = portnum - 1; | 
|  | info->ports[port].status |= USB_PORT_STAT_SUSPEND; | 
|  | info->devices[port].status = USB_STATE_SUSPENDED; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * ClearPortFeature(PORT_SUSPENDED) | 
|  | */ | 
|  | static void xenhcd_rhport_resume(struct xenhcd_info *info, int portnum) | 
|  | { | 
|  | int port; | 
|  |  | 
|  | port = portnum - 1; | 
|  | if (info->ports[port].status & USB_PORT_STAT_SUSPEND) { | 
|  | info->ports[port].resuming = true; | 
|  | info->ports[port].timeout = jiffies + msecs_to_jiffies(20); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * SetPortFeature(PORT_POWER) | 
|  | */ | 
|  | static void xenhcd_rhport_power_on(struct xenhcd_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) | 
|  | xenhcd_set_connect_state(info, portnum); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * ClearPortFeature(PORT_POWER) | 
|  | * SetConfiguration(non-zero) | 
|  | * Power_Source_Off | 
|  | * Over-current | 
|  | */ | 
|  | static void xenhcd_rhport_power_off(struct xenhcd_info *info, int portnum) | 
|  | { | 
|  | int port; | 
|  |  | 
|  | port = portnum - 1; | 
|  | if (info->ports[port].status & USB_PORT_STAT_POWER) { | 
|  | info->ports[port].status = 0; | 
|  | if (info->devices[port].status != USB_STATE_NOTATTACHED) | 
|  | info->devices[port].status = USB_STATE_ATTACHED; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * ClearPortFeature(PORT_ENABLE) | 
|  | */ | 
|  | static void xenhcd_rhport_disable(struct xenhcd_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 = false; | 
|  | if (info->devices[port].status != USB_STATE_NOTATTACHED) | 
|  | info->devices[port].status = USB_STATE_POWERED; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * SetPortFeature(PORT_RESET) | 
|  | */ | 
|  | static void xenhcd_rhport_reset(struct xenhcd_info *info, int portnum) | 
|  | { | 
|  | int port; | 
|  |  | 
|  | port = portnum - 1; | 
|  | info->ports[port].status &= ~(USB_PORT_STAT_ENABLE | | 
|  | USB_PORT_STAT_LOW_SPEED | | 
|  | USB_PORT_STAT_HIGH_SPEED); | 
|  | info->ports[port].status |= USB_PORT_STAT_RESET; | 
|  |  | 
|  | if (info->devices[port].status != USB_STATE_NOTATTACHED) | 
|  | info->devices[port].status = USB_STATE_ATTACHED; | 
|  |  | 
|  | /* 10msec reset signaling */ | 
|  | info->ports[port].timeout = jiffies + msecs_to_jiffies(10); | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_PM | 
|  | static int xenhcd_bus_suspend(struct usb_hcd *hcd) | 
|  | { | 
|  | struct xenhcd_info *info = xenhcd_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++) | 
|  | xenhcd_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 xenhcd_info *info = xenhcd_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++) | 
|  | xenhcd_rhport_resume(info, i); | 
|  | } | 
|  | spin_unlock_irq(&info->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static void xenhcd_hub_descriptor(struct xenhcd_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 xenhcd_info *info = xenhcd_hcd_to_info(hcd); | 
|  | int ports; | 
|  | int i; | 
|  | unsigned long flags; | 
|  | int ret; | 
|  | int changed = 0; | 
|  |  | 
|  | /* initialize the status to no-changes */ | 
|  | ports = info->rh_numports; | 
|  | ret = 1 + (ports / 8); | 
|  | memset(buf, 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) { | 
|  | buf[(i + 1) / 8] |= 1 << (i + 1) % 8; | 
|  | changed = 1; | 
|  | } | 
|  | } | 
|  |  | 
|  | if ((hcd->state == HC_STATE_SUSPENDED) && (changed == 1)) | 
|  | usb_hcd_resume_root_hub(hcd); | 
|  |  | 
|  | spin_unlock_irqrestore(&info->lock, flags); | 
|  |  | 
|  | return changed ? ret : 0; | 
|  | } | 
|  |  | 
|  | static int xenhcd_hub_control(struct usb_hcd *hcd, __u16 typeReq, __u16 wValue, | 
|  | __u16 wIndex, char *buf, __u16 wLength) | 
|  | { | 
|  | struct xenhcd_info *info = xenhcd_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: | 
|  | xenhcd_rhport_resume(info, wIndex); | 
|  | break; | 
|  | case USB_PORT_FEAT_POWER: | 
|  | xenhcd_rhport_power_off(info, wIndex); | 
|  | break; | 
|  | case USB_PORT_FEAT_ENABLE: | 
|  | xenhcd_rhport_disable(info, wIndex); | 
|  | break; | 
|  | case USB_PORT_FEAT_C_CONNECTION: | 
|  | info->ports[wIndex - 1].c_connection = false; | 
|  | fallthrough; | 
|  | 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 XENUSB_SPEED_LOW: | 
|  | info->ports[wIndex].status |= | 
|  | USB_PORT_STAT_LOW_SPEED; | 
|  | break; | 
|  | case XENUSB_SPEED_HIGH: | 
|  | info->ports[wIndex].status |= | 
|  | USB_PORT_STAT_HIGH_SPEED; | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | *(__le32 *)buf = cpu_to_le32(info->ports[wIndex].status); | 
|  | break; | 
|  | case SetPortFeature: | 
|  | if (!wIndex || wIndex > ports) | 
|  | goto error; | 
|  |  | 
|  | switch (wValue) { | 
|  | case USB_PORT_FEAT_POWER: | 
|  | xenhcd_rhport_power_on(info, wIndex); | 
|  | break; | 
|  | case USB_PORT_FEAT_RESET: | 
|  | xenhcd_rhport_reset(info, wIndex); | 
|  | break; | 
|  | case USB_PORT_FEAT_SUSPEND: | 
|  | xenhcd_rhport_suspend(info, wIndex); | 
|  | break; | 
|  | default: | 
|  | if (info->ports[wIndex-1].status & USB_PORT_STAT_POWER) | 
|  | info->ports[wIndex-1].status |= (1 << wValue); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case SetHubFeature: | 
|  | /* not supported */ | 
|  | default: | 
|  | error: | 
|  | ret = -EPIPE; | 
|  | } | 
|  | spin_unlock_irqrestore(&info->lock, flags); | 
|  |  | 
|  | /* check status for each port */ | 
|  | for (i = 0; i < ports; i++) { | 
|  | if (info->ports[i].status & PORT_C_MASK) | 
|  | changed = 1; | 
|  | } | 
|  | if (changed) | 
|  | usb_hcd_poll_rh_status(hcd); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void xenhcd_free_urb_priv(struct urb_priv *urbp) | 
|  | { | 
|  | urbp->urb->hcpriv = NULL; | 
|  | kmem_cache_free(xenhcd_urbp_cachep, urbp); | 
|  | } | 
|  |  | 
|  | static inline unsigned int xenhcd_get_id_from_freelist(struct xenhcd_info *info) | 
|  | { | 
|  | unsigned int free; | 
|  |  | 
|  | free = info->shadow_free; | 
|  | info->shadow_free = info->shadow[free].req.id; | 
|  | info->shadow[free].req.id = 0x0fff; /* debug */ | 
|  | return free; | 
|  | } | 
|  |  | 
|  | static inline void xenhcd_add_id_to_freelist(struct xenhcd_info *info, | 
|  | unsigned int id) | 
|  | { | 
|  | info->shadow[id].req.id	= info->shadow_free; | 
|  | info->shadow[id].urb = NULL; | 
|  | info->shadow_free = id; | 
|  | } | 
|  |  | 
|  | static inline int xenhcd_count_pages(void *addr, int length) | 
|  | { | 
|  | unsigned long vaddr = (unsigned long)addr; | 
|  |  | 
|  | return PFN_UP(vaddr + length) - PFN_DOWN(vaddr); | 
|  | } | 
|  |  | 
|  | static void xenhcd_gnttab_map(struct xenhcd_info *info, void *addr, int length, | 
|  | grant_ref_t *gref_head, | 
|  | struct xenusb_request_segment *seg, | 
|  | int nr_pages, int flags) | 
|  | { | 
|  | grant_ref_t ref; | 
|  | unsigned int offset; | 
|  | unsigned int len = length; | 
|  | unsigned int bytes; | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < nr_pages; i++) { | 
|  | offset = offset_in_page(addr); | 
|  |  | 
|  | bytes = PAGE_SIZE - offset; | 
|  | if (bytes > len) | 
|  | bytes = len; | 
|  |  | 
|  | ref = gnttab_claim_grant_reference(gref_head); | 
|  | gnttab_grant_foreign_access_ref(ref, info->xbdev->otherend_id, | 
|  | virt_to_gfn(addr), flags); | 
|  | seg[i].gref = ref; | 
|  | seg[i].offset = (__u16)offset; | 
|  | seg[i].length = (__u16)bytes; | 
|  |  | 
|  | addr += bytes; | 
|  | len -= bytes; | 
|  | } | 
|  | } | 
|  |  | 
|  | static __u32 xenhcd_pipe_urb_to_xenusb(__u32 urb_pipe, __u8 port) | 
|  | { | 
|  | static __u32 pipe; | 
|  |  | 
|  | pipe = usb_pipedevice(urb_pipe) << XENUSB_PIPE_DEV_SHIFT; | 
|  | pipe |= usb_pipeendpoint(urb_pipe) << XENUSB_PIPE_EP_SHIFT; | 
|  | if (usb_pipein(urb_pipe)) | 
|  | pipe |= XENUSB_PIPE_DIR; | 
|  | switch (usb_pipetype(urb_pipe)) { | 
|  | case PIPE_ISOCHRONOUS: | 
|  | pipe |= XENUSB_PIPE_TYPE_ISOC << XENUSB_PIPE_TYPE_SHIFT; | 
|  | break; | 
|  | case PIPE_INTERRUPT: | 
|  | pipe |= XENUSB_PIPE_TYPE_INT << XENUSB_PIPE_TYPE_SHIFT; | 
|  | break; | 
|  | case PIPE_CONTROL: | 
|  | pipe |= XENUSB_PIPE_TYPE_CTRL << XENUSB_PIPE_TYPE_SHIFT; | 
|  | break; | 
|  | case PIPE_BULK: | 
|  | pipe |= XENUSB_PIPE_TYPE_BULK << XENUSB_PIPE_TYPE_SHIFT; | 
|  | break; | 
|  | } | 
|  | pipe = xenusb_setportnum_pipe(pipe, port); | 
|  |  | 
|  | return pipe; | 
|  | } | 
|  |  | 
|  | static int xenhcd_map_urb_for_request(struct xenhcd_info *info, struct urb *urb, | 
|  | struct xenusb_urb_request *req) | 
|  | { | 
|  | grant_ref_t gref_head; | 
|  | int nr_buff_pages = 0; | 
|  | int nr_isodesc_pages = 0; | 
|  | int nr_grants = 0; | 
|  |  | 
|  | if (urb->transfer_buffer_length) { | 
|  | nr_buff_pages = xenhcd_count_pages(urb->transfer_buffer, | 
|  | urb->transfer_buffer_length); | 
|  |  | 
|  | if (usb_pipeisoc(urb->pipe)) | 
|  | nr_isodesc_pages = xenhcd_count_pages( | 
|  | &urb->iso_frame_desc[0], | 
|  | sizeof(struct usb_iso_packet_descriptor) * | 
|  | urb->number_of_packets); | 
|  |  | 
|  | nr_grants = nr_buff_pages + nr_isodesc_pages; | 
|  | if (nr_grants > XENUSB_MAX_SEGMENTS_PER_REQUEST) { | 
|  | pr_err("xenhcd: error: %d grants\n", nr_grants); | 
|  | return -E2BIG; | 
|  | } | 
|  |  | 
|  | if (gnttab_alloc_grant_references(nr_grants, &gref_head)) { | 
|  | pr_err("xenhcd: 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); | 
|  | } | 
|  |  | 
|  | req->pipe = xenhcd_pipe_urb_to_xenusb(urb->pipe, urb->dev->portnum); | 
|  | req->transfer_flags = 0; | 
|  | if (urb->transfer_flags & URB_SHORT_NOT_OK) | 
|  | req->transfer_flags |= XENUSB_SHORT_NOT_OK; | 
|  | 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; | 
|  |  | 
|  | 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); | 
|  | 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: | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (nr_grants) | 
|  | gnttab_free_grant_references(gref_head); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void xenhcd_gnttab_done(struct xenhcd_info *info, unsigned int id) | 
|  | { | 
|  | struct usb_shadow *shadow = info->shadow + id; | 
|  | int nr_segs = 0; | 
|  | int i; | 
|  |  | 
|  | if (!shadow->in_flight) { | 
|  | xenhcd_set_error(info, "Illegal request id"); | 
|  | return; | 
|  | } | 
|  | shadow->in_flight = false; | 
|  |  | 
|  | nr_segs = shadow->req.nr_buffer_segs; | 
|  |  | 
|  | if (xenusb_pipeisoc(shadow->req.pipe)) | 
|  | nr_segs += shadow->req.u.isoc.nr_frame_desc_segs; | 
|  |  | 
|  | for (i = 0; i < nr_segs; i++) { | 
|  | if (!gnttab_try_end_foreign_access(shadow->req.seg[i].gref)) | 
|  | xenhcd_set_error(info, "backend didn't release grant"); | 
|  | } | 
|  |  | 
|  | shadow->req.nr_buffer_segs = 0; | 
|  | shadow->req.u.isoc.nr_frame_desc_segs = 0; | 
|  | } | 
|  |  | 
|  | static int xenhcd_translate_status(int status) | 
|  | { | 
|  | switch (status) { | 
|  | case XENUSB_STATUS_OK: | 
|  | return 0; | 
|  | case XENUSB_STATUS_NODEV: | 
|  | return -ENODEV; | 
|  | case XENUSB_STATUS_INVAL: | 
|  | return -EINVAL; | 
|  | case XENUSB_STATUS_STALL: | 
|  | return -EPIPE; | 
|  | case XENUSB_STATUS_IOERROR: | 
|  | return -EPROTO; | 
|  | case XENUSB_STATUS_BABBLE: | 
|  | return -EOVERFLOW; | 
|  | default: | 
|  | return -ESHUTDOWN; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void xenhcd_giveback_urb(struct xenhcd_info *info, struct urb *urb, | 
|  | int status) | 
|  | { | 
|  | struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; | 
|  | int priv_status = urbp->status; | 
|  |  | 
|  | list_del_init(&urbp->list); | 
|  | xenhcd_free_urb_priv(urbp); | 
|  |  | 
|  | if (urb->status == -EINPROGRESS) | 
|  | urb->status = xenhcd_translate_status(status); | 
|  |  | 
|  | spin_unlock(&info->lock); | 
|  | usb_hcd_giveback_urb(xenhcd_info_to_hcd(info), urb, | 
|  | priv_status <= 0 ? priv_status : urb->status); | 
|  | spin_lock(&info->lock); | 
|  | } | 
|  |  | 
|  | static int xenhcd_do_request(struct xenhcd_info *info, struct urb_priv *urbp) | 
|  | { | 
|  | struct xenusb_urb_request *req; | 
|  | struct urb *urb = urbp->urb; | 
|  | unsigned int id; | 
|  | int notify; | 
|  | int ret; | 
|  |  | 
|  | id = xenhcd_get_id_from_freelist(info); | 
|  | req = &info->shadow[id].req; | 
|  | req->id = id; | 
|  |  | 
|  | if (unlikely(urbp->unlinked)) { | 
|  | req->u.unlink.unlink_id = urbp->req_id; | 
|  | req->pipe = xenusb_setunlink_pipe(xenhcd_pipe_urb_to_xenusb( | 
|  | urb->pipe, urb->dev->portnum)); | 
|  | urbp->unlink_req_id = id; | 
|  | } else { | 
|  | ret = xenhcd_map_urb_for_request(info, urb, req); | 
|  | if (ret) { | 
|  | xenhcd_add_id_to_freelist(info, id); | 
|  | return ret; | 
|  | } | 
|  | urbp->req_id = id; | 
|  | } | 
|  |  | 
|  | req = RING_GET_REQUEST(&info->urb_ring, info->urb_ring.req_prod_pvt); | 
|  | *req = info->shadow[id].req; | 
|  |  | 
|  | info->urb_ring.req_prod_pvt++; | 
|  | info->shadow[id].urb = urb; | 
|  | info->shadow[id].in_flight = true; | 
|  |  | 
|  | RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&info->urb_ring, notify); | 
|  | if (notify) | 
|  | notify_remote_via_irq(info->irq); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void xenhcd_kick_pending_urbs(struct xenhcd_info *info) | 
|  | { | 
|  | struct urb_priv *urbp; | 
|  |  | 
|  | while (!list_empty(&info->pending_submit_list)) { | 
|  | if (RING_FULL(&info->urb_ring)) { | 
|  | xenhcd_timer_action(info, TIMER_RING_WATCHDOG); | 
|  | return; | 
|  | } | 
|  |  | 
|  | urbp = list_entry(info->pending_submit_list.next, | 
|  | struct urb_priv, list); | 
|  | if (!xenhcd_do_request(info, urbp)) | 
|  | list_move_tail(&urbp->list, &info->in_progress_list); | 
|  | else | 
|  | xenhcd_giveback_urb(info, urbp->urb, -ESHUTDOWN); | 
|  | } | 
|  | xenhcd_timer_action_done(info, TIMER_SCAN_PENDING_URBS); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * caller must lock info->lock | 
|  | */ | 
|  | static void xenhcd_cancel_all_enqueued_urbs(struct xenhcd_info *info) | 
|  | { | 
|  | struct urb_priv *urbp, *tmp; | 
|  | int req_id; | 
|  |  | 
|  | list_for_each_entry_safe(urbp, tmp, &info->in_progress_list, list) { | 
|  | req_id = urbp->req_id; | 
|  | if (!urbp->unlinked) { | 
|  | xenhcd_gnttab_done(info, req_id); | 
|  | if (info->error) | 
|  | return; | 
|  | 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[req_id].urb = NULL; | 
|  | } | 
|  |  | 
|  | list_for_each_entry_safe(urbp, tmp, &info->pending_submit_list, list) | 
|  | xenhcd_giveback_urb(info, urbp->urb, -ESHUTDOWN); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * caller must lock info->lock | 
|  | */ | 
|  | static void xenhcd_giveback_unlinked_urbs(struct xenhcd_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 xenhcd_info *info, struct urb_priv *urbp) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | if (RING_FULL(&info->urb_ring)) { | 
|  | list_add_tail(&urbp->list, &info->pending_submit_list); | 
|  | xenhcd_timer_action(info, TIMER_RING_WATCHDOG); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (!list_empty(&info->pending_submit_list)) { | 
|  | list_add_tail(&urbp->list, &info->pending_submit_list); | 
|  | xenhcd_timer_action(info, TIMER_SCAN_PENDING_URBS); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | ret = xenhcd_do_request(info, urbp); | 
|  | if (ret == 0) | 
|  | list_add_tail(&urbp->list, &info->in_progress_list); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int xenhcd_unlink_urb(struct xenhcd_info *info, struct urb_priv *urbp) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | /* already unlinked? */ | 
|  | if (urbp->unlinked) | 
|  | return -EBUSY; | 
|  |  | 
|  | urbp->unlinked = true; | 
|  |  | 
|  | /* the urb is still in pending_submit queue */ | 
|  | if (urbp->req_id == ~0) { | 
|  | list_move_tail(&urbp->list, &info->giveback_waiting_list); | 
|  | xenhcd_timer_action(info, TIMER_SCAN_PENDING_URBS); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* send unlink request to backend */ | 
|  | if (RING_FULL(&info->urb_ring)) { | 
|  | list_move_tail(&urbp->list, &info->pending_unlink_list); | 
|  | xenhcd_timer_action(info, TIMER_RING_WATCHDOG); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (!list_empty(&info->pending_unlink_list)) { | 
|  | list_move_tail(&urbp->list, &info->pending_unlink_list); | 
|  | xenhcd_timer_action(info, TIMER_SCAN_PENDING_URBS); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | ret = xenhcd_do_request(info, urbp); | 
|  | if (ret == 0) | 
|  | list_move_tail(&urbp->list, &info->in_progress_list); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void xenhcd_res_to_urb(struct xenhcd_info *info, | 
|  | struct xenusb_urb_response *res, struct urb *urb) | 
|  | { | 
|  | if (unlikely(!urb)) | 
|  | return; | 
|  |  | 
|  | if (res->actual_length > urb->transfer_buffer_length) | 
|  | urb->actual_length = urb->transfer_buffer_length; | 
|  | else if (res->actual_length < 0) | 
|  | urb->actual_length = 0; | 
|  | else | 
|  | urb->actual_length = res->actual_length; | 
|  | urb->error_count = res->error_count; | 
|  | urb->start_frame = res->start_frame; | 
|  | xenhcd_giveback_urb(info, urb, res->status); | 
|  | } | 
|  |  | 
|  | static int xenhcd_urb_request_done(struct xenhcd_info *info, | 
|  | unsigned int *eoiflag) | 
|  | { | 
|  | struct xenusb_urb_response res; | 
|  | RING_IDX i, rp; | 
|  | __u16 id; | 
|  | int more_to_do = 0; | 
|  | unsigned long flags; | 
|  |  | 
|  | spin_lock_irqsave(&info->lock, flags); | 
|  |  | 
|  | rp = info->urb_ring.sring->rsp_prod; | 
|  | if (RING_RESPONSE_PROD_OVERFLOW(&info->urb_ring, rp)) { | 
|  | xenhcd_set_error(info, "Illegal index on urb-ring"); | 
|  | goto err; | 
|  | } | 
|  | rmb(); /* ensure we see queued responses up to "rp" */ | 
|  |  | 
|  | for (i = info->urb_ring.rsp_cons; i != rp; i++) { | 
|  | RING_COPY_RESPONSE(&info->urb_ring, i, &res); | 
|  | id = res.id; | 
|  | if (id >= XENUSB_URB_RING_SIZE) { | 
|  | xenhcd_set_error(info, "Illegal data on urb-ring"); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | if (likely(xenusb_pipesubmit(info->shadow[id].req.pipe))) { | 
|  | xenhcd_gnttab_done(info, id); | 
|  | if (info->error) | 
|  | goto err; | 
|  | xenhcd_res_to_urb(info, &res, info->shadow[id].urb); | 
|  | } | 
|  |  | 
|  | xenhcd_add_id_to_freelist(info, id); | 
|  |  | 
|  | *eoiflag = 0; | 
|  | } | 
|  | info->urb_ring.rsp_cons = i; | 
|  |  | 
|  | if (i != info->urb_ring.req_prod_pvt) | 
|  | RING_FINAL_CHECK_FOR_RESPONSES(&info->urb_ring, more_to_do); | 
|  | else | 
|  | info->urb_ring.sring->rsp_event = i + 1; | 
|  |  | 
|  | spin_unlock_irqrestore(&info->lock, flags); | 
|  |  | 
|  | return more_to_do; | 
|  |  | 
|  | err: | 
|  | spin_unlock_irqrestore(&info->lock, flags); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int xenhcd_conn_notify(struct xenhcd_info *info, unsigned int *eoiflag) | 
|  | { | 
|  | struct xenusb_conn_response res; | 
|  | struct xenusb_conn_request *req; | 
|  | RING_IDX rc, rp; | 
|  | __u16 id; | 
|  | __u8 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; | 
|  | if (RING_RESPONSE_PROD_OVERFLOW(&info->conn_ring, rp)) { | 
|  | xenhcd_set_error(info, "Illegal index on conn-ring"); | 
|  | spin_unlock_irqrestore(&info->lock, flags); | 
|  | return 0; | 
|  | } | 
|  | rmb(); /* ensure we see queued responses up to "rp" */ | 
|  |  | 
|  | while (rc != rp) { | 
|  | RING_COPY_RESPONSE(&info->conn_ring, rc, &res); | 
|  | id = res.id; | 
|  | portnum = res.portnum; | 
|  | speed = res.speed; | 
|  | info->conn_ring.rsp_cons = ++rc; | 
|  |  | 
|  | if (xenhcd_rhport_connect(info, portnum, speed)) { | 
|  | xenhcd_set_error(info, "Illegal data on conn-ring"); | 
|  | spin_unlock_irqrestore(&info->lock, flags); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | 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++; | 
|  |  | 
|  | *eoiflag = 0; | 
|  | } | 
|  |  | 
|  | 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(xenhcd_info_to_hcd(info)); | 
|  |  | 
|  | return more_to_do; | 
|  | } | 
|  |  | 
|  | static irqreturn_t xenhcd_int(int irq, void *dev_id) | 
|  | { | 
|  | struct xenhcd_info *info = (struct xenhcd_info *)dev_id; | 
|  | unsigned int eoiflag = XEN_EOI_FLAG_SPURIOUS; | 
|  |  | 
|  | if (unlikely(info->error)) { | 
|  | xen_irq_lateeoi(irq, XEN_EOI_FLAG_SPURIOUS); | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | while (xenhcd_urb_request_done(info, &eoiflag) | | 
|  | xenhcd_conn_notify(info, &eoiflag)) | 
|  | /* Yield point for this unbounded loop. */ | 
|  | cond_resched(); | 
|  |  | 
|  | xen_irq_lateeoi(irq, eoiflag); | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | static void xenhcd_destroy_rings(struct xenhcd_info *info) | 
|  | { | 
|  | if (info->irq) | 
|  | unbind_from_irqhandler(info->irq, info); | 
|  | info->irq = 0; | 
|  |  | 
|  | xenbus_teardown_ring((void **)&info->urb_ring.sring, 1, | 
|  | &info->urb_ring_ref); | 
|  | xenbus_teardown_ring((void **)&info->conn_ring.sring, 1, | 
|  | &info->conn_ring_ref); | 
|  | } | 
|  |  | 
|  | static int xenhcd_setup_rings(struct xenbus_device *dev, | 
|  | struct xenhcd_info *info) | 
|  | { | 
|  | struct xenusb_urb_sring *urb_sring; | 
|  | struct xenusb_conn_sring *conn_sring; | 
|  | int err; | 
|  |  | 
|  | info->conn_ring_ref = INVALID_GRANT_REF; | 
|  | err = xenbus_setup_ring(dev, GFP_NOIO | __GFP_HIGH, | 
|  | (void **)&urb_sring, 1, &info->urb_ring_ref); | 
|  | if (err) { | 
|  | xenbus_dev_fatal(dev, err, "allocating urb ring"); | 
|  | return err; | 
|  | } | 
|  | XEN_FRONT_RING_INIT(&info->urb_ring, urb_sring, PAGE_SIZE); | 
|  |  | 
|  | err = xenbus_setup_ring(dev, GFP_NOIO | __GFP_HIGH, | 
|  | (void **)&conn_sring, 1, &info->conn_ring_ref); | 
|  | if (err) { | 
|  | xenbus_dev_fatal(dev, err, "allocating conn ring"); | 
|  | goto fail; | 
|  | } | 
|  | XEN_FRONT_RING_INIT(&info->conn_ring, conn_sring, PAGE_SIZE); | 
|  |  | 
|  | err = xenbus_alloc_evtchn(dev, &info->evtchn); | 
|  | if (err) { | 
|  | xenbus_dev_fatal(dev, err, "xenbus_alloc_evtchn"); | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | err = bind_evtchn_to_irq_lateeoi(info->evtchn); | 
|  | if (err <= 0) { | 
|  | xenbus_dev_fatal(dev, err, "bind_evtchn_to_irq_lateeoi"); | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | info->irq = err; | 
|  |  | 
|  | err = request_threaded_irq(info->irq, NULL, xenhcd_int, | 
|  | IRQF_ONESHOT, "xenhcd", info); | 
|  | if (err) { | 
|  | xenbus_dev_fatal(dev, err, "request_threaded_irq"); | 
|  | goto free_irq; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | free_irq: | 
|  | unbind_from_irqhandler(info->irq, info); | 
|  | fail: | 
|  | xenhcd_destroy_rings(info); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int xenhcd_talk_to_backend(struct xenbus_device *dev, | 
|  | struct xenhcd_info *info) | 
|  | { | 
|  | const char *message; | 
|  | struct xenbus_transaction xbt; | 
|  | int err; | 
|  |  | 
|  | err = xenhcd_setup_rings(dev, info); | 
|  | if (err) | 
|  | return err; | 
|  |  | 
|  | again: | 
|  | err = xenbus_transaction_start(&xbt); | 
|  | if (err) { | 
|  | xenbus_dev_fatal(dev, err, "starting transaction"); | 
|  | goto destroy_ring; | 
|  | } | 
|  |  | 
|  | err = xenbus_printf(xbt, dev->nodename, "urb-ring-ref", "%u", | 
|  | info->urb_ring_ref); | 
|  | if (err) { | 
|  | message = "writing urb-ring-ref"; | 
|  | goto abort_transaction; | 
|  | } | 
|  |  | 
|  | err = xenbus_printf(xbt, dev->nodename, "conn-ring-ref", "%u", | 
|  | info->conn_ring_ref); | 
|  | if (err) { | 
|  | message = "writing conn-ring-ref"; | 
|  | goto abort_transaction; | 
|  | } | 
|  |  | 
|  | err = xenbus_printf(xbt, dev->nodename, "event-channel", "%u", | 
|  | info->evtchn); | 
|  | if (err) { | 
|  | message = "writing event-channel"; | 
|  | goto abort_transaction; | 
|  | } | 
|  |  | 
|  | err = xenbus_transaction_end(xbt, 0); | 
|  | if (err) { | 
|  | if (err == -EAGAIN) | 
|  | goto again; | 
|  | xenbus_dev_fatal(dev, err, "completing transaction"); | 
|  | goto destroy_ring; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | abort_transaction: | 
|  | xenbus_transaction_end(xbt, 1); | 
|  | xenbus_dev_fatal(dev, err, "%s", message); | 
|  |  | 
|  | destroy_ring: | 
|  | xenhcd_destroy_rings(info); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int xenhcd_connect(struct xenbus_device *dev) | 
|  | { | 
|  | struct xenhcd_info *info = dev_get_drvdata(&dev->dev); | 
|  | struct xenusb_conn_request *req; | 
|  | int idx, err; | 
|  | int notify; | 
|  | char name[TASK_COMM_LEN]; | 
|  | struct usb_hcd *hcd; | 
|  |  | 
|  | hcd = xenhcd_info_to_hcd(info); | 
|  | snprintf(name, TASK_COMM_LEN, "xenhcd.%d", hcd->self.busnum); | 
|  |  | 
|  | err = xenhcd_talk_to_backend(dev, info); | 
|  | if (err) | 
|  | return err; | 
|  |  | 
|  | /* prepare ring for hotplug notification */ | 
|  | for (idx = 0; idx < XENUSB_CONN_RING_SIZE; idx++) { | 
|  | req = RING_GET_REQUEST(&info->conn_ring, idx); | 
|  | req->id = idx; | 
|  | } | 
|  | info->conn_ring.req_prod_pvt = idx; | 
|  |  | 
|  | RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&info->conn_ring, notify); | 
|  | if (notify) | 
|  | notify_remote_via_irq(info->irq); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void xenhcd_disconnect(struct xenbus_device *dev) | 
|  | { | 
|  | struct xenhcd_info *info = dev_get_drvdata(&dev->dev); | 
|  | struct usb_hcd *hcd = xenhcd_info_to_hcd(info); | 
|  |  | 
|  | usb_remove_hcd(hcd); | 
|  | xenbus_frontend_closed(dev); | 
|  | } | 
|  |  | 
|  | static void xenhcd_watchdog(struct timer_list *timer) | 
|  | { | 
|  | struct xenhcd_info *info = from_timer(info, timer, watchdog); | 
|  | unsigned long flags; | 
|  |  | 
|  | spin_lock_irqsave(&info->lock, flags); | 
|  | if (likely(HC_IS_RUNNING(xenhcd_info_to_hcd(info)->state))) { | 
|  | xenhcd_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 xenhcd_info *info = xenhcd_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); | 
|  | timer_setup(&info->watchdog, xenhcd_watchdog, 0); | 
|  |  | 
|  | hcd->has_tt = (hcd->driver->flags & HCD_MASK) != HCD_USB11; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * start HC running | 
|  | */ | 
|  | static int xenhcd_run(struct usb_hcd *hcd) | 
|  | { | 
|  | hcd->uses_new_polling = 1; | 
|  | clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); | 
|  | hcd->state = HC_STATE_RUNNING; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * stop running HC | 
|  | */ | 
|  | static void xenhcd_stop(struct usb_hcd *hcd) | 
|  | { | 
|  | struct xenhcd_info *info = xenhcd_hcd_to_info(hcd); | 
|  |  | 
|  | del_timer_sync(&info->watchdog); | 
|  | 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 xenhcd_info *info = xenhcd_hcd_to_info(hcd); | 
|  | struct urb_priv *urbp; | 
|  | unsigned long flags; | 
|  | int ret; | 
|  |  | 
|  | if (unlikely(info->error)) | 
|  | return -ESHUTDOWN; | 
|  |  | 
|  | urbp = kmem_cache_zalloc(xenhcd_urbp_cachep, mem_flags); | 
|  | if (!urbp) | 
|  | return -ENOMEM; | 
|  |  | 
|  | spin_lock_irqsave(&info->lock, flags); | 
|  |  | 
|  | urbp->urb = urb; | 
|  | urb->hcpriv = urbp; | 
|  | urbp->req_id = ~0; | 
|  | urbp->unlink_req_id = ~0; | 
|  | INIT_LIST_HEAD(&urbp->list); | 
|  | urbp->status = 1; | 
|  | urb->unlinked = false; | 
|  |  | 
|  | ret = xenhcd_submit_urb(info, urbp); | 
|  |  | 
|  | if (ret) | 
|  | xenhcd_free_urb_priv(urbp); | 
|  |  | 
|  | 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 xenhcd_info *info = xenhcd_hcd_to_info(hcd); | 
|  | struct urb_priv *urbp; | 
|  | unsigned long flags; | 
|  | int ret = 0; | 
|  |  | 
|  | spin_lock_irqsave(&info->lock, flags); | 
|  |  | 
|  | urbp = urb->hcpriv; | 
|  | if (urbp) { | 
|  | urbp->status = status; | 
|  | ret = xenhcd_unlink_urb(info, urbp); | 
|  | } | 
|  |  | 
|  | spin_unlock_irqrestore(&info->lock, flags); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * called from usb_get_current_frame_number(), | 
|  | * but, almost all drivers not use such function. | 
|  | */ | 
|  | static int xenhcd_get_frame(struct usb_hcd *hcd) | 
|  | { | 
|  | /* it means error, but probably no problem :-) */ | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct hc_driver xenhcd_usb20_hc_driver = { | 
|  | .description = "xen-hcd", | 
|  | .product_desc = "Xen USB2.0 Virtual Host Controller", | 
|  | .hcd_priv_size = sizeof(struct xenhcd_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 CONFIG_PM | 
|  | .bus_suspend = xenhcd_bus_suspend, | 
|  | .bus_resume = xenhcd_bus_resume, | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | static struct hc_driver xenhcd_usb11_hc_driver = { | 
|  | .description = "xen-hcd", | 
|  | .product_desc = "Xen USB1.1 Virtual Host Controller", | 
|  | .hcd_priv_size = sizeof(struct xenhcd_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 CONFIG_PM | 
|  | .bus_suspend = xenhcd_bus_suspend, | 
|  | .bus_resume = xenhcd_bus_resume, | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | static struct usb_hcd *xenhcd_create_hcd(struct xenbus_device *dev) | 
|  | { | 
|  | int i; | 
|  | int err = 0; | 
|  | int num_ports; | 
|  | int usb_ver; | 
|  | struct usb_hcd *hcd = NULL; | 
|  | struct xenhcd_info *info; | 
|  |  | 
|  | err = xenbus_scanf(XBT_NIL, dev->otherend, "num-ports", "%d", | 
|  | &num_ports); | 
|  | if (err != 1) { | 
|  | xenbus_dev_fatal(dev, err, "reading num-ports"); | 
|  | return ERR_PTR(-EINVAL); | 
|  | } | 
|  | if (num_ports < 1 || num_ports > XENUSB_MAX_PORTNR) { | 
|  | 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 XENUSB_VER_USB11: | 
|  | hcd = usb_create_hcd(&xenhcd_usb11_hc_driver, &dev->dev, | 
|  | dev_name(&dev->dev)); | 
|  | break; | 
|  | case XENUSB_VER_USB20: | 
|  | hcd = usb_create_hcd(&xenhcd_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 = xenhcd_hcd_to_info(hcd); | 
|  | info->xbdev = dev; | 
|  | info->rh_numports = num_ports; | 
|  |  | 
|  | for (i = 0; i < XENUSB_URB_RING_SIZE; i++) { | 
|  | info->shadow[i].req.id = i + 1; | 
|  | info->shadow[i].urb = NULL; | 
|  | info->shadow[i].in_flight = false; | 
|  | } | 
|  | info->shadow[XENUSB_URB_RING_SIZE - 1].req.id = 0x0fff; | 
|  |  | 
|  | return hcd; | 
|  | } | 
|  |  | 
|  | static void xenhcd_backend_changed(struct xenbus_device *dev, | 
|  | enum xenbus_state backend_state) | 
|  | { | 
|  | switch (backend_state) { | 
|  | case XenbusStateInitialising: | 
|  | case XenbusStateReconfiguring: | 
|  | case XenbusStateReconfigured: | 
|  | case XenbusStateUnknown: | 
|  | break; | 
|  |  | 
|  | case XenbusStateInitWait: | 
|  | case XenbusStateInitialised: | 
|  | case XenbusStateConnected: | 
|  | if (dev->state != XenbusStateInitialising) | 
|  | break; | 
|  | if (!xenhcd_connect(dev)) | 
|  | xenbus_switch_state(dev, XenbusStateConnected); | 
|  | break; | 
|  |  | 
|  | case XenbusStateClosed: | 
|  | if (dev->state == XenbusStateClosed) | 
|  | break; | 
|  | fallthrough;	/* Missed the backend's Closing state. */ | 
|  | case XenbusStateClosing: | 
|  | xenhcd_disconnect(dev); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | xenbus_dev_fatal(dev, -EINVAL, "saw state %d at frontend", | 
|  | backend_state); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int xenhcd_remove(struct xenbus_device *dev) | 
|  | { | 
|  | struct xenhcd_info *info = dev_get_drvdata(&dev->dev); | 
|  | struct usb_hcd *hcd = xenhcd_info_to_hcd(info); | 
|  |  | 
|  | xenhcd_destroy_rings(info); | 
|  | usb_put_hcd(hcd); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int xenhcd_probe(struct xenbus_device *dev, | 
|  | const struct xenbus_device_id *id) | 
|  | { | 
|  | int err; | 
|  | struct usb_hcd *hcd; | 
|  | struct xenhcd_info *info; | 
|  |  | 
|  | if (usb_disabled()) | 
|  | return -ENODEV; | 
|  |  | 
|  | hcd = xenhcd_create_hcd(dev); | 
|  | if (IS_ERR(hcd)) { | 
|  | err = PTR_ERR(hcd); | 
|  | xenbus_dev_fatal(dev, err, | 
|  | "fail to create usb host controller"); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | info = xenhcd_hcd_to_info(hcd); | 
|  | dev_set_drvdata(&dev->dev, info); | 
|  |  | 
|  | err = usb_add_hcd(hcd, 0, 0); | 
|  | if (err) { | 
|  | xenbus_dev_fatal(dev, err, "fail to add USB host controller"); | 
|  | usb_put_hcd(hcd); | 
|  | dev_set_drvdata(&dev->dev, NULL); | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static const struct xenbus_device_id xenhcd_ids[] = { | 
|  | { "vusb" }, | 
|  | { "" }, | 
|  | }; | 
|  |  | 
|  | static struct xenbus_driver xenhcd_driver = { | 
|  | .ids			= xenhcd_ids, | 
|  | .probe			= xenhcd_probe, | 
|  | .otherend_changed	= xenhcd_backend_changed, | 
|  | .remove			= xenhcd_remove, | 
|  | }; | 
|  |  | 
|  | static int __init xenhcd_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) { | 
|  | pr_err("xenhcd failed to create kmem cache\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | return xenbus_register_frontend(&xenhcd_driver); | 
|  | } | 
|  | module_init(xenhcd_init); | 
|  |  | 
|  | static void __exit xenhcd_exit(void) | 
|  | { | 
|  | kmem_cache_destroy(xenhcd_urbp_cachep); | 
|  | xenbus_unregister_driver(&xenhcd_driver); | 
|  | } | 
|  | module_exit(xenhcd_exit); | 
|  |  | 
|  | MODULE_ALIAS("xen:vusb"); | 
|  | MODULE_AUTHOR("Juergen Gross <jgross@suse.com>"); | 
|  | MODULE_DESCRIPTION("Xen USB Virtual Host Controller driver (xen-hcd)"); | 
|  | MODULE_LICENSE("Dual BSD/GPL"); |