aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/dwc2/hcd_intr.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/dwc2/hcd_intr.c')
-rw-r--r--drivers/usb/dwc2/hcd_intr.c174
1 files changed, 96 insertions, 78 deletions
diff --git a/drivers/usb/dwc2/hcd_intr.c b/drivers/usb/dwc2/hcd_intr.c
index cadba8b13c48..906f223542ee 100644
--- a/drivers/usb/dwc2/hcd_intr.c
+++ b/drivers/usb/dwc2/hcd_intr.c
@@ -55,12 +55,16 @@
/* This function is for debug only */
static void dwc2_track_missed_sofs(struct dwc2_hsotg *hsotg)
{
-#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS
u16 curr_frame_number = hsotg->frame_number;
+ u16 expected = dwc2_frame_num_inc(hsotg->last_frame_num, 1);
+
+ if (expected != curr_frame_number)
+ dwc2_sch_vdbg(hsotg, "MISSED SOF %04x != %04x\n",
+ expected, curr_frame_number);
+#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS
if (hsotg->frame_num_idx < FRAME_NUM_ARRAY_SIZE) {
- if (((hsotg->last_frame_num + 1) & HFNUM_MAX_FRNUM) !=
- curr_frame_number) {
+ if (expected != curr_frame_number) {
hsotg->frame_num_array[hsotg->frame_num_idx] =
curr_frame_number;
hsotg->last_frame_num_array[hsotg->frame_num_idx] =
@@ -79,14 +83,15 @@ static void dwc2_track_missed_sofs(struct dwc2_hsotg *hsotg)
}
hsotg->dumped_frame_num_array = 1;
}
- hsotg->last_frame_num = curr_frame_number;
#endif
+ hsotg->last_frame_num = curr_frame_number;
}
static void dwc2_hc_handle_tt_clear(struct dwc2_hsotg *hsotg,
struct dwc2_host_chan *chan,
struct dwc2_qtd *qtd)
{
+ struct usb_device *root_hub = dwc2_hsotg_to_hcd(hsotg)->self.root_hub;
struct urb *usb_urb;
if (!chan->qh)
@@ -102,6 +107,15 @@ static void dwc2_hc_handle_tt_clear(struct dwc2_hsotg *hsotg,
if (!usb_urb || !usb_urb->dev || !usb_urb->dev->tt)
return;
+ /*
+ * The root hub doesn't really have a TT, but Linux thinks it
+ * does because how could you have a "high speed hub" that
+ * directly talks directly to low speed devices without a TT?
+ * It's all lies. Lies, I tell you.
+ */
+ if (usb_urb->dev->tt->hub == root_hub)
+ return;
+
if (qtd->urb->status != -EPIPE && qtd->urb->status != -EREMOTEIO) {
chan->qh->tt_buffer_dirty = 1;
if (usb_hub_clear_tt_buffer(usb_urb))
@@ -138,13 +152,19 @@ static void dwc2_sof_intr(struct dwc2_hsotg *hsotg)
while (qh_entry != &hsotg->periodic_sched_inactive) {
qh = list_entry(qh_entry, struct dwc2_qh, qh_list_entry);
qh_entry = qh_entry->next;
- if (dwc2_frame_num_le(qh->sched_frame, hsotg->frame_number))
+ if (dwc2_frame_num_le(qh->next_active_frame,
+ hsotg->frame_number)) {
+ dwc2_sch_vdbg(hsotg, "QH=%p ready fn=%04x, nxt=%04x\n",
+ qh, hsotg->frame_number,
+ qh->next_active_frame);
+
/*
* Move QH to the ready list to be executed next
* (micro)frame
*/
- list_move(&qh->qh_list_entry,
+ list_move_tail(&qh->qh_list_entry,
&hsotg->periodic_sched_ready);
+ }
}
tr_type = dwc2_hcd_select_transactions(hsotg);
if (tr_type != DWC2_TRANSACTION_NONE)
@@ -472,18 +492,6 @@ static int dwc2_update_urb_state(struct dwc2_hsotg *hsotg,
xfer_length = urb->length - urb->actual_length;
}
- /* Non DWORD-aligned buffer case handling */
- if (chan->align_buf && xfer_length) {
- dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__);
- dma_unmap_single(hsotg->dev, chan->qh->dw_align_buf_dma,
- chan->qh->dw_align_buf_size,
- chan->ep_is_in ?
- DMA_FROM_DEVICE : DMA_TO_DEVICE);
- if (chan->ep_is_in)
- memcpy(urb->buf + urb->actual_length,
- chan->qh->dw_align_buf, xfer_length);
- }
-
dev_vdbg(hsotg->dev, "urb->actual_length=%d xfer_length=%d\n",
urb->actual_length, xfer_length);
urb->actual_length += xfer_length;
@@ -573,21 +581,6 @@ static enum dwc2_halt_status dwc2_update_isoc_urb_state(
frame_desc->status = 0;
frame_desc->actual_length = dwc2_get_actual_xfer_length(hsotg,
chan, chnum, qtd, halt_status, NULL);
-
- /* Non DWORD-aligned buffer case handling */
- if (chan->align_buf && frame_desc->actual_length) {
- dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n",
- __func__);
- dma_unmap_single(hsotg->dev, chan->qh->dw_align_buf_dma,
- chan->qh->dw_align_buf_size,
- chan->ep_is_in ?
- DMA_FROM_DEVICE : DMA_TO_DEVICE);
- if (chan->ep_is_in)
- memcpy(urb->buf + frame_desc->offset +
- qtd->isoc_split_offset,
- chan->qh->dw_align_buf,
- frame_desc->actual_length);
- }
break;
case DWC2_HC_XFER_FRAME_OVERRUN:
urb->error_count++;
@@ -608,21 +601,6 @@ static enum dwc2_halt_status dwc2_update_isoc_urb_state(
frame_desc->actual_length = dwc2_get_actual_xfer_length(hsotg,
chan, chnum, qtd, halt_status, NULL);
- /* Non DWORD-aligned buffer case handling */
- if (chan->align_buf && frame_desc->actual_length) {
- dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n",
- __func__);
- dma_unmap_single(hsotg->dev, chan->qh->dw_align_buf_dma,
- chan->qh->dw_align_buf_size,
- chan->ep_is_in ?
- DMA_FROM_DEVICE : DMA_TO_DEVICE);
- if (chan->ep_is_in)
- memcpy(urb->buf + frame_desc->offset +
- qtd->isoc_split_offset,
- chan->qh->dw_align_buf,
- frame_desc->actual_length);
- }
-
/* Skip whole frame */
if (chan->qh->do_split &&
chan->ep_type == USB_ENDPOINT_XFER_ISOC && chan->ep_is_in &&
@@ -688,8 +666,6 @@ static void dwc2_deactivate_qh(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh,
}
no_qtd:
- if (qh->channel)
- qh->channel->align_buf = 0;
qh->channel = NULL;
dwc2_hcd_qh_deactivate(hsotg, qh, continue_split);
}
@@ -846,7 +822,7 @@ static void dwc2_halt_channel(struct dwc2_hsotg *hsotg,
* halt to be queued when the periodic schedule is
* processed.
*/
- list_move(&chan->qh->qh_list_entry,
+ list_move_tail(&chan->qh->qh_list_entry,
&hsotg->periodic_sched_assigned);
/*
@@ -954,14 +930,6 @@ static int dwc2_xfercomp_isoc_split_in(struct dwc2_hsotg *hsotg,
frame_desc->actual_length += len;
- if (chan->align_buf) {
- dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__);
- dma_unmap_single(hsotg->dev, chan->qh->dw_align_buf_dma,
- chan->qh->dw_align_buf_size, DMA_FROM_DEVICE);
- memcpy(qtd->urb->buf + frame_desc->offset +
- qtd->isoc_split_offset, chan->qh->dw_align_buf, len);
- }
-
qtd->isoc_split_offset += len;
if (frame_desc->actual_length >= frame_desc->length) {
@@ -1184,19 +1152,6 @@ static void dwc2_update_urb_state_abn(struct dwc2_hsotg *hsotg,
xfer_length = urb->length - urb->actual_length;
}
- /* Non DWORD-aligned buffer case handling */
- if (chan->align_buf && xfer_length && chan->ep_is_in) {
- dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__);
- dma_unmap_single(hsotg->dev, chan->qh->dw_align_buf_dma,
- chan->qh->dw_align_buf_size,
- chan->ep_is_in ?
- DMA_FROM_DEVICE : DMA_TO_DEVICE);
- if (chan->ep_is_in)
- memcpy(urb->buf + urb->actual_length,
- chan->qh->dw_align_buf,
- xfer_length);
- }
-
urb->actual_length += xfer_length;
hctsiz = dwc2_readl(hsotg->regs + HCTSIZ(chnum));
@@ -1416,14 +1371,50 @@ static void dwc2_hc_nyet_intr(struct dwc2_hsotg *hsotg,
if (chan->ep_type == USB_ENDPOINT_XFER_INT ||
chan->ep_type == USB_ENDPOINT_XFER_ISOC) {
- int frnum = dwc2_hcd_get_frame_number(hsotg);
+ struct dwc2_qh *qh = chan->qh;
+ bool past_end;
+
+ if (hsotg->core_params->uframe_sched <= 0) {
+ int frnum = dwc2_hcd_get_frame_number(hsotg);
+
+ /* Don't have num_hs_transfers; simple logic */
+ past_end = dwc2_full_frame_num(frnum) !=
+ dwc2_full_frame_num(qh->next_active_frame);
+ } else {
+ int end_frnum;
- if (dwc2_full_frame_num(frnum) !=
- dwc2_full_frame_num(chan->qh->sched_frame)) {
/*
- * No longer in the same full speed frame.
- * Treat this as a transaction error.
- */
+ * Figure out the end frame based on schedule.
+ *
+ * We don't want to go on trying again and again
+ * forever. Let's stop when we've done all the
+ * transfers that were scheduled.
+ *
+ * We're going to be comparing start_active_frame
+ * and next_active_frame, both of which are 1
+ * before the time the packet goes on the wire,
+ * so that cancels out. Basically if had 1
+ * transfer and we saw 1 NYET then we're done.
+ * We're getting a NYET here so if next >=
+ * (start + num_transfers) we're done. The
+ * complexity is that for all but ISOC_OUT we
+ * skip one slot.
+ */
+ end_frnum = dwc2_frame_num_inc(
+ qh->start_active_frame,
+ qh->num_hs_transfers);
+
+ if (qh->ep_type != USB_ENDPOINT_XFER_ISOC ||
+ qh->ep_is_in)
+ end_frnum =
+ dwc2_frame_num_inc(end_frnum, 1);
+
+ past_end = dwc2_frame_num_le(
+ end_frnum, qh->next_active_frame);
+ }
+
+ if (past_end) {
+ /* Treat this as a transaction error. */
#if 0
/*
* Todo: Fix system performance so this can
@@ -2008,6 +1999,16 @@ static void dwc2_hc_n_intr(struct dwc2_hsotg *hsotg, int chnum)
}
dwc2_writel(hcint, hsotg->regs + HCINT(chnum));
+
+ /*
+ * If we got an interrupt after someone called
+ * dwc2_hcd_endpoint_disable() we don't want to crash below
+ */
+ if (!chan->qh) {
+ dev_warn(hsotg->dev, "Interrupt on disabled channel\n");
+ return;
+ }
+
chan->hcint = hcint;
hcint &= hcintmsk;
@@ -2130,6 +2131,7 @@ static void dwc2_hc_intr(struct dwc2_hsotg *hsotg)
{
u32 haint;
int i;
+ struct dwc2_host_chan *chan, *chan_tmp;
haint = dwc2_readl(hsotg->regs + HAINT);
if (dbg_perio()) {
@@ -2138,6 +2140,22 @@ static void dwc2_hc_intr(struct dwc2_hsotg *hsotg)
dev_vdbg(hsotg->dev, "HAINT=%08x\n", haint);
}
+ /*
+ * According to USB 2.0 spec section 11.18.8, a host must
+ * issue complete-split transactions in a microframe for a
+ * set of full-/low-speed endpoints in the same relative
+ * order as the start-splits were issued in a microframe for.
+ */
+ list_for_each_entry_safe(chan, chan_tmp, &hsotg->split_order,
+ split_order_list_entry) {
+ int hc_num = chan->hc_num;
+
+ if (haint & (1 << hc_num)) {
+ dwc2_hc_n_intr(hsotg, hc_num);
+ haint &= ~(1 << hc_num);
+ }
+ }
+
for (i = 0; i < hsotg->core_params->host_channels; i++) {
if (haint & (1 << i))
dwc2_hc_n_intr(hsotg, i);