Patch to fix ohci usb isoc close/int race

Bruce R. Montague brucem at cruzio.com
Tue May 6 10:38:34 PDT 2003



Hi, I found a race condition between the /dev/usb
ohci.c isochronous case interrupt handler and its
close routine (really an abort). Both an incremental
patch to my previous ohci isoc patch and a complete
patch with respect to the FreeBSD 5.0-current sources
of 10-april are included below.

I have limited testing strategies other than letting
the web-camera run in a loop with input into either
a file or /dev/null, and generating random aborts
of the image capture program. With the latest patch,
I have run the camera constantly into /dev/null for
up to around 16 hours. No other error messages or
unusual activity observed.

One other individual has used this code without
problems, but it would probably be of interest to
test this on other ohci isochronous usb devices. I'm
not sure I even know what any such devices would be.
If anyone is interested in trying such a test, I'd
certainly be interested in the result. The ohci usb
isochronous code currently only handles "reads", the
driver as coded has no "output" support (not sure
what that would be... audio?).

This has still only been tested under FreeBSD 5.0
-Current, I will try to do a test under some version
of 4-stable within the next few days, it might not
be under 4.8...


Incremental patch to fix the interrupt/close race:


--- ../ohci_5_4may/ohci.c	Sun May  4 12:33:18 2003
+++ ohci.c	Tue May  6 08:57:29 2003
@@ -255,6 +255,7 @@
 struct ohci_pipe {
 	struct usbd_pipe pipe;
 	ohci_soft_ed_t *sed;
+	u_int32_t aborting;
 	union {
 		ohci_soft_td_t *td;
 		ohci_soft_itd_t *itd;
@@ -1472,11 +1473,14 @@
 			printf("ohci_softintr: sitd=%p is done\n", sitd);
 		sitd->isdone = 1;
 #endif
+                struct ohci_pipe *opipe =
+                                (struct ohci_pipe *)xfer->pipe;
+                if (opipe->aborting )
+                                continue;
+ 
 		cc = OHCI_ITD_GET_CC(le32toh(sitd->itd.itd_flags));
 		if (cc == OHCI_CC_NO_ERROR) {
 			/* XXX compute length for input */
-			struct ohci_pipe *opipe =
-				(struct ohci_pipe *)xfer->pipe;
 			if (sitd->flags & OHCI_CALL_DONE) {
 				opipe->u.iso.inuse -= xfer->nframes;
 				/* XXX update frlengths with actual length */
@@ -2105,6 +2109,7 @@
 			if (sitd == NULL)
 				goto bad1;
 			opipe->tail.itd = sitd;
+			opipe->aborting = 0;
 			tdphys = sitd->physaddr;
 			fmt = OHCI_ED_FORMAT_ISO;
 			if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN)
@@ -3420,6 +3425,7 @@
 	int s;
 
 	s = splusb();
+	opipe->aborting = 1;
 
 	DPRINTFN(1,("ohci_device_isoc_abort: xfer=%p\n", xfer));
 


Complete patch with respect to the 10-April-2003
FreeBSD-current. This also contains the fix to
the int/close race:



--- ../ohci_5_10apr/ohci.c	Sat May  3 15:37:04 2003
+++ ohci.c	Tue May  6 08:57:29 2003
@@ -255,6 +255,7 @@
 struct ohci_pipe {
 	struct usbd_pipe pipe;
 	ohci_soft_ed_t *sed;
+	u_int32_t aborting;
 	union {
 		ohci_soft_td_t *td;
 		ohci_soft_itd_t *itd;
@@ -635,6 +636,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 +644,7 @@
 			sitd->nextitd = sc->sc_freeitds;
 			sc->sc_freeitds = sitd;
 		}
+		splx(s);
 	}
 
 	s = splusb();
@@ -974,6 +977,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 +1395,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 +1437,10 @@
 				xfer->status = USBD_STALLED;
 			else
 				xfer->status = USBD_IOERROR;
+
+			s = splusb();
 			usb_transfer_complete(xfer);
+			splx(s);
 		}
 	}
 
@@ -1445,27 +1465,37 @@
 			/* 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);
 		sitd->isdone = 1;
 #endif
+                struct ohci_pipe *opipe =
+                                (struct ohci_pipe *)xfer->pipe;
+                if (opipe->aborting )
+                                continue;
+ 
 		cc = OHCI_ITD_GET_CC(le32toh(sitd->itd.itd_flags));
 		if (cc == OHCI_CC_NO_ERROR) {
 			/* XXX compute length for input */
-			struct ohci_pipe *opipe =
-				(struct ohci_pipe *)xfer->pipe;
 			if (sitd->flags & OHCI_CALL_DONE) {
 				opipe->u.iso.inuse -= xfer->nframes;
 				/* 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);
 		}
 	}
 
@@ -2079,6 +2109,7 @@
 			if (sitd == NULL)
 				goto bad1;
 			opipe->tail.itd = sitd;
+			opipe->aborting = 0;
 			tdphys = sitd->physaddr;
 			fmt = OHCI_ED_FORMAT_ISO;
 			if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN)
@@ -3223,8 +3254,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 +3274,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 +3360,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 +3392,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 +3407,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);
 }
 
@@ -3366,6 +3425,7 @@
 	int s;
 
 	s = splusb();
+	opipe->aborting = 1;
 
 	DPRINTFN(1,("ohci_device_isoc_abort: xfer=%p\n", xfer));
 
@@ -3391,11 +3451,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 +3470,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 +3481,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 +3523,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	Tue May  6 08:05:00 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