forked from mirrors/linux
		
	Fix vep_free_request() to return when usb_ep and usb_request are null instead of calling WARN_ON. Signed-off-by: Shuah Khan <shuah@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
		
			
				
	
	
		
			648 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			648 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0+
 | 
						|
/*
 | 
						|
 * Copyright (C) 2015 Karol Kosik <karo9@interia.eu>
 | 
						|
 * Copyright (C) 2015-2016 Samsung Electronics
 | 
						|
 *               Igor Kotrasinski <i.kotrasinsk@samsung.com>
 | 
						|
 *               Krzysztof Opasiak <k.opasiak@samsung.com>
 | 
						|
 */
 | 
						|
 | 
						|
#include <linux/device.h>
 | 
						|
#include <linux/kernel.h>
 | 
						|
#include <linux/list.h>
 | 
						|
#include <linux/platform_device.h>
 | 
						|
#include <linux/usb.h>
 | 
						|
#include <linux/usb/gadget.h>
 | 
						|
#include <linux/usb/hcd.h>
 | 
						|
#include <linux/kthread.h>
 | 
						|
#include <linux/file.h>
 | 
						|
#include <linux/byteorder/generic.h>
 | 
						|
 | 
						|
#include "usbip_common.h"
 | 
						|
#include "vudc.h"
 | 
						|
 | 
						|
#define VIRTUAL_ENDPOINTS (1 /* ep0 */ + 15 /* in eps */ + 15 /* out eps */)
 | 
						|
 | 
						|
/* urb-related structures alloc / free */
 | 
						|
 | 
						|
 | 
						|
static void free_urb(struct urb *urb)
 | 
						|
{
 | 
						|
	if (!urb)
 | 
						|
		return;
 | 
						|
 | 
						|
	kfree(urb->setup_packet);
 | 
						|
	urb->setup_packet = NULL;
 | 
						|
 | 
						|
	kfree(urb->transfer_buffer);
 | 
						|
	urb->transfer_buffer = NULL;
 | 
						|
 | 
						|
	usb_free_urb(urb);
 | 
						|
}
 | 
						|
 | 
						|
struct urbp *alloc_urbp(void)
 | 
						|
{
 | 
						|
	struct urbp *urb_p;
 | 
						|
 | 
						|
	urb_p = kzalloc(sizeof(*urb_p), GFP_KERNEL);
 | 
						|
	if (!urb_p)
 | 
						|
		return urb_p;
 | 
						|
 | 
						|
	urb_p->urb = NULL;
 | 
						|
	urb_p->ep = NULL;
 | 
						|
	INIT_LIST_HEAD(&urb_p->urb_entry);
 | 
						|
	return urb_p;
 | 
						|
}
 | 
						|
 | 
						|
static void free_urbp(struct urbp *urb_p)
 | 
						|
{
 | 
						|
	kfree(urb_p);
 | 
						|
}
 | 
						|
 | 
						|
void free_urbp_and_urb(struct urbp *urb_p)
 | 
						|
{
 | 
						|
	if (!urb_p)
 | 
						|
		return;
 | 
						|
	free_urb(urb_p->urb);
 | 
						|
	free_urbp(urb_p);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* utilities ; almost verbatim from dummy_hcd.c */
 | 
						|
 | 
						|
/* called with spinlock held */
 | 
						|
static void nuke(struct vudc *udc, struct vep *ep)
 | 
						|
{
 | 
						|
	struct vrequest	*req;
 | 
						|
 | 
						|
	while (!list_empty(&ep->req_queue)) {
 | 
						|
		req = list_first_entry(&ep->req_queue, struct vrequest,
 | 
						|
				       req_entry);
 | 
						|
		list_del_init(&req->req_entry);
 | 
						|
		req->req.status = -ESHUTDOWN;
 | 
						|
 | 
						|
		spin_unlock(&udc->lock);
 | 
						|
		usb_gadget_giveback_request(&ep->ep, &req->req);
 | 
						|
		spin_lock(&udc->lock);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/* caller must hold lock */
 | 
						|
static void stop_activity(struct vudc *udc)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
	struct urbp *urb_p, *tmp;
 | 
						|
 | 
						|
	udc->address = 0;
 | 
						|
 | 
						|
	for (i = 0; i < VIRTUAL_ENDPOINTS; i++)
 | 
						|
		nuke(udc, &udc->ep[i]);
 | 
						|
 | 
						|
	list_for_each_entry_safe(urb_p, tmp, &udc->urb_queue, urb_entry) {
 | 
						|
		list_del(&urb_p->urb_entry);
 | 
						|
		free_urbp_and_urb(urb_p);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
struct vep *vudc_find_endpoint(struct vudc *udc, u8 address)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
 | 
						|
	if ((address & ~USB_DIR_IN) == 0)
 | 
						|
		return &udc->ep[0];
 | 
						|
 | 
						|
	for (i = 1; i < VIRTUAL_ENDPOINTS; i++) {
 | 
						|
		struct vep *ep = &udc->ep[i];
 | 
						|
 | 
						|
		if (!ep->desc)
 | 
						|
			continue;
 | 
						|
		if (ep->desc->bEndpointAddress == address)
 | 
						|
			return ep;
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/* gadget ops */
 | 
						|
 | 
						|
static int vgadget_get_frame(struct usb_gadget *_gadget)
 | 
						|
{
 | 
						|
	struct timespec64 now;
 | 
						|
	struct vudc *udc = usb_gadget_to_vudc(_gadget);
 | 
						|
 | 
						|
	ktime_get_ts64(&now);
 | 
						|
	return ((now.tv_sec - udc->start_time.tv_sec) * 1000 +
 | 
						|
		(now.tv_nsec - udc->start_time.tv_nsec) / NSEC_PER_MSEC)
 | 
						|
			& 0x7FF;
 | 
						|
}
 | 
						|
 | 
						|
static int vgadget_set_selfpowered(struct usb_gadget *_gadget, int value)
 | 
						|
{
 | 
						|
	struct vudc *udc = usb_gadget_to_vudc(_gadget);
 | 
						|
 | 
						|
	if (value)
 | 
						|
		udc->devstatus |= (1 << USB_DEVICE_SELF_POWERED);
 | 
						|
	else
 | 
						|
		udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int vgadget_pullup(struct usb_gadget *_gadget, int value)
 | 
						|
{
 | 
						|
	struct vudc *udc = usb_gadget_to_vudc(_gadget);
 | 
						|
	unsigned long flags;
 | 
						|
	int ret;
 | 
						|
 | 
						|
 | 
						|
	spin_lock_irqsave(&udc->lock, flags);
 | 
						|
	value = !!value;
 | 
						|
	if (value == udc->pullup)
 | 
						|
		goto unlock;
 | 
						|
 | 
						|
	udc->pullup = value;
 | 
						|
	if (value) {
 | 
						|
		udc->gadget.speed = min_t(u8, USB_SPEED_HIGH,
 | 
						|
					   udc->driver->max_speed);
 | 
						|
		udc->ep[0].ep.maxpacket = 64;
 | 
						|
		/*
 | 
						|
		 * This is the first place where we can ask our
 | 
						|
		 * gadget driver for descriptors.
 | 
						|
		 */
 | 
						|
		ret = get_gadget_descs(udc);
 | 
						|
		if (ret) {
 | 
						|
			dev_err(&udc->gadget.dev, "Unable go get desc: %d", ret);
 | 
						|
			goto unlock;
 | 
						|
		}
 | 
						|
 | 
						|
		spin_unlock_irqrestore(&udc->lock, flags);
 | 
						|
		usbip_start_eh(&udc->ud);
 | 
						|
	} else {
 | 
						|
		/* Invalidate descriptors */
 | 
						|
		udc->desc_cached = 0;
 | 
						|
 | 
						|
		spin_unlock_irqrestore(&udc->lock, flags);
 | 
						|
		usbip_event_add(&udc->ud, VUDC_EVENT_REMOVED);
 | 
						|
		usbip_stop_eh(&udc->ud); /* Wait for eh completion */
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
 | 
						|
unlock:
 | 
						|
	spin_unlock_irqrestore(&udc->lock, flags);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int vgadget_udc_start(struct usb_gadget *g,
 | 
						|
		struct usb_gadget_driver *driver)
 | 
						|
{
 | 
						|
	struct vudc *udc = usb_gadget_to_vudc(g);
 | 
						|
	unsigned long flags;
 | 
						|
 | 
						|
	spin_lock_irqsave(&udc->lock, flags);
 | 
						|
	udc->driver = driver;
 | 
						|
	udc->pullup = udc->connected = udc->desc_cached = 0;
 | 
						|
	spin_unlock_irqrestore(&udc->lock, flags);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int vgadget_udc_stop(struct usb_gadget *g)
 | 
						|
{
 | 
						|
	struct vudc *udc = usb_gadget_to_vudc(g);
 | 
						|
	unsigned long flags;
 | 
						|
 | 
						|
	spin_lock_irqsave(&udc->lock, flags);
 | 
						|
	udc->driver = NULL;
 | 
						|
	spin_unlock_irqrestore(&udc->lock, flags);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static const struct usb_gadget_ops vgadget_ops = {
 | 
						|
	.get_frame	= vgadget_get_frame,
 | 
						|
	.set_selfpowered = vgadget_set_selfpowered,
 | 
						|
	.pullup		= vgadget_pullup,
 | 
						|
	.udc_start	= vgadget_udc_start,
 | 
						|
	.udc_stop	= vgadget_udc_stop,
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
/* endpoint ops */
 | 
						|
 | 
						|
static int vep_enable(struct usb_ep *_ep,
 | 
						|
		const struct usb_endpoint_descriptor *desc)
 | 
						|
{
 | 
						|
	struct vep	*ep;
 | 
						|
	struct vudc	*udc;
 | 
						|
	unsigned int	maxp;
 | 
						|
	unsigned long	flags;
 | 
						|
 | 
						|
	ep = to_vep(_ep);
 | 
						|
	udc = ep_to_vudc(ep);
 | 
						|
 | 
						|
	if (!_ep || !desc || ep->desc || _ep->caps.type_control
 | 
						|
			|| desc->bDescriptorType != USB_DT_ENDPOINT)
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	if (!udc->driver)
 | 
						|
		return -ESHUTDOWN;
 | 
						|
 | 
						|
	spin_lock_irqsave(&udc->lock, flags);
 | 
						|
 | 
						|
	maxp = usb_endpoint_maxp(desc);
 | 
						|
	_ep->maxpacket = maxp;
 | 
						|
	ep->desc = desc;
 | 
						|
	ep->type = usb_endpoint_type(desc);
 | 
						|
	ep->halted = ep->wedged = 0;
 | 
						|
 | 
						|
	spin_unlock_irqrestore(&udc->lock, flags);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int vep_disable(struct usb_ep *_ep)
 | 
						|
{
 | 
						|
	struct vep *ep;
 | 
						|
	struct vudc *udc;
 | 
						|
	unsigned long flags;
 | 
						|
 | 
						|
	ep = to_vep(_ep);
 | 
						|
	udc = ep_to_vudc(ep);
 | 
						|
	if (!_ep || !ep->desc || _ep->caps.type_control)
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	spin_lock_irqsave(&udc->lock, flags);
 | 
						|
	ep->desc = NULL;
 | 
						|
	nuke(udc, ep);
 | 
						|
	spin_unlock_irqrestore(&udc->lock, flags);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static struct usb_request *vep_alloc_request(struct usb_ep *_ep,
 | 
						|
		gfp_t mem_flags)
 | 
						|
{
 | 
						|
	struct vrequest *req;
 | 
						|
 | 
						|
	if (!_ep)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	req = kzalloc(sizeof(*req), mem_flags);
 | 
						|
	if (!req)
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	INIT_LIST_HEAD(&req->req_entry);
 | 
						|
 | 
						|
	return &req->req;
 | 
						|
}
 | 
						|
 | 
						|
static void vep_free_request(struct usb_ep *_ep, struct usb_request *_req)
 | 
						|
{
 | 
						|
	struct vrequest *req;
 | 
						|
 | 
						|
	/* ep is always valid here - see usb_ep_free_request() */
 | 
						|
	if (!_req)
 | 
						|
		return;
 | 
						|
 | 
						|
	req = to_vrequest(_req);
 | 
						|
	kfree(req);
 | 
						|
}
 | 
						|
 | 
						|
static int vep_queue(struct usb_ep *_ep, struct usb_request *_req,
 | 
						|
		gfp_t mem_flags)
 | 
						|
{
 | 
						|
	struct vep *ep;
 | 
						|
	struct vrequest *req;
 | 
						|
	struct vudc *udc;
 | 
						|
	unsigned long flags;
 | 
						|
 | 
						|
	if (!_ep || !_req)
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	ep = to_vep(_ep);
 | 
						|
	req = to_vrequest(_req);
 | 
						|
	udc = ep_to_vudc(ep);
 | 
						|
 | 
						|
	spin_lock_irqsave(&udc->lock, flags);
 | 
						|
	_req->actual = 0;
 | 
						|
	_req->status = -EINPROGRESS;
 | 
						|
 | 
						|
	list_add_tail(&req->req_entry, &ep->req_queue);
 | 
						|
	spin_unlock_irqrestore(&udc->lock, flags);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int vep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
 | 
						|
{
 | 
						|
	struct vep *ep;
 | 
						|
	struct vrequest *req;
 | 
						|
	struct vudc *udc;
 | 
						|
	struct vrequest *lst;
 | 
						|
	unsigned long flags;
 | 
						|
	int ret = -EINVAL;
 | 
						|
 | 
						|
	if (!_ep || !_req)
 | 
						|
		return ret;
 | 
						|
 | 
						|
	ep = to_vep(_ep);
 | 
						|
	req = to_vrequest(_req);
 | 
						|
	udc = req->udc;
 | 
						|
 | 
						|
	if (!udc->driver)
 | 
						|
		return -ESHUTDOWN;
 | 
						|
 | 
						|
	spin_lock_irqsave(&udc->lock, flags);
 | 
						|
	list_for_each_entry(lst, &ep->req_queue, req_entry) {
 | 
						|
		if (&lst->req == _req) {
 | 
						|
			list_del_init(&lst->req_entry);
 | 
						|
			_req->status = -ECONNRESET;
 | 
						|
			ret = 0;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	spin_unlock_irqrestore(&udc->lock, flags);
 | 
						|
 | 
						|
	if (ret == 0)
 | 
						|
		usb_gadget_giveback_request(_ep, _req);
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
vep_set_halt_and_wedge(struct usb_ep *_ep, int value, int wedged)
 | 
						|
{
 | 
						|
	struct vep *ep;
 | 
						|
	struct vudc *udc;
 | 
						|
	unsigned long flags;
 | 
						|
	int ret = 0;
 | 
						|
 | 
						|
	ep = to_vep(_ep);
 | 
						|
	if (!_ep)
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	udc = ep_to_vudc(ep);
 | 
						|
	if (!udc->driver)
 | 
						|
		return -ESHUTDOWN;
 | 
						|
 | 
						|
	spin_lock_irqsave(&udc->lock, flags);
 | 
						|
	if (!value)
 | 
						|
		ep->halted = ep->wedged = 0;
 | 
						|
	else if (ep->desc && (ep->desc->bEndpointAddress & USB_DIR_IN) &&
 | 
						|
			!list_empty(&ep->req_queue))
 | 
						|
		ret = -EAGAIN;
 | 
						|
	else {
 | 
						|
		ep->halted = 1;
 | 
						|
		if (wedged)
 | 
						|
			ep->wedged = 1;
 | 
						|
	}
 | 
						|
 | 
						|
	spin_unlock_irqrestore(&udc->lock, flags);
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
vep_set_halt(struct usb_ep *_ep, int value)
 | 
						|
{
 | 
						|
	return vep_set_halt_and_wedge(_ep, value, 0);
 | 
						|
}
 | 
						|
 | 
						|
static int vep_set_wedge(struct usb_ep *_ep)
 | 
						|
{
 | 
						|
	return vep_set_halt_and_wedge(_ep, 1, 1);
 | 
						|
}
 | 
						|
 | 
						|
static const struct usb_ep_ops vep_ops = {
 | 
						|
	.enable		= vep_enable,
 | 
						|
	.disable	= vep_disable,
 | 
						|
 | 
						|
	.alloc_request	= vep_alloc_request,
 | 
						|
	.free_request	= vep_free_request,
 | 
						|
 | 
						|
	.queue		= vep_queue,
 | 
						|
	.dequeue	= vep_dequeue,
 | 
						|
 | 
						|
	.set_halt	= vep_set_halt,
 | 
						|
	.set_wedge	= vep_set_wedge,
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
/* shutdown / reset / error handlers */
 | 
						|
 | 
						|
static void vudc_shutdown(struct usbip_device *ud)
 | 
						|
{
 | 
						|
	struct vudc *udc = container_of(ud, struct vudc, ud);
 | 
						|
	int call_disconnect = 0;
 | 
						|
	unsigned long flags;
 | 
						|
 | 
						|
	dev_dbg(&udc->pdev->dev, "device shutdown");
 | 
						|
	if (ud->tcp_socket)
 | 
						|
		kernel_sock_shutdown(ud->tcp_socket, SHUT_RDWR);
 | 
						|
 | 
						|
	if (ud->tcp_rx) {
 | 
						|
		kthread_stop_put(ud->tcp_rx);
 | 
						|
		ud->tcp_rx = NULL;
 | 
						|
	}
 | 
						|
	if (ud->tcp_tx) {
 | 
						|
		kthread_stop_put(ud->tcp_tx);
 | 
						|
		ud->tcp_tx = NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	if (ud->tcp_socket) {
 | 
						|
		sockfd_put(ud->tcp_socket);
 | 
						|
		ud->tcp_socket = NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	spin_lock_irqsave(&udc->lock, flags);
 | 
						|
	stop_activity(udc);
 | 
						|
	if (udc->connected && udc->driver->disconnect)
 | 
						|
		call_disconnect = 1;
 | 
						|
	udc->connected = 0;
 | 
						|
	spin_unlock_irqrestore(&udc->lock, flags);
 | 
						|
	if (call_disconnect)
 | 
						|
		udc->driver->disconnect(&udc->gadget);
 | 
						|
}
 | 
						|
 | 
						|
static void vudc_device_reset(struct usbip_device *ud)
 | 
						|
{
 | 
						|
	struct vudc *udc = container_of(ud, struct vudc, ud);
 | 
						|
	unsigned long flags;
 | 
						|
 | 
						|
	dev_dbg(&udc->pdev->dev, "device reset");
 | 
						|
	spin_lock_irqsave(&udc->lock, flags);
 | 
						|
	stop_activity(udc);
 | 
						|
	spin_unlock_irqrestore(&udc->lock, flags);
 | 
						|
	if (udc->driver)
 | 
						|
		usb_gadget_udc_reset(&udc->gadget, udc->driver);
 | 
						|
	spin_lock_irqsave(&ud->lock, flags);
 | 
						|
	ud->status = SDEV_ST_AVAILABLE;
 | 
						|
	spin_unlock_irqrestore(&ud->lock, flags);
 | 
						|
}
 | 
						|
 | 
						|
static void vudc_device_unusable(struct usbip_device *ud)
 | 
						|
{
 | 
						|
	unsigned long flags;
 | 
						|
 | 
						|
	spin_lock_irqsave(&ud->lock, flags);
 | 
						|
	ud->status = SDEV_ST_ERROR;
 | 
						|
	spin_unlock_irqrestore(&ud->lock, flags);
 | 
						|
}
 | 
						|
 | 
						|
/* device setup / cleanup */
 | 
						|
 | 
						|
struct vudc_device *alloc_vudc_device(int devid)
 | 
						|
{
 | 
						|
	struct vudc_device *udc_dev = NULL;
 | 
						|
 | 
						|
	udc_dev = kzalloc(sizeof(*udc_dev), GFP_KERNEL);
 | 
						|
	if (!udc_dev)
 | 
						|
		goto out;
 | 
						|
 | 
						|
	INIT_LIST_HEAD(&udc_dev->dev_entry);
 | 
						|
 | 
						|
	udc_dev->pdev = platform_device_alloc(GADGET_NAME, devid);
 | 
						|
	if (!udc_dev->pdev) {
 | 
						|
		kfree(udc_dev);
 | 
						|
		udc_dev = NULL;
 | 
						|
	}
 | 
						|
 | 
						|
out:
 | 
						|
	return udc_dev;
 | 
						|
}
 | 
						|
 | 
						|
void put_vudc_device(struct vudc_device *udc_dev)
 | 
						|
{
 | 
						|
	platform_device_put(udc_dev->pdev);
 | 
						|
	kfree(udc_dev);
 | 
						|
}
 | 
						|
 | 
						|
static int init_vudc_hw(struct vudc *udc)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
	struct usbip_device *ud = &udc->ud;
 | 
						|
	struct vep *ep;
 | 
						|
 | 
						|
	udc->ep = kcalloc(VIRTUAL_ENDPOINTS, sizeof(*udc->ep), GFP_KERNEL);
 | 
						|
	if (!udc->ep)
 | 
						|
		goto nomem_ep;
 | 
						|
 | 
						|
	INIT_LIST_HEAD(&udc->gadget.ep_list);
 | 
						|
 | 
						|
	/* create ep0 and 15 in, 15 out general purpose eps */
 | 
						|
	for (i = 0; i < VIRTUAL_ENDPOINTS; ++i) {
 | 
						|
		int is_out = i % 2;
 | 
						|
		int num = (i + 1) / 2;
 | 
						|
 | 
						|
		ep = &udc->ep[i];
 | 
						|
 | 
						|
		sprintf(ep->name, "ep%d%s", num,
 | 
						|
			i ? (is_out ? "out" : "in") : "");
 | 
						|
		ep->ep.name = ep->name;
 | 
						|
 | 
						|
		ep->ep.ops = &vep_ops;
 | 
						|
 | 
						|
		usb_ep_set_maxpacket_limit(&ep->ep, ~0);
 | 
						|
		ep->ep.max_streams = 16;
 | 
						|
		ep->gadget = &udc->gadget;
 | 
						|
		INIT_LIST_HEAD(&ep->req_queue);
 | 
						|
 | 
						|
		if (i == 0) {
 | 
						|
			/* ep0 */
 | 
						|
			ep->ep.caps.type_control = true;
 | 
						|
			ep->ep.caps.dir_out = true;
 | 
						|
			ep->ep.caps.dir_in = true;
 | 
						|
 | 
						|
			udc->gadget.ep0 = &ep->ep;
 | 
						|
		} else {
 | 
						|
			/* All other eps */
 | 
						|
			ep->ep.caps.type_iso = true;
 | 
						|
			ep->ep.caps.type_int = true;
 | 
						|
			ep->ep.caps.type_bulk = true;
 | 
						|
 | 
						|
			if (is_out)
 | 
						|
				ep->ep.caps.dir_out = true;
 | 
						|
			else
 | 
						|
				ep->ep.caps.dir_in = true;
 | 
						|
 | 
						|
			list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	spin_lock_init(&udc->lock);
 | 
						|
	spin_lock_init(&udc->lock_tx);
 | 
						|
	INIT_LIST_HEAD(&udc->urb_queue);
 | 
						|
	INIT_LIST_HEAD(&udc->tx_queue);
 | 
						|
	init_waitqueue_head(&udc->tx_waitq);
 | 
						|
 | 
						|
	spin_lock_init(&ud->lock);
 | 
						|
	ud->status = SDEV_ST_AVAILABLE;
 | 
						|
	ud->side = USBIP_VUDC;
 | 
						|
 | 
						|
	ud->eh_ops.shutdown = vudc_shutdown;
 | 
						|
	ud->eh_ops.reset    = vudc_device_reset;
 | 
						|
	ud->eh_ops.unusable = vudc_device_unusable;
 | 
						|
 | 
						|
	v_init_timer(udc);
 | 
						|
	return 0;
 | 
						|
 | 
						|
nomem_ep:
 | 
						|
		return -ENOMEM;
 | 
						|
}
 | 
						|
 | 
						|
static void cleanup_vudc_hw(struct vudc *udc)
 | 
						|
{
 | 
						|
	kfree(udc->ep);
 | 
						|
}
 | 
						|
 | 
						|
/* platform driver ops */
 | 
						|
 | 
						|
int vudc_probe(struct platform_device *pdev)
 | 
						|
{
 | 
						|
	struct vudc *udc;
 | 
						|
	int ret = -ENOMEM;
 | 
						|
 | 
						|
	udc = kzalloc(sizeof(*udc), GFP_KERNEL);
 | 
						|
	if (!udc)
 | 
						|
		goto out;
 | 
						|
 | 
						|
	udc->gadget.name = GADGET_NAME;
 | 
						|
	udc->gadget.ops = &vgadget_ops;
 | 
						|
	udc->gadget.max_speed = USB_SPEED_HIGH;
 | 
						|
	udc->gadget.dev.parent = &pdev->dev;
 | 
						|
	udc->pdev = pdev;
 | 
						|
 | 
						|
	ret = init_vudc_hw(udc);
 | 
						|
	if (ret)
 | 
						|
		goto err_init_vudc_hw;
 | 
						|
 | 
						|
	ret = usb_add_gadget_udc(&pdev->dev, &udc->gadget);
 | 
						|
	if (ret < 0)
 | 
						|
		goto err_add_udc;
 | 
						|
 | 
						|
	ret = sysfs_create_group(&pdev->dev.kobj, &vudc_attr_group);
 | 
						|
	if (ret) {
 | 
						|
		dev_err(&udc->pdev->dev, "create sysfs files\n");
 | 
						|
		goto err_sysfs;
 | 
						|
	}
 | 
						|
 | 
						|
	platform_set_drvdata(pdev, udc);
 | 
						|
 | 
						|
	return ret;
 | 
						|
 | 
						|
err_sysfs:
 | 
						|
	usb_del_gadget_udc(&udc->gadget);
 | 
						|
err_add_udc:
 | 
						|
	cleanup_vudc_hw(udc);
 | 
						|
err_init_vudc_hw:
 | 
						|
	kfree(udc);
 | 
						|
out:
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
int vudc_remove(struct platform_device *pdev)
 | 
						|
{
 | 
						|
	struct vudc *udc = platform_get_drvdata(pdev);
 | 
						|
 | 
						|
	sysfs_remove_group(&pdev->dev.kobj, &vudc_attr_group);
 | 
						|
	usb_del_gadget_udc(&udc->gadget);
 | 
						|
	cleanup_vudc_hw(udc);
 | 
						|
	kfree(udc);
 | 
						|
	return 0;
 | 
						|
}
 |