usb: gadget: u_serial: Implement remote wakeup capability

Implement the remote wakeup capability for u_serial. The newly added
function gserial_wakeup_host() wakes up the host when there is some
data to be sent while the device is suspended. Add gser_get_status()
callbacks to advertise f_serial interface as function wakeup capable.

Signed-off-by: Prashanth K <prashanth.k@oss.qualcomm.com>
Link: https://lore.kernel.org/r/20250424121142.4180241-1-prashanth.k@oss.qualcomm.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Prashanth K 2025-04-24 17:41:42 +05:30 committed by Greg Kroah-Hartman
parent 11e80d371b
commit 3baea29dc0
2 changed files with 50 additions and 0 deletions

View file

@ -364,6 +364,12 @@ static void gser_suspend(struct usb_function *f)
gserial_suspend(&gser->port);
}
static int gser_get_status(struct usb_function *f)
{
return (f->func_wakeup_armed ? USB_INTRF_STAT_FUNC_RW : 0) |
USB_INTRF_STAT_FUNC_RW_CAP;
}
static struct usb_function *gser_alloc(struct usb_function_instance *fi)
{
struct f_gser *gser;
@ -387,6 +393,7 @@ static struct usb_function *gser_alloc(struct usb_function_instance *fi)
gser->port.func.free_func = gser_free;
gser->port.func.resume = gser_resume;
gser->port.func.suspend = gser_suspend;
gser->port.func.get_status = gser_get_status;
return &gser->port.func;
}

View file

@ -592,6 +592,17 @@ static int gs_start_io(struct gs_port *port)
return status;
}
static int gserial_wakeup_host(struct gserial *gser)
{
struct usb_function *func = &gser->func;
struct usb_gadget *gadget = func->config->cdev->gadget;
if (func->func_suspended)
return usb_func_wakeup(func);
else
return usb_gadget_wakeup(gadget);
}
/*-------------------------------------------------------------------------*/
/* TTY Driver */
@ -746,6 +757,8 @@ static ssize_t gs_write(struct tty_struct *tty, const u8 *buf, size_t count)
{
struct gs_port *port = tty->driver_data;
unsigned long flags;
int ret = 0;
struct gserial *gser = port->port_usb;
pr_vdebug("gs_write: ttyGS%d (%p) writing %zu bytes\n",
port->port_num, tty, count);
@ -753,6 +766,17 @@ static ssize_t gs_write(struct tty_struct *tty, const u8 *buf, size_t count)
spin_lock_irqsave(&port->port_lock, flags);
if (count)
count = kfifo_in(&port->port_write_buf, buf, count);
if (port->suspended) {
spin_unlock_irqrestore(&port->port_lock, flags);
ret = gserial_wakeup_host(gser);
if (ret) {
pr_debug("ttyGS%d: Remote wakeup failed:%d\n", port->port_num, ret);
return count;
}
spin_lock_irqsave(&port->port_lock, flags);
}
/* treat count == 0 as flush_chars() */
if (port->port_usb)
gs_start_tx(port);
@ -781,10 +805,22 @@ static void gs_flush_chars(struct tty_struct *tty)
{
struct gs_port *port = tty->driver_data;
unsigned long flags;
int ret = 0;
struct gserial *gser = port->port_usb;
pr_vdebug("gs_flush_chars: (%d,%p)\n", port->port_num, tty);
spin_lock_irqsave(&port->port_lock, flags);
if (port->suspended) {
spin_unlock_irqrestore(&port->port_lock, flags);
ret = gserial_wakeup_host(gser);
if (ret) {
pr_debug("ttyGS%d: Remote wakeup failed:%d\n", port->port_num, ret);
return;
}
spin_lock_irqsave(&port->port_lock, flags);
}
if (port->port_usb)
gs_start_tx(port);
spin_unlock_irqrestore(&port->port_lock, flags);
@ -1464,6 +1500,13 @@ void gserial_suspend(struct gserial *gser)
return;
}
if (port->write_busy || port->write_started) {
/* Wakeup to host if there are ongoing transfers */
spin_unlock_irqrestore(&serial_port_lock, flags);
if (!gserial_wakeup_host(gser))
return;
}
spin_lock(&port->port_lock);
spin_unlock(&serial_port_lock);
port->suspended = true;