]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
ENGR00289645 usb: chipidea: udc: don't do hardware access if gadget has stopped
authorPeter Chen <peter.chen@freescale.com>
Tue, 26 Nov 2013 05:33:21 +0000 (13:33 +0800)
committerLothar Waßmann <LW@KARO-electronics.de>
Mon, 16 Jun 2014 16:08:54 +0000 (18:08 +0200)
After _gadget_stop_activity is executed, we can consider the hardware
operation for gadget has finished, and the udc can be stopped and enter
low power mode. So, any later hardware operations (from usb_ep_ops APIs
or usb_gadget_ops APIs) should be considered invalid, any deinitializatons
has been covered at _gadget_stop_activity.

I meet this problem when I plug out usb cable from PC (using g_mass_storage),
my callstack like: vbus interrupt->.vbus_session->composite_disconnect
->pm_runtime_put_sync(&_gadget->dev), the composite_disconnect will
call fsg_disable, but fsg_disable calls usb_ep_disable using async way,
there are register accesses for usb_ep_disable. So sometimes, I get system
hang due to visit register without clock, sometimes not.

The Linux Kernel USB maintainer Alan Stern suggests this kinds of solution.
See: http://marc.info/?l=linux-usb&m=138541769810983&w=2.

Signed-off-by: Peter Chen <peter.chen@freescale.com>
(cherry picked from commit ac760d29366f19eb1a4d4c37899b33019570a447)

drivers/usb/chipidea/udc.c

index d5eb1ed17585bb5b5e7bfae098bfa3e1ab211201..20736136b3dfd44b082e0a8b75406c67e2e3b914 100644 (file)
@@ -650,12 +650,6 @@ static int _gadget_stop_activity(struct usb_gadget *gadget)
        struct ci_hdrc    *ci = container_of(gadget, struct ci_hdrc, gadget);
        unsigned long flags;
 
-       spin_lock_irqsave(&ci->lock, flags);
-       ci->gadget.speed = USB_SPEED_UNKNOWN;
-       ci->remote_wakeup = 0;
-       ci->suspended = 0;
-       spin_unlock_irqrestore(&ci->lock, flags);
-
        /* flush all endpoints */
        gadget_for_each_ep(ep, gadget) {
                usb_ep_fifo_flush(ep);
@@ -673,6 +667,12 @@ static int _gadget_stop_activity(struct usb_gadget *gadget)
                ci->status = NULL;
        }
 
+       spin_lock_irqsave(&ci->lock, flags);
+       ci->gadget.speed = USB_SPEED_UNKNOWN;
+       ci->remote_wakeup = 0;
+       ci->suspended = 0;
+       spin_unlock_irqrestore(&ci->lock, flags);
+
        return 0;
 }
 
@@ -1222,6 +1222,10 @@ static int ep_disable(struct usb_ep *ep)
                return -EBUSY;
 
        spin_lock_irqsave(hwep->lock, flags);
+       if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) {
+               spin_unlock_irqrestore(hwep->lock, flags);
+               return 0;
+       }
 
        /* only internal SW should disable ctrl endpts */
 
@@ -1311,6 +1315,10 @@ static int ep_queue(struct usb_ep *ep, struct usb_request *req,
                return -EINVAL;
 
        spin_lock_irqsave(hwep->lock, flags);
+       if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) {
+               spin_unlock_irqrestore(hwep->lock, flags);
+               return 0;
+       }
        retval = _ep_queue(ep, req, gfp_flags);
        spin_unlock_irqrestore(hwep->lock, flags);
        return retval;
@@ -1333,8 +1341,8 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req)
                return -EINVAL;
 
        spin_lock_irqsave(hwep->lock, flags);
-
-       hw_ep_flush(hwep->ci, hwep->num, hwep->dir);
+       if (hwep->ci->gadget.speed != USB_SPEED_UNKNOWN)
+               hw_ep_flush(hwep->ci, hwep->num, hwep->dir);
 
        /* pop request */
        list_del_init(&hwreq->queue);
@@ -1372,6 +1380,10 @@ static int ep_set_halt(struct usb_ep *ep, int value)
 
        spin_lock_irqsave(hwep->lock, flags);
 
+       if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) {
+               spin_unlock_irqrestore(hwep->lock, flags);
+               return 0;
+       }
 #ifndef STALL_IN
        /* g_file_storage MS compliant but g_zero fails chapter 9 compliance */
        if (value && hwep->type == USB_ENDPOINT_XFER_BULK && hwep->dir == TX &&
@@ -1433,6 +1445,10 @@ static void ep_fifo_flush(struct usb_ep *ep)
        }
 
        spin_lock_irqsave(hwep->lock, flags);
+       if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) {
+               spin_unlock_irqrestore(hwep->lock, flags);
+               return;
+       }
 
        hw_ep_flush(hwep->ci, hwep->num, hwep->dir);
 
@@ -1499,6 +1515,10 @@ static int ci_udc_wakeup(struct usb_gadget *_gadget)
        int ret = 0;
 
        spin_lock_irqsave(&ci->lock, flags);
+       if (ci->gadget.speed == USB_SPEED_UNKNOWN) {
+               spin_unlock_irqrestore(&ci->lock, flags);
+               return 0;
+       }
        if (!ci->remote_wakeup) {
                ret = -EOPNOTSUPP;
                goto out;