152 lines
5.0 KiB
Diff
152 lines
5.0 KiB
Diff
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);
|