usb: dwc_otg: prevent re-submit urb when disconnect detected
[firefly-linux-kernel-4.4.55.git] / drivers / usb / dwc_otg_310 / dwc_otg_hcd.c
index cc5198bb4e750f95c09e297ea7704d7358006698..09881639192bd3c0206fe494a346d1a199fbd14c 100755 (executable)
@@ -174,11 +174,15 @@ static void kill_urbs_in_qh_list(dwc_otg_hcd_t *hcd, dwc_list_link_t *qh_list)
                qh = DWC_LIST_ENTRY(qh_item, dwc_otg_qh_t, qh_list_entry);
                DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp,
                                         &qh->qtd_list, qtd_list_entry) {
+                       if (DWC_CIRCLEQ_EMPTY(&qh->qtd_list))
+                               return;
                        qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list);
                        if (qtd->urb != NULL) {
                                hcd->fops->complete(hcd, qtd->urb->priv,
                                                    qtd->urb, -DWC_E_SHUTDOWN);
                                dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh);
+                       } else {
+                               return;
                        }
 
                }
@@ -286,10 +290,12 @@ static int32_t dwc_otg_hcd_disconnect_cb(void *p)
        hprt0.d32 = DWC_READ_REG32(dwc_otg_hcd->core_if->host_if->hprt0);
        /* In some case, we don't disconnect a usb device, but
         * disconnect intr was triggered, so check hprt0 here. */
-       if ((!hprt0.b.prtenchng)
+       if (((!hprt0.b.prtenchng)
            && (!hprt0.b.prtconndet)
-           && hprt0.b.prtconnsts) {
-               DWC_PRINTF("%s: hprt0 = 0x%08x\n", __func__, hprt0.d32);
+           && hprt0.b.prtconnsts)
+           || !hprt0.b.prtenchng) {
+               DWC_PRINTF("%s: Invalid disconnect interrupt "
+                          "hprt0 = 0x%08x\n", __func__, hprt0.d32);
                return 1;
        }
        /*
@@ -469,18 +475,28 @@ void dwc_otg_hcd_stop(dwc_otg_hcd_t *hcd)
 {
        hprt0_data_t hprt0 = {.d32 = 0 };
        struct dwc_otg_platform_data *pldata;
-       pldata = hcd->core_if->otg_dev->pldata;
+       dwc_irqflags_t flags;
 
+       pldata = hcd->core_if->otg_dev->pldata;
        DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD STOP\n");
 
+       /* Turn off all host-specific interrupts. */
+       dwc_otg_disable_host_interrupts(hcd->core_if);
+
+       /*
+        * Set status flags for the hub driver.
+        */
+       hcd->flags.b.port_connect_status_change = 1;
+       hcd->flags.b.port_connect_status = 0;
+
        /*
         * The root hub should be disconnected before this function is called.
         * The disconnect will clear the QTD lists (via ..._hcd_urb_dequeue)
         * and the QH lists (via ..._hcd_endpoint_disable).
         */
-
-       /* Turn off all host-specific interrupts. */
-       dwc_otg_disable_host_interrupts(hcd->core_if);
+       DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
+       kill_all_urbs(hcd);
+       DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
 
        /* Turn off the vbus power */
        DWC_PRINTF("PortPower off\n");
@@ -549,7 +565,7 @@ int dwc_otg_hcd_urb_dequeue(dwc_otg_hcd_t *hcd,
        dwc_otg_qtd_t *urb_qtd;
 
        urb_qtd = dwc_otg_urb->qtd;
-       if (((uint32_t) urb_qtd & 0xf0000000) == 0) {
+       if (!urb_qtd) {
                DWC_PRINTF("%s error: urb_qtd is %p dwc_otg_urb %p!!!\n",
                           __func__, urb_qtd, dwc_otg_urb);
                return 0;
@@ -953,13 +969,15 @@ static void dwc_otg_hcd_reinit(dwc_otg_hcd_t *hcd)
        int i;
        dwc_hc_t *channel;
        dwc_hc_t *channel_tmp;
+       dwc_irqflags_t flags;
+       dwc_spinlock_t *temp_lock;
 
        hcd->flags.d32 = 0;
-
        hcd->non_periodic_qh_ptr = &hcd->non_periodic_sched_active;
        hcd->non_periodic_channels = 0;
        hcd->periodic_channels = 0;
 
+       DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
        /*
         * Put all channels in the free channel list and clean up channel
         * states.
@@ -976,12 +994,20 @@ static void dwc_otg_hcd_reinit(dwc_otg_hcd_t *hcd)
                                        hc_list_entry);
                dwc_otg_hc_cleanup(hcd->core_if, channel);
        }
-
+       DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
        /* Initialize the DWC core for host mode operation. */
        dwc_otg_core_host_init(hcd->core_if);
 
        /* Set core_if's lock pointer to the hcd->lock */
-       hcd->core_if->lock = hcd->lock;
+       /* Should get this lock before modify it */
+       if (hcd->core_if->lock) {
+               DWC_SPINLOCK_IRQSAVE(hcd->core_if->lock, &flags);
+               temp_lock = hcd->core_if->lock;
+               hcd->core_if->lock = hcd->lock;
+               DWC_SPINUNLOCK_IRQRESTORE(temp_lock, flags);
+       } else {
+               hcd->core_if->lock = hcd->lock;
+       }
 }
 
 /**
@@ -1071,6 +1097,7 @@ static int assign_and_init_hc(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
         * Set the split attributes
         */
        hc->do_split = 0;
+       hc->csplit_nak = 0;
        if (qh->do_split) {
                uint32_t hub_addr, port_addr;
                hc->do_split = 1;
@@ -1324,7 +1351,8 @@ static int queue_transaction(dwc_otg_hcd_t *hcd,
                             dwc_hc_t *hc, uint16_t fifo_dwords_avail)
 {
        int retval;
-
+       if (!hc || !(hc->qh))
+               return -ENODEV;
        if (hcd->core_if->dma_enable) {
                if (hcd->core_if->dma_desc_enable) {
                        if (!hc->xfer_started
@@ -1333,6 +1361,8 @@ static int queue_transaction(dwc_otg_hcd_t *hcd,
                                hc->qh->ping_state = 0;
                        }
                } else if (!hc->xfer_started) {
+                       if (!hc || !(hc->qh))
+                               return -ENODEV;
                        dwc_otg_hc_start_transfer(hcd->core_if, hc);
                        hc->qh->ping_state = 0;
                }