usb: chipidea: udc: don’t do hardware access if gadget has stopped [Linux 4.19.70]

This Linux kernel change "usb: chipidea: udc: don’t do hardware access if gadget has stopped" is included in the Linux 4.19.70 release. This change is authored by Peter Chen <peter.chen [at] nxp.com> on Tue Aug 20 02:07:58 2019 +0000. The commit for this change in Linux stable tree is a209827 (patch) which is from upstream commit cbe85c8. The same Linux upstream change may have been applied to various maintained Linux releases and you can find all Linux releases containing changes from upstream cbe85c8.

usb: chipidea: udc: don't do hardware access if gadget has stopped

commit cbe85c88ce80fb92956a0793518d415864dcead8 upstream.

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 mass_storage
gadget, 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.

Cc: <stable@vger.kernel.org> #v4.9+
Signed-off-by: Peter Chen <peter.chen@nxp.com>
Link: https://lore.kernel.org/r/20190820020503.27080-2-peter.chen@nxp.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

There are 32 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 drivers/usb/chipidea/udc.c | 32 ++++++++++++++++++++++++--------
 1 file changed, 24 insertions(+), 8 deletions(-)

diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
index cc7c856..169ccfa 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -708,12 +708,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);
@@ -731,6 +725,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;
 }

@@ -1302,6 +1302,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 */

@@ -1391,6 +1395,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;
@@ -1414,8 +1422,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);

    list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) {
        dma_pool_free(hwep->td_pool, node->ptr, node->dma);
@@ -1486,6 +1494,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);

@@ -1558,6 +1570,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;

Leave a Reply

Your email address will not be published. Required fields are marked *