[RFC/PATCH 1/2] usb: gadget: u_char: introduce chardev abstraction layer

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

 



From: Roger Quadros <roger.quadros@xxxxxxxxx>

This provides a generic character driver implementation for use
by USB function drivers. It provides a glue between USB function
endpoints on one side and character device on the other.
The user space can read and write to the USB function's bulk OUT
and bulk IN endpoints by reading/writing the character device.

The USB function character devices will be available at
/dev/gc0...N

Signed-off-by: Roger Quadros <roger.quadros@xxxxxxxxx>
Signed-off-by: Felipe Balbi <felipe.balbi@xxxxxxxxx>
---
 drivers/usb/gadget/u_char.c |  979 +++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/gadget/u_char.h |   45 ++
 2 files changed, 1024 insertions(+), 0 deletions(-)
 create mode 100644 drivers/usb/gadget/u_char.c
 create mode 100644 drivers/usb/gadget/u_char.h

diff --git a/drivers/usb/gadget/u_char.c b/drivers/usb/gadget/u_char.c
new file mode 100644
index 0000000..7f3f437
--- /dev/null
+++ b/drivers/usb/gadget/u_char.c
@@ -0,0 +1,979 @@
+/*
+ * u_char.c - USB character device glue
+ *
+ * Copyright (C) 2009-2010 Nokia Corporation
+ * Author: Roger Quadros <roger.quadros@xxxxxxxxx>
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/poll.h>
+#include <linux/list.h>
+#include <linux/vmalloc.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include "u_char.h"
+
+/* Max simultaneous gchar devices. Increase if you need more */
+#define GC_MAX_DEVICES		4
+
+/* Number of USB requests that can be queued at a time */
+#define GC_QUEUE_SIZE		16
+
+/* size in bytes of RX and TX FIFOs */
+#define GC_BUF_SIZE		65536
+
+/*----------------USB glue----------------------------------*/
+/*
+ * gc_alloc_req
+ *
+ * Allocate a usb_request and its buffer.  Returns a pointer to the
+ * usb_request or NULL if there is an error.
+ */
+struct usb_request *
+gc_alloc_req(struct usb_ep *ep, unsigned len, gfp_t kmalloc_flags)
+{
+	struct usb_request *req;
+
+	req = usb_ep_alloc_request(ep, kmalloc_flags);
+
+	if (req != NULL) {
+		req->length = len;
+		req->buf = kmalloc(len, kmalloc_flags);
+		if (req->buf == NULL) {
+			usb_ep_free_request(ep, req);
+			return NULL;
+		}
+	}
+
+	return req;
+}
+
+/*
+ * gc_free_req
+ *
+ * Free a usb_request and its buffer.
+ */
+void gc_free_req(struct usb_ep *ep, struct usb_request *req)
+{
+	kfree(req->buf);
+	usb_ep_free_request(ep, req);
+}
+
+static int gc_alloc_requests(struct usb_ep *ep, struct list_head *head,
+		void (*fn)(struct usb_ep *, struct usb_request *))
+{
+	int                     i;
+	struct usb_request      *req;
+
+	/* Pre-allocate up to GC_QUEUE_SIZE transfers, but if we can't
+	 * do quite that many this time, don't fail ... we just won't
+	 * be as speedy as we might otherwise be.
+	 */
+	for (i = 0; i < GC_QUEUE_SIZE; i++) {
+		req = gc_alloc_req(ep, ep->maxpacket, GFP_ATOMIC);
+		if (!req)
+			return list_empty(head) ? -ENOMEM : 0;
+		req->complete = fn;
+		list_add_tail(&req->list, head);
+	}
+	return 0;
+}
+
+static void gc_free_requests(struct usb_ep *ep, struct list_head *head)
+{
+	struct usb_request      *req;
+
+	while (!list_empty(head)) {
+		req = list_entry(head->next, struct usb_request, list);
+		list_del(&req->list);
+		gc_free_req(ep, req);
+	}
+}
+
+/*----------------------------------------------------------------*/
+
+struct gc_req {
+	struct usb_request	*r;
+	ssize_t			l;
+	wait_queue_head_t	req_wait;
+};
+
+struct gc_dev {
+	struct gchar		*gchar;
+	struct device		*dev;		/* Driver model state */
+	spinlock_t		lock;		/* serialize access */
+	int			opened;		/* indicates if device open */
+	wait_queue_head_t	close_wait;	/* wait for device close */
+	int			index;		/* device index */
+
+	spinlock_t		rx_lock;	/* guard rx stuff */
+	struct kfifo		rx_fifo;
+	void			*rx_fifo_buf;
+	struct list_head	rx_pool;
+	struct tasklet_struct	rx_task;
+	wait_queue_head_t	rx_wait;	/* wait for data in RX buf */
+	unsigned int		rx_queued;	/* no. of queued requests */
+
+	spinlock_t		tx_lock;	/* guard tx stuff */
+	struct kfifo		tx_fifo;
+	void			*tx_fifo_buf;
+	struct list_head	tx_pool;
+	wait_queue_head_t	tx_wait;	/* wait for space in TX buf */
+	unsigned int		tx_flush:1;	/* flush TX buf */
+	wait_queue_head_t	tx_flush_wait;
+	int			tx_last_size;	/*last tx packet's size*/
+	struct tasklet_struct	tx_task;
+};
+
+struct gc_data {
+	struct gc_dev			*gcdevs;
+	u8				nr_devs;
+	struct class			*class;
+	dev_t				dev;
+	struct cdev			chdev;
+	struct usb_gadget		*gadget;
+};
+
+static struct gc_data gcdata;
+
+static void gc_rx_complete(struct usb_ep *ep, struct usb_request *req);
+static void gc_tx_complete(struct usb_ep *ep, struct usb_request *req);
+static int gc_do_rx(struct gc_dev *gc);
+
+
+/*----------some more USB glue---------------------------*/
+
+/* OUT complete, we have new data to read */
+static void gc_rx_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct gc_dev	*gc = ep->driver_data;
+	unsigned long	flags;
+	int		i;
+
+	spin_lock_irqsave(&gc->rx_lock, flags);
+
+	/* put received data into RX ring buffer */
+	/* we assume enough space is there in RX buffer for this request
+	 * the checking should be done in gc_do_rx() before this request
+	 * was queued */
+	switch (req->status) {
+	case 0:
+		/* normal completion */
+		i = kfifo_in(&gc->rx_fifo, req->buf, req->actual);
+		if (i != req->actual) {
+			WARN(1, KERN_ERR "%s: PUT(%d) != actual(%d) data "
+				"loss possible. rx_queued = %d\n", __func__, i,
+						req->actual, gc->rx_queued);
+		}
+		gc->rx_queued--;
+		dev_vdbg(gc->dev,
+			"%s: rx len=%d, 0x%02x 0x%02x 0x%02x ...\n", __func__,
+				req->actual, *((u8 *)req->buf),
+				*((u8 *)req->buf+1), *((u8 *)req->buf+2));
+
+		/* wake up rx_wait */
+		wake_up_interruptible(&gc->rx_wait);
+		break;
+	case -ESHUTDOWN:
+		/* disconnect */
+		dev_warn(gc->dev, "%s: %s shutdown\n", __func__, ep->name);
+		break;
+	default:
+		/* presumably a transient fault */
+		dev_warn(gc->dev, "%s: unexpected %s status %d\n",
+				__func__, ep->name, req->status);
+		break;
+	}
+
+	/* recycle req back to rx pool */
+	list_add_tail(&req->list, &gc->rx_pool);
+	spin_unlock_irqrestore(&gc->rx_lock, flags);
+	tasklet_schedule(&gc->rx_task);
+}
+
+static int gc_do_tx(struct gc_dev *gc);
+/* IN complete, i.e. USB write complete. we can free buffer */
+static void gc_tx_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct gc_dev	*gc = ep->driver_data;
+	unsigned long	flags;
+
+	spin_lock_irqsave(&gc->tx_lock, flags);
+	/* recycle back to pool */
+	list_add_tail(&req->list, &gc->tx_pool);
+	spin_unlock_irqrestore(&gc->tx_lock, flags);
+
+	switch (req->status) {
+	case 0:
+		/* normal completion, queue next request */
+		tasklet_schedule(&gc->tx_task);
+		break;
+	case -ESHUTDOWN:
+		/* disconnect */
+		dev_warn(gc->dev, "%s: %s shutdown\n", __func__, ep->name);
+		break;
+	default:
+		/* presumably a transient fault */
+		dev_warn(gc->dev, "%s: unexpected %s status %d\n",
+				__func__, ep->name, req->status);
+		break;
+	}
+}
+
+
+/* Read the TX buffer and send to USB */
+/* gc->tx_lock must be held */
+static int gc_do_tx(struct gc_dev *gc)
+{
+	struct list_head	*pool	= &gc->tx_pool;
+	struct usb_ep		*in	= gc->gchar->ep_in;
+	int			status;
+
+	if (!in)
+		return -ENODEV;
+
+	while (!list_empty(pool)) {
+		struct	usb_request	*req;
+		int	len;
+
+		req = list_entry(pool->next, struct usb_request, list);
+
+		len = kfifo_len(&gc->tx_fifo);
+		if (!len && !gc->tx_flush)
+			/* TX buf empty */
+			break;
+
+		list_del(&req->list);
+
+		req->zero = 0;
+		if (len > in->maxpacket) {
+			len = in->maxpacket;
+			gc->tx_last_size = 0;	/* not the last packet */
+		} else {
+			/* this is last packet in TX buf. send ZLP/SLP
+			 * if user has requested so
+			 */
+			req->zero = gc->tx_flush;
+			gc->tx_last_size = len;
+		}
+
+		len = kfifo_out(&gc->tx_fifo, req->buf, len);
+		req->length = len;
+
+		if (req->zero) {
+			gc->tx_flush = 0;
+			wake_up_interruptible(&gc->tx_flush_wait);
+		}
+
+		dev_vdbg(gc->dev,
+			"%s: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n", __func__,
+				len, *((u8 *)req->buf),
+				*((u8 *)req->buf+1), *((u8 *)req->buf+2));
+		/* Drop lock while we call out of driver; completions
+		 * could be issued while we do so.  Disconnection may
+		 * happen too; maybe immediately before we queue this!
+		 *
+		 * NOTE that we may keep sending data for a while after
+		 * the file is closed.
+		 */
+		spin_unlock(&gc->tx_lock);
+		status = usb_ep_queue(in, req, GFP_ATOMIC);
+		spin_lock(&gc->tx_lock);
+
+		if (status) {
+			dev_err(gc->dev, "%s: %s %s err %d\n",
+					__func__, "queue", in->name, status);
+			list_add(&req->list, pool);
+			break;
+		}
+
+		/* abort immediately after disconnect */
+		if (!gc->gchar) {
+			dev_dbg(gc->dev,
+				"%s: disconnected so aborting\n", __func__);
+			break;
+		}
+
+		/* wake up tx_wait */
+		wake_up_interruptible(&gc->tx_wait);
+	}
+	return 0;
+}
+
+static void gc_tx_task(unsigned long _gc)
+{
+	struct gc_dev	*gc = (void *)_gc;
+
+	spin_lock_irq(&gc->tx_lock);
+	if (gc->gchar && gc->gchar->ep_in)
+		gc_do_tx(gc);
+	spin_unlock_irq(&gc->tx_lock);
+}
+
+/* Tasklet:  Queue USB read requests whenever RX buffer available
+ *	Must be called with gc->rx_lock held
+ */
+static int gc_do_rx(struct gc_dev *gc)
+{
+	/* Queue the request only if required space is there in RX buffer */
+	struct list_head	*pool	= &gc->rx_pool;
+	struct usb_ep		*out	= gc->gchar->ep_out;
+	int			started = 0;
+
+	if (!out)
+		return -EINVAL;
+
+	while (!list_empty(pool)) {
+		struct usb_request      *req;
+		int                     status;
+
+		req = list_entry(pool->next, struct usb_request, list);
+		list_del(&req->list);
+		req->length = out->maxpacket;
+
+		/* check if space is available in RX buf for this request */
+		if (kfifo_avail(&gc->rx_fifo) <
+				(gc->rx_queued + 2)*req->length) {
+			/* insufficient space, recycle req */
+			list_add(&req->list, pool);
+			break;
+		}
+		gc->rx_queued++;
+
+		/* drop lock while we call out; the controller driver
+		 * may need to call us back (e.g. for disconnect)
+		 */
+		spin_unlock(&gc->rx_lock);
+		status = usb_ep_queue(out, req, GFP_ATOMIC);
+		spin_lock(&gc->rx_lock);
+
+		if (status) {
+			dev_warn(gc->dev, "%s: %s %s err %d\n",
+					__func__, "queue", out->name, status);
+			list_add(&req->list, pool);
+			break;
+		}
+
+		started++;
+
+		/* abort immediately after disconnect */
+		if (!gc->gchar) {
+			dev_dbg(gc->dev, "%s: disconnected so aborting\n",
+					__func__);
+			break;
+		}
+	}
+	return started;
+}
+
+
+static void gc_rx_task(unsigned long _gc)
+{
+	struct gc_dev	*gc = (void *)_gc;
+
+	spin_lock_irq(&gc->rx_lock);
+	if (gc->gchar && gc->gchar->ep_out)
+		gc_do_rx(gc);
+	spin_unlock_irq(&gc->rx_lock);
+}
+
+/*----------FILE Operations-------------------------------*/
+
+static int gc_open(struct inode *inode, struct file *filp)
+{
+	unsigned	minor = iminor(inode);
+	struct gc_dev	*gc;
+	int		index;
+
+	index = minor - MINOR(gcdata.dev);
+	if (index >= gcdata.nr_devs)
+		return -ENODEV;
+
+	if (!gcdata.gcdevs)
+		return -ENODEV;
+
+	if (!gcdata.gcdevs[index].gchar)
+		return -ENODEV;
+
+	filp->private_data = &gcdata.gcdevs[index];
+	gc = filp->private_data;
+
+	/* prevent multiple opens for now */
+	if (gc->opened)
+		return -EBUSY;
+	spin_lock_irq(&gc->lock);
+	if (gc->opened) {
+		spin_unlock_irq(&gc->lock);
+		return -EBUSY;
+	}
+	gc->opened = 1;
+	spin_unlock_irq(&gc->lock);
+	gc->index = index;
+
+	if (!gc->tx_fifo_buf && gc->gchar->ep_in) {
+		gc->tx_fifo_buf = vmalloc(GC_BUF_SIZE);
+		if (gc->tx_fifo_buf == NULL)
+			return -ENOMEM;
+		kfifo_init(&gcdata.gcdevs[minor].tx_fifo,
+				gc->tx_fifo_buf, GC_BUF_SIZE);
+	}
+
+	if (!gc->rx_fifo_buf && gc->gchar->ep_out) {
+		gc->rx_fifo_buf = vmalloc(GC_BUF_SIZE);
+		if (gc->rx_fifo_buf == NULL) {
+			vfree(gc->tx_fifo_buf);
+			return -ENOMEM;
+		}
+		kfifo_init(&gcdata.gcdevs[minor].rx_fifo,
+				gc->rx_fifo_buf, GC_BUF_SIZE);
+	}
+
+	if (gc->gchar && gc->gchar->open)
+		gc->gchar->open(gc->gchar);
+
+	/* if connected, start receiving */
+	if (gc->gchar)
+		tasklet_schedule(&gc->rx_task);
+
+	dev_dbg(gc->dev, "%s: gc%d opened\n", __func__, gc->index);
+	return 0;
+}
+
+static int gc_release(struct inode *inode, struct file *filp)
+{
+	struct gc_dev			*gc = filp->private_data;
+
+	filp->private_data = NULL;
+
+	dev_dbg(gc->dev, "%s: releasing gc%d\n", __func__, gc->index);
+
+	if (!gc->opened)
+		goto gc_release_exit;
+
+	vfree(gc->tx_fifo_buf);
+	gc->tx_fifo_buf = NULL;
+	vfree(gc->rx_fifo_buf);
+	gc->rx_fifo_buf = NULL;
+
+	if (gc->gchar && gc->gchar->close)
+		gc->gchar->close(gc->gchar);
+
+	spin_lock_irq(&gc->lock);
+	gc->opened = 0;
+	spin_unlock_irq(&gc->lock);
+
+	wake_up_interruptible(&gc->close_wait);
+
+gc_release_exit:
+	dev_dbg(gc->dev, "%s: gc%d released!!\n", __func__, gc->index);
+	return 0;
+}
+
+static int gc_can_read(struct gc_dev *gc)
+{
+	int ret;
+
+	spin_lock_irq(&gc->rx_lock);
+	ret = kfifo_len(&gc->rx_fifo) ? 1 : 0;
+	spin_unlock_irq(&gc->rx_lock);
+
+	return ret;
+}
+
+static ssize_t gc_read(struct file *filp, char __user *buff,
+				size_t len, loff_t *o)
+{
+	struct	gc_dev	*gc = filp->private_data;
+	int	read = 0;
+
+	if (!gc->gchar || !gc->gchar->ep_out) {
+		/* not yet connected or reading not possible*/
+		return -EINVAL;
+	}
+
+	if (len) {
+		read = kfifo_len(&gc->rx_fifo);
+		if (!read) {
+			/* if NONBLOCK then return immediately */
+			if (filp->f_flags & O_NONBLOCK)
+				return -EAGAIN;
+
+			/* sleep till we have some data */
+			if (wait_event_interruptible(gc->rx_wait,
+							gc_can_read(gc)))
+				return -ERESTARTSYS;
+
+		}
+		read = kfifo_to_user(&gc->rx_fifo, buff, len);
+	}
+
+	if (read > 0) {
+		spin_lock_irq(&gc->rx_lock);
+		gc_do_rx(gc);
+		spin_unlock_irq(&gc->rx_lock);
+	}
+
+	dev_vdbg(gc->dev, "%s done %d/%d\n", __func__, read, len);
+	return read;
+}
+
+static int gc_can_write(struct gc_dev *gc)
+{
+	int ret;
+
+	spin_lock_irq(&gc->tx_lock);
+	ret = !kfifo_is_full(&gc->tx_fifo);
+	spin_unlock_irq(&gc->tx_lock);
+
+	return ret;
+}
+
+static ssize_t gc_write(struct file *filp, const char __user *buff,
+						size_t len, loff_t *o)
+{
+	struct	gc_dev	*gc = filp->private_data;
+	int	wrote = 0;
+
+	if (!gc->gchar || !gc->gchar->ep_in) {
+		/* not yet connected or writing not possible */
+		return -EINVAL;
+	}
+
+	if (len) {
+		if (kfifo_is_full(&gc->tx_fifo)) {
+			if (filp->f_flags & O_NONBLOCK)
+				return -EAGAIN;
+
+			/* sleep till we have some space to write into */
+			if (wait_event_interruptible(gc->tx_wait,
+						gc_can_write(gc)))
+				return -ERESTARTSYS;
+
+		}
+		wrote = kfifo_from_user(&gc->tx_fifo, buff, len);
+		if (wrote < 0)
+			dev_warn(gc->dev, "%s fault %d\n", __func__, wrote);
+	}
+
+	if (wrote > 0) {
+		spin_lock_irq(&gc->tx_lock);
+		gc_do_tx(gc);
+		spin_unlock_irq(&gc->tx_lock);
+	}
+
+	dev_vdbg(gc->dev, "%s done %d/%d\n", __func__, wrote, len);
+	return wrote;
+}
+
+static long gc_ioctl(struct file *filp, unsigned code, unsigned long value)
+{
+	struct gc_dev			*gc = filp->private_data;
+	int status = -EINVAL;
+
+	if (gc->gchar && gc->gchar->ioctl)
+		status = gc->gchar->ioctl(gc->gchar, code, value);
+
+	dev_dbg(gc->dev, "%s done\n", __func__);
+	return status;
+}
+
+static unsigned int gc_poll(struct file *filp, struct poll_table_struct *pt)
+{
+	struct gc_dev			*gc = filp->private_data;
+	int				ret = 0;
+	int				rx = 0, tx = 0;
+
+	/* generic poll implementation */
+	poll_wait(filp, &gc->rx_wait, pt);
+	poll_wait(filp, &gc->tx_wait, pt);
+
+	if (!gc->gchar) {
+		/* not yet connected */
+		goto poll_exit;
+	}
+
+	/* check if data is available to read */
+	spin_lock_irq(&gc->rx_lock);
+	if (gc->gchar->ep_out) {
+		rx = kfifo_len(&gc->rx_fifo);
+		if (rx)
+			ret |= POLLIN | POLLRDNORM;
+	}
+	spin_unlock_irq(&gc->rx_lock);
+
+	/* check if space is available to write */
+	spin_lock_irq(&gc->tx_lock);
+	if (gc->gchar->ep_in) {
+		tx = kfifo_avail(&gc->tx_fifo);
+		if (tx)
+			ret |= POLLOUT | POLLWRNORM;
+	}
+	spin_unlock_irq(&gc->tx_lock);
+
+	dev_dbg(gc->dev, "%s: rx avl %d, tx space %d\n", __func__, rx, tx);
+poll_exit:
+
+	return ret;
+}
+
+int gc_fsync(struct file *filp, struct dentry *dentry, int datasync)
+{
+	struct gc_dev	*gc = filp->private_data;
+
+	if (!gc->gchar || !gc->gchar->ep_in) {
+		/* not yet connected or writing not possible */
+		return -EINVAL;
+	}
+
+	/* flush the TX buffer and send ZLP/SLP
+	 * we will wait till TX buffer is empty
+	 */
+	spin_lock_irq(&gc->tx_lock);
+
+	if (gc->tx_flush) {
+		dev_err(gc->dev, "%s tx_flush already requested\n", __func__);
+		spin_unlock_irq(&gc->tx_lock);
+		return -EINVAL;
+	}
+
+	if (!kfifo_len(&gc->tx_fifo)) {
+		if (gc->tx_last_size == gc->gchar->ep_in->maxpacket)
+			gc->tx_flush = 1;
+	} else
+		gc->tx_flush = 1;
+
+	if (gc->tx_flush) {
+		gc_do_tx(gc);
+
+		spin_unlock_irq(&gc->tx_lock);
+
+		if (wait_event_interruptible(gc->tx_flush_wait,
+					!gc->tx_flush))
+			return -ERESTARTSYS;
+	} else
+		spin_unlock_irq(&gc->tx_lock);
+
+	dev_dbg(gc->dev, "%s complete\n", __func__);
+	return 0;
+}
+
+static const struct file_operations gc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= gc_open,
+	.poll		= gc_poll,
+	.unlocked_ioctl	= gc_ioctl,
+	.release	= gc_release,
+	.read		= gc_read,
+	.write		= gc_write,
+	.fsync		= gc_fsync,
+};
+
+/*------------USB Gadget Driver Interface----------------------------*/
+
+/**
+ * gchar_setup - initialize the character driver for one or more devices
+ * @g: gadget to associate with these devices
+ * @devs_num: number of character devices to support
+ * Context: may sleep
+ *
+ * This driver needs to know how many char. devices it should manage.
+ * Use this call to set up the devices that will be exported through USB.
+ * Later, connect them to functions based on what configuration is activated
+ * by the USB host, and disconnect them as appropriate.
+ *
+ * Returns negative errno or zero.
+ */
+int __init gchar_setup(struct usb_gadget *g, u8 devs_num)
+{
+	int		status;
+	int		i = 0;
+
+	if (gcdata.nr_devs)
+		return -EBUSY;
+
+	if (devs_num == 0 || devs_num > GC_MAX_DEVICES)
+		return -EINVAL;
+
+	gcdata.gcdevs = kzalloc(sizeof(struct gc_dev) * devs_num, GFP_KERNEL);
+	if (!gcdata.gcdevs)
+		return -ENOMEM;
+
+	/* created char dev */
+	status = alloc_chrdev_region(&gcdata.dev, 0, devs_num, "gchar");
+	if (status)
+		goto fail1;
+
+	cdev_init(&gcdata.chdev, &gc_fops);
+
+	gcdata.chdev.owner = THIS_MODULE;
+	gcdata.nr_devs	= devs_num;
+
+	status = cdev_add(&gcdata.chdev, gcdata.dev, devs_num);
+	if (status)
+		goto fail2;
+
+	/* register with sysfs */
+	gcdata.class = class_create(THIS_MODULE, "gchar");
+	if (IS_ERR(gcdata.class)) {
+		pr_err("%s: could not create class gchar\n", __func__);
+		status = PTR_ERR(gcdata.class);
+		goto fail3;
+	}
+
+	for (i = 0; i < devs_num; i++) {
+		struct gc_dev	*gc;
+
+		gc = &gcdata.gcdevs[i];
+		spin_lock_init(&gc->lock);
+		spin_lock_init(&gc->rx_lock);
+		spin_lock_init(&gc->tx_lock);
+		INIT_LIST_HEAD(&gc->rx_pool);
+		INIT_LIST_HEAD(&gc->tx_pool);
+		init_waitqueue_head(&gc->rx_wait);
+		init_waitqueue_head(&gc->tx_wait);
+		init_waitqueue_head(&gc->tx_flush_wait);
+		init_waitqueue_head(&gc->close_wait);
+
+		tasklet_init(&gc->rx_task, gc_rx_task, (unsigned long) gc);
+		tasklet_init(&gc->tx_task, gc_tx_task, (unsigned long) gc);
+		gc->dev = device_create(gcdata.class, NULL,
+			MKDEV(MAJOR(gcdata.dev), MINOR(gcdata.dev) + i),
+			NULL, "gc%d", i);
+		if (IS_ERR(gc->dev)) {
+			pr_err("%s: device_create() failed for device %d\n",
+					__func__, i);
+			for ( ; i > 0; i--) {
+				device_destroy(gcdata.class,
+						MKDEV(MAJOR(gcdata.dev),
+						MINOR(gcdata.dev) + i));
+			}
+			goto fail4;
+		}
+	}
+
+	gcdata.gadget = g;
+
+	return 0;
+
+fail4:
+	class_destroy(gcdata.class);
+fail3:
+	cdev_del(&gcdata.chdev);
+fail2:
+	unregister_chrdev_region(gcdata.dev, gcdata.nr_devs);
+fail1:
+	kfree(gcdata.gcdevs);
+	gcdata.gcdevs = NULL;
+	gcdata.nr_devs = 0;
+
+	return status;
+}
+
+static int gc_closed(struct gc_dev *gc)
+{
+	int ret;
+
+	spin_lock_irq(&gc->lock);
+	ret = !gc->opened;
+	spin_unlock_irq(&gc->lock);
+	return ret;
+}
+
+/**
+ * gchar_cleanup - remove the USB to character devicer and devices
+ * Context: may sleep
+ *
+ * This is called to free all resources allocated by @gchar_setup().
+ * It may need to wait until some open /dev/ files have been closed.
+ */
+void gchar_cleanup(void)
+{
+	int i;
+
+	if (!gcdata.gcdevs)
+		return;
+
+	for (i = 0; i < gcdata.nr_devs; i++) {
+		struct gc_dev *gc = &gcdata.gcdevs[i];
+
+		tasklet_kill(&gc->rx_task);
+		tasklet_kill(&gc->tx_task);
+		device_destroy(gcdata.class, MKDEV(MAJOR(gcdata.dev),
+				MINOR(gcdata.dev) + i));
+		/* wait till open files are closed */
+		wait_event(gc->close_wait, gc_closed(gc));
+	}
+
+	cdev_del(&gcdata.chdev);
+	class_destroy(gcdata.class);
+
+	/* cdev_put(&gchar>chdev); */
+	unregister_chrdev_region(gcdata.dev, gcdata.nr_devs);
+
+	kfree(gcdata.gcdevs);
+	gcdata.gcdevs = NULL;
+	gcdata.nr_devs = 0;
+}
+
+/**
+ * gchar_connect - notify the driver that USB link is active
+ * @gchar: the function, setup with endpoints and descriptors
+ * @num: the device number that is active
+ * @name: name of the function
+ * Context: any (usually from irq)
+ *
+ * This is called to activate the endpoints and let the driver know
+ * that USB link is active.
+ *
+ * Caller needs to have set up the endpoints and USB function in @gchar
+ * before calling this, as well as the appropriate (speed-specific)
+ * endpoint descriptors, and also have set up the char driver by calling
+ * @gchar_setup().
+ *
+ * Returns negative error or zeroa
+ * On success, ep->driver_data will be overwritten
+ */
+int gchar_connect(struct gchar *gchar, u8 num, const char *name)
+{
+	int		status = 0;
+	struct gc_dev	*gc;
+
+	if (num >= gcdata.nr_devs) {
+		pr_err("%s: invalid device number\n", __func__);
+		return -EINVAL;
+	}
+
+	gc = &gcdata.gcdevs[num];
+
+	dev_dbg(gc->dev, "%s %s %d\n", __func__, name, num);
+
+	if (!gchar->ep_out && !gchar->ep_in) {
+		dev_err(gc->dev, "%s: Neither IN nor OUT endpoint available\n",
+								__func__);
+		return -EINVAL;
+	}
+
+	if (gchar->ep_out) {
+		status = usb_ep_enable(gchar->ep_out, gchar->ep_out_desc);
+		if (status < 0)
+			return status;
+
+		gchar->ep_out->driver_data = gc;
+	}
+
+	if (gchar->ep_in) {
+		status = usb_ep_enable(gchar->ep_in, gchar->ep_in_desc);
+		if (status < 0)
+			goto fail1;
+
+		gchar->ep_in->driver_data = gc;
+	}
+
+	kfifo_reset(&gc->tx_fifo);
+	kfifo_reset(&gc->rx_fifo);
+	gc->rx_queued = 0;
+	gc->tx_flush = 0;
+	gc->tx_last_size = 0;
+
+	if (gchar->ep_out) {
+		status = gc_alloc_requests(gchar->ep_out, &gc->rx_pool,
+						&gc_rx_complete);
+		if (status)
+			goto fail2;
+	}
+
+	if (gchar->ep_in) {
+		status = gc_alloc_requests(gchar->ep_in, &gc->tx_pool,
+						&gc_tx_complete);
+		if (status)
+			goto fail3;
+	}
+
+	/* connect gchar */
+	gc->gchar = gchar;
+
+	/* if userspace has opened the device, enable function */
+	if (gc->opened)
+		gc->gchar->open(gc->gchar);
+
+	/* if device is opened by user space then start RX */
+	if (gc->opened)
+		tasklet_schedule(&gc->rx_task);
+
+	dev_dbg(gc->dev, "%s complete\n", __func__);
+	return 0;
+
+fail3:
+	if (gchar->ep_out)
+		gc_free_requests(gchar->ep_out, &gc->rx_pool);
+
+fail2:
+	if (gchar->ep_in) {
+		gchar->ep_in->driver_data = NULL;
+		usb_ep_disable(gchar->ep_in);
+	}
+fail1:
+	if (gchar->ep_out) {
+		gchar->ep_out->driver_data = NULL;
+		usb_ep_disable(gchar->ep_out);
+	}
+
+	return status;
+}
+
+/**
+ * gchar_disconnect - notify the driver that USB link is inactive
+ * @gchar: the function, on which, gchar_connect() was called
+ * Context: any (usually from irq)
+ *
+ * this is called to deactivate the endpoints (related to @gchar)
+ * and let the driver know that the USB link is inactive
+ */
+void gchar_disconnect(struct gchar *gchar)
+{
+	struct gc_dev *gc;
+
+	if (!gchar->ep_out && !gchar->ep_in)
+		return;
+
+	if (gchar->ep_out)
+		gc = gchar->ep_out->driver_data;
+	else
+		gc = gchar->ep_in->driver_data;
+
+	if (!gc) {
+		pr_err("%s Invalid gc_dev\n", __func__);
+		return;
+	}
+
+	spin_lock(&gc->lock);
+
+	if (gchar->ep_out) {
+		usb_ep_disable(gchar->ep_out);
+		gc_free_requests(gc->gchar->ep_out, &gc->rx_pool);
+	}
+
+	if (gchar->ep_in) {
+		usb_ep_disable(gchar->ep_in);
+		gc_free_requests(gc->gchar->ep_in, &gc->tx_pool);
+	}
+
+	gc->gchar = NULL;
+	gchar->ep_out->driver_data = NULL;
+	gchar->ep_in->driver_data = NULL;
+
+	spin_unlock(&gc->lock);
+}
diff --git a/drivers/usb/gadget/u_char.h b/drivers/usb/gadget/u_char.h
new file mode 100644
index 0000000..abbf550
--- /dev/null
+++ b/drivers/usb/gadget/u_char.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2009-2010 Nokia Corporation
+ * Contact: Roger Quadros <roger.quadros@xxxxxxxxx>
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __U_CHAR_H
+#define __U_CHAR_H
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/cdev.h>
+
+struct gchar {
+	struct usb_gadget		*gadget;
+	struct usb_function		func;
+
+	struct usb_ep			*ep_out;
+	struct usb_ep			*ep_in;
+	struct usb_endpoint_descriptor	*ep_out_desc;
+	struct usb_endpoint_descriptor	*ep_in_desc;
+
+	void (*open)(struct gchar *);
+	void (*close)(struct gchar *);
+	long (*ioctl)(struct gchar *, unsigned int, unsigned long);
+};
+
+int __init gchar_setup(struct usb_gadget *g, u8 devs_num);
+void gchar_cleanup(void);
+int gchar_connect(struct gchar *gchar, u8 num, const char *name);
+void gchar_disconnect(struct gchar *gchar);
+#endif
-- 
1.7.0.rc0.33.g7c3932

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