Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a5beaaf3 authored by Baolin Wang's avatar Baolin Wang Committed by Felipe Balbi
Browse files

usb: gadget: Add the console support for usb-to-serial port



It dose not work when we want to use the usb-to-serial port based
on one usb gadget as a console. Thus this patch adds the console
initialization to support this request.

To avoid the re-entrance when transferring data with usb endpoint,
it introduces a kthread to do the IO transmission.

Signed-off-by: default avatarBaolin Wang <baolin.wang@linaro.org>
Signed-off-by: default avatarFelipe Balbi <balbi@ti.com>
parent be99c843
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -127,6 +127,12 @@ config USB_GADGET_STORAGE_NUM_BUFFERS
	   a module parameter as well.
	   If unsure, say 2.

config U_SERIAL_CONSOLE
	bool "Serial gadget console support"
	depends on USB_G_SERIAL
	help
	   It supports the serial gadget can be used as a console.

source "drivers/usb/gadget/udc/Kconfig"

#
+258 −0
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@
#include <linux/slab.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/console.h>
#include <linux/kthread.h>

#include "u_serial.h"

@@ -79,6 +81,7 @@
 */
#define QUEUE_SIZE		16
#define WRITE_BUF_SIZE		8192		/* TX only */
#define GS_CONSOLE_BUF_SIZE	8192

/* circular buffer */
struct gs_buf {
@@ -88,6 +91,17 @@ struct gs_buf {
	char			*buf_put;
};

/* console info */
struct gscons_info {
	struct gs_port		*port;
	struct task_struct	*console_thread;
	struct gs_buf		con_buf;
	/* protect the buf and busy flag */
	spinlock_t		con_lock;
	int			req_busy;
	struct usb_request	*console_req;
};

/*
 * The port structure holds info for each port, one for each minor number
 * (and thus for each /dev/ node).
@@ -1023,6 +1037,246 @@ static const struct tty_operations gs_tty_ops = {

static struct tty_driver *gs_tty_driver;

#ifdef CONFIG_U_SERIAL_CONSOLE

static struct gscons_info gscons_info;
static struct console gserial_cons;

static struct usb_request *gs_request_new(struct usb_ep *ep)
{
	struct usb_request *req = usb_ep_alloc_request(ep, GFP_ATOMIC);
	if (!req)
		return NULL;

	req->buf = kmalloc(ep->maxpacket, GFP_ATOMIC);
	if (!req->buf) {
		usb_ep_free_request(ep, req);
		return NULL;
	}

	return req;
}

static void gs_request_free(struct usb_request *req, struct usb_ep *ep)
{
	if (!req)
		return;

	kfree(req->buf);
	usb_ep_free_request(ep, req);
}

static void gs_complete_out(struct usb_ep *ep, struct usb_request *req)
{
	struct gscons_info *info = &gscons_info;

	switch (req->status) {
	default:
		pr_warn("%s: unexpected %s status %d\n",
			__func__, ep->name, req->status);
	case 0:
		/* normal completion */
		spin_lock(&info->con_lock);
		info->req_busy = 0;
		spin_unlock(&info->con_lock);

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

static int gs_console_connect(int port_num)
{
	struct gscons_info *info = &gscons_info;
	struct gs_port *port;
	struct usb_ep *ep;

	if (port_num != gserial_cons.index) {
		pr_err("%s: port num [%d] is not support console\n",
		       __func__, port_num);
		return -ENXIO;
	}

	port = ports[port_num].port;
	ep = port->port_usb->in;
	if (!info->console_req) {
		info->console_req = gs_request_new(ep);
		if (!info->console_req)
			return -ENOMEM;
		info->console_req->complete = gs_complete_out;
	}

	info->port = port;
	spin_lock(&info->con_lock);
	info->req_busy = 0;
	spin_unlock(&info->con_lock);
	pr_vdebug("port[%d] console connect!\n", port_num);
	return 0;
}

static void gs_console_disconnect(struct usb_ep *ep)
{
	struct gscons_info *info = &gscons_info;
	struct usb_request *req = info->console_req;

	gs_request_free(req, ep);
	info->console_req = NULL;
}

static int gs_console_thread(void *data)
{
	struct gscons_info *info = &gscons_info;
	struct gs_port *port;
	struct usb_request *req;
	struct usb_ep *ep;
	int xfer, ret, count, size;

	do {
		port = info->port;
		set_current_state(TASK_INTERRUPTIBLE);
		if (!port || !port->port_usb
		    || !port->port_usb->in || !info->console_req)
			goto sched;

		req = info->console_req;
		ep = port->port_usb->in;

		spin_lock_irq(&info->con_lock);
		count = gs_buf_data_avail(&info->con_buf);
		size = ep->maxpacket;

		if (count > 0 && !info->req_busy) {
			set_current_state(TASK_RUNNING);
			if (count < size)
				size = count;

			xfer = gs_buf_get(&info->con_buf, req->buf, size);
			req->length = xfer;

			spin_unlock(&info->con_lock);
			ret = usb_ep_queue(ep, req, GFP_ATOMIC);
			spin_lock(&info->con_lock);
			if (ret < 0)
				info->req_busy = 0;
			else
				info->req_busy = 1;

			spin_unlock_irq(&info->con_lock);
		} else {
			spin_unlock_irq(&info->con_lock);
sched:
			if (kthread_should_stop()) {
				set_current_state(TASK_RUNNING);
				break;
			}
			schedule();
		}
	} while (1);

	return 0;
}

static int gs_console_setup(struct console *co, char *options)
{
	struct gscons_info *info = &gscons_info;
	int status;

	info->port = NULL;
	info->console_req = NULL;
	info->req_busy = 0;
	spin_lock_init(&info->con_lock);

	status = gs_buf_alloc(&info->con_buf, GS_CONSOLE_BUF_SIZE);
	if (status) {
		pr_err("%s: allocate console buffer failed\n", __func__);
		return status;
	}

	info->console_thread = kthread_create(gs_console_thread,
					      co, "gs_console");
	if (IS_ERR(info->console_thread)) {
		pr_err("%s: cannot create console thread\n", __func__);
		gs_buf_free(&info->con_buf);
		return PTR_ERR(info->console_thread);
	}
	wake_up_process(info->console_thread);

	return 0;
}

static void gs_console_write(struct console *co,
			     const char *buf, unsigned count)
{
	struct gscons_info *info = &gscons_info;
	unsigned long flags;

	spin_lock_irqsave(&info->con_lock, flags);
	gs_buf_put(&info->con_buf, buf, count);
	spin_unlock_irqrestore(&info->con_lock, flags);

	wake_up_process(info->console_thread);
}

static struct tty_driver *gs_console_device(struct console *co, int *index)
{
	struct tty_driver **p = (struct tty_driver **)co->data;

	if (!*p)
		return NULL;

	*index = co->index;
	return *p;
}

static struct console gserial_cons = {
	.name =		"ttyGS",
	.write =	gs_console_write,
	.device =	gs_console_device,
	.setup =	gs_console_setup,
	.flags =	CON_PRINTBUFFER,
	.index =	-1,
	.data =		&gs_tty_driver,
};

static void gserial_console_init(void)
{
	register_console(&gserial_cons);
}

static void gserial_console_exit(void)
{
	struct gscons_info *info = &gscons_info;

	unregister_console(&gserial_cons);
	kthread_stop(info->console_thread);
	gs_buf_free(&info->con_buf);
}

#else

static int gs_console_connect(int port_num)
{
	return 0;
}

static void gs_console_disconnect(struct usb_ep *ep)
{
}

static void gserial_console_init(void)
{
}

static void gserial_console_exit(void)
{
}

#endif

static int
gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding)
{
@@ -1096,6 +1350,7 @@ void gserial_free_line(unsigned char port_num)

	gserial_free_port(port);
	tty_unregister_device(gs_tty_driver, port_num);
	gserial_console_exit();
}
EXPORT_SYMBOL_GPL(gserial_free_line);

@@ -1138,6 +1393,7 @@ int gserial_alloc_line(unsigned char *line_num)
		goto err;
	}
	*line_num = port_num;
	gserial_console_init();
err:
	return ret;
}
@@ -1219,6 +1475,7 @@ int gserial_connect(struct gserial *gser, u8 port_num)
			gser->disconnect(gser);
	}

	status = gs_console_connect(port_num);
	spin_unlock_irqrestore(&port->port_lock, flags);

	return status;
@@ -1277,6 +1534,7 @@ void gserial_disconnect(struct gserial *gser)
	port->read_allocated = port->read_started =
		port->write_allocated = port->write_started = 0;

	gs_console_disconnect(gser->in);
	spin_unlock_irqrestore(&port->port_lock, flags);
}
EXPORT_SYMBOL_GPL(gserial_disconnect);