// 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 void 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);
}

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");
