usb: gadget: Add the console support for usb-to-serial port
authorBaolin Wang <baolin.wang@linaro.org>
Sat, 21 Nov 2015 07:44:53 +0000 (15:44 +0800)
committerFelipe Balbi <balbi@ti.com>
Tue, 22 Dec 2015 17:49:39 +0000 (11:49 -0600)
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: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Felipe Balbi <balbi@ti.com>
drivers/usb/gadget/Kconfig
drivers/usb/gadget/function/u_serial.c

index 33834aa09ed43be03b0dd0ad418febf0ca25432d..be5aab9c13f2da50f6111222f838f142e1f25213 100644 (file)
@@ -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"
 
 #
index f7771d86ad6c8d19e2016773c1b5ba4b2f3f33eb..6af145f2a99dda265bd87021f3d50f6d38611140 100644 (file)
@@ -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);