patch to make ohci isochronous xfers work.

Bruce R. Montague brucem at cruzio.com
Sun May 4 14:56:56 PDT 2003



Hi, I have fixed isochronous usb transfers to work
under FreeBSD 5.0 -current when using the ohci
controller (dev/usb/ohci.c), at least to the degree
required to support the Dlink DSB-C100 web-camera
and the "vid" image capture app. Patches attached.
Can any usb/ohci committers evaluate this?


The FreeBSD usb stack is basically shared between
all BSDs. I'm sending this patch message to just
about everyone that I've corresponded with about
*BSD usb source, since I don't seem to know of a
`one-stop' active BSD usb e-mail list.

The ohci.c code as it stands has a long-outstanding
bug that precludes sustained isochronous transfers
from ever working.  Isochronous transfers are basically
transfers that last forever, from open to close on
the stream, basically a continuous dma input. The
real definition of "isoc" here is that it's OK to
simply drop data without reporting any errors :).
You can use isoc to maintain a ring buffer of data,
such as audio or video, that can tolerate drops.

The core problem is that after an interrupt, the
interrupt handler allocates resources for the next
isoc I/O iteration, but then these resources are
immediately freed because the I/O is considered done.
Another problem is that the hardware is never
started, because the bit in the listhead that the
controller looks at to see if it should process the
isoc list is never set. This might have been
deliberate, because it was known not to work.

The specific problem was that "xfer->hcpriv" is used
in "ohci_device_isoc_enter()" to anchor a list of
allocated "sitd" structures that are constructed to
map the next I/O transfer that the "xfer" structure
will be used for, but this allocation is almost
immediately followed by a call to "ohci_device_isoc_done()"
which frees all the sitd's on the "xfer->hcpriv"
list and then does a "xfer->hcpriv = NULL;". The
result is that only the initial UGEN_NISOREQS that
were created by the initial device open can work.
After these active operations complete, the driver
stalls, as it in effect never allocates new sitds.

The above calls are performed by the "usbdi.c"
"usb_transfer_complete()" routine (not in the ohci
driver proper), which is itself called by the ohci
interrupt handler.

A couple of other problems have also been fixed which
make it possible to reliably abort and close the
isoc pipe.

The patch is for FreeBSD -current code as of about
10-april-2003, however, it doesn't look like this
file has been touched in any cvs for over two months.
The OpenBSD and NetBSD webcvs ohci.c files look
similar, but not identical to FreeBSD -current. Their
isoc implementaions appear to contain the same bug
as in FreeBSD. I have _not_ tested this under FreeBSD
4-stable.

I'm not claiming this is a perfect solution; perhaps
the entire isoc code in ohci.c needs to be restructured.
However, with this patch the "vid" image capture app
can be placed in a loop that "runs forever".

This patch includes two of the unrelated splx()
patches that were reported by jordbaer at mac.com on
30-april-2003 (the others already appear to be in
the FreeBSD-current cvs).

Can whoever is responsible for various commits
associated with "dev/usb/ohci.c" please evaluate
this? This bug has been "outstanding" for a good
while, is hard to pin down, and keeps all but the
most trivial isoc operations from being used with
the ohci (specifically, web-cams won't work). Thanks!

 - bruce



--- ../ohci_5_10apr/ohci.c	Sat May  3 15:37:04 2003
+++ ohci.c	Sun May  4 12:33:18 2003
@@ -635,6 +635,7 @@
 			  OHCI_ITD_ALIGN, &dma);
 		if (err)
 			return (NULL);
+		s = splusb();
 		for(i = 0; i < OHCI_SITD_CHUNK; i++) {
 			offs = i * OHCI_SITD_SIZE;
 			sitd = KERNADDR(&dma, offs);
@@ -642,6 +643,7 @@
 			sitd->nextitd = sc->sc_freeitds;
 			sc->sc_freeitds = sitd;
 		}
+		splx(s);
 	}
 
 	s = splusb();
@@ -974,6 +976,18 @@
 ohci_freex(struct usbd_bus *bus, usbd_xfer_handle xfer)
 {
 	struct ohci_softc *sc = (struct ohci_softc *)bus;
+	struct ohci_xfer *oxfer = (struct ohci_xfer *)xfer;
+	ohci_soft_itd_t *sitd;
+
+        if (oxfer->ohci_xfer_flags & OHCI_ISOC_DIRTY) {
+            sitd = xfer->hcpriv;
+            if (sitd) {
+                for (; sitd->xfer == xfer; sitd = sitd->nextitd) {
+                        if ( sitd == NULL ) break;
+                        ohci_free_sitd(sc, sitd);
+                }
+            }
+        }
 
 #ifdef DIAGNOSTIC
 	if (xfer->busy_free != XFER_BUSY) {
@@ -1380,7 +1394,9 @@
 				xfer->actlen += len;
 			if (std->flags & OHCI_CALL_DONE) {
 				xfer->status = USBD_NORMAL_COMPLETION;
+				s = splusb();
 				usb_transfer_complete(xfer);
+				splx(s);
 			}
 			ohci_free_std(sc, std);
 		} else {
@@ -1420,7 +1436,10 @@
 				xfer->status = USBD_STALLED;
 			else
 				xfer->status = USBD_IOERROR;
+
+			s = splusb();
 			usb_transfer_complete(xfer);
+			splx(s);
 		}
 	}
 
@@ -1445,6 +1464,9 @@
 			/* Handled by abort routine. */
 			continue;
 		}
+                if (xfer->pipe) {
+                    if (xfer->pipe->aborting) continue; /*Ignore.*/
+                }
 #ifdef DIAGNOSTIC
 		if (sitd->isdone)
 			printf("ohci_softintr: sitd=%p is done\n", sitd);
@@ -1460,12 +1482,16 @@
 				/* XXX update frlengths with actual length */
 				/* XXX xfer->actlen = actlen; */
 				xfer->status = USBD_NORMAL_COMPLETION;
+				s = splusb();
 				usb_transfer_complete(xfer);
+				splx(s);
 			}
 		} else {
 			/* XXX Do more */
 			xfer->status = USBD_IOERROR;
+			s = splusb();
 			usb_transfer_complete(xfer);
+			splx(s);
 		}
 	}
 
@@ -3223,8 +3249,9 @@
 	ohci_softc_t *sc = (ohci_softc_t *)dev->bus;
 	ohci_soft_ed_t *sed = opipe->sed;
 	struct iso *iso = &opipe->u.iso;
+	struct ohci_xfer *oxfer = (struct ohci_xfer *)xfer;
 	ohci_soft_itd_t *sitd, *nsitd;
-	ohci_physaddr_t buf, offs, noffs, bp0;
+	ohci_physaddr_t buf, offs, noffs, bp0, tdphys;
 	int i, ncur, nframes;
 	int s;
 
@@ -3242,6 +3269,24 @@
 			    iso->next));
 	}
 
+	if (xfer->hcpriv) {
+		sitd = xfer->hcpriv;
+	   	for (; sitd->xfer == xfer; sitd = sitd->nextitd) {
+			if (sitd == NULL) break;
+			ohci_free_sitd(sc, sitd); /* Free ITDs in prev xfer*/
+	   	}
+		if (sitd == NULL) {
+			sitd = ohci_alloc_sitd(sc);
+			if (sitd == NULL) panic( "cant alloc isoc" );
+			opipe->tail.itd = sitd;
+			tdphys = sitd->physaddr;
+			sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); /* Stop*/
+			sed->ed.ed_headp =
+			sed->ed.ed_tailp = htole32(tdphys);
+			sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); /* Start.*/
+		}
+	}
+
 	sitd = opipe->tail.itd;
 	buf = DMAADDR(&xfer->dmabuf, 0);
 	bp0 = OHCI_PAGE(buf);
@@ -3310,6 +3355,8 @@
 
 	xfer->status = USBD_IN_PROGRESS;
 
+	oxfer->ohci_xfer_flags |= OHCI_ISOC_DIRTY;
+
 #ifdef USB_DEBUG
 	if (ohcidebug > 5) {
 		DPRINTF(("ohci_device_isoc_enter: frame=%d\n",
@@ -3340,6 +3387,8 @@
 {
 	struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe;
 	ohci_softc_t *sc = (ohci_softc_t *)opipe->pipe.device->bus;
+	ohci_soft_ed_t *sed;
+	int s;
 
 	DPRINTFN(5,("ohci_device_isoc_start: xfer=%p\n", xfer));
 
@@ -3353,6 +3402,11 @@
 
 	/* XXX anything to do? */
 
+	s = splusb();
+	sed = opipe->sed;  /*  Turn off ED skip-bit to start processing */
+	sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP);    /* ED's ITD list.*/
+	splx(s);
+
 	return (USBD_IN_PROGRESS);
 }
 
@@ -3391,11 +3445,14 @@
 		return;
 	}
 #endif
-	for (; sitd->xfer == xfer; sitd = sitd->nextitd) {
+	if ( sitd ) { /* Only if xfer has ITDs. */
+	   for (; sitd->xfer == xfer; sitd = sitd->nextitd) {
+		if ( sitd == NULL ) break;
 #ifdef DIAGNOSTIC
 		DPRINTFN(1,("abort sets done sitd=%p\n", sitd));
 		sitd->isdone = 1;
 #endif
+	   }
 	}
 
 	splx(s);
@@ -3407,7 +3464,9 @@
 	/* Run callback. */
 	usb_transfer_complete(xfer);
 
-	sed->ed.ed_headp = htole32(sitd->physaddr); /* unlink TDs */
+	if ( sitd ) { /* Only if there is an ITD... */
+	   sed->ed.ed_headp = htole32(sitd->physaddr); /* unlink TDs */
+	}
 	sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); /* remove hardware skip */
 
 	splx(s);
@@ -3416,21 +3475,23 @@
 void
 ohci_device_isoc_done(usbd_xfer_handle xfer)
 {
-	struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe;
-	ohci_softc_t *sc = (ohci_softc_t *)opipe->pipe.device->bus;
-	ohci_soft_itd_t *sitd, *nsitd;
-
-	DPRINTFN(1,("ohci_device_isoc_done: xfer=%p\n", xfer));
+/* This null routine corresponds to non-isoc "done()" routines
+ * that free the stds associated with an xfer after a completed
+ * xfer interrupt. However, in the case of isoc transfers, the
+ * sitds associated with the transfer have already been processed
+ * and reallocated for the next iteration by "ohci_device_isoc_transfer()".
+ *
+ * Routine "usb_transfer_complete()" is called at the end of every
+ * relevant usb interrupt. "usb_transfer_complete()" indirectly
+ * calls 1) "ohci_device_isoc_transfer()" (which keeps pumping the
+ * pipeline by setting up the next transfer iteration) and 2) then 
+ * calls "ohci_device_isoc_done()". Isoc transfers have not been 
+ * working for the ohci usb because this routine was trashing the
+ * xfer set up for the next iteration (thus, only the first 
+ * UGEN_NISOREQS xfers outstanding on an open would work). Perhaps
+ * this could all be re-factored, but that's another pass...
+ */
 
-	for (sitd = xfer->hcpriv;
-	     !(sitd->flags & OHCI_CALL_DONE);
-	     sitd = nsitd) {
-		nsitd = sitd->nextitd;
-		DPRINTFN(1,("ohci_device_isoc_done: free sitd=%p\n", sitd));
-		ohci_free_sitd(sc, sitd);
-	}
-	ohci_free_sitd(sc, sitd);
-	xfer->hcpriv = NULL;
 }
 
 usbd_status
@@ -3456,11 +3517,20 @@
 {
 	struct ohci_pipe *opipe = (struct ohci_pipe *)pipe;
 	ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus;
+	ohci_soft_ed_t *sed;
 
 	DPRINTF(("ohci_device_isoc_close: pipe=%p\n", pipe));
-	ohci_close_pipe(pipe, sc->sc_isoc_head);
+
+	sed = opipe->sed;
+	sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); /* Stop device. */
+
+	ohci_close_pipe(pipe, sc->sc_isoc_head); /* Stop isoc list, free ED.*/
+
+	/* up to NISOREQs xfers still outstanding. */
+	
+
 #ifdef DIAGNOSTIC
 	opipe->tail.itd->isdone = 1;
 #endif
-	ohci_free_sitd(sc, opipe->tail.itd);
+	ohci_free_sitd(sc, opipe->tail.itd);	/* Next `avail free' sitd.*/
 }





--- ../ohci_5_10apr/ohcivar.h	Sat May  3 15:37:11 2003
+++ ohcivar.h	Sun May  4 12:33:22 2003
@@ -149,7 +149,9 @@
 struct ohci_xfer {
 	struct usbd_xfer xfer;
 	struct usb_task	abort_task;
+	u_int32_t ohci_xfer_flags;
 };
+#define OHCI_ISOC_DIRTY  0x01
 
 #define OXFER(xfer) ((struct ehci_xfer *)(xfer))
 



More information about the freebsd-hackers mailing list