diff options
Diffstat (limited to 'drivers/usb/host/xen-hcd.c')
| -rw-r--r-- | drivers/usb/host/xen-hcd.c | 1609 | 
1 files changed, 1609 insertions, 0 deletions
diff --git a/drivers/usb/host/xen-hcd.c b/drivers/usb/host/xen-hcd.c new file mode 100644 index 000000000000..be09fd9bac58 --- /dev/null +++ b/drivers/usb/host/xen-hcd.c @@ -0,0 +1,1609 @@ +// 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 <[email protected]> + */ + +#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; +}; + +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 GRANT_INVALID_REF 0 + +#define XENHCD_RING_JIFFIES (HZ/200) +#define XENHCD_SCAN_JIFFIES 1 + +enum xenhcd_timer_action { +	TIMER_RING_WATCHDOG, +	TIMER_SCAN_PENDING_URBS, +}; + +static struct kmem_cache *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 long buffer_mfn; +	unsigned int offset; +	unsigned int len = length; +	unsigned int bytes; +	int i; + +	for (i = 0; i < nr_pages; i++) { +		buffer_mfn = PFN_DOWN(arbitrary_virt_to_machine(addr).maddr); +		offset = offset_in_page(addr); + +		bytes = PAGE_SIZE - offset; +		if (bytes > len) +			bytes = len; + +		ref = gnttab_claim_grant_reference(gref_head); +		gnttab_grant_foreign_access_ref(ref, info->xbdev->otherend_id, +						buffer_mfn, 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 usb_shadow *shadow) +{ +	int nr_segs = 0; +	int i; + +	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++) +		gnttab_end_foreign_access(shadow->req.seg[i].gref, 0, 0UL); + +	shadow->req.nr_buffer_segs = 0; +	shadow->req.u.isoc.nr_frame_desc_segs = 0; +} + +static 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; + +	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->shadow[req_id]); +			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 int xenhcd_urb_request_done(struct xenhcd_info *info) +{ +	struct xenusb_urb_response res; +	struct urb *urb; +	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"); +		spin_unlock_irqrestore(&info->lock, flags); +		return 0; +	} +	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"); +			continue; +		} + +		if (likely(xenusb_pipesubmit(info->shadow[id].req.pipe))) { +			xenhcd_gnttab_done(&info->shadow[id]); +			urb = info->shadow[id].urb; +			if (likely(urb)) { +				urb->actual_length = res.actual_length; +				urb->error_count = res.error_count; +				urb->start_frame = res.start_frame; +				xenhcd_giveback_urb(info, urb, res.status); +			} +		} + +		xenhcd_add_id_to_freelist(info, id); +	} +	info->urb_ring.rsp_cons = i; + +	if (i != info->urb_ring.req_prod_pvt) +		RING_FINAL_CHECK_FOR_RESPONSES(&info->urb_ring, more_to_do); +	else +		info->urb_ring.sring->rsp_event = i + 1; + +	spin_unlock_irqrestore(&info->lock, flags); + +	return more_to_do; +} + +static int xenhcd_conn_notify(struct xenhcd_info *info) +{ +	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++; +	} + +	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; + +	if (unlikely(info->error)) +		return IRQ_HANDLED; + +	while (xenhcd_urb_request_done(info) | xenhcd_conn_notify(info)) +		/* Yield point for this unbounded loop. */ +		cond_resched(); + +	return IRQ_HANDLED; +} + +static void xenhcd_destroy_rings(struct xenhcd_info *info) +{ +	if (info->irq) +		unbind_from_irqhandler(info->irq, info); +	info->irq = 0; + +	if (info->urb_ring_ref != GRANT_INVALID_REF) { +		gnttab_end_foreign_access(info->urb_ring_ref, 0, +					  (unsigned long)info->urb_ring.sring); +		info->urb_ring_ref = GRANT_INVALID_REF; +	} +	info->urb_ring.sring = NULL; + +	if (info->conn_ring_ref != GRANT_INVALID_REF) { +		gnttab_end_foreign_access(info->conn_ring_ref, 0, +					  (unsigned long)info->conn_ring.sring); +		info->conn_ring_ref = GRANT_INVALID_REF; +	} +	info->conn_ring.sring = NULL; +} + +static int xenhcd_setup_rings(struct xenbus_device *dev, +			      struct xenhcd_info *info) +{ +	struct xenusb_urb_sring *urb_sring; +	struct xenusb_conn_sring *conn_sring; +	grant_ref_t gref; +	int err; + +	info->urb_ring_ref = GRANT_INVALID_REF; +	info->conn_ring_ref = GRANT_INVALID_REF; + +	urb_sring = (struct xenusb_urb_sring *)get_zeroed_page( +							GFP_NOIO | __GFP_HIGH); +	if (!urb_sring) { +		xenbus_dev_fatal(dev, -ENOMEM, "allocating urb ring"); +		return -ENOMEM; +	} +	SHARED_RING_INIT(urb_sring); +	FRONT_RING_INIT(&info->urb_ring, urb_sring, PAGE_SIZE); + +	err = xenbus_grant_ring(dev, urb_sring, 1, &gref); +	if (err < 0) { +		free_page((unsigned long)urb_sring); +		info->urb_ring.sring = NULL; +		goto fail; +	} +	info->urb_ring_ref = gref; + +	conn_sring = (struct xenusb_conn_sring *)get_zeroed_page( +							GFP_NOIO | __GFP_HIGH); +	if (!conn_sring) { +		xenbus_dev_fatal(dev, -ENOMEM, "allocating conn ring"); +		err = -ENOMEM; +		goto fail; +	} +	SHARED_RING_INIT(conn_sring); +	FRONT_RING_INIT(&info->conn_ring, conn_sring, PAGE_SIZE); + +	err = xenbus_grant_ring(dev, conn_sring, 1, &gref); +	if (err < 0) { +		free_page((unsigned long)conn_sring); +		info->conn_ring.sring = NULL; +		goto fail; +	} +	info->conn_ring_ref = gref; + +	err = xenbus_alloc_evtchn(dev, &info->evtchn); +	if (err) { +		xenbus_dev_fatal(dev, err, "xenbus_alloc_evtchn"); +		goto fail; +	} + +	err = bind_evtchn_to_irq(info->evtchn); +	if (err <= 0) { +		xenbus_dev_fatal(dev, err, "bind_evtchn_to_irq"); +		goto fail; +	} + +	info->irq = err; + +	err = request_threaded_irq(info->irq, NULL, 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[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 <[email protected]>"); +MODULE_DESCRIPTION("Xen USB Virtual Host Controller driver (xen-hcd)"); +MODULE_LICENSE("Dual BSD/GPL");  |