Skip to content

Commit 6ea91ce

Browse files
P33MP33M
P33M
authored and
P33M
committed
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)
1 parent e25207d commit 6ea91ce

File tree

3 files changed

+48
-6
lines changed

3 files changed

+48
-6
lines changed

drivers/usb/host/dwc_otg/dwc_otg_hcd.c

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ static int last_sel_trans_num_avail_hc_at_end = 0;
5959

6060
extern int g_next_sched_frame, g_np_count, g_np_sent;
6161

62+
extern haint_data_t haint_saved;
63+
extern hcintmsk_data_t hcintmsk_saved[MAX_EPS_CHANNELS];
64+
extern hcint_data_t hcint_saved[MAX_EPS_CHANNELS];
65+
extern gintsts_data_t ginsts_saved;
66+
6267
dwc_otg_hcd_t *dwc_otg_hcd_alloc_hcd(void)
6368
{
6469
return DWC_ALLOC(sizeof(dwc_otg_hcd_t));
@@ -168,31 +173,43 @@ static void del_timers(dwc_otg_hcd_t * hcd)
168173

169174
/**
170175
* Processes all the URBs in a single list of QHs. Completes them with
171-
* -ETIMEDOUT and frees the QTD.
176+
* -ESHUTDOWN and frees the QTD.
172177
*/
173178
static void kill_urbs_in_qh_list(dwc_otg_hcd_t * hcd, dwc_list_link_t * qh_list)
174179
{
175-
dwc_list_link_t *qh_item;
180+
dwc_list_link_t *qh_item, *qh_tmp;
176181
dwc_otg_qh_t *qh;
177182
dwc_otg_qtd_t *qtd, *qtd_tmp;
178183

179-
DWC_LIST_FOREACH(qh_item, qh_list) {
184+
DWC_LIST_FOREACH_SAFE(qh_item, qh_tmp, qh_list) {
180185
qh = DWC_LIST_ENTRY(qh_item, dwc_otg_qh_t, qh_list_entry);
181186
DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp,
182187
&qh->qtd_list, qtd_list_entry) {
183188
qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list);
184189
if (qtd->urb != NULL) {
185190
hcd->fops->complete(hcd, qtd->urb->priv,
186-
qtd->urb, -DWC_E_TIMEOUT);
191+
qtd->urb, -DWC_E_SHUTDOWN);
187192
dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh);
188193
}
189194

190195
}
196+
if(qh->channel) {
197+
/* Using hcchar.chen == 1 is not a reliable test.
198+
* It is possible that the channel has already halted
199+
* but not yet been through the IRQ handler.
200+
*/
201+
dwc_otg_hc_halt(hcd->core_if, qh->channel,
202+
DWC_OTG_HC_XFER_URB_DEQUEUE);
203+
if(microframe_schedule)
204+
hcd->available_host_channels++;
205+
qh->channel = NULL;
206+
}
207+
dwc_otg_hcd_qh_remove(hcd, qh);
191208
}
192209
}
193210

194211
/**
195-
* Responds with an error status of ETIMEDOUT to all URBs in the non-periodic
212+
* Responds with an error status of ESHUTDOWN to all URBs in the non-periodic
196213
* and periodic schedules. The QTD associated with each URB is removed from
197214
* the schedule and freed. This function may be called when a disconnect is
198215
* detected or when the HCD is being stopped.
@@ -278,7 +295,8 @@ static int32_t dwc_otg_hcd_disconnect_cb(void *p)
278295
*/
279296
dwc_otg_hcd->flags.b.port_connect_status_change = 1;
280297
dwc_otg_hcd->flags.b.port_connect_status = 0;
281-
298+
if(fiq_fix_enable)
299+
local_fiq_disable();
282300
/*
283301
* Shutdown any transfers in process by clearing the Tx FIFO Empty
284302
* interrupt mask and status bits and disabling subsequent host
@@ -374,8 +392,22 @@ static int32_t dwc_otg_hcd_disconnect_cb(void *p)
374392
channel->qh = NULL;
375393
}
376394
}
395+
if(fiq_split_enable) {
396+
for(i=0; i < 128; i++) {
397+
dwc_otg_hcd->hub_port[i] = 0;
398+
}
399+
haint_saved.d32 = 0;
400+
for(i=0; i < MAX_EPS_CHANNELS; i++) {
401+
hcint_saved[i].d32 = 0;
402+
hcintmsk_saved[i].d32 = 0;
403+
}
404+
}
405+
377406
}
378407

408+
if(fiq_fix_enable)
409+
local_fiq_enable();
410+
379411
if (dwc_otg_hcd->fops->disconnect) {
380412
dwc_otg_hcd->fops->disconnect(dwc_otg_hcd);
381413
}

drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2660,6 +2660,13 @@ int32_t dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd_t * dwc_otg_hcd, uint32_t num)
26602660

26612661
hc = dwc_otg_hcd->hc_ptr_array[num];
26622662
hc_regs = dwc_otg_hcd->core_if->host_if->hc_regs[num];
2663+
if(hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) {
2664+
/* We are responding to a channel disable. Driver
2665+
* state is cleared - our qtd has gone away.
2666+
*/
2667+
release_channel(dwc_otg_hcd, hc, NULL, hc->halt_status);
2668+
return 1;
2669+
}
26632670
qtd = DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list);
26642671

26652672
hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);

drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,9 @@ static int _complete(dwc_otg_hcd_t * hcd, void *urb_handle,
309309
case -DWC_E_OVERFLOW:
310310
status = -EOVERFLOW;
311311
break;
312+
case -DWC_E_SHUTDOWN:
313+
status = -ESHUTDOWN;
314+
break;
312315
default:
313316
if (status) {
314317
DWC_PRINTF("Uknown urb status %d\n", status);

0 commit comments

Comments
 (0)