[PATCH] USB: gadget: add function module for vendor specific interface

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

 



This new gadget function driver adds a vendor-specific interface with
two bulk endpoints. A misc device is registered and data sent to the
OUT endpoint of the interface is available as input stream to userspace
applications, and writing data to the char device from userspace will
issue IN packets on the USB interface.

There is currently no user of this module in mainline, but it can be
easily added to any existing HID device drivers.

Signed-off-by: Daniel Mack <zonque@xxxxxxxxx>
Cc: Felipe Balbi <balbi@xxxxxx>
Cc: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
---
 drivers/usb/gadget/f_vendor_spec.c |  608 ++++++++++++++++++++++++++++++++++++
 1 file changed, 608 insertions(+)
 create mode 100644 drivers/usb/gadget/f_vendor_spec.c

diff --git a/drivers/usb/gadget/f_vendor_spec.c b/drivers/usb/gadget/f_vendor_spec.c
new file mode 100644
index 0000000..e582723
--- /dev/null
+++ b/drivers/usb/gadget/f_vendor_spec.c
@@ -0,0 +1,608 @@
+/*
+ * f_vspec.c - USB peripheral driver for vendor specific bulk endpoints
+ *
+ * Copyright (C) 2012 Daniel Mack
+ *
+ * Uses code from f_vspec.c and f_loopback.c,
+ *   Copyright (C) 2003-2008 David Brownell
+ *   Copyright (C) 2008 by Nokia Corporation
+ *
+ * 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 driver registers a misc device which serves as user-space
+ * interface for reading and writing data. See /proc/misc for the
+ * dynamically allocated minor number.
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <linux/usb/composite.h>
+
+#include "gadget_chips.h"
+
+struct f_vspec_req_list {
+	struct usb_request	*req;
+	unsigned int		pos;
+	struct list_head 	list;
+};
+
+struct f_vspec {
+	struct usb_function	function;
+
+	unsigned int		ep_size;
+	unsigned int		qlen;
+
+	struct usb_ep		*in_ep;
+	struct usb_ep		*out_ep;
+
+	struct list_head	completed_out_req;
+	struct usb_request	*in_req;
+	int			write_pending:1;
+
+	wait_queue_head_t	read_queue;
+	wait_queue_head_t	write_queue;
+
+	struct mutex		mutex;
+	spinlock_t		spinlock;
+	struct miscdevice	miscdev;
+};
+
+static inline struct f_vspec *func_to_vspec(struct usb_function *f)
+{
+	return container_of(f, struct f_vspec, function);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_interface_descriptor vspec_intf = {
+	.bLength		= sizeof vspec_intf,
+	.bDescriptorType	= USB_DT_INTERFACE,
+	.bNumEndpoints		= 2,
+	.bInterfaceClass	= USB_CLASS_VENDOR_SPEC,
+	/* .iInterface = DYNAMIC */
+};
+
+/* full speed support */
+
+static struct usb_endpoint_descriptor fs_vspec_source_desc = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+
+	.bEndpointAddress	= USB_DIR_IN,
+	.bmAttributes		= USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor fs_vspec_sink_desc = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+
+	.bEndpointAddress	= USB_DIR_OUT,
+	.bmAttributes		= USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *fs_vspec_descs[] = {
+	(struct usb_descriptor_header *) &vspec_intf,
+	(struct usb_descriptor_header *) &fs_vspec_sink_desc,
+	(struct usb_descriptor_header *) &fs_vspec_source_desc,
+	NULL,
+};
+
+/* high speed support */
+
+static struct usb_endpoint_descriptor hs_vspec_source_desc = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+	.bmAttributes		= USB_ENDPOINT_XFER_BULK,
+	/* .wMaxPacketSize is set dynamically */
+};
+
+static struct usb_endpoint_descriptor hs_vspec_sink_desc = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+
+	.bmAttributes		= USB_ENDPOINT_XFER_BULK,
+	/* .wMaxPacketSize is set dynamically */
+};
+
+static struct usb_descriptor_header *hs_vspec_descs[] = {
+	(struct usb_descriptor_header *) &vspec_intf,
+	(struct usb_descriptor_header *) &hs_vspec_source_desc,
+	(struct usb_descriptor_header *) &hs_vspec_sink_desc,
+	NULL,
+};
+
+/* super speed support */
+
+static struct usb_endpoint_descriptor ss_vspec_source_desc = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+	.bmAttributes		= USB_ENDPOINT_XFER_BULK,
+	/* .wMaxPacketSize is set dynamically */
+};
+
+struct usb_ss_ep_comp_descriptor ss_vspec_source_comp_desc = {
+	.bLength		= USB_DT_SS_EP_COMP_SIZE,
+	.bDescriptorType	= USB_DT_SS_ENDPOINT_COMP,
+	.bMaxBurst		= 0,
+	.bmAttributes		= 0,
+	.wBytesPerInterval	= 0,
+};
+
+static struct usb_endpoint_descriptor ss_vspec_sink_desc = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+	.bmAttributes		= USB_ENDPOINT_XFER_BULK,
+	/* .wMaxPacketSize is set dynamically */
+};
+
+struct usb_ss_ep_comp_descriptor ss_vspec_sink_comp_desc = {
+	.bLength		= USB_DT_SS_EP_COMP_SIZE,
+	.bDescriptorType	= USB_DT_SS_ENDPOINT_COMP,
+	.bMaxBurst		= 0,
+	.bmAttributes		= 0,
+	.wBytesPerInterval	= 0,
+};
+
+static struct usb_descriptor_header *ss_vspec_descs[] = {
+	(struct usb_descriptor_header *) &vspec_intf,
+	(struct usb_descriptor_header *) &ss_vspec_source_desc,
+	(struct usb_descriptor_header *) &ss_vspec_source_comp_desc,
+	(struct usb_descriptor_header *) &ss_vspec_sink_desc,
+	(struct usb_descriptor_header *) &ss_vspec_sink_comp_desc,
+	NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int __init
+vspec_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_vspec *vspec = func_to_vspec(f);
+	int id;
+
+	/* allocate interface ID(s) */
+	id = usb_interface_id(c, f);
+	if (id < 0)
+		return id;
+	vspec_intf.bInterfaceNumber = id;
+
+	/* allocate endpoints */
+	vspec->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_vspec_source_desc);
+	if (!vspec->in_ep) {
+autoconf_fail:
+		ERROR(cdev, "%s: can't autoconfigure on %s\n",
+			f->name, cdev->gadget->name);
+		return -ENODEV;
+	}
+	vspec->in_ep->driver_data = cdev;	/* claim */
+
+	vspec->out_ep = usb_ep_autoconfig(cdev->gadget, &fs_vspec_sink_desc);
+	if (!vspec->out_ep)
+		goto autoconf_fail;
+	vspec->out_ep->driver_data = cdev;	/* claim */
+
+	/* support high speed hardware */
+	if (gadget_is_dualspeed(c->cdev->gadget)) {
+		hs_vspec_source_desc.bEndpointAddress =
+				fs_vspec_source_desc.bEndpointAddress;
+		hs_vspec_sink_desc.bEndpointAddress =
+				fs_vspec_sink_desc.bEndpointAddress;
+		f->hs_descriptors = hs_vspec_descs;
+	}
+
+	/* support super speed hardware */
+	if (gadget_is_superspeed(c->cdev->gadget)) {
+		ss_vspec_source_desc.bEndpointAddress =
+				fs_vspec_source_desc.bEndpointAddress;
+		ss_vspec_sink_desc.bEndpointAddress =
+				fs_vspec_sink_desc.bEndpointAddress;
+		f->ss_descriptors = ss_vspec_descs;
+	}
+
+	DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
+	    (gadget_is_superspeed(c->cdev->gadget) ? "super" :
+	     (gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full")),
+			f->name, vspec->in_ep->name, vspec->out_ep->name);
+	return 0;
+}
+
+static void vspec_unbind(struct usb_configuration *c,
+			 struct usb_function *f)
+{
+	struct f_vspec *vspec = func_to_vspec(f);
+
+	if (vspec->in_req) {
+		free_ep_req(vspec->in_ep, vspec->in_req);
+		vspec->in_req = NULL;
+	}
+
+	misc_deregister(&vspec->miscdev);
+	kfree(vspec);
+}
+
+static void f_vspec_handle_in_completion(struct f_vspec *vspec,
+					 struct usb_request *req)
+{
+	vspec->write_pending = 0;
+	wake_up(&vspec->write_queue);
+}
+
+static void f_vspec_handle_out_completion(struct f_vspec *vspec,
+					  struct usb_request *req)
+{
+	struct f_vspec_req_list *req_list;
+	unsigned long flags;
+
+	req_list = kzalloc(sizeof(*req_list), GFP_ATOMIC);
+	if (!req_list)
+		return;
+
+	req_list->req = req;
+
+	spin_lock_irqsave(&vspec->spinlock, flags);
+	list_add_tail(&req_list->list, &vspec->completed_out_req);
+	spin_unlock_irqrestore(&vspec->spinlock, flags);
+
+	wake_up(&vspec->read_queue);
+}
+
+static void vspec_complete(struct usb_ep *ep,
+			   struct usb_request *req)
+{
+	struct f_vspec *vspec = ep->driver_data;
+	struct usb_composite_dev *cdev = vspec->function.config->cdev;
+
+	switch (req->status) {
+	case 0:	/* normal completion */
+		if (ep == vspec->in_ep)
+			f_vspec_handle_in_completion(vspec, req);
+
+		if (ep == vspec->out_ep)
+			f_vspec_handle_out_completion(vspec, req);
+
+		break;
+
+	default:
+		ERROR(cdev, "%s vspec complete --> %d, %d/%d\n", ep->name,
+				req->status, req->actual, req->length);
+		/* FALLTHROUGH */
+
+	/*
+	 * We rely on the hardware driver to clean up on disconnect
+	 * or endpoint disable.
+	 */
+	case -ECONNABORTED:	/* hardware forced ep reset */
+	case -ECONNRESET:	/* request dequeued */
+	case -ESHUTDOWN:	/* disconnect from host */
+		free_ep_req(ep, req);
+		return;
+	}
+}
+
+static void disable_ep(struct usb_composite_dev *cdev,
+		       struct usb_ep *ep)
+{
+	int ret;
+
+	if (ep->driver_data) {
+		ret = usb_ep_disable(ep);
+		if (ret < 0)
+			DBG(cdev, "disable %s --> %d\n", ep->name, ret);
+		ep->driver_data = NULL;
+	}
+}
+
+static void disable_vspec(struct f_vspec *vspec)
+{
+	struct usb_composite_dev *cdev;
+	struct f_vspec_req_list *list, *next;
+
+	cdev = vspec->function.config->cdev;
+	disable_ep(cdev, vspec->in_ep);
+	disable_ep(cdev, vspec->out_ep);
+
+	list_for_each_entry_safe(list, next, &vspec->completed_out_req, list) {
+		list_del(&list->list);
+		kfree(list);
+	}
+
+	VDBG(cdev, "%s disabled\n", vspec->function.name);
+}
+
+static int enable_vspec(struct usb_composite_dev *cdev,
+			struct f_vspec *vspec)
+{
+	int			result = 0;
+	struct usb_ep		*ep;
+	struct usb_request	*req;
+	unsigned		i;
+
+	/* one endpoint writes data IN to the host */
+	ep = vspec->in_ep;
+	result = config_ep_by_speed(cdev->gadget, &(vspec->function), ep);
+	if (result)
+		return result;
+	result = usb_ep_enable(ep);
+	if (result < 0)
+		return result;
+	ep->driver_data = vspec;
+
+	/* one endpoint just reads OUT packets */
+	ep = vspec->out_ep;
+	result = config_ep_by_speed(cdev->gadget, &(vspec->function), ep);
+	if (result)
+		goto fail0;
+
+	result = usb_ep_enable(ep);
+	if (result < 0) {
+fail0:
+		ep = vspec->in_ep;
+		usb_ep_disable(ep);
+		ep->driver_data = NULL;
+		return result;
+	}
+	ep->driver_data = vspec;
+
+	/*
+	 * allocate a bunch of read buffers and queue them all at once.
+	 */
+	for (i = 0; i < vspec->qlen && result == 0; i++) {
+		req = alloc_ep_req(ep, vspec->ep_size);
+		if (req) {
+			req->complete = vspec_complete;
+			result = usb_ep_queue(ep, req, GFP_ATOMIC);
+			if (result)
+				ERROR(cdev, "%s queue req --> %d\n",
+						ep->name, result);
+		} else {
+			usb_ep_disable(ep);
+			ep->driver_data = NULL;
+			result = -ENOMEM;
+			goto fail0;
+		}
+	}
+
+	DBG(cdev, "%s enabled\n", vspec->function.name);
+	return result;
+}
+
+static int vspec_set_alt(struct usb_function *f,
+			 unsigned intf, unsigned alt)
+{
+	struct f_vspec	*vspec = func_to_vspec(f);
+	struct usb_composite_dev *cdev = f->config->cdev;
+
+	/* we know alt is zero */
+	if (vspec->in_ep->driver_data)
+		disable_vspec(vspec);
+
+	return enable_vspec(cdev, vspec);
+}
+
+static void vspec_disable(struct usb_function *f)
+{
+	struct f_vspec *vspec = func_to_vspec(f);
+	disable_vspec(vspec);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static struct f_vspec *file_to_vspec(const struct file *file)
+{
+	return container_of(file->private_data, struct f_vspec, miscdev);
+}
+
+static ssize_t vspec_miscdev_read(struct file *file, char __user *buf,
+				  size_t count, loff_t *ppos)
+{
+	struct f_vspec *vspec = file_to_vspec(file);
+	struct f_vspec_req_list *list;
+	struct usb_request *req;
+	unsigned long flags;
+	int ret;
+
+	if (!count)
+		return 0;
+
+	if (!access_ok(VERIFY_WRITE, buf, count))
+		return -EFAULT;
+
+	spin_lock_irqsave(&vspec->spinlock, flags);
+
+	/* wait for at least one buffer to complete */
+	while (list_empty(&vspec->completed_out_req)) {
+		spin_unlock_irqrestore(&vspec->spinlock, flags);
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+
+		if (wait_event_interruptible(vspec->read_queue,
+			!list_empty(&vspec->completed_out_req)))
+			return -ERESTARTSYS;
+
+		spin_lock_irqsave(&vspec->spinlock, flags);
+	}
+
+	/* pick the first one */
+	list = list_first_entry(&vspec->completed_out_req,
+				struct f_vspec_req_list, list);
+	req = list->req;
+	count = min_t(unsigned int, count, req->actual - list->pos);
+	spin_unlock_irqrestore(&vspec->spinlock, flags);
+
+	/* copy to user outside spinlock */
+	count -= copy_to_user(buf, req->buf + list->pos, count);
+	list->pos += count;
+
+	/*
+	 * if this request is completely handled and transfered to
+	 * userspace, remove its entry from the list and requeue it
+	 * again. Otherwise, we will revisit it again upon the next
+	 * call, taking into account its current read position.
+	 */
+	if (list->pos == req->actual) {
+		spin_lock_irqsave(&vspec->spinlock, flags);
+		list_del(&list->list);
+		kfree(list);
+		spin_unlock_irqrestore(&vspec->spinlock, flags);
+
+		req->length = vspec->ep_size;
+		ret = usb_ep_queue(vspec->out_ep, req, GFP_KERNEL);
+		if (ret < 0)
+			return ret;
+	}
+
+	return count;
+}
+
+static ssize_t vspec_miscdev_write(struct file *file, const char __user *buf,
+				   size_t count, loff_t *ppos)
+{
+	struct f_vspec *vspec = file_to_vspec(file);
+	int ret;
+
+	if (!access_ok(VERIFY_READ, buf, count))
+		return -EFAULT;
+
+	mutex_lock(&vspec->mutex);
+
+	/* wait for any pending transaction to complete */
+	while (vspec->write_pending) {
+		mutex_unlock(&vspec->mutex);
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+
+		if (wait_event_interruptible_exclusive(vspec->write_queue,
+					vspec->write_pending == 0))
+			return -ERESTARTSYS;
+
+		mutex_lock(&vspec->mutex);
+	}
+
+	count = min_t(unsigned int, count, vspec->ep_size);
+	ret = copy_from_user(vspec->in_req->buf, buf, count);
+
+	if (ret < 0) {
+		mutex_unlock(&vspec->mutex);
+		return -EINVAL;
+	}
+
+	vspec->in_req->status   = 0;
+	vspec->in_req->zero     = 0;
+	vspec->in_req->length   = count;
+	vspec->in_req->complete = vspec_complete;
+	vspec->in_req->context  = vspec;
+	vspec->write_pending = 1;
+
+	ret = usb_ep_queue(vspec->in_ep, vspec->in_req, GFP_ATOMIC);
+	if (ret < 0) {
+		ERROR(vspec->function.config->cdev,
+			"usb_ep_queue error on IN endpoint %zd\n", ret);
+		vspec->write_pending = 0;
+		count = ret;
+		wake_up(&vspec->write_queue);
+	}
+
+	mutex_unlock(&vspec->mutex);
+
+	return count;
+}
+
+static unsigned int vspec_miscdev_poll(struct file *file, poll_table *wait)
+{
+	struct f_vspec *vspec = file_to_vspec(file);
+	unsigned int ret = 0;
+
+	poll_wait(file, &vspec->read_queue, wait);
+	poll_wait(file, &vspec->write_queue, wait);
+
+	if (!vspec->write_pending)
+		ret |= POLLOUT | POLLWRNORM;
+
+	if (!list_empty(&vspec->completed_out_req))
+		ret |= POLLIN | POLLRDNORM;
+
+	return ret;
+}
+
+static const struct file_operations f_vspec_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.open		= nonseekable_open,
+	.read		= vspec_miscdev_read,
+	.write		= vspec_miscdev_write,
+	.poll		= vspec_miscdev_poll,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int __init f_vendor_spec_bind_config(struct usb_configuration *c,
+					    unsigned int ep_size,
+					    unsigned int qlen)
+{
+	struct f_vspec *vspec;
+	int ret;
+
+	vspec = kzalloc(sizeof *vspec, GFP_KERNEL);
+	if (!vspec)
+		return -ENOMEM;
+
+	vspec->function.name = "vspec";
+	vspec->function.descriptors = fs_vspec_descs;
+	vspec->function.bind = vspec_bind;
+	vspec->function.unbind = vspec_unbind;
+	vspec->function.set_alt = vspec_set_alt;
+	vspec->function.disable = vspec_disable;
+
+	hs_vspec_source_desc.wMaxPacketSize = cpu_to_le16(ep_size);
+	hs_vspec_sink_desc.wMaxPacketSize   = cpu_to_le16(ep_size);
+	ss_vspec_source_desc.wMaxPacketSize = cpu_to_le16(ep_size);
+	ss_vspec_sink_desc.wMaxPacketSize   = cpu_to_le16(ep_size);
+
+	vspec->ep_size = ep_size;
+	vspec->qlen = qlen;
+
+	INIT_LIST_HEAD(&vspec->completed_out_req);
+	spin_lock_init(&vspec->spinlock);
+	mutex_init(&vspec->mutex);
+
+	init_waitqueue_head(&vspec->write_queue);
+	init_waitqueue_head(&vspec->read_queue);
+
+	ret = usb_add_function(c, &vspec->function);
+	if (ret)
+		goto error_free_vspec;
+
+	vspec->in_req = alloc_ep_req(vspec->in_ep, ep_size);
+	if (vspec->in_req == NULL) {
+		ret = -ENOMEM;
+		goto error_free_vspec;
+	}
+
+	vspec->miscdev.minor = MISC_DYNAMIC_MINOR;
+	vspec->miscdev.name = "usb_gadget_vspec";
+	vspec->miscdev.fops = &f_vspec_fops;
+	ret = misc_register(&vspec->miscdev);
+	if (ret)
+		goto error_free_req;
+
+	return 0;
+
+error_free_req:
+	free_ep_req(vspec->in_ep, vspec->in_req);
+	vspec->in_req = NULL;
+
+error_free_vspec:
+	kfree(vspec);
+
+	return ret;
+}
-- 
1.7.10.2

--
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