Skip to content

Commit

Permalink
usb: gadget: u_serial: add suspend resume callbacks
Browse files Browse the repository at this point in the history
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 <[email protected]>
Signed-off-by: Felipe Balbi <[email protected]>
  • Loading branch information
Fabrice Gasnier authored and felipebalbi committed May 25, 2020
1 parent 8c935de commit aba3a8d
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 8 deletions.
57 changes: 49 additions & 8 deletions drivers/usb/gadget/function/u_serial.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -680,7 +688,7 @@ static void gs_close(struct tty_struct *tty, struct file *file)
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
Expand Down Expand Up @@ -708,6 +716,7 @@ static void gs_close(struct tty_struct *tty, struct file *file)
else
kfifo_reset(&port->port_write_buf);

port->start_delayed = false;
port->port.count = 0;
port->port.tty = NULL;

Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions drivers/usb/gadget/function/u_serial.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit aba3a8d

Please sign in to comment.