]> git.hungrycats.org Git - linux/commitdiff
dwc_otg: prevent crashes on host port disconnects
authorP33M <P33M@github.com>
Mon, 5 Aug 2013 10:47:12 +0000 (11:47 +0100)
committerP33M <P33M@github.com>
Mon, 5 Aug 2013 12:22:15 +0000 (13:22 +0100)
Fix several issues resulting in crashes or inconsistent state
if a Model A root port was disconnected.

- Clean up queue heads properly in kill_urbs_in_qh_list by
  removing the empty QHs from the schedule lists
- Set the halt status properly to prevent IRQ handlers from
  using freed memory
- Add fiq_split related cleanup for saved registers
- Make microframe scheduling reclaim host channels if
  active during a disconnect
- Abort URBs with -ESHUTDOWN status response, informing
  device drivers so they respond in a more correct fashion
  and don't try to resubmit URBs
- Prevent IRQ handlers from attempting to handle channel
  interrupts if the associated URB was dequeued (and the
  driver state was cleared)

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

index c42172ffebb20e88f0551768044edf565d7b50e7..be1d25b4a326b9faa6d6fee8ea78830287ed7cbd 100644 (file)
@@ -59,6 +59,11 @@ static int last_sel_trans_num_avail_hc_at_end = 0;
 
 extern int g_next_sched_frame, g_np_count, g_np_sent;
 
+extern haint_data_t haint_saved;
+extern hcintmsk_data_t hcintmsk_saved[MAX_EPS_CHANNELS];
+extern hcint_data_t hcint_saved[MAX_EPS_CHANNELS];
+extern gintsts_data_t ginsts_saved;
+
 dwc_otg_hcd_t *dwc_otg_hcd_alloc_hcd(void)
 {
        return DWC_ALLOC(sizeof(dwc_otg_hcd_t));
@@ -168,31 +173,43 @@ static void del_timers(dwc_otg_hcd_t * hcd)
 
 /**
  * Processes all the URBs in a single list of QHs. Completes them with
- * -ETIMEDOUT and frees the QTD.
+ * -ESHUTDOWN and frees the QTD.
  */
 static void kill_urbs_in_qh_list(dwc_otg_hcd_t * hcd, dwc_list_link_t * qh_list)
 {
-       dwc_list_link_t *qh_item;
+       dwc_list_link_t *qh_item, *qh_tmp;
        dwc_otg_qh_t *qh;
        dwc_otg_qtd_t *qtd, *qtd_tmp;
 
-       DWC_LIST_FOREACH(qh_item, qh_list) {
+       DWC_LIST_FOREACH_SAFE(qh_item, qh_tmp, 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) {
                        qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list);
                        if (qtd->urb != NULL) {
                                hcd->fops->complete(hcd, qtd->urb->priv,
-                                                   qtd->urb, -DWC_E_TIMEOUT);
+                                                   qtd->urb, -DWC_E_SHUTDOWN);
                                dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh);
                        }
 
                }
+               if(qh->channel) {
+                       /* Using hcchar.chen == 1 is not a reliable test.
+                        * It is possible that the channel has already halted
+                        * but not yet been through the IRQ handler.
+                        */
+                       dwc_otg_hc_halt(hcd->core_if, qh->channel,
+                               DWC_OTG_HC_XFER_URB_DEQUEUE);
+                       if(microframe_schedule)
+                               hcd->available_host_channels++;
+                       qh->channel = NULL;
+               }
+               dwc_otg_hcd_qh_remove(hcd, qh);
        }
 }
 
 /**
- * Responds with an error status of ETIMEDOUT to all URBs in the non-periodic
+ * Responds with an error status of ESHUTDOWN to all URBs in the non-periodic
  * and periodic schedules. The QTD associated with each URB is removed from
  * the schedule and freed. This function may be called when a disconnect is
  * detected or when the HCD is being stopped.
@@ -278,7 +295,8 @@ static int32_t dwc_otg_hcd_disconnect_cb(void *p)
         */
        dwc_otg_hcd->flags.b.port_connect_status_change = 1;
        dwc_otg_hcd->flags.b.port_connect_status = 0;
-
+       if(fiq_fix_enable)
+               local_fiq_disable();
        /*
         * Shutdown any transfers in process by clearing the Tx FIFO Empty
         * interrupt mask and status bits and disabling subsequent host
@@ -374,8 +392,22 @@ static int32_t dwc_otg_hcd_disconnect_cb(void *p)
                                channel->qh = NULL;
                        }
                }
+               if(fiq_split_enable) {
+                       for(i=0; i < 128; i++) {
+                               dwc_otg_hcd->hub_port[i] = 0;
+                       }
+                       haint_saved.d32 = 0;
+                       for(i=0; i < MAX_EPS_CHANNELS; i++) {
+                               hcint_saved[i].d32 = 0;
+                               hcintmsk_saved[i].d32 = 0;
+                       }
+               }
+
        }
 
+       if(fiq_fix_enable)
+               local_fiq_enable();
+
        if (dwc_otg_hcd->fops->disconnect) {
                dwc_otg_hcd->fops->disconnect(dwc_otg_hcd);
        }
index 2340dff83e8ec419ac662600e832832b9d100ef7..4fc0445c09901143a4473aca34bb8bd839118ea7 100644 (file)
@@ -2660,6 +2660,13 @@ int32_t dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd_t * dwc_otg_hcd, uint32_t num)
 
        hc = dwc_otg_hcd->hc_ptr_array[num];
        hc_regs = dwc_otg_hcd->core_if->host_if->hc_regs[num];
+       if(hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) {
+               /* We are responding to a channel disable. Driver
+                * state is cleared - our qtd has gone away.
+                */
+               release_channel(dwc_otg_hcd, hc, NULL, hc->halt_status);
+               return 1;
+       }
        qtd = DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list);
 
        hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
index d93b83d360febca89eb49448a611a52421f9cd23..2449ee02f26879d6b7045c4c7b8f64cc8d3a2843 100644 (file)
@@ -309,6 +309,9 @@ static int _complete(dwc_otg_hcd_t * hcd, void *urb_handle,
        case -DWC_E_OVERFLOW:
                status = -EOVERFLOW;
                break;
+       case -DWC_E_SHUTDOWN:
+               status = -ESHUTDOWN;
+               break;
        default:
                if (status) {
                        DWC_PRINTF("Uknown urb status %d\n", status);