summaryrefslogtreecommitdiff
path: root/target/linux/brcm2708/patches-3.10/0088-dwc_otg-prevent-crashes-on-host-port-disconnects.patch
diff options
context:
space:
mode:
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.patch151
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);