In case you are playing around with isoc transfers (uvideo, uaudio) on
dwctwo(4), and you are facing kernel crashes, you can try this diff.
Or if you just want to regression test it.  It basically does:

* Fix DMA allocation out of interrupt context.
* Fix a memory leak on DMA allocation during pipe closing/opening.
* Fix a kernel crash for low/full speed devices which use split in
  transfers (often seen for uaudio devices).


Index: dev/usb/dwc2/dwc2.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/dwc2/dwc2.c,v
retrieving revision 1.58
diff -u -p -u -p -r1.58 dwc2.c
--- dev/usb/dwc2/dwc2.c 30 Jul 2021 18:56:01 -0000      1.58
+++ dev/usb/dwc2/dwc2.c 4 Sep 2021 10:22:16 -0000
@@ -455,7 +455,10 @@ dwc2_poll(struct usbd_bus *bus)
 STATIC void
 dwc2_close_pipe(struct usbd_pipe *pipe)
 {
-       /* nothing */
+       struct dwc2_pipe *dpipe = DWC2_PIPE2DPIPE(pipe);
+       struct dwc2_softc *sc = DWC2_DPIPE2SC(dpipe);
+
+       dwc2_hcd_endpoint_disable(sc->sc_hsotg, dpipe, 250);
 }
 
 /*
Index: dev/usb/dwc2/dwc2_core.h
===================================================================
RCS file: /cvs/src/sys/dev/usb/dwc2/dwc2_core.h,v
retrieving revision 1.11
diff -u -p -u -p -r1.11 dwc2_core.h
--- dev/usb/dwc2/dwc2_core.h    27 Jul 2021 13:36:59 -0000      1.11
+++ dev/usb/dwc2/dwc2_core.h    4 Sep 2021 10:22:17 -0000
@@ -556,6 +556,19 @@ struct dwc2_hregs_backup {
 };
 
 /**
+ * struct dwc2_usbdma - Holds the pre-allocated USB DMA memory slots
+ *
+ * @use:       -1 = usb dma memory slot un-initialized
+ *              0 = usb dma memory slot initialized but un-used
+ *              1 = usb dma memory slot initialized and used
+ * @dma:        usb_dma memory which gets initialized by usb_allocmem()
+ */
+struct dwc2_usbdma {
+       int use;
+       struct usb_dma dma;
+};
+
+/**
  * struct dwc2_hsotg - Holds the state of the driver, including the 
non-periodic
  * and periodic schedules
  *
@@ -804,6 +817,10 @@ struct dwc2_hsotg {
        u32 frame_list_sz;
        struct kmem_cache *desc_gen_cache;
        struct kmem_cache *desc_hsisoc_cache;
+
+#define DWC2_USBDMA_SLOTS 8
+       struct dwc2_usbdma usbdma_norm[DWC2_USBDMA_SLOTS];
+       struct dwc2_usbdma usbdma_isoc[DWC2_USBDMA_SLOTS];
 
 #ifdef DEBUG
        u32 frrem_samples;
Index: dev/usb/dwc2/dwc2_hcd.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/dwc2/dwc2_hcd.c,v
retrieving revision 1.25
diff -u -p -u -p -r1.25 dwc2_hcd.c
--- dev/usb/dwc2/dwc2_hcd.c     4 Sep 2021 10:19:28 -0000       1.25
+++ dev/usb/dwc2/dwc2_hcd.c     4 Sep 2021 10:22:17 -0000
@@ -537,6 +537,64 @@ dwc2_hcd_urb_dequeue(struct dwc2_hsotg *
        return 0;
 }
 
+/* Must NOT be called with interrupt disabled or spinlock held */
+int dwc2_hcd_endpoint_disable(struct dwc2_hsotg *hsotg,
+                             struct dwc2_pipe *dpipe, int retry)
+{
+       struct dwc2_qtd *qtd, *qtd_tmp;
+       struct dwc2_qh *qh;
+       unsigned long flags;
+       int rc;
+
+       spin_lock_irqsave(&hsotg->lock, flags);
+
+       qh = dpipe->priv;
+       if (!qh) {
+               rc = -EINVAL;
+               goto err;
+       }
+
+       while (!list_empty(&qh->qtd_list) && retry--) {
+               if (retry == 0) {
+                       dev_err(hsotg->dev,
+                               "## timeout in dwc2_hcd_endpoint_disable() 
##\n");
+                       rc = -EBUSY;
+                       goto err;
+               }
+
+               spin_unlock_irqrestore(&hsotg->lock, flags);
+               usleep_range(200000, 250000);
+               spin_lock_irqsave(&hsotg->lock, flags);
+               qh = dpipe->priv;
+               if (!qh) {
+                       rc = -EINVAL;
+                       goto err;
+               }
+        }
+
+       dwc2_hcd_qh_unlink(hsotg, qh);
+
+       /* Free each QTD in the QH's QTD list */
+       list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry)
+               dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh);
+
+       dpipe->priv = NULL;
+
+       if (qh->channel && qh->channel->qh == qh)
+               qh->channel->qh = NULL;
+
+       spin_unlock_irqrestore(&hsotg->lock, flags);
+
+       dwc2_hcd_qh_free(hsotg, qh);
+
+       return 0;
+
+err:
+       dpipe->priv = NULL;
+       spin_unlock_irqrestore(&hsotg->lock, flags);
+
+       return rc;
+}
 
 /*
  * Initializes dynamic portions of the DWC_otg HCD state
@@ -720,20 +778,12 @@ STATIC int dwc2_hc_setup_align_buf(struc
        u32 buf_size;
 
        if (!qh->dw_align_buf) {
-               int err;
-
-               if (chan->ep_type != USB_ENDPOINT_XFER_ISOC)
-                       buf_size = hsotg->core_params->max_transfer_size;
-               else
-                       /* 3072 = 3 max-size Isoc packets */
-                       buf_size = 3072;
-
                qh->dw_align_buf = NULL;
                qh->dw_align_buf_dma = 0;
-               err = usb_allocmem(&hsotg->hsotg_sc->sc_bus, buf_size, 0,
-                   USB_DMA_COHERENT, &qh->dw_align_buf_usbdma);
-               if (!err) {
-                       struct usb_dma *ud = &qh->dw_align_buf_usbdma;
+               qh->dw_align_buf_usbdma = dwc2_usbdma_getmem(hsotg,
+                   chan->ep_type);
+               if (qh->dw_align_buf_usbdma) {
+                       struct usb_dma *ud = qh->dw_align_buf_usbdma;
 
                        qh->dw_align_buf = KERNADDR(ud, 0);
                        qh->dw_align_buf_dma = DMAADDR(ud, 0);
@@ -757,7 +807,7 @@ STATIC int dwc2_hc_setup_align_buf(struc
                }
        }
 
-       usb_syncmem(&qh->dw_align_buf_usbdma, 0, qh->dw_align_buf_size,
+       usb_syncmem(qh->dw_align_buf_usbdma, 0, qh->dw_align_buf_size,
            chan->ep_is_in ?  BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE);
 
        chan->align_buf = qh->dw_align_buf_dma;
@@ -2251,6 +2301,9 @@ STATIC void dwc2_hcd_free(struct dwc2_hs
                hsotg->status_buf = NULL;
        }
 
+       /* Free pre-allocated USB DMA memory */
+       dwc2_usbdma_freemem(hsotg);
+
        ahbcfg = DWC2_READ_4(hsotg, GAHBCFG);
 
        /* Disable all interrupts */
@@ -2400,6 +2453,10 @@ int dwc2_hcd_init(struct dwc2_hsotg *hso
 
        /* retval is already -ENOMEM */
        if (!hsotg->status_buf)
+               goto error3;
+
+       /* Pre-allocate USB DMA memory */
+       if (dwc2_usbdma_allocmem(hsotg))
                goto error3;
 
        hsotg->otg_port = 1;
Index: dev/usb/dwc2/dwc2_hcd.h
===================================================================
RCS file: /cvs/src/sys/dev/usb/dwc2/dwc2_hcd.h,v
retrieving revision 1.15
diff -u -p -u -p -r1.15 dwc2_hcd.h
--- dev/usb/dwc2/dwc2_hcd.h     27 Jul 2021 13:36:59 -0000      1.15
+++ dev/usb/dwc2/dwc2_hcd.h     4 Sep 2021 10:22:17 -0000
@@ -296,7 +296,7 @@ struct dwc2_qh {
        u16 frame_usecs[8];
        u16 start_split_frame;
        u16 ntd;
-       struct usb_dma dw_align_buf_usbdma;
+       struct usb_dma *dw_align_buf_usbdma;
        u8 *dw_align_buf;
        int dw_align_buf_size;
        dma_addr_t dw_align_buf_dma;
@@ -511,6 +511,12 @@ extern int dwc2_hcd_qh_init_ddma(struct 
                                 gfp_t mem_flags);
 extern void dwc2_hcd_qh_free_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh 
*qh);
 
+/* Pre-allocates USB DMA memory to be safely used during interrupt context */
+extern int dwc2_usbdma_allocmem(struct dwc2_hsotg *);
+extern void dwc2_usbdma_freemem(struct dwc2_hsotg *);
+extern struct usb_dma *dwc2_usbdma_getmem(struct dwc2_hsotg *, u8);
+extern void dwc2_usbdma_retmem(struct dwc2_hsotg *, struct usb_dma *);
+
 /* Check if QH is non-periodic */
 #define dwc2_qh_is_non_per(_qh_ptr_) \
        ((_qh_ptr_)->ep_type == USB_ENDPOINT_XFER_BULK || \
@@ -783,6 +789,7 @@ do {                                                        
                \
 void dwc2_wakeup_detected(void *);
 
 int dwc2_hcd_urb_dequeue(struct dwc2_hsotg *, struct dwc2_hcd_urb *);
+int dwc2_hcd_endpoint_disable(struct dwc2_hsotg *, struct dwc2_pipe *, int);
 void dwc2_hcd_reinit(struct dwc2_hsotg *);
 int dwc2_hcd_hub_control(struct dwc2_hsotg *, u16, u16, u16, char *, u16);
 struct dwc2_hsotg *dwc2_hcd_to_hsotg(struct usb_hcd *);
Index: dev/usb/dwc2/dwc2_hcdddma.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/dwc2/dwc2_hcdddma.c,v
retrieving revision 1.19
diff -u -p -u -p -r1.19 dwc2_hcdddma.c
--- dev/usb/dwc2/dwc2_hcdddma.c 4 Sep 2021 10:19:28 -0000       1.19
+++ dev/usb/dwc2/dwc2_hcdddma.c 4 Sep 2021 10:22:17 -0000
@@ -1360,3 +1360,117 @@ void dwc2_hcd_complete_xfer_ddma(struct 
                dwc2_hcd_queue_transactions(hsotg, tr_type);
        }
 }
+
+int
+dwc2_usbdma_allocmem(struct dwc2_hsotg *hsotg)
+{
+       int i, err;
+       u32 buf_size;
+
+       for (i = 0; i < DWC2_USBDMA_SLOTS; i++) {
+               hsotg->usbdma_norm[i].use = -1;
+               hsotg->usbdma_isoc[i].use = -1;
+       }
+
+       buf_size = hsotg->core_params->max_transfer_size;
+       for (i = 0; i < DWC2_USBDMA_SLOTS; i++) {
+               err = usb_allocmem(&hsotg->hsotg_sc->sc_bus, buf_size, 0,
+                   USB_DMA_COHERENT, &hsotg->usbdma_norm[i].dma);
+               if (err) {
+                       dwc2_usbdma_freemem(hsotg);
+                       return err;
+               }
+               hsotg->usbdma_norm[i].use = 0;
+       }
+
+       buf_size = 3072; /* 3072 = 3 max-size Isoc packets */
+       for (i = 0; i < DWC2_USBDMA_SLOTS; i++) {
+               err = usb_allocmem(&hsotg->hsotg_sc->sc_bus, buf_size, 0,
+                   USB_DMA_COHERENT, &hsotg->usbdma_isoc[i].dma);
+               if (err) {
+                       dwc2_usbdma_freemem(hsotg);
+                       return err;
+               }
+               hsotg->usbdma_isoc[i].use = 0;
+       }
+
+       dev_dbg(hsotg->dev, "%s: memory allocated\n", __func__);
+
+       return 0;
+}
+
+void
+dwc2_usbdma_freemem(struct dwc2_hsotg *hsotg)
+{
+       int i;
+
+       for (i = 0; i < DWC2_USBDMA_SLOTS; i++) {
+               if (hsotg->usbdma_norm[i].use != -1) {
+                       usb_freemem(&hsotg->hsotg_sc->sc_bus,
+                           &hsotg->usbdma_norm[i].dma);
+               }
+       }
+
+       for (i = 0; i < DWC2_USBDMA_SLOTS; i++) {
+               if (hsotg->usbdma_isoc[i].use != -1) {
+                       usb_freemem(&hsotg->hsotg_sc->sc_bus,
+                           &hsotg->usbdma_isoc[i].dma);
+               }
+       }
+
+       dev_dbg(hsotg->dev, "%s: memory freed\n", __func__);
+}
+
+struct usb_dma *
+dwc2_usbdma_getmem(struct dwc2_hsotg *hsotg, u8 ep_type)
+{
+       int i;
+
+       if (ep_type != USB_ENDPOINT_XFER_ISOC) {
+               for (i = 0; i < DWC2_USBDMA_SLOTS; i++) {
+                       if (hsotg->usbdma_norm[i].use == 0) {
+                               dev_dbg(hsotg->dev, "%s: norm slot %d\n",
+                                   __func__, i);
+                               hsotg->usbdma_norm[i].use = 1;
+                               return &hsotg->usbdma_norm[i].dma;
+                       }
+               }
+       } else {
+               for (i = 0; i < DWC2_USBDMA_SLOTS; i++) {
+                       if (hsotg->usbdma_isoc[i].use == 0) {
+                               dev_dbg(hsotg->dev, "%s: isoc slot %d\n",
+                                   __func__, i);
+                               hsotg->usbdma_isoc[i].use = 1;
+                               return &hsotg->usbdma_isoc[i].dma;
+                       }
+               }
+       }
+
+       return NULL;
+}
+
+void
+dwc2_usbdma_retmem(struct dwc2_hsotg *hsotg, struct usb_dma *p)
+{
+       int i;
+
+       for (i = 0; i < DWC2_USBDMA_SLOTS; i++) {
+               if (&hsotg->usbdma_norm[i].dma == p &&
+                    hsotg->usbdma_norm[i].use == 1) {
+                       dev_dbg(hsotg->dev, "%s: norm slot %d\n", __func__, i);
+                       hsotg->usbdma_norm[i].use = 0;
+                       return;
+               }
+       }
+
+       for (i = 0; i < DWC2_USBDMA_SLOTS; i++) {
+               if (&hsotg->usbdma_isoc[i].dma == p &&
+                    hsotg->usbdma_isoc[i].use == 1) {
+                       dev_dbg(hsotg->dev, "%s: isoc slot %d\n", __func__, i);
+                       hsotg->usbdma_isoc[i].use = 0;
+                       return;
+               }
+       }
+
+       panic("%s: memory slot was not in use", __func__);
+}
Index: dev/usb/dwc2/dwc2_hcdintr.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/dwc2/dwc2_hcdintr.c,v
retrieving revision 1.13
diff -u -p -u -p -r1.13 dwc2_hcdintr.c
--- dev/usb/dwc2/dwc2_hcdintr.c 4 Sep 2021 10:19:28 -0000       1.13
+++ dev/usb/dwc2/dwc2_hcdintr.c 4 Sep 2021 10:22:18 -0000
@@ -589,7 +589,7 @@ STATIC enum dwc2_halt_status dwc2_update
                if (chan->align_buf && frame_desc->actual_length) {
                        dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n",
                                 __func__);
-                       struct usb_dma *ud = &chan->qh->dw_align_buf_usbdma;
+                       struct usb_dma *ud = chan->qh->dw_align_buf_usbdma;
 
                        usb_syncmem(ud, 0, chan->qh->dw_align_buf_size,
                            chan->ep_is_in ?
@@ -627,7 +627,7 @@ STATIC enum dwc2_halt_status dwc2_update
                if (chan->align_buf && frame_desc->actual_length) {
                        dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n",
                                 __func__);
-                       struct usb_dma *ud = &chan->qh->dw_align_buf_usbdma;
+                       struct usb_dma *ud = chan->qh->dw_align_buf_usbdma;
 
                        usb_syncmem(ud, 0, chan->qh->dw_align_buf_size,
                            chan->ep_is_in ?
@@ -975,11 +975,11 @@ STATIC int dwc2_xfercomp_isoc_split_in(s
 
        if (chan->align_buf) {
                dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__);
-               usb_syncmem(qtd->urb->usbdma, chan->qh->dw_align_buf_dma,
+               usb_syncmem(qtd->urb->usbdma, 0,
                    chan->qh->dw_align_buf_size, BUS_DMASYNC_POSTREAD);
                memcpy(qtd->urb->buf + frame_desc->offset +
                       qtd->isoc_split_offset, chan->qh->dw_align_buf, len);
-               usb_syncmem(qtd->urb->usbdma, chan->qh->dw_align_buf_dma,
+               usb_syncmem(qtd->urb->usbdma, 0,
                    chan->qh->dw_align_buf_size, BUS_DMASYNC_PREREAD);
        }
 
@@ -1208,7 +1208,7 @@ STATIC void dwc2_update_urb_state_abn(st
        if (chan->align_buf && xfer_length && chan->ep_is_in) {
                dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__);
 
-               struct usb_dma *ud = &chan->qh->dw_align_buf_usbdma;
+               struct usb_dma *ud = chan->qh->dw_align_buf_usbdma;
 
                usb_syncmem(ud, 0, chan->qh->dw_align_buf_size,
                    chan->ep_is_in ?
Index: dev/usb/dwc2/dwc2_hcdqueue.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/dwc2/dwc2_hcdqueue.c,v
retrieving revision 1.12
diff -u -p -u -p -r1.12 dwc2_hcdqueue.c
--- dev/usb/dwc2/dwc2_hcdqueue.c        4 Sep 2021 10:19:28 -0000       1.12
+++ dev/usb/dwc2/dwc2_hcdqueue.c        4 Sep 2021 10:22:18 -0000
@@ -268,7 +268,7 @@ void dwc2_hcd_qh_free(struct dwc2_hsotg 
        if (qh->desc_list) {
                dwc2_hcd_qh_free_ddma(hsotg, qh);
        } else if (qh->dw_align_buf) {
-               usb_freemem(&sc->sc_bus, &qh->dw_align_buf_usbdma);
+               dwc2_usbdma_retmem(hsotg, qh->dw_align_buf_usbdma);
                qh->dw_align_buf_dma = (dma_addr_t)0;
        }
 

Reply via email to