Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e3d4ff77 authored by Rohith Kollalsi's avatar Rohith Kollalsi Committed by Gerrit - the friendly Code Review server
Browse files

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: default avatarRohith Kollalsi <rkollals@codeaurora.org>
parent de4a2139
Loading
Loading
Loading
Loading
+132 −21
Original line number Diff line number Diff line
@@ -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;
	} else {
		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 {
		ret = usb_ep_queue(in, req, GFP_KERNEL);
		if (ret) {
			pr_err("EP QUEUE failed:%d\n", ret);
@@ -1350,8 +1458,9 @@ ssize_t f_cdev_write(struct file *file,
		spin_unlock_irqrestore(&port->port_lock, flags);
	}

	return xfer_size;

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)
@@ -1359,10 +1468,8 @@ ssize_t f_cdev_write(struct file *file,
	else
		usb_cser_free_req(in, req);
	spin_unlock_irqrestore(&port->port_lock, flags);
		return ret;
	}

	return xfer_size;
	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;