Merge tag 'staging-4.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh...
[deliverable/linux.git] / drivers / usb / dwc2 / hcd_intr.c
index f8253803a0507543f617af6e2aacc266b43762de..906f223542ee6436edb5680af1bd100529cb5d2a 100644 (file)
 /* 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;
@@ -525,11 +533,19 @@ void dwc2_hcd_save_data_toggle(struct dwc2_hsotg *hsotg,
        u32 pid = (hctsiz & TSIZ_SC_MC_PID_MASK) >> TSIZ_SC_MC_PID_SHIFT;
 
        if (chan->ep_type != USB_ENDPOINT_XFER_CONTROL) {
+               if (WARN(!chan || !chan->qh,
+                        "chan->qh must be specified for non-control eps\n"))
+                       return;
+
                if (pid == TSIZ_SC_MC_PID_DATA0)
                        chan->qh->data_toggle = DWC2_HC_PID_DATA0;
                else
                        chan->qh->data_toggle = DWC2_HC_PID_DATA1;
        } else {
+               if (WARN(!qtd,
+                        "qtd must be specified for control eps\n"))
+                       return;
+
                if (pid == TSIZ_SC_MC_PID_DATA0)
                        qtd->data_toggle = DWC2_HC_PID_DATA0;
                else
@@ -565,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++;
@@ -600,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 &&
@@ -680,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);
 }
@@ -838,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);
 
                        /*
@@ -946,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) {
@@ -1176,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));
@@ -1408,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
@@ -2000,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;
 
@@ -2122,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()) {
@@ -2130,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);
This page took 0.044041 seconds and 5 git commands to generate.