NetBSD-Bugs archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
kern/39356: DMA memory leak in Ohci Driver (USB)
>Number: 39356
>Category: kern
>Synopsis: DMA memory leak in Ohci Driver (USB)
>Confidential: no
>Severity: non-critical
>Priority: medium
>Responsible: kern-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Fri Aug 15 09:10:00 +0000 2008
>Originator: Andreas Jacobs
>Release: CVS Head from 2008-08-14
>Organization:
LANCOM Systems GmbH
>Environment:
I do not use the NetBSD operating system itself but only its USB-Stack ported
to another OS.
>Description:
If you attach an Ohci and later detach it again, it does not free all the
memory it has allocated (note that this is another leak than in PR 39322):
ohci_alloc_sed() in ohci.c allocates new ohci_soft_ed_t objects, taking them
from a linked list rooted at sc_freeeds. If that list is empty, that function
allocates a new chunk of memory in line 420 to make further OHCI_SED_CHUCK
ohci_soft_ed_t's available. This allocated memory is never released although in
my opinion it should be released in ohci_detach at latest.
An equivalent problem exists with the function ohci_alloc_std which allocates
memory in line 459.
I've got a patch for that (see below) and I'm curious what you think about it.
Thank you in advance
Andreas Jacobs
>How-To-Repeat:
Attach and detach an Ohci over and over again. This is hard to do, if that chip
is part of your PC, but is possible, if you use a UMTS/HSPA modem attached via
PCMCIA.
>Fix:
The ohci_soft_ed_t and ohci_soft_td_t structures may be in use until
ohci_detach runs, but surely not longer, so in my opinion the blocks that
ohci_attach_sed() and ohci_attach_std() allocate should be released exactly in
ohci_detach().
Unfortunately, ohci_attach_sed() and ohci_attach_std() do not save pointers to
these blocks yet, so there is no easy way for ohci_detach() to release these.
Therefore my patch proposal becomes a little bit complicated:
- add a SIMPLEQ to struct ohci_softc to hold the allocated usb_dma structures
- add a SIMPLEQ_ENTRY to usb_dma_t, s.t. usb_dma_t's can be linked
- initialize that queue in ohci_init
- in ohci_attach_sed() and ohci_attach_std() add the allocated usb_dma_t's to
the queue; note that the usb_dma_t's must be allocated on the heap instead of
the stack for that
- finally in ohci_detach(): iterate through that queue and deallocate all the
blocks
Here is my patch as a universal diff:
Index: ohci.c
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/ohci.c,v
retrieving revision 1.196
diff -U3 -r1.196 ohci.c
--- ohci.c 13 Aug 2008 09:43:56 -0000 1.196
+++ ohci.c 15 Aug 2008 08:26:37 -0000
@@ -385,6 +385,7 @@
ohci_detach(struct ohci_softc *sc, int flags)
{
int rv = 0;
+ usb_dma_t *dma;
usbd_xfer_handle xfer;
if (sc->sc_child != NULL)
@@ -397,6 +398,11 @@
usb_delay_ms(&sc->sc_bus, 300); /* XXX let stray task complete */
+ while((dma = SIMPLEQ_FIRST(&sc->sc_allocated_dmas)) != NULL) {
+ SIMPLEQ_REMOVE_HEAD(&sc->sc_allocated_dmas, next);
+ usb_freemem(&sc->sc_bus, dma);
+ free(dma, M_USB);
+ }
usb_freemem(&sc->sc_bus, &sc->sc_hccadma);
while((xfer = SIMPLEQ_FIRST(&sc->sc_free_xfers)) != NULL) {
SIMPLEQ_REMOVE_HEAD(&sc->sc_free_xfers, next);
@@ -413,18 +419,24 @@
ohci_soft_ed_t *sed;
usbd_status err;
int i, offs;
- usb_dma_t dma;
+ usb_dma_t *dma;
if (sc->sc_freeeds == NULL) {
DPRINTFN(2, ("ohci_alloc_sed: allocating chunk\n"));
+ dma = malloc(sizeof *dma, M_USB, M_NOWAIT);
+ if(dma == NULL)
+ return (0);
err = usb_allocmem(&sc->sc_bus, OHCI_SED_SIZE * OHCI_SED_CHUNK,
- OHCI_ED_ALIGN, &dma);
- if (err)
+ OHCI_ED_ALIGN, dma);
+ if (err) {
+ free(dma, M_USB);
return (0);
+ }
+ SIMPLEQ_INSERT_HEAD(&sc->sc_allocated_dmas, dma, next);
for(i = 0; i < OHCI_SED_CHUNK; i++) {
offs = i * OHCI_SED_SIZE;
- sed = KERNADDR(&dma, offs);
- sed->physaddr = DMAADDR(&dma, offs);
+ sed = KERNADDR(dma, offs);
+ sed->physaddr = DMAADDR(dma, offs);
sed->dma = dma;
sed->offs = offs;
sed->next = sc->sc_freeeds;
@@ -451,20 +463,26 @@
ohci_soft_td_t *std;
usbd_status err;
int i, offs;
- usb_dma_t dma;
+ usb_dma_t *dma;
int s;
if (sc->sc_freetds == NULL) {
DPRINTFN(2, ("ohci_alloc_std: allocating chunk\n"));
+ dma = malloc(sizeof *dma, M_USB, M_NOWAIT);
+ if(dma == NULL)
+ return (0);
err = usb_allocmem(&sc->sc_bus, OHCI_STD_SIZE * OHCI_STD_CHUNK,
- OHCI_TD_ALIGN, &dma);
- if (err)
+ OHCI_TD_ALIGN, dma);
+ if (err) {
+ free(dma, M_USB);
return (NULL);
+ }
s = splusb();
+ SIMPLEQ_INSERT_HEAD(&sc->sc_allocated_dmas, dma, next);
for(i = 0; i < OHCI_STD_CHUNK; i++) {
offs = i * OHCI_STD_SIZE;
- std = KERNADDR(&dma, offs);
- std->physaddr = DMAADDR(&dma, offs);
+ std = KERNADDR(dma, offs);
+ std->physaddr = DMAADDR(dma, offs);
std->dma = dma;
std->offs = offs;
std->nexttd = sc->sc_freetds;
@@ -708,6 +726,7 @@
for (i = 0; i < OHCI_HASH_SIZE; i++)
LIST_INIT(&sc->sc_hash_itds[i]);
+ SIMPLEQ_INIT(&sc->sc_allocated_dmas);
SIMPLEQ_INIT(&sc->sc_free_xfers);
#ifdef __NetBSD__
Index: ohcivar.h
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/ohcivar.h,v
retrieving revision 1.45
diff -U3 -r1.45 ohcivar.h
--- ohcivar.h 28 Jun 2008 17:42:53 -0000 1.45
+++ ohcivar.h 15 Aug 2008 08:26:37 -0000
@@ -117,6 +117,7 @@
ohci_soft_td_t *sc_freetds;
ohci_soft_itd_t *sc_freeitds;
+ SIMPLEQ_HEAD(, usb_dma) sc_allocated_dmas;
SIMPLEQ_HEAD(, usbd_xfer) sc_free_xfers; /* free xfers */
usbd_xfer_handle sc_intrxfer;
Index: usb_port.h
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/usb_port.h,v
retrieving revision 1.85
diff -U3 -r1.85 usb_port.h
--- usb_port.h 28 Jun 2008 09:06:20 -0000 1.85
+++ usb_port.h 15 Aug 2008 08:26:37 -0000
@@ -102,9 +102,10 @@
#define DECLARE_USB_DMA_T \
struct usb_dma_block; \
- typedef struct { \
+ typedef struct usb_dma { \
struct usb_dma_block *block; \
u_int offs; \
+ SIMPLEQ_ENTRY(usb_dma) next; \
} usb_dma_t
typedef struct callout usb_callout_t;
Home |
Main Index |
Thread Index |
Old Index