OSDN Git Service

usb: gadget: u_serial: add suspend resume callbacks
authorFabrice Gasnier <fabrice.gasnier@st.com>
Thu, 23 Apr 2020 11:55:54 +0000 (13:55 +0200)
committerFelipe Balbi <balbi@kernel.org>
Mon, 25 May 2020 08:09:39 +0000 (11:09 +0300)
Add suspend resume callbacks to handle the case seen when the bus is
suspended by the HOST, and the device opens the port (cat /dev/ttyGS0).

Gadget controller (like DWC2) doesn't accept usb requests to be queued in
this case (when in L2 state), from the gs_open() call. Error log is printed
- configfs-gadget gadget: acm ttyGS0 can't notify serial state, -11
If the HOST resumes (opens) the bus, the port still isn't functional.

Use suspend/resume callbacks to monitor the gadget suspended state by using
'suspended' flag. In case the port gets opened (cat /dev/ttyGS0), the I/O
stream will be delayed until the bus gets resumed by the HOST.

Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com>
Signed-off-by: Felipe Balbi <balbi@kernel.org>
drivers/usb/gadget/function/u_serial.c
drivers/usb/gadget/function/u_serial.h

index 8167d37..3cfc6e2 100644 (file)
@@ -120,6 +120,8 @@ struct gs_port {
        wait_queue_head_t       drain_wait;     /* wait while writes drain */
        bool                    write_busy;
        wait_queue_head_t       close_wait;
+       bool                    suspended;      /* port suspended */
+       bool                    start_delayed;  /* delay start when suspended */
 
        /* REVISIT this state ... */
        struct usb_cdc_line_coding port_line_coding;    /* 8-N-1 etc */
@@ -630,13 +632,19 @@ static int gs_open(struct tty_struct *tty, struct file *file)
 
        /* if connected, start the I/O stream */
        if (port->port_usb) {
-               struct gserial  *gser = port->port_usb;
-
-               pr_debug("gs_open: start ttyGS%d\n", port->port_num);
-               gs_start_io(port);
-
-               if (gser->connect)
-                       gser->connect(gser);
+               /* if port is suspended, wait resume to start I/0 stream */
+               if (!port->suspended) {
+                       struct gserial  *gser = port->port_usb;
+
+                       pr_debug("gs_open: start ttyGS%d\n", port->port_num);
+                       gs_start_io(port);
+
+                       if (gser->connect)
+                               gser->connect(gser);
+               } else {
+                       pr_debug("delay start of ttyGS%d\n", port->port_num);
+                       port->start_delayed = true;
+               }
        }
 
        pr_debug("gs_open: ttyGS%d (%p,%p)\n", port->port_num, tty, file);
@@ -680,7 +688,7 @@ raced_with_open:
        pr_debug("gs_close: ttyGS%d (%p,%p) ...\n", port->port_num, tty, file);
 
        gser = port->port_usb;
-       if (gser && gser->disconnect)
+       if (gser && !port->suspended && gser->disconnect)
                gser->disconnect(gser);
 
        /* wait for circular write buffer to drain, disconnect, or at
@@ -708,6 +716,7 @@ raced_with_open:
        else
                kfifo_reset(&port->port_write_buf);
 
+       port->start_delayed = false;
        port->port.count = 0;
        port->port.tty = NULL;
 
@@ -1403,6 +1412,38 @@ void gserial_disconnect(struct gserial *gser)
 }
 EXPORT_SYMBOL_GPL(gserial_disconnect);
 
+void gserial_suspend(struct gserial *gser)
+{
+       struct gs_port  *port = gser->ioport;
+       unsigned long   flags;
+
+       spin_lock_irqsave(&port->port_lock, flags);
+       port->suspended = true;
+       spin_unlock_irqrestore(&port->port_lock, flags);
+}
+EXPORT_SYMBOL_GPL(gserial_suspend);
+
+void gserial_resume(struct gserial *gser)
+{
+       struct gs_port *port = gser->ioport;
+       unsigned long   flags;
+
+       spin_lock_irqsave(&port->port_lock, flags);
+       port->suspended = false;
+       if (!port->start_delayed) {
+               spin_unlock_irqrestore(&port->port_lock, flags);
+               return;
+       }
+
+       pr_debug("delayed start ttyGS%d\n", port->port_num);
+       gs_start_io(port);
+       if (gser->connect)
+               gser->connect(gser);
+       port->start_delayed = false;
+       spin_unlock_irqrestore(&port->port_lock, flags);
+}
+EXPORT_SYMBOL_GPL(gserial_resume);
+
 static int userial_init(void)
 {
        unsigned                        i;
index dbe75b2..cadb76e 100644 (file)
@@ -68,6 +68,8 @@ ssize_t gserial_get_console(unsigned char port_num, char *page);
 /* connect/disconnect is handled by individual functions */
 int gserial_connect(struct gserial *, u8 port_num);
 void gserial_disconnect(struct gserial *);
+void gserial_suspend(struct gserial *p);
+void gserial_resume(struct gserial *p);
 
 /* functions are bound to configurations by a config or gadget driver */
 int gser_bind_config(struct usb_configuration *c, u8 port_num);