usb: f_cdev: USB remote wake up feature implementation for DUN

Implement usb remote wake up feature for dun on sdx12.
For super speed this feature supports wake up of a
particular function from suspend, where as for high speed,
remote wakeup doesn't support wakeup of a particular function
from suspend, but entire usb bus needs to wakeup. When usb_func_wakeup
in composite driver fails because it needs to wake usb bus before
function wakeup is triggered, -EAGAIN is returned which cannot be
considered as a function wakeup failure. Hence add proper handling
in function driver for EAGAIN return case.

Change-Id: I15c18a4a7203e82c74ad1b4905c08eff91d91b4f
Signed-off-by: Rohith Kollalsi <rkollals@codeaurora.org>
This commit is contained in:
Rohith Kollalsi 2021-05-28 11:03:30 +05:30 • committed by Gerrit - the friendly Code Review server
commit e3d4ff77a7

View file

@ -38,6 +38,7 @@
#include <linux/module.h>
#include <asm/ioctls.h>
#include <asm-generic/termios.h>
#include <linux/usb/dwc3-msm.h>
#define DEVICE_NAME "at_usb"
#define MODULE_NAME "msm_usb_bridge"
@ -99,6 +100,7 @@ struct f_cdev {
struct list_head read_pool;
struct list_head read_queued;
struct list_head write_pool;
struct list_head write_pending;
/* current active USB RX request */
struct usb_request *current_rx_req;
@ -127,6 +129,8 @@ struct f_cdev {
struct workqueue_struct *fcdev_wq;
bool is_connected;
bool port_open;
bool is_suspended;
bool pending_state_notify;
unsigned long nbytes_from_host;
unsigned long nbytes_to_host;
@ -536,19 +540,56 @@ static int usb_cser_set_alt(struct usb_function *f, unsigned int intf,
return rc;
}
static int port_notify_serial_state(struct cserial *cser);
static void usb_cser_resume(struct usb_function *f)
{
struct f_cdev *port = func_to_port(f);
struct usb_composite_dev *cdev = f->config->cdev;
unsigned long flags;
int ret;
if (cdev->gadget->speed >= USB_SPEED_SUPER && port->func_is_suspended) {
if (port->func_wakeup_pending) {
dev_dbg(&cdev->gadget->dev,
"func_wakeup for port:%s\n", port->name);
usb_func_wakeup(&port->port_usb.func);
port->func_wakeup_pending = false;
struct usb_request *req, *t;
struct usb_ep *in;
pr_debug("%s\n", __func__);
port->is_suspended = false;
/* process pending state notifications */
if (port->pending_state_notify)
port_notify_serial_state(&port->port_usb);
spin_lock_irqsave(&port->port_lock, flags);
in = port->port_usb.in;
/* process any pending requests */
list_for_each_entry_safe(req, t, &port->write_pending, list) {
list_del_init(&req->list);
if (!port->is_connected) {
pr_err("%s: cable is disconnected.\n", __func__);
list_add(&req->list, &port->write_pool);
spin_unlock_irqrestore(&port->port_lock, flags);
return;
}
spin_unlock_irqrestore(&port->port_lock, flags);
ret = usb_ep_queue(in, req, GFP_KERNEL);
spin_lock_irqsave(&port->port_lock, flags);
if (ret) {
pr_err("EP QUEUE failed:%d\n", ret);
list_add(&req->list, &port->write_pool);
} else {
port->nbytes_from_port_bridge += req->length;
}
}
spin_unlock_irqrestore(&port->port_lock, flags);
}
static void usb_cser_suspend(struct usb_function *f)
{
struct f_cdev *port = func_to_port(f);
pr_debug("%s\n", __func__);
port->is_suspended = true;
}
static int usb_cser_func_suspend(struct usb_function *f, u8 options)
@ -557,11 +598,31 @@ static int usb_cser_func_suspend(struct usb_function *f, u8 options)
port->func_wakeup_allowed =
!!(options & (USB_INTRF_FUNC_SUSPEND_RW >> 8));
port->func_is_suspended = options & (USB_INTRF_FUNC_SUSPEND_LP >> 8);
if (options & (USB_INTRF_FUNC_SUSPEND_LP >> 8)) {
if (!port->func_is_suspended) {
usb_cser_suspend(f);
port->func_is_suspended = true;
} else {
if (port->func_is_suspended) {
port->func_is_suspended = false;
usb_cser_resume(f);
}
}
}
return 0;
}
static int usb_cser_get_remote_wakeup_capable(struct usb_function *f,
struct usb_gadget *g)
{
struct f_cdev *port = func_to_port(f);
return ((g->speed >= USB_SPEED_SUPER && port->func_wakeup_allowed) ||
(g->speed < USB_SPEED_SUPER && usb_get_remote_wakeup_status(g)));
}
static int usb_cser_get_status(struct usb_function *f)
{
#ifdef CONFIG_USB_FUNC_WAKEUP_SUPPORTED
@ -643,7 +704,16 @@ static int port_notify_serial_state(struct cserial *cser)
unsigned long flags;
struct usb_composite_dev *cdev = port->port_usb.func.config->cdev;
if (port->is_suspended) {
port->pending_state_notify = true;
pr_debug("%s: port is suspended\n", __func__);
return 0;
}
spin_lock_irqsave(&port->port_lock, flags);
if (port->pending_state_notify)
port->pending_state_notify = false;
if (!port->port_usb.pending) {
port->port_usb.pending = true;
spin_unlock_irqrestore(&port->port_lock, flags);
@ -1133,6 +1203,7 @@ static void usb_cser_stop_io(struct f_cdev *port)
usb_cser_free_requests(out, &port->read_queued);
usb_cser_free_requests(out, &port->read_pool);
usb_cser_free_requests(in, &port->write_pool);
usb_cser_free_requests(in, &port->write_pending);
spin_unlock_irqrestore(&port->port_lock, flags);
}
@ -1298,6 +1369,9 @@ ssize_t f_cdev_write(struct file *file,
struct list_head *pool;
unsigned int xfer_size;
struct usb_ep *in;
struct cserial *cser;
struct usb_function *func;
struct usb_gadget *gadget;
port = file->private_data;
if (!port) {
@ -1305,6 +1379,9 @@ ssize_t f_cdev_write(struct file *file,
return -EINVAL;
}
cser = &port->port_usb;
func = &cser->func;
spin_lock_irqsave(&port->port_lock, flags);
pr_debug("write on port(%s)(%pK)\n", port->name, port);
@ -1336,9 +1413,40 @@ ssize_t f_cdev_write(struct file *file,
if (ret) {
pr_err("copy_from_user failed: err %d\n", ret);
ret = -EFAULT;
goto err_exit;
}
req->length = xfer_size;
req->zero = 1;
if (port->is_suspended) {
gadget = cser->func.config->cdev->gadget;
if (!usb_cser_get_remote_wakeup_capable(func, gadget)) {
pr_debug("%s remote-wakeup not capable\n",
__func__);
ret = -EOPNOTSUPP;
goto err_exit;
}
spin_lock_irqsave(&port->port_lock, flags);
list_add(&req->list, &port->write_pending);
spin_unlock_irqrestore(&port->port_lock, flags);
if (gadget->speed >= USB_SPEED_SUPER
&& port->func_is_suspended)
ret = usb_func_wakeup(func);
else
ret = usb_gadget_wakeup(gadget);
if (ret < 0 && ret != -EACCES && ret != -EAGAIN) {
pr_err("Remote wakeup failed:%d\n", ret);
spin_lock_irqsave(&port->port_lock, flags);
req = list_first_entry(&port->write_pending,
struct usb_request, list);
list_del(&req->list);
spin_unlock_irqrestore(&port->port_lock, flags);
goto err_exit;
}
} else {
req->length = xfer_size;
req->zero = 1;
ret = usb_ep_queue(in, req, GFP_KERNEL);
if (ret) {
pr_err("EP QUEUE failed:%d\n", ret);
@ -1350,19 +1458,18 @@ ssize_t f_cdev_write(struct file *file,
spin_unlock_irqrestore(&port->port_lock, flags);
}
err_exit:
if (ret) {
spin_lock_irqsave(&port->port_lock, flags);
/* USB cable is connected, add it back otherwise free request */
if (port->is_connected)
list_add(&req->list, &port->write_pool);
else
usb_cser_free_req(in, req);
spin_unlock_irqrestore(&port->port_lock, flags);
return ret;
}
return xfer_size;
err_exit:
spin_lock_irqsave(&port->port_lock, flags);
/* USB cable is connected, add it back otherwise free request */
if (port->is_connected)
list_add(&req->list, &port->write_pool);
else
usb_cser_free_req(in, req);
spin_unlock_irqrestore(&port->port_lock, flags);
return ret;
}
static unsigned int f_cdev_poll(struct file *file, poll_table *wait)
@ -1598,6 +1705,8 @@ int usb_cser_connect(struct f_cdev *port)
cser->pending = false;
cser->q_again = false;
port->is_connected = true;
port->pending_state_notify = false;
port->is_suspended = false;
spin_unlock_irqrestore(&port->port_lock, flags);
usb_cser_start_io(port);
@ -1799,6 +1908,7 @@ static struct f_cdev *f_cdev_alloc(char *func_name, int portno)
INIT_LIST_HEAD(&port->read_pool);
INIT_LIST_HEAD(&port->read_queued);
INIT_LIST_HEAD(&port->write_pool);
INIT_LIST_HEAD(&port->write_pending);
port->fcdev_wq = create_singlethread_workqueue(port->name);
if (!port->fcdev_wq) {
@ -2089,6 +2199,7 @@ static struct usb_function *cser_alloc(struct usb_function_instance *fi)
port->port_usb.func.setup = usb_cser_setup;
port->port_usb.func.func_suspend = usb_cser_func_suspend;
port->port_usb.func.resume = usb_cser_resume;
port->port_usb.func.suspend = usb_cser_suspend;
port->port_usb.func.get_status = usb_cser_get_status;
port->port_usb.func.free_func = usb_cser_free_func;