]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/commitdiff
dwc_otg: fix several potential crash sources
authorP33M <p33m@github.com>
Fri, 12 May 2017 11:24:00 +0000 (12:24 +0100)
committerAndy Whitcroft <apw@canonical.com>
Thu, 13 Jul 2017 16:50:08 +0000 (17:50 +0100)
On root port disconnect events, the host driver state is cleared and
in-progress host channels are forcibly stopped. This doesn't play
well with the FIQ running in the background, so:
- Guard the disconnect callback with both the host spinlock and FIQ
  spinlock
- Move qtd dereference in dwc_otg_handle_hc_fsm() after the early-out
  so we don't dereference a qtd that has gone away
- Turn catch-all BUG()s in dwc_otg_handle_hc_fsm() into warnings.

drivers/usb/host/dwc_otg/dwc_otg_cil_intr.c
drivers/usb/host/dwc_otg/dwc_otg_hcd.c
drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c

index 96c76e38cd372b8ca8c375ae8d8653f32a3faf80..9fb7229f43ae4561c9dc1980065381a48711ae41 100644 (file)
@@ -973,7 +973,9 @@ int32_t dwc_otg_handle_disconnect_intr(dwc_otg_core_if_t * core_if)
        } else {
                if (core_if->op_state == A_HOST) {
                        /* A-Cable still connected but device disconnected. */
+                       DWC_SPINUNLOCK(core_if->lock);
                        cil_hcd_disconnect(core_if);
+                       DWC_SPINLOCK(core_if->lock);
                        if (core_if->adp_enable) {
                                gpwrdn_data_t gpwrdn = { .d32 = 0 };
                                cil_hcd_stop(core_if);
index 37c9d7d38287b616fcd335e80aa4110521fa98ca..9a647b75027378e25eef00b5fc2a9166514b5c59 100644 (file)
@@ -290,13 +290,16 @@ static int32_t dwc_otg_hcd_disconnect_cb(void *p)
        gintsts_data_t intr;
        dwc_otg_hcd_t *dwc_otg_hcd = p;
 
+       DWC_SPINLOCK(dwc_otg_hcd->lock);
        /*
         * Set status flags for the hub driver.
         */
        dwc_otg_hcd->flags.b.port_connect_status_change = 1;
        dwc_otg_hcd->flags.b.port_connect_status = 0;
-       if(fiq_enable)
+       if(fiq_enable) {
                local_fiq_disable();
+               fiq_fsm_spin_lock(&dwc_otg_hcd->fiq_state->lock);
+       }
        /*
         * Shutdown any transfers in process by clearing the Tx FIFO Empty
         * interrupt mask and status bits and disabling subsequent host
@@ -389,7 +392,8 @@ static int32_t dwc_otg_hcd_disconnect_cb(void *p)
                                 * in release_channel_ddma(). Which called from ep_disable
                                 * when device disconnect.
                                 */
-                               channel->qh = NULL;
+                               if (dwc_otg_hcd->core_if->dma_desc_enable)
+                                       channel->qh = NULL;
                        }
                }
                if(fiq_fsm_enable) {
@@ -400,13 +404,16 @@ static int32_t dwc_otg_hcd_disconnect_cb(void *p)
 
        }
 
-       if(fiq_enable)
+       if(fiq_enable) {
+               fiq_fsm_spin_unlock(&dwc_otg_hcd->fiq_state->lock);
                local_fiq_enable();
+       }
 
        if (dwc_otg_hcd->fops->disconnect) {
                dwc_otg_hcd->fops->disconnect(dwc_otg_hcd);
        }
 
+       DWC_SPINUNLOCK(dwc_otg_hcd->lock);
        return 1;
 }
 
@@ -1750,8 +1757,20 @@ int fiq_fsm_queue_split_transaction(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
        int hub_addr, port_addr, frame, uframe;
        struct fiq_channel_state *st = &hcd->fiq_state->channel[hc->hc_num];
 
-       if (st->fsm != FIQ_PASSTHROUGH)
+       /*
+        * Non-periodic channel assignments stay in the non_periodic_active queue.
+        * Therefore we get repeatedly called until the FIQ's done processing this channel.
+        */
+       if (qh->channel->xfer_started == 1)
                return 0;
+
+       if (st->fsm != FIQ_PASSTHROUGH) {
+               pr_warn_ratelimited("%s:%d: Queue called for an active channel\n", __func__, __LINE__);
+               return 0;
+       }
+
+       qh->channel->xfer_started = 1;
+
        st->nr_errors = 0;
 
        st->hcchar_copy.d32 = 0;
index 718a1accc0c219a1764ce53d291de6a2b6f93608..cf23baaa388562b5843be4cfa6c206cbdc4e780d 100644 (file)
@@ -2374,8 +2374,7 @@ void dwc_otg_hcd_handle_hc_fsm(dwc_otg_hcd_t *hcd, uint32_t num)
 {
        struct fiq_channel_state *st = &hcd->fiq_state->channel[num];
        dwc_hc_t *hc = hcd->hc_ptr_array[num];
-       dwc_otg_qtd_t *qtd = DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list);
-       dwc_otg_qh_t *qh = hc->qh;
+       dwc_otg_qtd_t *qtd;
        dwc_otg_hc_regs_t *hc_regs = hcd->core_if->host_if->hc_regs[num];
        hcint_data_t hcint = hcd->fiq_state->channel[num].hcint_copy;
        hctsiz_data_t hctsiz = hcd->fiq_state->channel[num].hctsiz_copy;
@@ -2385,16 +2384,19 @@ void dwc_otg_hcd_handle_hc_fsm(dwc_otg_hcd_t *hcd, uint32_t num)
        hostchannels = hcd->available_host_channels;
        if (hc->halt_pending) {
                /* Dequeue: The FIQ was allowed to complete the transfer but state has been cleared. */
-               if (st->fsm == FIQ_NP_SPLIT_DONE && hcint.b.xfercomp && qh->ep_type == UE_BULK) {
+               if (hc->qh && st->fsm == FIQ_NP_SPLIT_DONE &&
+                               hcint.b.xfercomp && hc->qh->ep_type == UE_BULK) {
                        if (hctsiz.b.pid == DWC_HCTSIZ_DATA0) {
-                               qh->data_toggle = DWC_OTG_HC_PID_DATA1;
+                               hc->qh->data_toggle = DWC_OTG_HC_PID_DATA1;
                        } else {
-                               qh->data_toggle = DWC_OTG_HC_PID_DATA0;
+                               hc->qh->data_toggle = DWC_OTG_HC_PID_DATA0;
                        }
                }
                release_channel(hcd, hc, NULL, hc->halt_status);
                return;
        }
+
+       qtd = DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list);
        switch (st->fsm) {
        case FIQ_TEST:
                break;
@@ -2413,6 +2415,11 @@ void dwc_otg_hcd_handle_hc_fsm(dwc_otg_hcd_t *hcd, uint32_t num)
                        handle_hc_xfercomp_intr(hcd, hc, hc_regs, qtd);
                } else if (hcint.b.nak) {
                        handle_hc_nak_intr(hcd, hc, hc_regs, qtd);
+               } else {
+                       DWC_WARN("Unexpected IRQ state on FSM transaction:"
+                                       "dev_addr=%d ep=%d fsm=%d, hcint=0x%08x\n",
+                               hc->dev_addr, hc->ep_num, st->fsm, hcint.d32);
+                       release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS);
                }
                break;
 
@@ -2428,8 +2435,10 @@ void dwc_otg_hcd_handle_hc_fsm(dwc_otg_hcd_t *hcd, uint32_t num)
                } else if (hcint.b.ahberr) {
                        handle_hc_ahberr_intr(hcd, hc, hc_regs, qtd);
                } else {
-                       local_fiq_disable();
-                       BUG();
+                       DWC_WARN("Unexpected IRQ state on FSM transaction:"
+                                       "dev_addr=%d ep=%d fsm=%d, hcint=0x%08x\n",
+                               hc->dev_addr, hc->ep_num, st->fsm, hcint.d32);
+                       release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS);
                }
                break;
 
@@ -2445,8 +2454,10 @@ void dwc_otg_hcd_handle_hc_fsm(dwc_otg_hcd_t *hcd, uint32_t num)
                } else if (hcint.b.ahberr) {
                        handle_hc_ahberr_intr(hcd, hc, hc_regs, qtd);
                } else {
-                       local_fiq_disable();
-                       BUG();
+                       DWC_WARN("Unexpected IRQ state on FSM transaction:"
+                                       "dev_addr=%d ep=%d fsm=%d, hcint=0x%08x\n",
+                               hc->dev_addr, hc->ep_num, st->fsm, hcint.d32);
+                       release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS);
                }
                break;
 
@@ -2504,7 +2515,7 @@ void dwc_otg_hcd_handle_hc_fsm(dwc_otg_hcd_t *hcd, uint32_t num)
                        } else {
                                frame_desc->status = 0;
                                /* Unswizzle dma */
-                               len = dwc_otg_fiq_unsetup_per_dma(hcd, qh, qtd, num);
+                               len = dwc_otg_fiq_unsetup_per_dma(hcd, hc->qh, qtd, num);
                                frame_desc->actual_length = len;
                        }
                        qtd->isoc_frame_index++;
@@ -2566,7 +2577,7 @@ void dwc_otg_hcd_handle_hc_fsm(dwc_otg_hcd_t *hcd, uint32_t num)
                 * The status is recorded as the interrupt state should the transaction
                 * fail.
                 */
-               dwc_otg_fiq_unmangle_isoc(hcd, qh, qtd, num);
+               dwc_otg_fiq_unmangle_isoc(hcd, hc->qh, qtd, num);
                hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0);
                release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE);
                break;