tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

memory leak in USB stack



Hi,
I have a USB device appearing as a ucom, that I open/close several
time per minute to collect data. After some time (several weeks) the
open(/dev/ttyU0) would fail with ENOMEM.

I tracked it down to a leak of a usbd_xfer_handle, one per open/close.
At open() time, a intr pipe is allocated via usbd_open_pipe_intr(),
which calls usbd_alloc_xfer() for pipe->intrxfer.
At close() time, usbd_abort_pipe() is called for the intr_pipe, then
usbd_close_pipe() which is supported to free pipe->intrxfer.
But usbd_abort_pipe() calls ehci_device_intr_abort() (but the same is true
for ehci and ohci) which, if the xfer to be aborted is the pipe's intrxfer,
sets pipe->intrxfer to NULL.  So usbd_close_pipe() has nothing to free any
more and the intrxfer is lost.
This happens with all 3 host drivers ([eou]hci) and a lot of
USB device drivers.

The attached patch fixes this by freeing the xfer in
[eou]hci_device_intr_abort() and [eou]hci_root_intr_abort()
when the xfer to be aborted is the pipe's intrxfer.
I went through the device drivers and it looks safe, as nothing
gets a cached value of pipe->intrxfer.

Comments ?

-- 
Manuel Bouyer <bouyer%antioche.eu.org@localhost>
     NetBSD: 26 ans d'experience feront toujours la difference
--
Index: ehci.c
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/ehci.c,v
retrieving revision 1.154.4.1
diff -u -p -u -r1.154.4.1 ehci.c
--- ehci.c      29 Nov 2008 20:47:05 -0000      1.154.4.1
+++ ehci.c      12 Jan 2010 15:45:39 -0000
@@ -2454,15 +2454,19 @@ Static void
 ehci_root_intr_abort(usbd_xfer_handle xfer)
 {
        int s;
+       usbd_xfer_handle intrxfer = NULL;
 
        if (xfer->pipe->intrxfer == xfer) {
                DPRINTF(("ehci_root_intr_abort: remove\n"));
+               intrxfer = xfer->pipe->intrxfer;
                xfer->pipe->intrxfer = NULL;
        }
        xfer->status = USBD_CANCELLED;
        s = splusb();
        usb_transfer_complete(xfer);
        splx(s);
+       if (intrxfer)
+               usbd_free_xfer(intrxfer);
 }
 
 /* Close the root pipe. */
@@ -3686,9 +3690,11 @@ ehci_device_intr_start(usbd_xfer_handle 
 Static void
 ehci_device_intr_abort(usbd_xfer_handle xfer)
 {
+       usbd_xfer_handle intrxfer = NULL;
        DPRINTFN(1, ("ehci_device_intr_abort: xfer=%p\n", xfer));
        if (xfer->pipe->intrxfer == xfer) {
                DPRINTFN(1, ("echi_device_intr_abort: remove\n"));
+               intrxfer = xfer->pipe->intrxfer;
                xfer->pipe->intrxfer = NULL;
        }
        /*
@@ -3697,6 +3703,8 @@ ehci_device_intr_abort(usbd_xfer_handle 
         *       intr xfers are periodic, should not use this?
         */
        ehci_abort_xfer(xfer, USBD_CANCELLED);
+       if (intrxfer)
+               usbd_free_xfer(intrxfer);
 }
 
 Static void
Index: ohci.c
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/ohci.c,v
retrieving revision 1.196
diff -u -p -u -r1.196 ohci.c
--- ohci.c      13 Aug 2008 09:43:56 -0000      1.196
+++ ohci.c      12 Jan 2010 15:45:39 -0000
@@ -2838,15 +2838,19 @@ Static void
 ohci_root_intr_abort(usbd_xfer_handle xfer)
 {
        int s;
+       usbd_xfer_handle intrxfer = NULL;
 
        if (xfer->pipe->intrxfer == xfer) {
                DPRINTF(("ohci_root_intr_abort: remove\n"));
+               intrxfer = xfer->pipe->intrxfer;
                xfer->pipe->intrxfer = NULL;
        }
        xfer->status = USBD_CANCELLED;
        s = splusb();
        usb_transfer_complete(xfer);
        splx(s);
+       if (intrxfer)
+               usbd_free_xfer(intrxfer);
 }
 
 /* Close the root pipe. */
@@ -3185,11 +3189,15 @@ ohci_device_intr_start(usbd_xfer_handle 
 Static void
 ohci_device_intr_abort(usbd_xfer_handle xfer)
 {
+       usbd_xfer_handle intrxfer = NULL;
        if (xfer->pipe->intrxfer == xfer) {
                DPRINTF(("ohci_device_intr_abort: remove\n"));
+               intrxfer = xfer->pipe->intrxfer;
                xfer->pipe->intrxfer = NULL;
        }
        ohci_abort_xfer(xfer, USBD_CANCELLED);
+       if (intrxfer)
+               usbd_free_xfer(intrxfer);
 }
 
 /* Close a device interrupt pipe. */
Index: uhci.c
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/uhci.c,v
retrieving revision 1.223.6.1
diff -u -p -u -r1.223.6.1 uhci.c
--- uhci.c      4 Oct 2009 00:01:16 -0000       1.223.6.1
+++ uhci.c      12 Jan 2010 15:45:39 -0000
@@ -2377,12 +2377,16 @@ uhci_device_ctrl_close(usbd_pipe_handle 
 void
 uhci_device_intr_abort(usbd_xfer_handle xfer)
 {
+       usbd_xfer_handle intrxfer = NULL;
        DPRINTFN(1,("uhci_device_intr_abort: xfer=%p\n", xfer));
        if (xfer->pipe->intrxfer == xfer) {
                DPRINTFN(1,("uhci_device_intr_abort: remove\n"));
+               intrxfer = xfer->pipe->intrxfer;
                xfer->pipe->intrxfer = NULL;
        }
        uhci_abort_xfer(xfer, USBD_CANCELLED);
+       if (intrxfer)
+               usbd_free_xfer(intrxfer);
 }
 
 /* Close a device interrupt pipe. */
@@ -3816,6 +3820,7 @@ uhci_root_ctrl_close(usbd_pipe_handle pi
 void
 uhci_root_intr_abort(usbd_xfer_handle xfer)
 {
+       usbd_xfer_handle intrxfer = NULL;
        uhci_softc_t *sc = xfer->pipe->device->bus->hci_private;
 
        usb_uncallout(sc->sc_poll_handle, uhci_poll_hub, xfer);
@@ -3823,13 +3828,16 @@ uhci_root_intr_abort(usbd_xfer_handle xf
 
        if (xfer->pipe->intrxfer == xfer) {
                DPRINTF(("uhci_root_intr_abort: remove\n"));
-               xfer->pipe->intrxfer = 0;
+               intrxfer = xfer->pipe->intrxfer;
+               xfer->pipe->intrxfer = NULL;
        }
        xfer->status = USBD_CANCELLED;
 #ifdef DIAGNOSTIC
        UXFER(xfer)->iinfo.isdone = 1;
 #endif
        usb_transfer_complete(xfer);
+       if (intrxfer)
+               usbd_free_xfer(intrxfer);
 }
 
 usbd_status


Home | Main Index | Thread Index | Old Index