Implemented from the Linux driver:
authormglocker <mglocker@openbsd.org>
Tue, 27 Jul 2021 13:36:59 +0000 (13:36 +0000)
committermglocker <mglocker@openbsd.org>
Tue, 27 Jul 2021 13:36:59 +0000 (13:36 +0000)
* Introduce split transaction order queues.
* Improve the NAK interrupt handler routine.
* Mostly move from list_move() to list_move_tail().

Those changes fix an attachment problem seen for certain devices which
are issuing NAK interrupts during split transactions, which don't get
handled correctly by the driver today.  This could result in unexpected
channel halting, printing "ChHltd set, but reason is unknown", which
finally leaves the device back on a disabled USB port.

ok kettenis@

sys/dev/usb/dwc2/dwc2_core.c
sys/dev/usb/dwc2/dwc2_core.h
sys/dev/usb/dwc2/dwc2_hcd.c
sys/dev/usb/dwc2/dwc2_hcd.h
sys/dev/usb/dwc2/dwc2_hcdddma.c
sys/dev/usb/dwc2/dwc2_hcdintr.c
sys/dev/usb/dwc2/dwc2_hcdqueue.c
sys/dev/usb/dwc2/list.h

index 08fcd46..e7c5a14 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: dwc2_core.c,v 1.10 2021/07/22 18:32:33 mglocker Exp $ */
+/*     $OpenBSD: dwc2_core.c,v 1.11 2021/07/27 13:36:59 mglocker Exp $ */
 /*     $NetBSD: dwc2_core.c,v 1.6 2014/04/03 06:34:58 skrll Exp $      */
 
 /*
@@ -1695,6 +1695,8 @@ void dwc2_hc_cleanup(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan)
 
        chan->xfer_started = 0;
 
+       list_del_init(&chan->split_order_list_entry);
+
        /*
         * Clear channel interrupt enables and any unhandled channel interrupt
         * conditions
index 0a66b8e..2a4979d 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: dwc2_core.h,v 1.10 2021/07/22 18:32:33 mglocker Exp $ */
+/*     $OpenBSD: dwc2_core.h,v 1.11 2021/07/27 13:36:59 mglocker Exp $ */
 /*     $NetBSD: dwc2_core.h,v 1.5 2014/04/03 06:34:58 skrll Exp $      */
 
 /*
@@ -640,6 +640,7 @@ struct dwc2_hregs_backup {
  *                      periodic_sched_ready because it must be rescheduled for
  *                      the next frame. Otherwise, the item moves to
  *                      periodic_sched_inactive.
+ * @split_order:        List keeping track of channels doing splits, in order.
  * @periodic_usecs:     Total bandwidth claimed so far for periodic transfers.
  *                      This value is in microseconds per (micro)frame. The
  *                      assumption is that all periodic transfers may occur in
@@ -767,6 +768,7 @@ struct dwc2_hsotg {
        struct list_head periodic_sched_ready;
        struct list_head periodic_sched_assigned;
        struct list_head periodic_sched_queued;
+       struct list_head split_order;
        u16 periodic_usecs;
        u16 frame_usecs[8];
        u16 frame_number;
index 1ddf2cd..9881c89 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: dwc2_hcd.c,v 1.23 2021/07/22 18:32:33 mglocker Exp $  */
+/*     $OpenBSD: dwc2_hcd.c,v 1.24 2021/07/27 13:36:59 mglocker Exp $  */
 /*     $NetBSD: dwc2_hcd.c,v 1.15 2014/11/24 10:14:14 skrll Exp $      */
 
 /*
@@ -932,7 +932,8 @@ enum dwc2_transaction_type dwc2_hcd_select_transactions(
                 * periodic assigned schedule
                 */
                qh_ptr = qh_ptr->next;
-               list_move(&qh->qh_list_entry, &hsotg->periodic_sched_assigned);
+               list_move_tail(&qh->qh_list_entry,
+                              &hsotg->periodic_sched_assigned);
                ret_val = DWC2_TRANSACTION_PERIODIC;
        }
 
@@ -981,7 +982,7 @@ enum dwc2_transaction_type dwc2_hcd_select_transactions(
                 * non-periodic active schedule
                 */
                qh_ptr = qh_ptr->next;
-               list_move(&qh->qh_list_entry,
+               list_move_tail(&qh->qh_list_entry,
                          &hsotg->non_periodic_sched_active);
 
                if (ret_val == DWC2_TRANSACTION_NONE)
@@ -1023,7 +1024,12 @@ STATIC int dwc2_queue_transaction(struct dwc2_hsotg *hsotg,
 {
        int retval = 0;
 
-       if (hsotg->core_params->dma_enable > 0) {
+       if (chan->do_split)
+               /* Put ourselves on the list to keep order straight */
+               list_move_tail(&chan->split_order_list_entry,
+                              &hsotg->split_order);
+
+       if (hsotg->core_params->dma_enable > 0 && chan->qh) {
                if (hsotg->core_params->dma_desc_enable > 0) {
                        if (!chan->xfer_started ||
                            chan->ep_type == USB_ENDPOINT_XFER_ISOC) {
@@ -1155,7 +1161,7 @@ STATIC void dwc2_process_periodic_channels(struct dwc2_hsotg *hsotg)
                         * Move the QH from the periodic assigned schedule to
                         * the periodic queued schedule
                         */
-                       list_move(&qh->qh_list_entry,
+                       list_move_tail(&qh->qh_list_entry,
                                  &hsotg->periodic_sched_queued);
 
                        /* done queuing high bandwidth */
@@ -2347,6 +2353,8 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg)
        INIT_LIST_HEAD(&hsotg->periodic_sched_assigned);
        INIT_LIST_HEAD(&hsotg->periodic_sched_queued);
 
+       INIT_LIST_HEAD(&hsotg->split_order);
+
        /*
         * Create a host channel descriptor for each host channel implemented
         * in the controller. Initialize the channel descriptor array.
@@ -2360,6 +2368,7 @@ int dwc2_hcd_init(struct dwc2_hsotg *hsotg)
                if (channel == NULL)
                        goto error3;
                channel->hc_num = i;
+               INIT_LIST_HEAD(&channel->split_order_list_entry);
                hsotg->hc_ptr_array[i] = channel;
        }
 
index f25c534..c822b0f 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: dwc2_hcd.h,v 1.14 2021/07/22 18:32:33 mglocker Exp $  */
+/*     $OpenBSD: dwc2_hcd.h,v 1.15 2021/07/27 13:36:59 mglocker Exp $  */
 /*     $NetBSD: dwc2_hcd.h,v 1.9 2014/09/03 10:00:08 skrll Exp $       */
 
 /*
@@ -111,6 +111,7 @@ struct dwc2_qh;
  * @hc_list_entry:      For linking to list of host channels
  * @desc_list_addr:     Current QH's descriptor list DMA address
  * @desc_list_sz:       Current QH's descriptor list size
+ * @split_order_list_entry: List entry for keeping track of the order of splits
  *
  * This structure represents the state of a single host channel when acting in
  * host mode. It contains the data items needed to transfer packets to an
@@ -166,6 +167,7 @@ struct dwc2_host_chan {
        struct usb_dma desc_list_usbdma;
        dma_addr_t desc_list_addr;
        u32 desc_list_sz;
+       struct list_head split_order_list_entry;
 };
 
 struct dwc2_hcd_pipe_info {
index de4b0d0..db1b632 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: dwc2_hcdddma.c,v 1.17 2021/07/22 18:32:33 mglocker Exp $      */
+/*     $OpenBSD: dwc2_hcdddma.c,v 1.18 2021/07/27 13:36:59 mglocker Exp $      */
 /*     $NetBSD: dwc2_hcdddma.c,v 1.6 2014/04/03 06:34:58 skrll Exp $   */
 
 /*
@@ -1317,8 +1317,8 @@ void dwc2_hcd_complete_xfer_ddma(struct dwc2_hsotg *hsotg,
                        dwc2_hcd_qh_unlink(hsotg, qh);
                } else {
                        /* Keep in assigned schedule to continue transfer */
-                       list_move(&qh->qh_list_entry,
-                                 &hsotg->periodic_sched_assigned);
+                       list_move_tail(&qh->qh_list_entry,
+                                      &hsotg->periodic_sched_assigned);
                        /*
                         * If channel has been halted during giveback of urb
                         * then prevent any new scheduling.
index 3e17e34..c9e4146 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: dwc2_hcdintr.c,v 1.11 2021/07/22 18:32:33 mglocker Exp $      */
+/*     $OpenBSD: dwc2_hcdintr.c,v 1.12 2021/07/27 13:36:59 mglocker Exp $      */
 /*     $NetBSD: dwc2_hcdintr.c,v 1.11 2014/11/24 10:14:14 skrll Exp $  */
 
 /*
@@ -154,8 +154,8 @@ STATIC void dwc2_sof_intr(struct dwc2_hsotg *hsotg)
                         * Move QH to the ready list to be executed next
                         * (micro)frame
                         */
-                       list_move(&qh->qh_list_entry,
-                                 &hsotg->periodic_sched_ready);
+                       list_move_tail(&qh->qh_list_entry,
+                                      &hsotg->periodic_sched_ready);
        }
        tr_type = dwc2_hcd_select_transactions(hsotg);
        if (tr_type != DWC2_TRANSACTION_NONE)
@@ -865,8 +865,8 @@ 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,
-                                 &hsotg->periodic_sched_assigned);
+                       list_move_tail(&chan->qh->qh_list_entry,
+                                      &hsotg->periodic_sched_assigned);
 
                        /*
                         * Make sure the Periodic Tx FIFO Empty interrupt is
@@ -1269,29 +1269,25 @@ STATIC void dwc2_hc_nak_intr(struct dwc2_hsotg *hsotg,
         * avoid interrupt storms we'll wait before retrying if we've got
         * several NAKs. If we didn't do this we'd retry directly from the
         * interrupt handler and could end up quickly getting another
-        * interrupt (another NAK), which we'd retry.
+        * interrupt (another NAK), which we'd retry. Note that we do not
+        * delay retries for IN parts of control requests, as those are expected
+        * to complete fairly quickly, and if we delay them we risk confusing
+        * the device and cause it issue STALL.
         *
         * Note that in DMA mode software only gets involved to re-send NAKed
-        * transfers for split transactions unless the core is missing OUT NAK
-        * enhancement.
+        * transfers for split transactions, so we only need to apply this
+        * delaying logic when handling splits. In non-DMA mode presumably we
+        * might want a similar delay if someone can demonstrate this problem
+        * affects that code path too.
         */
        if (chan->do_split) {
-               /*
-                * When we get control/bulk NAKs then remember this so we holdoff on
-                * this qh until the beginning of the next frame
-                */
-               switch (dwc2_hcd_get_pipe_type(&qtd->urb->pipe_info)) {
-               case USB_ENDPOINT_XFER_CONTROL:
-               case USB_ENDPOINT_XFER_BULK:
-                       chan->qh->nak_frame = dwc2_hcd_get_frame_number(hsotg);
-                       break;
-               }
-
                if (chan->complete_split)
                        qtd->error_count = 0;
                qtd->complete_split = 0;
                qtd->num_naks++;
-               qtd->qh->want_wait = qtd->num_naks >= dwc2_naks_before_delay;
+               qtd->qh->want_wait = qtd->num_naks >= DWC2_NAKS_BEFORE_DELAY &&
+                               !(chan->ep_type == USB_ENDPOINT_XFER_CONTROL &&
+                                 chan->ep_is_in);
                dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_NAK);
                goto handle_nak_done;
        }
@@ -1317,13 +1313,6 @@ STATIC void dwc2_hc_nak_intr(struct dwc2_hsotg *hsotg,
                 */
                qtd->error_count = 0;
 
-               if (hsotg->core_params->dma_enable > 0 && !chan->ep_is_in) {
-                       /*
-                        * Avoid interrupt storms.
-                        */
-                       qtd->num_naks++;
-                       qtd->qh->want_wait = qtd->num_naks >= dwc2_out_naks_before_delay;
-               }
                if (!chan->qh->ping_state) {
                        dwc2_update_urb_state_abn(hsotg, chan, chnum, qtd->urb,
                                                  qtd, DWC2_HC_XFER_NAK);
@@ -1984,6 +1973,18 @@ error:
                qtd->error_count++;
                dwc2_update_urb_state_abn(hsotg, chan, chnum, qtd->urb,
                                          qtd, DWC2_HC_XFER_XACT_ERR);
+               /*
+                * We can get here after a completed transaction
+                * (urb->actual_length >= urb->length) which was not reported
+                * as completed. If that is the case, and we do not abort
+                * the transfer, a transfer of size 0 will be enqueued
+                * subsequently. If urb->actual_length is not DMA-aligned,
+                * the buffer will then point to an unaligned address, and
+                * the resulting behavior is undefined. Bail out in that
+                * situation.
+                */
+               if (qtd->urb->actual_length >= qtd->urb->length)
+                       qtd->error_count = 3;
                dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd);
                dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_XACT_ERR);
        }
@@ -2183,6 +2184,7 @@ STATIC void dwc2_hc_intr(struct dwc2_hsotg *hsotg)
 {
        u32 haint;
        int i;
+       struct dwc2_host_chan *chan, *chan_tmp;
 
        haint = DWC2_READ_4(hsotg, HAINT);
        if (dbg_perio()) {
@@ -2191,6 +2193,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);
index ba5bb61..91c105a 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: dwc2_hcdqueue.c,v 1.10 2021/07/22 18:32:33 mglocker Exp $     */
+/*     $OpenBSD: dwc2_hcdqueue.c,v 1.11 2021/07/27 13:36:59 mglocker Exp $     */
 /*     $NetBSD: dwc2_hcdqueue.c,v 1.11 2014/09/03 10:00:08 skrll Exp $ */
 
 /*
@@ -831,9 +831,11 @@ void dwc2_hcd_qh_deactivate(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh,
             dwc2_frame_num_le(qh->sched_frame, frame_number)) ||
            (hsotg->core_params->uframe_sched <= 0 &&
             qh->sched_frame == frame_number))
-               list_move(&qh->qh_list_entry, &hsotg->periodic_sched_ready);
+               list_move_tail(&qh->qh_list_entry,
+                              &hsotg->periodic_sched_ready);
        else
-               list_move(&qh->qh_list_entry, &hsotg->periodic_sched_inactive);
+               list_move_tail(&qh->qh_list_entry,
+                              &hsotg->periodic_sched_inactive);
 }
 
 /**
index 65435a2..2fbc472 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: list.h,v 1.1 2021/07/22 18:32:33 mglocker Exp $       */
+/*     $OpenBSD: list.h,v 1.2 2021/07/27 13:36:59 mglocker Exp $       */
 /* linux list functions for the BSDs, cut down for dwctwo(4).
  * Created: Mon Apr 7 14:30:16 1999 by anholt@FreeBSD.org
  */
@@ -93,6 +93,13 @@ static inline void list_move(struct list_head *list, struct list_head *head)
        list_add(list, head);
 }
 
+static inline void list_move_tail(struct list_head *list,
+    struct list_head *head)
+{
+       list_del(list);
+       list_add_tail(list, head);
+}
+
 static inline void
 list_del_init(struct list_head *entry) {
        (entry)->next->prev = (entry)->prev;