diff --git a/drivers/usb/gadget/function/f_cdev.c b/drivers/usb/gadget/function/f_cdev.c index 720e21950e44..29bc22bb2ef5 100644 --- a/drivers/usb/gadget/function/f_cdev.c +++ b/drivers/usb/gadget/function/f_cdev.c @@ -38,6 +38,7 @@ #include #include #include +#include #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;