]> rtime.felk.cvut.cz Git - sojka/nv-tegra/linux-3.10.git/commitdiff
tegra: usb: disable interrupts when locking
authorSang-Hun Lee <sanlee@nvidia.com>
Tue, 15 May 2012 23:04:41 +0000 (16:04 -0700)
committerDan Willemsen <dwillemsen@nvidia.com>
Sat, 14 Sep 2013 19:39:46 +0000 (12:39 -0700)
Problem description:
 - tegra_udc_irq uses udc->lock
 - Some functions running in the process context was not disabling
   interrupts when locking udc->lock
 - If a function gets interrupted by tegra_udc_irq after locking
   udc->lock, a deadlock occurs, as tegra_udc_irq would also try to
   lock

Fix description:
 - Use an interruption disabling variant of spin_lock

Bug 983958

Change-Id: Ib774847212da64f1f727a207a4821860ffa7b4a8
Signed-off-by: Sang-Hun Lee <sanlee@nvidia.com>
Reviewed-on: http://git-master/r/102693
(cherry picked from commit 168971ab0977d04e958671651c0be4be116fee01)
Reviewed-on: http://git-master/r/147718
Reviewed-by: Simone Willett <swillett@nvidia.com>
Tested-by: Simone Willett <swillett@nvidia.com>
Rebase-Id: R3270a9b2f31880c8a797ddf3ab9fe929cab46bfc

drivers/usb/gadget/tegra_udc.c

index 2625055bd8df8792c5b1ec610b79c6980753b883..c3123cc26e02c4c2dd640549fe1d48633c4a41fc 100644 (file)
@@ -142,6 +142,7 @@ static void done(struct tegra_ep *ep, struct tegra_req *req, int status)
        int j;
        int count;
 
+       BUG_ON(!(in_irq() || irqs_disabled()));
        udc = (struct tegra_udc *)ep->udc;
        /* Removed the req from tegra_ep->queue */
        list_del_init(&req->queue);
@@ -204,19 +205,20 @@ static void done(struct tegra_ep *ep, struct tegra_req *req, int status)
        }
 #endif
 
-       spin_unlock(&ep->udc->lock);
        /* complete() is from gadget layer,
         * eg fsg->bulk_in_complete() */
-       if (req->req.complete)
+       if (req->req.complete) {
+               spin_unlock(&ep->udc->lock);
                req->req.complete(&ep->ep, &req->req);
+               spin_lock(&ep->udc->lock);
+       }
 
-       spin_lock(&ep->udc->lock);
        ep->stopped = stopped;
 }
 
 /*
  * nuke(): delete all requests related to this ep
- * called with spinlock held
+ * Must be called with spinlock held and interrupt disabled
  */
 static void nuke(struct tegra_ep *ep, int status)
 {
@@ -1360,14 +1362,14 @@ static void tegra_detect_charging_type_is_cdp_or_dcp(struct tegra_udc *udc)
 static int tegra_vbus_session(struct usb_gadget *gadget, int is_active)
 {
        struct tegra_udc *udc = container_of(gadget, struct tegra_udc, gadget);
-
+       unsigned long flags;
        DBG("%s(%d) turn VBUS state from %s to %s", __func__, __LINE__,
                udc->vbus_active ? "on" : "off", is_active ? "on" : "off");
 
        if (udc->vbus_active && !is_active) {
                /* If cable disconnected, cancel any delayed work */
                cancel_delayed_work(&udc->work);
-               spin_lock(&udc->lock);
+               spin_lock_irqsave(&udc->lock, flags);
                /* reset all internal Queues and inform client driver */
                reset_queues(udc);
                /* stop the controller and turn off the clocks */
@@ -1376,7 +1378,7 @@ static int tegra_vbus_session(struct usb_gadget *gadget, int is_active)
                udc->vbus_active = 0;
                udc->usb_state = USB_STATE_DEFAULT;
                udc->connect_type = CONNECT_TYPE_NONE;
-               spin_unlock(&udc->lock);
+               spin_unlock_irqrestore(&udc->lock, flags);
                tegra_usb_phy_power_off(udc->phy);
                tegra_usb_set_charging_current(udc);
        } else if (!udc->vbus_active && is_active) {
@@ -2864,6 +2866,7 @@ static int __exit tegra_udc_remove(struct platform_device *pdev)
 static int tegra_udc_suspend(struct platform_device *pdev, pm_message_t state)
 {
        struct tegra_udc *udc = platform_get_drvdata(pdev);
+       unsigned long flags;
        DBG("%s(%d) BEGIN\n", __func__, __LINE__);
 
        /* If the controller is in otg mode, return */
@@ -2871,12 +2874,12 @@ static int tegra_udc_suspend(struct platform_device *pdev, pm_message_t state)
                        return 0;
 
        if (udc->vbus_active) {
-               spin_lock(&udc->lock);
+               spin_lock_irqsave(&udc->lock, flags);
                /* Reset all internal Queues and inform client driver */
                reset_queues(udc);
                udc->vbus_active = 0;
                udc->usb_state = USB_STATE_DEFAULT;
-               spin_unlock(&udc->lock);
+               spin_unlock_irqrestore(&udc->lock, flags);
        }
        /* Stop the controller and turn off the clocks */
        dr_controller_stop(udc);