git: 99977369433d - stable/13 - Factor out repeated code in the USB controller drivers to avoid bugs computing the same isochronous start frame number over and over again.

From: Hans Petter Selasky <hselasky_at_FreeBSD.org>
Date: Thu, 10 Mar 2022 08:35:38 UTC
The branch stable/13 has been updated by hselasky:

URL: https://cgit.FreeBSD.org/src/commit/?id=99977369433de3eaec6907e51bcabc1dbb088628

commit 99977369433de3eaec6907e51bcabc1dbb088628
Author:     Hans Petter Selasky <hselasky@FreeBSD.org>
AuthorDate: 2021-07-10 16:17:51 +0000
Commit:     Hans Petter Selasky <hselasky@FreeBSD.org>
CommitDate: 2022-03-10 08:29:19 +0000

    Factor out repeated code in the USB controller drivers to avoid bugs
    computing the same isochronous start frame number over and over again.
    
    PR:             257082
    Sponsored by:   NVIDIA Networking
    
    (cherry picked from commit 8fc2a3c41791b205a107dc2bec16ac7514a57958)
    (cherry picked from commit f52783fcf5cc60734121d061beef0d4ea47b224a)
    (cherry picked from commit cf48d1f77126d8de4c03c4dd7c8502be2b5f1954)
---
 sys/dev/usb/controller/atmegadci.c   | 37 +-------------
 sys/dev/usb/controller/avr32dci.c    | 37 +-------------
 sys/dev/usb/controller/dwc_otg.c     | 45 +----------------
 sys/dev/usb/controller/ehci.c        | 96 ++++++------------------------------
 sys/dev/usb/controller/musb_otg.c    | 42 +---------------
 sys/dev/usb/controller/ohci.c        | 33 +++----------
 sys/dev/usb/controller/saf1761_otg.c | 70 ++------------------------
 sys/dev/usb/controller/uhci.c        | 41 +++------------
 sys/dev/usb/controller/uss820dci.c   | 35 +------------
 sys/dev/usb/controller/xhci.c        | 88 ++++++++++++---------------------
 sys/dev/usb/usb_transfer.c           | 69 ++++++++++++++++++++++++++
 sys/dev/usb/usb_transfer.h           |  1 +
 12 files changed, 142 insertions(+), 452 deletions(-)

diff --git a/sys/dev/usb/controller/atmegadci.c b/sys/dev/usb/controller/atmegadci.c
index a52b8ef0c19c..84f331f557a3 100644
--- a/sys/dev/usb/controller/atmegadci.c
+++ b/sys/dev/usb/controller/atmegadci.c
@@ -1420,7 +1420,6 @@ static void
 atmegadci_device_isoc_fs_enter(struct usb_xfer *xfer)
 {
 	struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
-	uint32_t temp;
 	uint32_t nframes;
 
 	DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
@@ -1432,41 +1431,9 @@ atmegadci_device_isoc_fs_enter(struct usb_xfer *xfer)
 	    (ATMEGA_READ_1(sc, ATMEGA_UDFNUMH) << 8) |
 	    (ATMEGA_READ_1(sc, ATMEGA_UDFNUML));
 
-	nframes &= ATMEGA_FRAME_MASK;
-
-	/*
-	 * check if the frame index is within the window where the frames
-	 * will be inserted
-	 */
-	temp = (nframes - xfer->endpoint->isoc_next) & ATMEGA_FRAME_MASK;
-
-	if ((xfer->endpoint->is_synced == 0) ||
-	    (temp < xfer->nframes)) {
-		/*
-		 * If there is data underflow or the pipe queue is
-		 * empty we schedule the transfer a few frames ahead
-		 * of the current frame position. Else two isochronous
-		 * transfers might overlap.
-		 */
-		xfer->endpoint->isoc_next = (nframes + 3) & ATMEGA_FRAME_MASK;
-		xfer->endpoint->is_synced = 1;
+	if (usbd_xfer_get_isochronous_start_frame(
+	    xfer, nframes, 0, 1, ATMEGA_FRAME_MASK, NULL))
 		DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
-	}
-	/*
-	 * compute how many milliseconds the insertion is ahead of the
-	 * current frame position:
-	 */
-	temp = (xfer->endpoint->isoc_next - nframes) & ATMEGA_FRAME_MASK;
-
-	/*
-	 * pre-compute when the isochronous transfer will be finished:
-	 */
-	xfer->isoc_time_complete =
-	    usb_isoc_time_expand(&sc->sc_bus, nframes) + temp +
-	    xfer->nframes;
-
-	/* compute frame number for next insertion */
-	xfer->endpoint->isoc_next += xfer->nframes;
 
 	/* setup TDs */
 	atmegadci_setup_standard_chain(xfer);
diff --git a/sys/dev/usb/controller/avr32dci.c b/sys/dev/usb/controller/avr32dci.c
index d999f1982e9e..20f9a0e6bd4b 100644
--- a/sys/dev/usb/controller/avr32dci.c
+++ b/sys/dev/usb/controller/avr32dci.c
@@ -1354,7 +1354,6 @@ static void
 avr32dci_device_isoc_fs_enter(struct usb_xfer *xfer)
 {
 	struct avr32dci_softc *sc = AVR32_BUS2SC(xfer->xroot->bus);
-	uint32_t temp;
 	uint32_t nframes;
 	uint8_t ep_no;
 
@@ -1365,41 +1364,9 @@ avr32dci_device_isoc_fs_enter(struct usb_xfer *xfer)
 	ep_no = xfer->endpointno & UE_ADDR;
 	nframes = (AVR32_READ_4(sc, AVR32_FNUM) / 8);
 
-	nframes &= AVR32_FRAME_MASK;
-
-	/*
-	 * check if the frame index is within the window where the frames
-	 * will be inserted
-	 */
-	temp = (nframes - xfer->endpoint->isoc_next) & AVR32_FRAME_MASK;
-
-	if ((xfer->endpoint->is_synced == 0) ||
-	    (temp < xfer->nframes)) {
-		/*
-		 * If there is data underflow or the pipe queue is
-		 * empty we schedule the transfer a few frames ahead
-		 * of the current frame position. Else two isochronous
-		 * transfers might overlap.
-		 */
-		xfer->endpoint->isoc_next = (nframes + 3) & AVR32_FRAME_MASK;
-		xfer->endpoint->is_synced = 1;
+	if (usbd_xfer_get_isochronous_start_frame(
+	    xfer, nframes, 0, 1, AVR32_FRAME_MASK, NULL))
 		DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
-	}
-	/*
-	 * compute how many milliseconds the insertion is ahead of the
-	 * current frame position:
-	 */
-	temp = (xfer->endpoint->isoc_next - nframes) & AVR32_FRAME_MASK;
-
-	/*
-	 * pre-compute when the isochronous transfer will be finished:
-	 */
-	xfer->isoc_time_complete =
-	    usb_isoc_time_expand(&sc->sc_bus, nframes) + temp +
-	    xfer->nframes;
-
-	/* compute frame number for next insertion */
-	xfer->endpoint->isoc_next += xfer->nframes;
 
 	/* setup TDs */
 	avr32dci_setup_standard_chain(xfer);
diff --git a/sys/dev/usb/controller/dwc_otg.c b/sys/dev/usb/controller/dwc_otg.c
index 869c44c3bb1a..5a1f2d271251 100644
--- a/sys/dev/usb/controller/dwc_otg.c
+++ b/sys/dev/usb/controller/dwc_otg.c
@@ -4188,9 +4188,7 @@ dwc_otg_device_isoc_start(struct usb_xfer *xfer)
 {
 	struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(xfer->xroot->bus);
 	uint32_t temp;
-	uint32_t msframes;
 	uint32_t framenum;
-	uint8_t shift = usbd_xfer_get_fps_shift(xfer);
 
 	DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
 	    xfer, xfer->endpoint->isoc_next, xfer->nframes);
@@ -4213,52 +4211,13 @@ dwc_otg_device_isoc_start(struct usb_xfer *xfer)
 	if (sc->sc_flags.status_high_speed)
 		framenum /= 8;
 
-	framenum &= DWC_OTG_FRAME_MASK;
-
-	/*
-	 * Compute number of milliseconds worth of data traffic for
-	 * this USB transfer:
-	 */ 
-	if (xfer->xroot->udev->speed == USB_SPEED_HIGH)
-		msframes = ((xfer->nframes << shift) + 7) / 8;
-	else
-		msframes = xfer->nframes;
-
-	/*
-	 * check if the frame index is within the window where the frames
-	 * will be inserted
-	 */
-	temp = (framenum - xfer->endpoint->isoc_next) & DWC_OTG_FRAME_MASK;
-
-	if ((xfer->endpoint->is_synced == 0) || (temp < msframes)) {
-		/*
-		 * If there is data underflow or the pipe queue is
-		 * empty we schedule the transfer a few frames ahead
-		 * of the current frame position. Else two isochronous
-		 * transfers might overlap.
-		 */
-		xfer->endpoint->isoc_next = (framenum + 3) & DWC_OTG_FRAME_MASK;
-		xfer->endpoint->is_synced = 1;
+	if (usbd_xfer_get_isochronous_start_frame(
+	    xfer, framenum, 0, 1, DWC_OTG_FRAME_MASK, NULL))
 		DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
-	}
-	/*
-	 * compute how many milliseconds the insertion is ahead of the
-	 * current frame position:
-	 */
-	temp = (xfer->endpoint->isoc_next - framenum) & DWC_OTG_FRAME_MASK;
-
-	/*
-	 * pre-compute when the isochronous transfer will be finished:
-	 */
-	xfer->isoc_time_complete =
-		usb_isoc_time_expand(&sc->sc_bus, framenum) + temp + msframes;
 
 	/* setup TDs */
 	dwc_otg_setup_standard_chain(xfer);
 
-	/* compute frame number for next insertion */
-	xfer->endpoint->isoc_next += msframes;
-
 	/* start TD chain */
 	dwc_otg_start_standard_chain(xfer);
 }
diff --git a/sys/dev/usb/controller/ehci.c b/sys/dev/usb/controller/ehci.c
index 0b7f41b39234..dd4f7c568625 100644
--- a/sys/dev/usb/controller/ehci.c
+++ b/sys/dev/usb/controller/ehci.c
@@ -2440,6 +2440,7 @@ ehci_device_isoc_fs_enter(struct usb_xfer *xfer)
 	uint32_t *plen;
 	uint32_t buf_offset;
 	uint32_t nframes;
+	uint32_t startframe;
 	uint32_t temp;
 	uint32_t sitd_mask;
 	uint16_t tlen;
@@ -2458,39 +2459,9 @@ ehci_device_isoc_fs_enter(struct usb_xfer *xfer)
 
 	nframes = EOREAD4(sc, EHCI_FRINDEX) / 8;
 
-	/*
-	 * check if the frame index is within the window where the frames
-	 * will be inserted
-	 */
-	buf_offset = (nframes - xfer->endpoint->isoc_next) &
-	    (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
-
-	if ((xfer->endpoint->is_synced == 0) ||
-	    (buf_offset < xfer->nframes)) {
-		/*
-		 * If there is data underflow or the pipe queue is empty we
-		 * schedule the transfer a few frames ahead of the current
-		 * frame position. Else two isochronous transfers might
-		 * overlap.
-		 */
-		xfer->endpoint->isoc_next = (nframes + 3) &
-		    (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
-		xfer->endpoint->is_synced = 1;
-		DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
-	}
-	/*
-	 * compute how many milliseconds the insertion is ahead of the
-	 * current frame position:
-	 */
-	buf_offset = (xfer->endpoint->isoc_next - nframes) &
-	    (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
-
-	/*
-	 * pre-compute when the isochronous transfer will be finished:
-	 */
-	xfer->isoc_time_complete =
-	    usb_isoc_time_expand(&sc->sc_bus, nframes) +
-	    buf_offset + xfer->nframes;
+	if (usbd_xfer_get_isochronous_start_frame(
+	    xfer, nframes, 0, 1, EHCI_VIRTUAL_FRAMELIST_COUNT - 1, &startframe))
+		DPRINTFN(3, "start next=%d\n", startframe);
 
 	/* get the real number of frames */
 
@@ -2507,11 +2478,11 @@ ehci_device_isoc_fs_enter(struct usb_xfer *xfer)
 	td = xfer->td_start[xfer->flags_int.curr_dma_set];
 	xfer->td_transfer_first = td;
 
-	pp_last = &sc->sc_isoc_fs_p_last[xfer->endpoint->isoc_next];
+	pp_last = &sc->sc_isoc_fs_p_last[startframe];
 
 	/* store starting position */
 
-	xfer->qh_pos = xfer->endpoint->isoc_next;
+	xfer->qh_pos = startframe;
 
 	while (nframes--) {
 		if (td == NULL) {
@@ -2633,10 +2604,6 @@ ehci_device_isoc_fs_enter(struct usb_xfer *xfer)
 
 	xfer->td_transfer_last = td_last;
 
-	/* update isoc_next */
-	xfer->endpoint->isoc_next = (pp_last - &sc->sc_isoc_fs_p_last[0]) &
-	    (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
-
 	/*
 	 * We don't allow cancelling of the SPLIT transaction USB FULL
 	 * speed transfer, because it disturbs the bandwidth
@@ -2743,11 +2710,11 @@ ehci_device_isoc_hs_enter(struct usb_xfer *xfer)
 	uint32_t status;
 	uint32_t buf_offset;
 	uint32_t nframes;
+	uint32_t startframe;
 	uint32_t itd_offset[8 + 1];
 	uint8_t x;
 	uint8_t td_no;
 	uint8_t page_no;
-	uint8_t shift = usbd_xfer_get_fps_shift(xfer);
 
 #ifdef USB_DEBUG
 	uint8_t once = 1;
@@ -2755,47 +2722,16 @@ ehci_device_isoc_hs_enter(struct usb_xfer *xfer)
 #endif
 
 	DPRINTFN(6, "xfer=%p next=%d nframes=%d shift=%d\n",
-	    xfer, xfer->endpoint->isoc_next, xfer->nframes, (int)shift);
+	    xfer, xfer->endpoint->isoc_next, xfer->nframes,
+	    usbd_xfer_get_fps_shift(xfer));
 
 	/* get the current frame index */
 
 	nframes = EOREAD4(sc, EHCI_FRINDEX) / 8;
 
-	/*
-	 * check if the frame index is within the window where the frames
-	 * will be inserted
-	 */
-	buf_offset = (nframes - xfer->endpoint->isoc_next) &
-	    (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
-
-	if ((xfer->endpoint->is_synced == 0) ||
-	    (buf_offset < (((xfer->nframes << shift) + 7) / 8))) {
-		/*
-		 * If there is data underflow or the pipe queue is empty we
-		 * schedule the transfer a few frames ahead of the current
-		 * frame position. Else two isochronous transfers might
-		 * overlap.
-		 */
-		xfer->endpoint->isoc_next = (nframes + 3) &
-		    (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
-		xfer->endpoint->is_synced = 1;
-		DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
-	}
-	/*
-	 * compute how many milliseconds the insertion is ahead of the
-	 * current frame position:
-	 */
-	buf_offset = (xfer->endpoint->isoc_next - nframes) &
-	    (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
-
-	/*
-	 * pre-compute when the isochronous transfer will be finished:
-	 */
-	xfer->isoc_time_complete =
-	    usb_isoc_time_expand(&sc->sc_bus, nframes) + buf_offset +
-	    (((xfer->nframes << shift) + 7) / 8);
-
-	/* get the real number of frames */
+	if (usbd_xfer_get_isochronous_start_frame(
+	    xfer, nframes, 0, 1, EHCI_VIRTUAL_FRAMELIST_COUNT - 1, &startframe))
+		DPRINTFN(3, "start next=%d\n", startframe);
 
 	nframes = xfer->nframes;
 
@@ -2811,11 +2747,11 @@ ehci_device_isoc_hs_enter(struct usb_xfer *xfer)
 	td = xfer->td_start[xfer->flags_int.curr_dma_set];
 	xfer->td_transfer_first = td;
 
-	pp_last = &sc->sc_isoc_hs_p_last[xfer->endpoint->isoc_next];
+	pp_last = &sc->sc_isoc_hs_p_last[startframe];
 
 	/* store starting position */
 
-	xfer->qh_pos = xfer->endpoint->isoc_next;
+	xfer->qh_pos = startframe;
 
 	while (nframes) {
 		if (td == NULL) {
@@ -2927,10 +2863,6 @@ ehci_device_isoc_hs_enter(struct usb_xfer *xfer)
 	}
 
 	xfer->td_transfer_last = td_last;
-
-	/* update isoc_next */
-	xfer->endpoint->isoc_next = (pp_last - &sc->sc_isoc_hs_p_last[0]) &
-	    (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
 }
 
 static void
diff --git a/sys/dev/usb/controller/musb_otg.c b/sys/dev/usb/controller/musb_otg.c
index 9dd24a837316..aa24544f8893 100644
--- a/sys/dev/usb/controller/musb_otg.c
+++ b/sys/dev/usb/controller/musb_otg.c
@@ -3435,9 +3435,7 @@ static void
 musbotg_device_isoc_enter(struct usb_xfer *xfer)
 {
 	struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
-	uint32_t temp;
 	uint32_t nframes;
-	uint32_t fs_frames;
 
 	DPRINTFN(5, "xfer=%p next=%d nframes=%d\n",
 	    xfer, xfer->endpoint->isoc_next, xfer->nframes);
@@ -3446,45 +3444,9 @@ musbotg_device_isoc_enter(struct usb_xfer *xfer)
 
 	nframes = MUSB2_READ_2(sc, MUSB2_REG_FRAME);
 
-	/*
-	 * check if the frame index is within the window where the frames
-	 * will be inserted
-	 */
-	temp = (nframes - xfer->endpoint->isoc_next) & MUSB2_MASK_FRAME;
-
-	if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) {
-		fs_frames = (xfer->nframes + 7) / 8;
-	} else {
-		fs_frames = xfer->nframes;
-	}
-
-	if ((xfer->endpoint->is_synced == 0) ||
-	    (temp < fs_frames)) {
-		/*
-		 * If there is data underflow or the pipe queue is
-		 * empty we schedule the transfer a few frames ahead
-		 * of the current frame position. Else two isochronous
-		 * transfers might overlap.
-		 */
-		xfer->endpoint->isoc_next = (nframes + 3) & MUSB2_MASK_FRAME;
-		xfer->endpoint->is_synced = 1;
+	if (usbd_xfer_get_isochronous_start_frame(
+	    xfer, nframes, 0, 1, MUSB2_MASK_FRAME, NULL))
 		DPRINTFN(2, "start next=%d\n", xfer->endpoint->isoc_next);
-	}
-	/*
-	 * compute how many milliseconds the insertion is ahead of the
-	 * current frame position:
-	 */
-	temp = (xfer->endpoint->isoc_next - nframes) & MUSB2_MASK_FRAME;
-
-	/*
-	 * pre-compute when the isochronous transfer will be finished:
-	 */
-	xfer->isoc_time_complete =
-	    usb_isoc_time_expand(&sc->sc_bus, nframes) + temp +
-	    fs_frames;
-
-	/* compute frame number for next insertion */
-	xfer->endpoint->isoc_next += fs_frames;
 
 	/* setup TDs */
 	musbotg_setup_standard_chain(xfer);
diff --git a/sys/dev/usb/controller/ohci.c b/sys/dev/usb/controller/ohci.c
index 39f3bd54f507..7268af06a602 100644
--- a/sys/dev/usb/controller/ohci.c
+++ b/sys/dev/usb/controller/ohci.c
@@ -1824,6 +1824,7 @@ ohci_device_isoc_enter(struct usb_xfer *xfer)
 	struct ohci_hcca *hcca;
 	uint32_t buf_offset;
 	uint32_t nframes;
+	uint32_t startframe;
 	uint32_t ed_flags;
 	uint32_t *plen;
 	uint16_t itd_offset[OHCI_ITD_NOFFSET];
@@ -1840,31 +1841,9 @@ ohci_device_isoc_enter(struct usb_xfer *xfer)
 	DPRINTFN(6, "xfer=%p isoc_next=%u nframes=%u hcca_fn=%u\n",
 	    xfer, xfer->endpoint->isoc_next, xfer->nframes, nframes);
 
-	if ((xfer->endpoint->is_synced == 0) ||
-	    (((nframes - xfer->endpoint->isoc_next) & 0xFFFF) < xfer->nframes) ||
-	    (((xfer->endpoint->isoc_next - nframes) & 0xFFFF) >= 128)) {
-		/*
-		 * If there is data underflow or the pipe queue is empty we
-		 * schedule the transfer a few frames ahead of the current
-		 * frame position. Else two isochronous transfers might
-		 * overlap.
-		 */
-		xfer->endpoint->isoc_next = (nframes + 3) & 0xFFFF;
-		xfer->endpoint->is_synced = 1;
-		DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
-	}
-	/*
-	 * compute how many milliseconds the insertion is ahead of the
-	 * current frame position:
-	 */
-	buf_offset = ((xfer->endpoint->isoc_next - nframes) & 0xFFFF);
-
-	/*
-	 * pre-compute when the isochronous transfer will be finished:
-	 */
-	xfer->isoc_time_complete =
-	    (usb_isoc_time_expand(&sc->sc_bus, nframes) + buf_offset +
-	    xfer->nframes);
+	if (usbd_xfer_get_isochronous_start_frame(
+	    xfer, nframes, 0, 1, 0xFFFF, &startframe))
+		DPRINTFN(3, "start next=%d\n", startframe);
 
 	/* get the real number of frames */
 
@@ -1905,12 +1884,12 @@ ohci_device_isoc_enter(struct usb_xfer *xfer)
 			/* fill current ITD */
 			td->itd_flags = htole32(
 			    OHCI_ITD_NOCC |
-			    OHCI_ITD_SET_SF(xfer->endpoint->isoc_next) |
+			    OHCI_ITD_SET_SF(startframe) |
 			    OHCI_ITD_NOINTR |
 			    OHCI_ITD_SET_FC(ncur));
 
 			td->frames = ncur;
-			xfer->endpoint->isoc_next += ncur;
+			startframe += ncur;
 
 			if (length == 0) {
 				/* all zero */
diff --git a/sys/dev/usb/controller/saf1761_otg.c b/sys/dev/usb/controller/saf1761_otg.c
index df0162e51749..f5725a3cb48a 100644
--- a/sys/dev/usb/controller/saf1761_otg.c
+++ b/sys/dev/usb/controller/saf1761_otg.c
@@ -2632,7 +2632,6 @@ static void
 saf1761_otg_device_isoc_enter(struct usb_xfer *xfer)
 {
 	struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(xfer->xroot->bus);
-	uint32_t temp;
 	uint32_t nframes;
 
 	DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
@@ -2642,39 +2641,9 @@ saf1761_otg_device_isoc_enter(struct usb_xfer *xfer)
 
 	nframes = SAF1761_READ_LE_4(sc, SOTG_FRAME_NUM);
 
-	/*
-	 * check if the frame index is within the window where the
-	 * frames will be inserted
-	 */
-	temp = (nframes - xfer->endpoint->isoc_next) & SOTG_FRAME_NUM_SOFR_MASK;
-
-	if ((xfer->endpoint->is_synced == 0) ||
-	    (temp < xfer->nframes)) {
-		/*
-		 * If there is data underflow or the pipe queue is
-		 * empty we schedule the transfer a few frames ahead
-		 * of the current frame position. Else two isochronous
-		 * transfers might overlap.
-		 */
-		xfer->endpoint->isoc_next = (nframes + 3) & SOTG_FRAME_NUM_SOFR_MASK;
-		xfer->endpoint->is_synced = 1;
+	if (usbd_xfer_get_isochronous_start_frame(
+	    xfer, nframes, 0, 1, SOTG_FRAME_NUM_SOFR_MASK, NULL))
 		DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
-	}
-	/*
-	 * compute how many milliseconds the insertion is ahead of the
-	 * current frame position:
-	 */
-	temp = (xfer->endpoint->isoc_next - nframes) & SOTG_FRAME_NUM_SOFR_MASK;
-
-	/*
-	 * pre-compute when the isochronous transfer will be finished:
-	 */
-	xfer->isoc_time_complete =
-	    usb_isoc_time_expand(&sc->sc_bus, nframes) + temp +
-	    xfer->nframes;
-
-	/* compute frame number for next insertion */
-	xfer->endpoint->isoc_next += xfer->nframes;
 
 	/* setup TDs */
 	saf1761_otg_setup_standard_chain(xfer);
@@ -2714,7 +2683,6 @@ static void
 saf1761_otg_host_isoc_enter(struct usb_xfer *xfer)
 {
 	struct saf1761_otg_softc *sc = SAF1761_OTG_BUS2SC(xfer->xroot->bus);
-	uint32_t temp;
 	uint32_t nframes;
 
 	DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
@@ -2724,39 +2692,9 @@ saf1761_otg_host_isoc_enter(struct usb_xfer *xfer)
 
 	nframes = (SAF1761_READ_LE_4(sc, SOTG_FRINDEX) & SOTG_FRINDEX_MASK) >> 3;
 
-	/*
-	 * check if the frame index is within the window where the
-	 * frames will be inserted
-	 */
-	temp = (nframes - xfer->endpoint->isoc_next) & (SOTG_FRINDEX_MASK >> 3);
-
-	if ((xfer->endpoint->is_synced == 0) ||
-	    (temp < xfer->nframes)) {
-		/*
-		 * If there is data underflow or the pipe queue is
-		 * empty we schedule the transfer a few frames ahead
-		 * of the current frame position. Else two isochronous
-		 * transfers might overlap.
-		 */
-		xfer->endpoint->isoc_next = (nframes + 3) & (SOTG_FRINDEX_MASK >> 3);
-		xfer->endpoint->is_synced = 1;
+	if (usbd_xfer_get_isochronous_start_frame(
+	    xfer, nframes, 0, 1, SOTG_FRINDEX_MASK >> 3, NULL))
 		DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
-	}
-	/*
-	 * compute how many milliseconds the insertion is ahead of the
-	 * current frame position:
-	 */
-	temp = (xfer->endpoint->isoc_next - nframes) & (SOTG_FRINDEX_MASK >> 3);
-
-	/*
-	 * pre-compute when the isochronous transfer will be finished:
-	 */
-	xfer->isoc_time_complete =
-	    usb_isoc_time_expand(&sc->sc_bus, nframes) + temp +
-	    xfer->nframes;
-
-	/* compute frame number for next insertion */
-	xfer->endpoint->isoc_next += xfer->nframes;
 
 	/* setup TDs */
 	saf1761_otg_setup_standard_chain(xfer);
diff --git a/sys/dev/usb/controller/uhci.c b/sys/dev/usb/controller/uhci.c
index 86bfe0b1108a..4c72118d9900 100644
--- a/sys/dev/usb/controller/uhci.c
+++ b/sys/dev/usb/controller/uhci.c
@@ -2111,7 +2111,7 @@ uhci_device_isoc_enter(struct usb_xfer *xfer)
 	struct uhci_mem_layout ml;
 	uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
 	uint32_t nframes;
-	uint32_t temp;
+	uint32_t startframe;
 	uint32_t *plen;
 
 #ifdef USB_DEBUG
@@ -2127,34 +2127,9 @@ uhci_device_isoc_enter(struct usb_xfer *xfer)
 
 	nframes = UREAD2(sc, UHCI_FRNUM);
 
-	temp = (nframes - xfer->endpoint->isoc_next) &
-	    (UHCI_VFRAMELIST_COUNT - 1);
-
-	if ((xfer->endpoint->is_synced == 0) ||
-	    (temp < xfer->nframes)) {
-		/*
-		 * If there is data underflow or the pipe queue is empty we
-		 * schedule the transfer a few frames ahead of the current
-		 * frame position. Else two isochronous transfers might
-		 * overlap.
-		 */
-		xfer->endpoint->isoc_next = (nframes + 3) & (UHCI_VFRAMELIST_COUNT - 1);
-		xfer->endpoint->is_synced = 1;
-		DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
-	}
-	/*
-	 * compute how many milliseconds the insertion is ahead of the
-	 * current frame position:
-	 */
-	temp = (xfer->endpoint->isoc_next - nframes) &
-	    (UHCI_VFRAMELIST_COUNT - 1);
-
-	/*
-	 * pre-compute when the isochronous transfer will be finished:
-	 */
-	xfer->isoc_time_complete =
-	    usb_isoc_time_expand(&sc->sc_bus, nframes) + temp +
-	    xfer->nframes;
+	if (usbd_xfer_get_isochronous_start_frame(
+	    xfer, nframes, 0, 1, UHCI_VFRAMELIST_COUNT - 1, &startframe))
+		DPRINTFN(3, "start next=%d\n", startframe);
 
 	/* get the real number of frames */
 
@@ -2171,11 +2146,11 @@ uhci_device_isoc_enter(struct usb_xfer *xfer)
 	td = xfer->td_start[xfer->flags_int.curr_dma_set];
 	xfer->td_transfer_first = td;
 
-	pp_last = &sc->sc_isoc_p_last[xfer->endpoint->isoc_next];
+	pp_last = &sc->sc_isoc_p_last[startframe];
 
 	/* store starting position */
 
-	xfer->qh_pos = xfer->endpoint->isoc_next;
+	xfer->qh_pos = startframe;
 
 	while (nframes--) {
 		if (td == NULL) {
@@ -2252,10 +2227,6 @@ uhci_device_isoc_enter(struct usb_xfer *xfer)
 	}
 
 	xfer->td_transfer_last = td_last;
-
-	/* update isoc_next */
-	xfer->endpoint->isoc_next = (pp_last - &sc->sc_isoc_p_last[0]) &
-	    (UHCI_VFRAMELIST_COUNT - 1);
 }
 
 static void
diff --git a/sys/dev/usb/controller/uss820dci.c b/sys/dev/usb/controller/uss820dci.c
index 4fc6d5dbfea2..7bbfdace3e72 100644
--- a/sys/dev/usb/controller/uss820dci.c
+++ b/sys/dev/usb/controller/uss820dci.c
@@ -1705,7 +1705,6 @@ static void
 uss820dci_device_isoc_fs_enter(struct usb_xfer *xfer)
 {
 	struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus);
-	uint32_t temp;
 	uint32_t nframes;
 
 	DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
@@ -1715,39 +1714,9 @@ uss820dci_device_isoc_fs_enter(struct usb_xfer *xfer)
 
 	nframes = USS820_READ_1(sc, USS820_SOFL);
 
-	/*
-	 * check if the frame index is within the window where the
-	 * frames will be inserted
-	 */
-	temp = (nframes - xfer->endpoint->isoc_next) & USS820_SOFL_MASK;
-
-	if ((xfer->endpoint->is_synced == 0) ||
-	    (temp < xfer->nframes)) {
-		/*
-		 * If there is data underflow or the pipe queue is
-		 * empty we schedule the transfer a few frames ahead
-		 * of the current frame position. Else two isochronous
-		 * transfers might overlap.
-		 */
-		xfer->endpoint->isoc_next = (nframes + 3) & USS820_SOFL_MASK;
-		xfer->endpoint->is_synced = 1;
+	if (usbd_xfer_get_isochronous_start_frame(
+	    xfer, nframes, 0, 1, USS820_SOFL_MASK, NULL))
 		DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
-	}
-	/*
-	 * compute how many milliseconds the insertion is ahead of the
-	 * current frame position:
-	 */
-	temp = (xfer->endpoint->isoc_next - nframes) & USS820_SOFL_MASK;
-
-	/*
-	 * pre-compute when the isochronous transfer will be finished:
-	 */
-	xfer->isoc_time_complete =
-	    usb_isoc_time_expand(&sc->sc_bus, nframes) + temp +
-	    xfer->nframes;
-
-	/* compute frame number for next insertion */
-	xfer->endpoint->isoc_next += xfer->nframes;
 
 	/* setup TDs */
 	uss820dci_setup_standard_chain(xfer);
diff --git a/sys/dev/usb/controller/xhci.c b/sys/dev/usb/controller/xhci.c
index f42428614bbb..9c0f53de4924 100644
--- a/sys/dev/usb/controller/xhci.c
+++ b/sys/dev/usb/controller/xhci.c
@@ -133,8 +133,8 @@ struct xhci_std_temp {
 	uint32_t		offset;
 	uint32_t		max_packet_size;
 	uint32_t		average;
+	uint32_t		isoc_frame;
 	uint16_t		isoc_delta;
-	uint16_t		isoc_frame;
 	uint8_t			shortpkt;
 	uint8_t			multishort;
 	uint8_t			last_frame;
@@ -2078,64 +2078,58 @@ xhci_setup_generic_chain(struct usb_xfer *xfer)
 
 		x = XREAD4(temp.sc, runt, XHCI_MFINDEX);
 
-		DPRINTF("MFINDEX=0x%08x IST=0x%x\n", x, sc->sc_ist);
-
-		/* add isochronous scheduling threshold */
-		if (temp.sc->sc_ist & 8)
-			x += (temp.sc->sc_ist & 7) << 3;
-		else
-			x += (temp.sc->sc_ist & 7);
+		DPRINTF("MFINDEX=0x%08x IST=0x%x\n", x, temp.sc->sc_ist);
 
 		switch (usbd_get_speed(xfer->xroot->udev)) {
 		case USB_SPEED_FULL:
 			shift = 3;
 			temp.isoc_delta = 8;	/* 1ms */
-			x += temp.isoc_delta - 1;
-			x &= ~(temp.isoc_delta - 1);
 			break;
 		default:
 			shift = usbd_xfer_get_fps_shift(xfer);
 			temp.isoc_delta = 1U << shift;
-			x += temp.isoc_delta - 1;
-			x &= ~(temp.isoc_delta - 1);
-			/* simple frame load balancing */
-			x += xfer->endpoint->usb_uframe;
 			break;
 		}
 
-		y = XHCI_MFINDEX_GET(x - xfer->endpoint->isoc_next);
+		/* Compute isochronous scheduling threshold. */
+		if (temp.sc->sc_ist & 8)
+			y = (temp.sc->sc_ist & 7) << 3;
+		else
+			y = (temp.sc->sc_ist & 7);
 
-		if ((xfer->endpoint->is_synced == 0) ||
-		    (y < (xfer->nframes << shift)) ||
-		    (XHCI_MFINDEX_GET(-y) >= (128 * 8))) {
+		/* Range check the IST. */
+		if (y < 8) {
+			y = 0;
+		} else if (y > 15) {
+			DPRINTFN(3, "IST(%d) is too big!\n", temp.sc->sc_ist);
 			/*
-			 * If there is data underflow or the pipe
-			 * queue is empty we schedule the transfer a
-			 * few frames ahead of the current frame
-			 * position. Else two isochronous transfers
-			 * might overlap.
+			 * The USB stack minimum isochronous transfer
+			 * size is typically 2x2 ms of payload. If the
+			 * IST makes is above 15 microframes, we have
+			 * an effective scheduling delay of more than
+			 * or equal to 2 milliseconds, which is too
+			 * much.
 			 */
-			xfer->endpoint->isoc_next = XHCI_MFINDEX_GET(x + (3 * 8));
-			xfer->endpoint->is_synced = 1;
-			temp.do_isoc_sync = 1;
-
-			DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
+			y = 7;
+		} else {
+			/*
+			 * Subtract one millisecond, because the
+			 * generic code adds that to the latency.
+			 */
+			y -= 8;
 		}
 
-		/* compute isochronous completion time */
-
-		y = XHCI_MFINDEX_GET(xfer->endpoint->isoc_next - (x & ~7));
+		if (usbd_xfer_get_isochronous_start_frame(
+		    xfer, x, y, 8, XHCI_MFINDEX_GET(-1), &temp.isoc_frame)) {
+			/* Start isochronous transfer at specified time. */
+			temp.do_isoc_sync = 1;
 
-		xfer->isoc_time_complete =
-		    usb_isoc_time_expand(&temp.sc->sc_bus, x / 8) +
-		    (y / 8) + (((xfer->nframes << shift) + 7) / 8);
+			DPRINTFN(3, "start next=%d\n", temp.isoc_frame);
+		}
 
 		x = 0;
-		temp.isoc_frame = xfer->endpoint->isoc_next;
 		temp.trb_type = XHCI_TRB_TYPE_ISOCH;
 
-		xfer->endpoint->isoc_next += xfer->nframes << shift;
-
 	} else if (xfer->flags_int.control_xfr) {
 		/* check if we should prepend a setup message */
 
@@ -3072,15 +3066,7 @@ xhci_device_done(struct usb_xfer *xfer, usb_error_t error)
 static void
 xhci_device_generic_open(struct usb_xfer *xfer)
 {
-	if (xfer->flags_int.isochronous_xfr) {
-		switch (xfer->xroot->udev->speed) {
-		case USB_SPEED_FULL:
-			break;
-		default:
-			usb_hs_bandwidth_alloc(xfer);
-			break;
-		}
-	}
+	DPRINTF("\n");
 }
 
 static void
@@ -3089,16 +3075,6 @@ xhci_device_generic_close(struct usb_xfer *xfer)
 	DPRINTF("\n");
 
 	xhci_device_done(xfer, USB_ERR_CANCELLED);
-
-	if (xfer->flags_int.isochronous_xfr) {
-		switch (xfer->xroot->udev->speed) {
-		case USB_SPEED_FULL:
-			break;
-		default:
-			usb_hs_bandwidth_free(xfer);
-			break;
-		}
-	}
 }
 
 static void
diff --git a/sys/dev/usb/usb_transfer.c b/sys/dev/usb/usb_transfer.c
index 871f8d729658..436e08db14a1 100644
--- a/sys/dev/usb/usb_transfer.c
+++ b/sys/dev/usb/usb_transfer.c
@@ -3627,3 +3627,72 @@ usbd_xfer_maxp_was_clamped(struct usb_xfer *xfer)
 {
 	return (xfer->flags_int.maxp_was_clamped);
 }
+
+/*
+ * The following function computes the next isochronous frame number
+ * where the first isochronous packet should be queued.
+ *
+ * The function returns non-zero if there was a discontinuity.
+ * Else zero is returned for normal operation.
+ */
+uint8_t
+usbd_xfer_get_isochronous_start_frame(struct usb_xfer *xfer, uint32_t frame_curr,
+    uint32_t frame_min, uint32_t frame_ms, uint32_t frame_mask, uint32_t *p_frame_start)
+{
+	uint32_t duration;
+	uint32_t delta;
+	uint8_t retval;
+	uint8_t shift;
+
+	/* Compute time ahead of current schedule. */
+	delta = (xfer->endpoint->isoc_next - frame_curr) & frame_mask;
+
+	/*
+	 * Check if it is the first transfer or if the future frame
+	 * delta is less than one millisecond or if the frame delta is
+	 * negative:
+	 */
+	if (xfer->endpoint->is_synced == 0 ||
+	    delta < (frame_ms + frame_min) ||
+	    delta > (frame_mask / 2)) {
+		/* Schedule transfer 2 milliseconds into the future. */
+		xfer->endpoint->isoc_next = (frame_curr + 2 * frame_ms + frame_min) & frame_mask;
+		xfer->endpoint->is_synced = 1;
+
+		retval = 1;
+	} else {
+		retval = 0;
+	}
+
+	/* Store start time, if any. */
+	if (p_frame_start != NULL)
+		*p_frame_start = xfer->endpoint->isoc_next & frame_mask;
+
+	/* Get relative completion time, in milliseconds. */
+	delta = xfer->endpoint->isoc_next - frame_curr + (frame_curr % frame_ms);
+	delta &= frame_mask;
+	delta /= frame_ms;
+
+	switch (usbd_get_speed(xfer->xroot->udev)) {
+	case USB_SPEED_FULL:
+		shift = 3;
+		break;
+	default:
+		shift = usbd_xfer_get_fps_shift(xfer);
+		break;
+	}
+
+	/* Get duration in milliseconds, rounded up. */
+	duration = ((xfer->nframes << shift) + 7) / 8;
+
+	/* Compute full 32-bit completion time, in milliseconds. */
+	xfer->isoc_time_complete =
+	    usb_isoc_time_expand(xfer->xroot->bus, frame_curr / frame_ms) +
+	    delta + duration;
+
+	/* Compute next isochronous frame. */
+	xfer->endpoint->isoc_next += duration * frame_ms;
+	xfer->endpoint->isoc_next &= frame_mask;
+
+	return (retval);
+}
diff --git a/sys/dev/usb/usb_transfer.h b/sys/dev/usb/usb_transfer.h
index 0dd750c33c0d..60841f473334 100644
--- a/sys/dev/usb/usb_transfer.h
+++ b/sys/dev/usb/usb_transfer.h
@@ -147,5 +147,6 @@ void	usbd_transfer_timeout_ms(struct usb_xfer *xfer,
 	    void (*cb) (void *arg), usb_timeout_t ms);
 usb_timeout_t usbd_get_dma_delay(struct usb_device *udev);
 void	usbd_transfer_power_ref(struct usb_xfer *xfer, int val);
+uint8_t	usbd_xfer_get_isochronous_start_frame(struct usb_xfer *, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t *);
 
 #endif					/* _USB_TRANSFER_H_ */