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

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
......@@ -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"
#
......
......@@ -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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment