9c3e40868e3783813dc959b4657fc61b59c62e6c
[openwrt/openwrt.git] / target / linux / brcm2708 / patches-3.10 / 0088-dwc_otg-prevent-crashes-on-host-port-disconnects.patch
1 From afde583fbb644cff07984f2b47f75c0410d72205 Mon Sep 17 00:00:00 2001
2 From: P33M <P33M@github.com>
3 Date: Mon, 5 Aug 2013 11:47:12 +0100
4 Subject: [PATCH 088/174] dwc_otg: prevent crashes on host port disconnects
5
6 Fix several issues resulting in crashes or inconsistent state
7 if a Model A root port was disconnected.
8
9 - Clean up queue heads properly in kill_urbs_in_qh_list by
10 removing the empty QHs from the schedule lists
11 - Set the halt status properly to prevent IRQ handlers from
12 using freed memory
13 - Add fiq_split related cleanup for saved registers
14 - Make microframe scheduling reclaim host channels if
15 active during a disconnect
16 - Abort URBs with -ESHUTDOWN status response, informing
17 device drivers so they respond in a more correct fashion
18 and don't try to resubmit URBs
19 - Prevent IRQ handlers from attempting to handle channel
20 interrupts if the associated URB was dequeued (and the
21 driver state was cleared)
22 ---
23 drivers/usb/host/dwc_otg/dwc_otg_hcd.c | 44 ++++++++++++++++++++++++----
24 drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c | 7 +++++
25 drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c | 3 ++
26 3 files changed, 48 insertions(+), 6 deletions(-)
27
28 --- a/drivers/usb/host/dwc_otg/dwc_otg_hcd.c
29 +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c
30 @@ -59,6 +59,11 @@ static int last_sel_trans_num_avail_hc_a
31
32 extern int g_next_sched_frame, g_np_count, g_np_sent;
33
34 +extern haint_data_t haint_saved;
35 +extern hcintmsk_data_t hcintmsk_saved[MAX_EPS_CHANNELS];
36 +extern hcint_data_t hcint_saved[MAX_EPS_CHANNELS];
37 +extern gintsts_data_t ginsts_saved;
38 +
39 dwc_otg_hcd_t *dwc_otg_hcd_alloc_hcd(void)
40 {
41 return DWC_ALLOC(sizeof(dwc_otg_hcd_t));
42 @@ -168,31 +173,43 @@ static void del_timers(dwc_otg_hcd_t * h
43
44 /**
45 * Processes all the URBs in a single list of QHs. Completes them with
46 - * -ETIMEDOUT and frees the QTD.
47 + * -ESHUTDOWN and frees the QTD.
48 */
49 static void kill_urbs_in_qh_list(dwc_otg_hcd_t * hcd, dwc_list_link_t * qh_list)
50 {
51 - dwc_list_link_t *qh_item;
52 + dwc_list_link_t *qh_item, *qh_tmp;
53 dwc_otg_qh_t *qh;
54 dwc_otg_qtd_t *qtd, *qtd_tmp;
55
56 - DWC_LIST_FOREACH(qh_item, qh_list) {
57 + DWC_LIST_FOREACH_SAFE(qh_item, qh_tmp, qh_list) {
58 qh = DWC_LIST_ENTRY(qh_item, dwc_otg_qh_t, qh_list_entry);
59 DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp,
60 &qh->qtd_list, qtd_list_entry) {
61 qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list);
62 if (qtd->urb != NULL) {
63 hcd->fops->complete(hcd, qtd->urb->priv,
64 - qtd->urb, -DWC_E_TIMEOUT);
65 + qtd->urb, -DWC_E_SHUTDOWN);
66 dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh);
67 }
68
69 }
70 + if(qh->channel) {
71 + /* Using hcchar.chen == 1 is not a reliable test.
72 + * It is possible that the channel has already halted
73 + * but not yet been through the IRQ handler.
74 + */
75 + dwc_otg_hc_halt(hcd->core_if, qh->channel,
76 + DWC_OTG_HC_XFER_URB_DEQUEUE);
77 + if(microframe_schedule)
78 + hcd->available_host_channels++;
79 + qh->channel = NULL;
80 + }
81 + dwc_otg_hcd_qh_remove(hcd, qh);
82 }
83 }
84
85 /**
86 - * Responds with an error status of ETIMEDOUT to all URBs in the non-periodic
87 + * Responds with an error status of ESHUTDOWN to all URBs in the non-periodic
88 * and periodic schedules. The QTD associated with each URB is removed from
89 * the schedule and freed. This function may be called when a disconnect is
90 * detected or when the HCD is being stopped.
91 @@ -278,7 +295,8 @@ static int32_t dwc_otg_hcd_disconnect_cb
92 */
93 dwc_otg_hcd->flags.b.port_connect_status_change = 1;
94 dwc_otg_hcd->flags.b.port_connect_status = 0;
95 -
96 + if(fiq_fix_enable)
97 + local_fiq_disable();
98 /*
99 * Shutdown any transfers in process by clearing the Tx FIFO Empty
100 * interrupt mask and status bits and disabling subsequent host
101 @@ -374,8 +392,22 @@ static int32_t dwc_otg_hcd_disconnect_cb
102 channel->qh = NULL;
103 }
104 }
105 + if(fiq_split_enable) {
106 + for(i=0; i < 128; i++) {
107 + dwc_otg_hcd->hub_port[i] = 0;
108 + }
109 + haint_saved.d32 = 0;
110 + for(i=0; i < MAX_EPS_CHANNELS; i++) {
111 + hcint_saved[i].d32 = 0;
112 + hcintmsk_saved[i].d32 = 0;
113 + }
114 + }
115 +
116 }
117
118 + if(fiq_fix_enable)
119 + local_fiq_enable();
120 +
121 if (dwc_otg_hcd->fops->disconnect) {
122 dwc_otg_hcd->fops->disconnect(dwc_otg_hcd);
123 }
124 --- a/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c
125 +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c
126 @@ -2660,6 +2660,13 @@ int32_t dwc_otg_hcd_handle_hc_n_intr(dwc
127
128 hc = dwc_otg_hcd->hc_ptr_array[num];
129 hc_regs = dwc_otg_hcd->core_if->host_if->hc_regs[num];
130 + if(hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) {
131 + /* We are responding to a channel disable. Driver
132 + * state is cleared - our qtd has gone away.
133 + */
134 + release_channel(dwc_otg_hcd, hc, NULL, hc->halt_status);
135 + return 1;
136 + }
137 qtd = DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list);
138
139 hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
140 --- a/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c
141 +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c
142 @@ -309,6 +309,9 @@ static int _complete(dwc_otg_hcd_t * hcd
143 case -DWC_E_OVERFLOW:
144 status = -EOVERFLOW;
145 break;
146 + case -DWC_E_SHUTDOWN:
147 + status = -ESHUTDOWN;
148 + break;
149 default:
150 if (status) {
151 DWC_PRINTF("Uknown urb status %d\n", status);