diff options
Diffstat (limited to 'target/linux/brcm2708/patches-3.10/0088-dwc_otg-prevent-crashes-on-host-port-disconnects.patch')
-rw-r--r-- | target/linux/brcm2708/patches-3.10/0088-dwc_otg-prevent-crashes-on-host-port-disconnects.patch | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/target/linux/brcm2708/patches-3.10/0088-dwc_otg-prevent-crashes-on-host-port-disconnects.patch b/target/linux/brcm2708/patches-3.10/0088-dwc_otg-prevent-crashes-on-host-port-disconnects.patch new file mode 100644 index 0000000000..9c3e40868e --- /dev/null +++ b/target/linux/brcm2708/patches-3.10/0088-dwc_otg-prevent-crashes-on-host-port-disconnects.patch @@ -0,0 +1,151 @@ +From afde583fbb644cff07984f2b47f75c0410d72205 Mon Sep 17 00:00:00 2001 +From: P33M <P33M@github.com> +Date: Mon, 5 Aug 2013 11:47:12 +0100 +Subject: [PATCH 088/174] dwc_otg: prevent crashes on host port disconnects + +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 | 44 ++++++++++++++++++++++++---- + drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c | 7 +++++ + drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c | 3 ++ + 3 files changed, 48 insertions(+), 6 deletions(-) + +--- a/drivers/usb/host/dwc_otg/dwc_otg_hcd.c ++++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c +@@ -59,6 +59,11 @@ static int last_sel_trans_num_avail_hc_a + + 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 * h + + /** + * 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 + */ + 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 + 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); + } +--- a/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c ++++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c +@@ -2660,6 +2660,13 @@ int32_t dwc_otg_hcd_handle_hc_n_intr(dwc + + 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); +--- a/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c ++++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c +@@ -309,6 +309,9 @@ static int _complete(dwc_otg_hcd_t * hcd + 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); |