Newer
Older
barebox / drivers / usb / gadget / u_serial.c
@Sascha Hauer Sascha Hauer on 19 Oct 2009 13 KB commands: remove maxargs
/*
 * u_serial.c - utilities for USB gadget "serial port"/TTY support
 *
 * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
 * Copyright (C) 2008 David Brownell
 * Copyright (C) 2008 by Nokia Corporation
 *
 * This code also borrows from usbserial.c, which is
 * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com)
 * Copyright (C) 2000 Peter Berger (pberger@brimson.com)
 * Copyright (C) 2000 Al Borchers (alborchers@steinerpoint.com)
 *
 * This software is distributed under the terms of the GNU General
 * Public License ("GPL") as published by the Free Software Foundation,
 * either version 2 of that License or (at your option) any later version.
 */

/* #define VERBOSE_DEBUG */

#include <common.h>
#include <usb/cdc.h>
#include <kfifo.h>

#include "u_serial.h"


/*
 * This component encapsulates the TTY layer glue needed to provide basic
 * "serial port" functionality through the USB gadget stack.  Each such
 * port is exposed through a /dev/ttyGS* node.
 *
 * After initialization (gserial_setup), these TTY port devices stay
 * available until they are removed (gserial_cleanup).  Each one may be
 * connected to a USB function (gserial_connect), or disconnected (with
 * gserial_disconnect) when the USB host issues a config change event.
 * Data can only flow when the port is connected to the host.
 *
 * A given TTY port can be made available in multiple configurations.
 * For example, each one might expose a ttyGS0 node which provides a
 * login application.  In one case that might use CDC ACM interface 0,
 * while another configuration might use interface 3 for that.  The
 * work to handle that (including descriptor management) is not part
 * of this component.
 *
 * Configurations may expose more than one TTY port.  For example, if
 * ttyGS0 provides login service, then ttyGS1 might provide dialer access
 * for a telephone or fax link.  And ttyGS2 might be something that just
 * needs a simple byte stream interface for some messaging protocol that
 * is managed in userspace ... OBEX, PTP, and MTP have been mentioned.
 */

#define PREFIX	"ttyGS"

/*
 * gserial is the lifecycle interface, used by USB functions
 * gs_port is the I/O nexus, used by the tty driver
 * tty_struct links to the tty/filesystem framework
 *
 * gserial <---> gs_port ... links will be null when the USB link is
 * inactive; managed by gserial_{connect,disconnect}().  each gserial
 * instance can wrap its own USB control protocol.
 *	gserial->ioport == usb_ep->driver_data ... gs_port
 *	gs_port->port_usb ... gserial
 *
 * gs_port <---> tty_struct ... links will be null when the TTY file
 * isn't opened; managed by gs_open()/gs_close()
 *	gserial->port_tty ... tty_struct
 *	tty_struct->driver_data ... gserial
 */

/* RX and TX queues can buffer QUEUE_SIZE packets before they hit the
 * next layer of buffering.  For TX that's a circular buffer; for RX
 * consider it a NOP.  A third layer is provided by the TTY code.
 */
#define QUEUE_SIZE		16
#define WRITE_BUF_SIZE		8192		/* TX only */

/*
 * The port structure holds info for each port, one for each minor number
 * (and thus for each /dev/ node).
 */
struct gs_port {

	struct gserial		*port_usb;
	struct console_device	cdev;
	struct kfifo		*recv_fifo;

	unsigned		open_count;
	int			openclose;	/* open/close in progress */
	u8			port_num;

	struct list_head	read_pool;
	unsigned		n_read;

	struct list_head	write_pool;

	/* REVISIT this state ... */
	struct usb_cdc_line_coding port_line_coding;	/* 8-N-1 etc */
};

/* increase N_PORTS if you need more */
#define N_PORTS		4
static struct portmaster {
	struct gs_port	*port;
} ports[N_PORTS];
static unsigned	n_ports;

#define GS_CLOSE_TIMEOUT		15		/* seconds */



#ifdef VERBOSE_DEBUG
#define pr_vdebug(fmt, arg...) \
	pr_debug(fmt, ##arg)
#else
#define pr_vdebug(fmt, arg...) \
	({ if (0) pr_debug(fmt, ##arg); })
#endif

static unsigned gs_start_rx(struct gs_port *port)
{
	struct list_head	*pool = &port->read_pool;
	struct usb_ep		*out = port->port_usb->out;
	unsigned		started = 0;

	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;

		/* drop lock while we call out; the controller driver
		 * may need to call us back (e.g. for disconnect)
		 */
		status = usb_ep_queue(out, req);

		if (status) {
			pr_debug("%s: %s %s err %d\n",
					__func__, "queue", out->name, status);
			list_add(&req->list, pool);
			break;
		}
		started++;

		/* abort immediately after disconnect */
		if (!port->port_usb)
			break;
	}
	return started;
}

/*-------------------------------------------------------------------------*/

static void gs_read_complete(struct usb_ep *ep, struct usb_request *req)
{
	struct gs_port	*port = ep->driver_data;

	if (req->status == -ESHUTDOWN)
		return;

	kfifo_put(port->recv_fifo, req->buf, req->actual);

	list_add_tail(&req->list, &port->read_pool);
	gs_start_rx(port);
}

static void gs_write_complete(struct usb_ep *ep, struct usb_request *req)
{
	struct gs_port	*port = ep->driver_data;

	list_add(&req->list, &port->write_pool);

	switch (req->status) {
	default:
		/* presumably a transient fault */
		pr_warning("%s: unexpected %s status %d\n",
				__func__, ep->name, req->status);
		/* FALL THROUGH */
	case 0:
		/* normal completion */
//		gs_start_tx(port);
		break;

	case -ESHUTDOWN:
		/* disconnect */
		pr_vdebug("%s: %s shutdown\n", __func__, ep->name);
		break;
	}
}

/*
 * gs_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 *
gs_alloc_req(struct usb_ep *ep, unsigned len)
{
	struct usb_request *req;

	req = usb_ep_alloc_request(ep);

	if (req != NULL) {
		req->length = len;
		req->buf = memalign(32, len);
		if (req->buf == NULL) {
			usb_ep_free_request(ep, req);
			return NULL;
		}
	}

	return req;
}

static void gs_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);
		gs_free_req(ep, req);
	}
}

static int gs_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 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 < QUEUE_SIZE; i++) {
		req = gs_alloc_req(ep, ep->maxpacket);
		if (!req)
			return list_empty(head) ? -ENOMEM : 0;
		req->complete = fn;
		list_add_tail(&req->list, head);
	}
	return 0;
}

/**
 * gs_start_io - start USB I/O streams
 * @dev: encapsulates endpoints to use
 * Context: holding port_lock; port_tty and port_usb are non-null
 *
 * We only start I/O when something is connected to both sides of
 * this port.  If nothing is listening on the host side, we may
 * be pointlessly filling up our TX buffers and FIFO.
 */
static int gs_start_io(struct gs_port *port)
{
	struct list_head	*head = &port->read_pool;
	struct usb_ep		*ep = port->port_usb->out;
	int			status;
	unsigned		started;

	/* Allocate RX and TX I/O buffers.  We can't easily do this much
	 * earlier (with GFP_KERNEL) because the requests are coupled to
	 * endpoints, as are the packet sizes we'll be using.  Different
	 * configurations may use different endpoints with a given port;
	 * and high speed vs full speed changes packet sizes too.
	 */
	status = gs_alloc_requests(ep, head, gs_read_complete);
	if (status)
		return status;

	status = gs_alloc_requests(port->port_usb->in, &port->write_pool,
			gs_write_complete);
	if (status) {
		gs_free_requests(ep, head);
		return status;
	}

	/* queue read requests */
	port->n_read = 0;
	started = gs_start_rx(port);

	/* unblock any pending writes into our circular buffer */
	if (started) {
//		tty_wakeup(port->port_tty);
	} else {
		gs_free_requests(ep, head);
		gs_free_requests(port->port_usb->in, &port->write_pool);
		status = -EIO;
	}

	return status;
}

/*
 * gs_free_req
 *
 * Free a usb_request and its buffer.
 */
void gs_free_req(struct usb_ep *ep, struct usb_request *req)
{
	kfree(req->buf);
	usb_ep_free_request(ep, req);
}

static int __init
gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding)
{
	struct gs_port	*port;

	port = kzalloc(sizeof(struct gs_port), GFP_KERNEL);
	if (port == NULL)
		return -ENOMEM;

	port->port_num = port_num;
	port->port_line_coding = *coding;

	INIT_LIST_HEAD(&port->read_pool);
	INIT_LIST_HEAD(&port->write_pool);

	ports[port_num].port = port;

	return 0;
}

/**
 * gserial_setup - initialize TTY driver for one or more ports
 * @g: gadget to associate with these ports
 * @count: how many ports to support
 * Context: may sleep
 *
 * The TTY stack needs to know in advance how many devices it should
 * plan to manage.  Use this call to set up the ports you will be
 * exporting through USB.  Later, connect them to functions based
 * on what configuration is activated by the USB host; and disconnect
 * them as appropriate.
 *
 * An example would be a two-configuration device in which both
 * configurations expose port 0, but through different functions.
 * One configuration could even expose port 1 while the other
 * one doesn't.
 *
 * Returns negative errno or zero.
 */
int __init gserial_setup(struct usb_gadget *g, unsigned count)
{
	struct usb_cdc_line_coding	coding;
	int i, status;

	/* make devices be openable */
	for (i = 0; i < count; i++) {
		status = gs_port_alloc(i, &coding);
		if (status) {
			count = i;
			goto fail;
		}
	}
	n_ports = count;
	return 0;
fail:
	while (count--)
		kfree(ports[count].port);
	return status;
}

static void serial_putc(struct console_device *cdev, char c)
{
	struct gs_port	*port = container_of(cdev,
					struct gs_port, cdev);
	struct list_head	*pool = &port->write_pool;
	struct usb_ep		*in = port->port_usb->in;
	struct usb_request	*req;
	int status;

	if (list_empty(pool))
		return;
	req = list_entry(pool->next, struct usb_request, list);

	req->length = 1;
	list_del(&req->list);

	*(unsigned char *)req->buf = c;
	status = usb_ep_queue(in, req);

	while (list_empty(pool))
		fsl_udc_irq();
}

static int serial_tstc(struct console_device *cdev)
{
	struct gs_port	*port = container_of(cdev,
					struct gs_port, cdev);

	return (kfifo_len(port->recv_fifo) == 0) ? 0 : 1;
}

static int serial_getc(struct console_device *cdev)
{
	struct gs_port	*port = container_of(cdev,
					struct gs_port, cdev);
	unsigned char ch;

	while (kfifo_getc(port->recv_fifo, &ch));

	return ch;
}

static void serial_flush(struct console_device *cdev)
{
}

static struct console_device *mycdev;

int gserial_connect(struct gserial *gser, u8 port_num)
{
	struct gs_port	*port;
	int		status;
	struct console_device *cdev;

	printf("%s %p %d\n", __func__, gser, port_num);

	/* we "know" gserial_cleanup() hasn't been called */
	port = ports[port_num].port;

	/* activate the endpoints */
	status = usb_ep_enable(gser->in, gser->in_desc);
	if (status < 0)
		return status;
	gser->in->driver_data = port;

	status = usb_ep_enable(gser->out, gser->out_desc);
	if (status < 0)
		goto fail_out;
	gser->out->driver_data = port;

	/* then tell the tty glue that I/O can work */
	gser->ioport = port;
	port->port_usb = gser;

	/* REVISIT unclear how best to handle this state...
	 * we don't really couple it with the Linux TTY.
	 */
	gser->port_line_coding = port->port_line_coding;

	port->recv_fifo = kfifo_alloc(1024);

	printf("gserial_connect: start ttyGS%d\n", port->port_num);
	gs_start_io(port);
	if (gser->connect)
		gser->connect(gser);

	cdev = &port->cdev;
	cdev->f_caps = CONSOLE_STDIN | CONSOLE_STDOUT | CONSOLE_STDERR;
	cdev->tstc = serial_tstc;
	cdev->putc = serial_putc;
	cdev->getc = serial_getc;
	cdev->flush = serial_flush;
	console_register(cdev);
	mycdev = cdev;

	return status;

fail_out:
	usb_ep_disable(gser->in);
	gser->in->driver_data = NULL;
	return status;
}
#include <command.h>

static int do_mycdev (cmd_tbl_t *cmdtp, int argc, char *argv[])
{

	int i,j;
	for (i = 'a'; i < 'z'; i++) {
		mycdev->putc(mycdev, i);
		printf("%c", i);
		mdelay(500);
		for (j = 0; j < 100; j++)
			fsl_udc_irq();
	}
	return 0;
}

U_BOOT_CMD_START(mycdev)
	.cmd		= do_mycdev,
U_BOOT_CMD_END

/**
 * gserial_disconnect - notify TTY I/O glue that USB link is inactive
 * @gser: the function, on which gserial_connect() was called
 * Context: any (usually from irq)
 *
 * This is called to deactivate endpoints and let the TTY layer know
 * that the connection went inactive ... not unlike "hangup".
 *
 * On return, the state is as if gserial_connect() had never been called;
 * there is no active USB I/O on these endpoints.
 */
void gserial_disconnect(struct gserial *gser)
{
	struct gs_port	*port = gser->ioport;
printf("%s\n", __func__);
	if (!port)
		return;

	/* tell the TTY glue not to do I/O here any more */

	/* REVISIT as above: how best to track this? */
	port->port_line_coding = gser->port_line_coding;

	port->port_usb = NULL;
	gser->ioport = NULL;

	/* disable endpoints, aborting down any active I/O */
	usb_ep_disable(gser->out);
	gser->out->driver_data = NULL;

	usb_ep_disable(gser->in);
	gser->in->driver_data = NULL;

	/* finally, free any unused/unusable I/O buffers */

	gs_free_requests(gser->out, &port->read_pool);
	gs_free_requests(gser->in, &port->write_pool);
}