[PATCH 07/12] usbip: vudc: Add UDC specific ops

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Igor Kotrasinski <i.kotrasinsk@xxxxxxxxxxx>

Add endpoints definitions and ops for both endpoints and gadget.
Add also a suitable platform driver and functions for handling
usbip events.

This commit is a result of cooperation between Samsung R&D Institute
Poland and Open Operating Systems Student Society at University
of Warsaw (O2S3@UW) consisting of:

    Igor Kotrasinski <ikotrasinsk@xxxxxxxxx>
    Karol Kosik <karo9@xxxxxxxxxx>
    Ewelina Kosmider <3w3lfin@xxxxxxxxx>
    Dawid Lazarczyk <lazarczyk.dawid@xxxxxxxxx>
    Piotr Szulc <ps347277@xxxxxxxxxxxxxxxxxxxxx>

Tutor and project owner:
    Krzysztof Opasiak <k.opasiak@xxxxxxxxxxx>

Signed-off-by: Igor Kotrasinski <i.kotrasinsk@xxxxxxxxxxx>
Signed-off-by: Karol Kosik <karo9@xxxxxxxxxx>
[Various bug fixes, improvements and commit msg update]
Signed-off-by: Krzysztof Opasiak <k.opasiak@xxxxxxxxxxx>
---
 drivers/usb/usbip/vudc_dev.c |  662 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 662 insertions(+)
 create mode 100644 drivers/usb/usbip/vudc_dev.c

diff --git a/drivers/usb/usbip/vudc_dev.c b/drivers/usb/usbip/vudc_dev.c
new file mode 100644
index 0000000..43047f4
--- /dev/null
+++ b/drivers/usb/usbip/vudc_dev.c
@@ -0,0 +1,662 @@
+/*
+ * Copyright (C) 2015 Karol Kosik <karo9@xxxxxxxxxx>
+ * Copyright (C) 2015-2016 Samsung Electronics
+ *               Igor Kotrasinski <i.kotrasinsk@xxxxxxxxxxx>
+ *               Krzysztof Opasiak <k.opasiak@xxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#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 *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 */
+
+/* FIXME - this will probably misbehave when suspend/resume is added */
+static int vgadget_get_frame(struct usb_gadget *_gadget)
+{
+	struct timeval now;
+	struct vudc *udc = usb_gadget_to_vudc(_gadget);
+
+	do_gettimeofday(&now);
+	return ((now.tv_sec - udc->start_time.tv_sec) * 1000 +
+			(now.tv_usec - udc->start_time.tv_usec) / 1000)
+			% 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 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) & 0x7ff;
+	_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 vep *ep;
+	struct vrequest *req;
+
+	if (!_ep)
+		return NULL;
+	ep = to_vep(_ep);
+
+	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;
+
+	if (!_ep || !_req) {
+		WARN_ON(1);
+		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_tx) {
+		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;
+		if (i == 0) {
+			ep->ep.caps.type_control = true;
+			ep->ep.caps.dir_out = true;
+			ep->ep.caps.dir_in = true;
+		} else {
+			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;
+
+		ep->ep.ops = &vep_ops;
+		list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list);
+		ep->halted = ep->wedged = ep->already_seen =
+			ep->setup_stage = 0;
+		usb_ep_set_maxpacket_limit(&ep->ep, ~0);
+		ep->ep.max_streams = 16;
+		ep->gadget = &udc->gadget;
+		ep->desc = NULL;
+		INIT_LIST_HEAD(&ep->req_queue);
+	}
+
+	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;
+
+	udc->gadget.ep0 = &udc->ep[0].ep;
+	list_del_init(&udc->ep[0].ep.ep_list);
+
+	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;
+}
-- 
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux