Subject: USB reserve memory patch
To: None <port-i386@netbsd.org>
From: Frank van der Linden <fvdl@netbsd.org>
List: port-i386
Date: 10/11/2004 23:21:04
--6c2NcOVqGQ03X4Wi
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

I cooked up a patch that implements a workaround for runtime attachment
of USB devices that need more than one page of contiguous memory, and
have problems because memory is too fragmented at that point.

The workaround is to have the host controller (ohci, uhci or ehci) set
aside some contiguous memory at bootup, which is used should a USB
allocation request failed later.

The chunk of memory reserved is set via the USB_MEM_RESERVE option.
Default is 256k.

I tested this by forcing the plain allocation to fail for larger
requests, so that it was forced to fall back to using the reserve.
I plugged in a 250G external WD drive, used it, plugged in a USB
floppy drive, used it, plugged them both out, plugged in the
250G drive again, and used it again. They all used allocation
buffers from the reserve (since I forced it), and it worked.

The diff is attached. If you have a system where you had problems
plugging in USB devices (umass, probably) after it had been up
for a while, maybe you can try this patch. It's against -current,
but I think it should apply for the 2.0 branch too.

- Frank

--6c2NcOVqGQ03X4Wi
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename=diff

Index: conf/files
===================================================================
RCS file: /cvsroot/src/sys/conf/files,v
retrieving revision 1.691
diff -c -r1.691 files
*** conf/files	4 Oct 2004 01:16:39 -0000	1.691
--- conf/files	11 Oct 2004 21:01:18 -0000
***************
*** 931,936 ****
--- 931,939 ----
  # use them in an 'attach-with'.
  # UHCI USB controller
  #
+ 
+ defparam USB_MEM_RESERVE
+ 
  device	uhci: usbus
  file	dev/usb/uhci.c			uhci			needs-flag
  
Index: dev/pci/usb_pci.c
===================================================================
RCS file: /cvsroot/src/sys/dev/pci/usb_pci.c,v
retrieving revision 1.3
diff -c -r1.3 usb_pci.c
*** dev/pci/usb_pci.c	14 Feb 2002 21:58:30 -0000	1.3
--- dev/pci/usb_pci.c	11 Oct 2004 21:01:18 -0000
***************
*** 51,56 ****
--- 51,57 ----
  #include <dev/usb/usb.h>
  #include <dev/usb/usbdi.h>
  #include <dev/usb/usbdivar.h>
+ #include <dev/usb/usb_mem.h>
  
  #include <dev/usb/ehcireg.h>
  #include <dev/usb/ehcivar.h>
Index: dev/usb/ehci.c
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/ehci.c,v
retrieving revision 1.70
diff -c -r1.70 ehci.c
*** dev/usb/ehci.c	17 Sep 2004 10:55:07 -0000	1.70
--- dev/usb/ehci.c	11 Oct 2004 21:01:19 -0000
***************
*** 359,364 ****
--- 359,367 ----
  
  	sc->sc_bus.usbrev = USBREV_2_0;
  
+ 	usb_setup_reserve(sc, &sc->sc_dma_reserve, sc->sc_bus.dmatag,
+ 	    USB_MEM_RESERVE);
+ 
  	/* Reset the controller */
  	DPRINTF(("%s: resetting\n", USBDEVNAME(sc->sc_bus.bdev)));
  	EOWRITE4(sc, EHCI_USBCMD, 0);	/* Halt controller */
***************
*** 979,984 ****
--- 982,989 ----
  	usbd_status err;
  
  	err = usb_allocmem(&sc->sc_bus, size, 0, dma);
+ 	if (err == USBD_NOMEM)
+ 		err = usb_reserve_allocm(&sc->sc_dma_reserve, dma, size);
  #ifdef EHCI_DEBUG
  	if (err)
  		printf("ehci_allocm: usb_allocmem()=%d\n", err);
***************
*** 991,996 ****
--- 996,1006 ----
  {
  	struct ehci_softc *sc = (struct ehci_softc *)bus;
  
+ 	if (dma->block->flags & USB_DMA_RESERVE) {
+ 		usb_reserve_freem(&((struct ehci_softc *)bus)->sc_dma_reserve,
+ 		    dma);
+ 		return;
+ 	}
  	usb_freemem(&sc->sc_bus, dma);
  }
  
Index: dev/usb/ehcivar.h
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/ehcivar.h,v
retrieving revision 1.12
diff -c -r1.12 ehcivar.h
*** dev/usb/ehcivar.h	31 Dec 2001 12:16:57 -0000	1.12
--- dev/usb/ehcivar.h	11 Oct 2004 21:01:19 -0000
***************
*** 116,121 ****
--- 116,124 ----
  	device_ptr_t sc_child;		/* /dev/usb# device */
  
  	char sc_dying;
+ #ifdef __NetBSD__
+ 	struct usb_dma_reserve sc_dma_reserve;
+ #endif
  } ehci_softc_t;
  
  #define EREAD1(sc, a) bus_space_read_1((sc)->iot, (sc)->ioh, (a))
Index: dev/usb/ohci.c
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/ohci.c,v
retrieving revision 1.151
diff -c -r1.151 ohci.c
*** dev/usb/ohci.c	17 Jul 2004 20:24:15 -0000	1.151
--- dev/usb/ohci.c	11 Oct 2004 21:01:19 -0000
***************
*** 55,60 ****
--- 55,61 ----
  #include <sys/kernel.h>
  #include <sys/device.h>
  #include <sys/select.h>
+ #include <uvm/uvm_extern.h>
  #elif defined(__FreeBSD__)
  #include <sys/module.h>
  #include <sys/bus.h>
***************
*** 679,684 ****
--- 680,690 ----
  
  	SIMPLEQ_INIT(&sc->sc_free_xfers);
  
+ #ifdef __NetBSD__
+ 	usb_setup_reserve(sc, &sc->sc_dma_reserve, sc->sc_bus.dmatag,
+ 	    USB_MEM_RESERVE);
+ #endif
+ 
  	/* XXX determine alignment by R/W */
  	/* Allocate the HCCA area. */
  	err = usb_allocmem(&sc->sc_bus, OHCI_HCCA_SIZE,
***************
*** 899,906 ****
  #if defined(__NetBSD__) || defined(__OpenBSD__)
  	struct ohci_softc *sc = (struct ohci_softc *)bus;
  #endif
  
! 	return (usb_allocmem(&sc->sc_bus, size, 0, dma));
  }
  
  void
--- 905,918 ----
  #if defined(__NetBSD__) || defined(__OpenBSD__)
  	struct ohci_softc *sc = (struct ohci_softc *)bus;
  #endif
+ 	usbd_status status;
  
! 	status = usb_allocmem(&sc->sc_bus, size, 0, dma);
! #ifdef __NetBSD__
! 	if (status == USBD_NOMEM)
! 		status = usb_reserve_allocm(&sc->sc_dma_reserve, dma, size);
! #endif
! 	return status;
  }
  
  void
***************
*** 909,915 ****
  #if defined(__NetBSD__) || defined(__OpenBSD__)
  	struct ohci_softc *sc = (struct ohci_softc *)bus;
  #endif
! 
  	usb_freemem(&sc->sc_bus, dma);
  }
  
--- 921,933 ----
  #if defined(__NetBSD__) || defined(__OpenBSD__)
  	struct ohci_softc *sc = (struct ohci_softc *)bus;
  #endif
! #ifdef __NetBSD__
! 	if (dma->block->flags & USB_DMA_RESERVE) {
! 		usb_reserve_freem(&((struct ohci_softc *)bus)->sc_dma_reserve,
! 		    dma);
! 		return;
! 	}
! #endif
  	usb_freemem(&sc->sc_bus, dma);
  }
  
Index: dev/usb/ohcivar.h
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/ohcivar.h,v
retrieving revision 1.33
diff -c -r1.33 ohcivar.h
*** dev/usb/ohcivar.h	17 Jul 2004 20:24:15 -0000	1.33
--- dev/usb/ohcivar.h	11 Oct 2004 21:01:19 -0000
***************
*** 139,144 ****
--- 139,147 ----
  	device_ptr_t sc_child;
  
  	char sc_dying;
+ #ifdef __NetBSD__
+ 	struct usb_dma_reserve sc_dma_reserve;
+ #endif
  } ohci_softc_t;
  
  struct ohci_xfer {
Index: dev/usb/uhci.c
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/uhci.c,v
retrieving revision 1.181
diff -c -r1.181 uhci.c
*** dev/usb/uhci.c	22 Jul 2004 18:45:40 -0000	1.181
--- dev/usb/uhci.c	11 Oct 2004 21:01:20 -0000
***************
*** 58,63 ****
--- 58,65 ----
  #if defined(__NetBSD__) || defined(__OpenBSD__)
  #include <sys/device.h>
  #include <sys/select.h>
+ #include <sys/extent.h>
+ #include <uvm/uvm_extern.h>
  #elif defined(__FreeBSD__)
  #include <sys/module.h>
  #include <sys/bus.h>
***************
*** 414,419 ****
--- 416,426 ----
  	uhci_globalreset(sc);			/* reset the controller */
  	uhci_reset(sc);
  
+ #ifdef __NetBSD__
+ 	usb_setup_reserve(sc, &sc->sc_dma_reserve, sc->sc_bus.dmatag,
+ 	    USB_MEM_RESERVE);
+ #endif
+ 
  	/* Allocate and initialize real frame array. */
  	err = usb_allocmem(&sc->sc_bus,
  		  UHCI_FRAMELIST_COUNT * sizeof(uhci_physaddr_t),
***************
*** 587,592 ****
--- 594,600 ----
  uhci_allocm(struct usbd_bus *bus, usb_dma_t *dma, u_int32_t size)
  {
  	struct uhci_softc *sc = (struct uhci_softc *)bus;
+ 	usbd_status status;
  	u_int32_t n;
  
  	/*
***************
*** 611,622 ****
  		free(stds, M_TEMP);
  	}
  
! 	return (usb_allocmem(&sc->sc_bus, size, 0, dma));
  }
  
  void
  uhci_freem(struct usbd_bus *bus, usb_dma_t *dma)
  {
  	usb_freemem(&((struct uhci_softc *)bus)->sc_bus, dma);
  }
  
--- 619,643 ----
  		free(stds, M_TEMP);
  	}
  
! 
! 	status = usb_allocmem(&sc->sc_bus, size, 0, dma);
! #ifdef __NetBSD__
! 	if (status == USBD_NOMEM)
! 		status = usb_reserve_allocm(&sc->sc_dma_reserve, dma, size);
! #endif
! 	return status;
  }
  
  void
  uhci_freem(struct usbd_bus *bus, usb_dma_t *dma)
  {
+ #ifdef __NetBSD__
+ 	if (dma->block->flags & USB_DMA_RESERVE) {
+ 		usb_reserve_freem(&((struct uhci_softc *)bus)->sc_dma_reserve,
+ 		    dma);
+ 		return;
+ 	}
+ #endif
  	usb_freemem(&((struct uhci_softc *)bus)->sc_bus, dma);
  }
  
Index: dev/usb/uhcivar.h
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/uhcivar.h,v
retrieving revision 1.36
diff -c -r1.36 uhcivar.h
*** dev/usb/uhcivar.h	31 Dec 2002 00:39:11 -0000	1.36
--- dev/usb/uhcivar.h	11 Oct 2004 21:01:20 -0000
***************
*** 183,188 ****
--- 183,191 ----
  	void *sc_shutdownhook;		/* cookie from shutdown hook */
  
  	device_ptr_t sc_child;		/* /dev/usb# device */
+ #ifdef __NetBSD__
+ 	struct usb_dma_reserve sc_dma_reserve;
+ #endif
  } uhci_softc_t;
  
  usbd_status	uhci_init(uhci_softc_t *);
Index: dev/usb/usb_mem.c
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/usb_mem.c,v
retrieving revision 1.27
diff -c -r1.27 usb_mem.c
*** dev/usb/usb_mem.c	5 Jan 2004 13:30:45 -0000	1.27
--- dev/usb/usb_mem.c	11 Oct 2004 21:01:20 -0000
***************
*** 55,60 ****
--- 55,65 ----
  #include <sys/device.h>		/* for usbdivar.h */
  #include <machine/bus.h>
  
+ #ifdef __NetBSD__
+ #include <sys/extent.h>
+ #include <uvm/uvm_extern.h>
+ #endif
+ 
  #ifdef DIAGNOSTIC
  #include <sys/proc.h>
  #endif
***************
*** 233,239 ****
  		size = (size + USB_MEM_BLOCK - 1) & ~(USB_MEM_BLOCK - 1);
  		err = usb_block_allocmem(tag, size, align, &p->block);
  		if (!err) {
! 			p->block->fullblock = 1;
  			p->offs = 0;
  		}
  		return (err);
--- 238,244 ----
  		size = (size + USB_MEM_BLOCK - 1) & ~(USB_MEM_BLOCK - 1);
  		err = usb_block_allocmem(tag, size, align, &p->block);
  		if (!err) {
! 			p->block->flags = USB_DMA_FULLBLOCK;
  			p->offs = 0;
  		}
  		return (err);
***************
*** 251,257 ****
  			splx(s);
  			return (err);
  		}
! 		b->fullblock = 0;
  		for (i = 0; i < USB_MEM_BLOCK; i += USB_MEM_SMALL) {
  			f = (struct usb_frag_dma *)(b->kaddr + i);
  			f->block = b;
--- 256,262 ----
  			splx(s);
  			return (err);
  		}
! 		b->flags = 0;
  		for (i = 0; i < USB_MEM_BLOCK; i += USB_MEM_SMALL) {
  			f = (struct usb_frag_dma *)(b->kaddr + i);
  			f->block = b;
***************
*** 262,267 ****
--- 267,273 ----
  	}
  	p->block = f->block;
  	p->offs = f->offs;
+ 	p->block->flags &= ~USB_DMA_RESERVE;
  	LIST_REMOVE(f, next);
  	splx(s);
  	DPRINTFN(5, ("usb_allocmem: use frag=%p size=%d\n", f, (int)size));
***************
*** 274,280 ****
  	struct usb_frag_dma *f;
  	int s;
  
! 	if (p->block->fullblock) {
  		DPRINTFN(1, ("usb_freemem: large free\n"));
  		usb_block_freemem(p->block);
  		return;
--- 280,286 ----
  	struct usb_frag_dma *f;
  	int s;
  
! 	if (p->block->flags & USB_DMA_FULLBLOCK) {
  		DPRINTFN(1, ("usb_freemem: large free\n"));
  		usb_block_freemem(p->block);
  		return;
***************
*** 287,289 ****
--- 293,413 ----
  	splx(s);
  	DPRINTFN(5, ("usb_freemem: frag=%p\n", f));
  }
+ 
+ 
+ #ifdef __NetBSD__
+ usbd_status
+ usb_reserve_allocm(struct usb_dma_reserve *rs, usb_dma_t *dma, u_int32_t size)
+ {
+ 	int error;
+ 	u_long start;
+ 	bus_addr_t baddr;
+ 
+ 	if (rs->vaddr == 0)
+ 		return USBD_NOMEM;
+ 
+ 	dma->block = malloc(sizeof *dma->block, M_USB, M_ZERO | M_NOWAIT);
+ 	if (dma->block == NULL)
+ 		return USBD_NOMEM;
+ 
+ 	error = extent_alloc(rs->extent, size, PAGE_SIZE, 0,
+ 	    EX_NOWAIT, &start);
+ 
+ 	if (error != 0) {
+ 		printf("%s: usb_reserve_allocm of size %u failed (error %d)\n",
+ 		    ((struct device *)rs->softc)->dv_xname, size, error);
+ 		return USBD_NOMEM;
+ 	}
+ 
+ 	baddr = start;
+ 	dma->offs = baddr - rs->paddr;
+ 	dma->block->flags = USB_DMA_RESERVE;
+ 	dma->block->align = PAGE_SIZE;
+ 	dma->block->size = size;
+ 	dma->block->nsegs = 1;
+ 	/* XXX segs appears to be unused */
+ 	dma->block->segs[0] = rs->map->dm_segs[0];
+ 	dma->block->map = rs->map;
+ 	dma->block->kaddr = rs->vaddr;
+ 	dma->block->tag = rs->dtag;
+ 
+ 	printf("%s: usb_reserve_allocm of size %u --> offs %u\n",
+ 	    ((struct device *)rs->softc)->dv_xname, size, dma->offs);
+ 
+ 	return USBD_NORMAL_COMPLETION;
+ }
+ 
+ void
+ usb_reserve_freem(struct usb_dma_reserve *rs, usb_dma_t *dma)
+ {
+ 	int error;
+ 
+ 	error = extent_free(rs->extent,
+ 	    (u_long)(rs->paddr + dma->offs), dma->block->size, 0);
+ 	printf("%s: usb_reserve_freem offs %u --> %d\n", 
+ 	    ((struct device *)rs->softc)->dv_xname, dma->offs, error);
+ 	free(dma->block, M_USB);
+ }
+ 
+ int
+ usb_setup_reserve(void *softc, struct usb_dma_reserve *rs, bus_dma_tag_t dtag,
+ 		  size_t size)
+ {
+ 	int error, nseg;
+ 	bus_dma_segment_t seg;
+ 	struct device *dv = softc;
+ 
+ 	rs->dtag = dtag;
+ 	rs->size = size;
+ 	rs->softc = softc;
+ 
+ 	error = bus_dmamem_alloc(dtag, USB_MEM_RESERVE, PAGE_SIZE, 0,
+ 	    &seg, 1, &nseg, BUS_DMA_NOWAIT);
+ 	if (error != 0)
+ 		return error;
+ 
+ 	error = bus_dmamem_map(dtag, &seg, nseg, USB_MEM_RESERVE,
+ 	    &rs->vaddr, BUS_DMA_NOWAIT|BUS_DMA_COHERENT);
+ 	if (error != 0)
+ 		goto freeit;
+ 
+ 	error = bus_dmamap_create(dtag, USB_MEM_RESERVE, 1,
+ 	    USB_MEM_RESERVE, 0, BUS_DMA_NOWAIT, &rs->map);
+ 	if (error != 0)
+ 		goto unmap;
+ 
+ 	error = bus_dmamap_load(dtag, rs->map, rs->vaddr, USB_MEM_RESERVE,
+ 	    NULL, BUS_DMA_NOWAIT);
+ 	if (error != 0)
+ 		goto destroy;
+ 
+ 	rs->paddr = rs->map->dm_segs[0].ds_addr;
+ 	rs->extent = extent_create(dv->dv_xname, (u_long)rs->paddr,
+ 	    (u_long)(rs->paddr + USB_MEM_RESERVE),
+ 	    M_USB, 0, 0, 0);
+ 	if (rs->extent == NULL) {
+ 		rs->vaddr = 0;
+ 		return ENOMEM;
+ 	}
+ 
+ 	printf("%s: setup of reserve of size %ld successful; p %08lx v %p\n",
+ 	    ((struct device *)softc)->dv_xname, (long)size,
+ 	    (u_long)rs->paddr, rs->vaddr);
+ 
+ 	return 0;
+ 
+  destroy:
+ 	bus_dmamap_destroy(dtag, rs->map);
+  unmap:
+ 	bus_dmamem_unmap(dtag, rs->vaddr, size);
+  freeit:
+ 	bus_dmamem_free(dtag, &seg, nseg);
+ 
+ 	rs->vaddr = 0;
+ 
+ 	printf("%s: setup of reserve of size %ld failed (error %d)\n",
+ 	    ((struct device *)softc)->dv_xname, (long)size, error);
+ 
+ 	return error;
+ }
+ #endif
Index: dev/usb/usb_mem.h
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/usb_mem.h,v
retrieving revision 1.20
diff -c -r1.20 usb_mem.h
*** dev/usb/usb_mem.h	3 May 2003 18:11:42 -0000	1.20
--- dev/usb/usb_mem.h	11 Oct 2004 21:01:20 -0000
***************
*** 47,53 ****
          int nsegs;
          size_t size;
          size_t align;
! 	int fullblock;
  	LIST_ENTRY(usb_dma_block) next;
  } usb_dma_block_t;
  
--- 47,55 ----
          int nsegs;
          size_t size;
          size_t align;
! 	int flags;
! #define USB_DMA_FULLBLOCK	0x0001
! #define USB_DMA_RESERVE		0x0002
  	LIST_ENTRY(usb_dma_block) next;
  } usb_dma_block_t;
  
***************
*** 58,63 ****
--- 60,93 ----
  usbd_status	usb_allocmem(usbd_bus_handle,size_t,size_t, usb_dma_t *);
  void		usb_freemem(usbd_bus_handle, usb_dma_t *);
  
+ #ifdef __NetBSD__
+ struct extent;
+ 
+ struct usb_dma_reserve {
+ 	bus_dma_tag_t dtag;
+ 	bus_dmamap_t map;
+ 	caddr_t vaddr;
+ 	bus_addr_t paddr;
+ 	size_t size;
+ 	struct extent *extent;
+ 	void *softc;
+ };
+ 
+ #if defined(_KERNEL_OPT)
+ #include "opt_usb_mem_reserve.h"
+ #endif
+ 
+ #ifndef USB_MEM_RESERVE
+ #define USB_MEM_RESERVE (256 * 1024 * 1024)
+ #endif
+ 
+ usbd_status usb_reserve_allocm(struct usb_dma_reserve *, usb_dma_t *,
+ 				u_int32_t);
+ int usb_setup_reserve(void *, struct usb_dma_reserve *, bus_dma_tag_t, size_t);
+ void usb_reserve_freem(struct usb_dma_reserve *, usb_dma_t *);
+ 
+ #endif
+ 
  #elif defined(__FreeBSD__)
  
  /*

--6c2NcOVqGQ03X4Wi--