Module Name:    src
Committed By:   prlw1
Date:           Mon Jan  7 15:07:41 UTC 2013

Modified Files:
        src/sys/dev: video.c
        src/sys/dev/usb: ehci.c usb_mem.c usb_mem.h

Log Message:
Allow USB memory allocation by multiple segments in scatter/gather lists
rather than in a single contiguous block which causes problems with
large USB video frames.
Based on a patch by Jeremy Morse in the thread
http://mail-index.netbsd.org/current-users/2011/01/26/msg015532.html
Tested by developing http://code.opencv.org/issues/2360
OK jmcneill@


To generate a diff of this commit:
cvs rdiff -u -r1.28 -r1.29 src/sys/dev/video.c
cvs rdiff -u -r1.196 -r1.197 src/sys/dev/usb/ehci.c
cvs rdiff -u -r1.54 -r1.55 src/sys/dev/usb/usb_mem.c
cvs rdiff -u -r1.28 -r1.29 src/sys/dev/usb/usb_mem.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/dev/video.c
diff -u src/sys/dev/video.c:1.28 src/sys/dev/video.c:1.29
--- src/sys/dev/video.c:1.28	Thu Feb  2 17:21:18 2012
+++ src/sys/dev/video.c	Mon Jan  7 15:07:40 2013
@@ -1,4 +1,4 @@
-/* $NetBSD: video.c,v 1.28 2012/02/02 17:21:18 drochner Exp $ */
+/* $NetBSD: video.c,v 1.29 2013/01/07 15:07:40 prlw1 Exp $ */
 
 /*
  * Copyright (c) 2008 Patrick Mahoney <p...@polycrystal.org>
@@ -36,7 +36,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: video.c,v 1.28 2012/02/02 17:21:18 drochner Exp $");
+__KERNEL_RCSID(0, "$NetBSD: video.c,v 1.29 2013/01/07 15:07:40 prlw1 Exp $");
 
 #include "video.h"
 #if NVIDEO > 0
@@ -1604,7 +1604,8 @@ videoopen(dev_t dev, int flags, int ifmt
 
 	sc = device_private(device_lookup(&video_cd, VIDEOUNIT(dev)));
 	if (sc == NULL) {
-		DPRINTF(("videoopen: failed to get softc\n"));
+		DPRINTF(("videoopen: failed to get softc for unit %d\n",
+			VIDEOUNIT(dev)));
 		return ENXIO;
 	}
 	

Index: src/sys/dev/usb/ehci.c
diff -u src/sys/dev/usb/ehci.c:1.196 src/sys/dev/usb/ehci.c:1.197
--- src/sys/dev/usb/ehci.c:1.196	Sat Jan  5 23:34:16 2013
+++ src/sys/dev/usb/ehci.c	Mon Jan  7 15:07:40 2013
@@ -1,4 +1,4 @@
-/*	$NetBSD: ehci.c,v 1.196 2013/01/05 23:34:16 christos Exp $ */
+/*	$NetBSD: ehci.c,v 1.197 2013/01/07 15:07:40 prlw1 Exp $ */
 
 /*
  * Copyright (c) 2004-2012 The NetBSD Foundation, Inc.
@@ -53,7 +53,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ehci.c,v 1.196 2013/01/05 23:34:16 christos Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ehci.c,v 1.197 2013/01/07 15:07:40 prlw1 Exp $");
 
 #include "ohci.h"
 #include "uhci.h"
@@ -1336,12 +1336,18 @@ ehci_allocm(struct usbd_bus *bus, usb_dm
 	struct ehci_softc *sc = bus->hci_private;
 	usbd_status err;
 
-	err = usb_allocmem(&sc->sc_bus, size, 0, dma);
+	err = usb_allocmem_flags(&sc->sc_bus, size, 0, dma, USBMALLOC_MULTISEG);
+#ifdef EHCI_DEBUG
+	if (err)
+		printf("ehci_allocm: usb_allocmem_flags()= %s (%d)\n",
+			usbd_errstr(err), err);
+#endif
 	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);
+		printf("ehci_allocm: usb_reserve_allocm()= %s (%d)\n",
+			usbd_errstr(err), err);
 #endif
 	return (err);
 }
@@ -2727,18 +2733,20 @@ ehci_alloc_sqtd_chain(struct ehci_pipe *
 		     ehci_soft_qtd_t **sp, ehci_soft_qtd_t **ep)
 {
 	ehci_soft_qtd_t *next, *cur;
-	ehci_physaddr_t dataphys, dataphyspage, dataphyslastpage, nextphys;
+	ehci_physaddr_t nextphys;
 	u_int32_t qtdstatus;
 	int len, curlen, mps;
 	int i, tog;
+	int pages, pageoffs;
+	bus_size_t curoffs;
+	vaddr_t va, va_offs;
 	usb_dma_t *dma = &xfer->dmabuf;
 	u_int16_t flags = xfer->flags;
+	paddr_t a;
 
 	DPRINTFN(alen<4*4096,("ehci_alloc_sqtd_chain: start len=%d\n", alen));
 
 	len = alen;
-	dataphys = DMAADDR(dma, 0);
-	dataphyslastpage = EHCI_PAGE(dataphys + len - 1);
 	qtdstatus = EHCI_QTD_ACTIVE |
 	    EHCI_QTD_SET_PID(rd ? EHCI_QTD_PID_IN : EHCI_QTD_PID_OUT) |
 	    EHCI_QTD_SET_CERR(3)
@@ -2756,28 +2764,18 @@ ehci_alloc_sqtd_chain(struct ehci_pipe *
 
 	usb_syncmem(dma, 0, alen,
 	    rd ? BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE);
+	curoffs = 0;
 	for (;;) {
-		dataphyspage = EHCI_PAGE(dataphys);
 		/* The EHCI hardware can handle at most 5 pages. */
-		if (dataphyslastpage - dataphyspage <
-		    EHCI_QTD_NBUFFERS * EHCI_PAGE_SIZE) {
+		va_offs = (vaddr_t)KERNADDR(dma, curoffs);
+		va_offs = EHCI_PAGE_OFFSET(va_offs);
+		if (len-curoffs < EHCI_QTD_NBUFFERS*EHCI_PAGE_SIZE - va_offs) {
 			/* we can handle it in this QTD */
-			curlen = len;
+			curlen = len - curoffs;
 		} else {
 			/* must use multiple TDs, fill as much as possible. */
-			curlen = EHCI_QTD_NBUFFERS * EHCI_PAGE_SIZE -
-				 EHCI_PAGE_OFFSET(dataphys);
-#ifdef DIAGNOSTIC
-			if (curlen > len) {
-				printf("ehci_alloc_sqtd_chain: curlen=0x%x "
-				       "len=0x%x offs=0x%x\n", curlen, len,
-				       EHCI_PAGE_OFFSET(dataphys));
-				printf("lastpage=0x%x page=0x%x phys=0x%x\n",
-				       dataphyslastpage, dataphyspage,
-				       dataphys);
-				curlen = len;
-			}
-#endif
+			curlen = EHCI_QTD_NBUFFERS * EHCI_PAGE_SIZE - va_offs;
+
 			/* the length must be a multiple of the max size */
 			curlen -= curlen % mps;
 			DPRINTFN(1,("ehci_alloc_sqtd_chain: multiple QTDs, "
@@ -2787,18 +2785,16 @@ ehci_alloc_sqtd_chain(struct ehci_pipe *
 				panic("ehci_alloc_sqtd_chain: curlen == 0");
 #endif
 		}
-		DPRINTFN(4,("ehci_alloc_sqtd_chain: dataphys=0x%08x "
-			    "dataphyslastpage=0x%08x len=%d curlen=%d\n",
-			    dataphys, dataphyslastpage,
-			    len, curlen));
-		len -= curlen;
+		DPRINTFN(4,("ehci_alloc_sqtd_chain: len=%d curlen=%d "
+			    "curoffs=%d\n", len, curlen, curoffs));
 
 		/*
 		 * Allocate another transfer if there's more data left,
 		 * or if force last short transfer flag is set and we're
 		 * allocating a multiple of the max packet size.
 		 */
-		if (len != 0 ||
+
+		if (curoffs + curlen != len ||
 		    ((curlen % mps) == 0 && !rd && curlen != 0 &&
 		     (flags & USBD_FORCE_SHORT_XFER))) {
 			next = ehci_alloc_sqtd(sc);
@@ -2810,20 +2806,21 @@ ehci_alloc_sqtd_chain(struct ehci_pipe *
 			nextphys = EHCI_NULL;
 		}
 
-		for (i = 0; i * EHCI_PAGE_SIZE <
-		            curlen + EHCI_PAGE_OFFSET(dataphys); i++) {
-			ehci_physaddr_t a = dataphys + i * EHCI_PAGE_SIZE;
-			if (i != 0) /* use offset only in first buffer */
-				a = EHCI_PAGE(a);
-			if (i >= EHCI_QTD_NBUFFERS) {
-#ifdef DIAGNOSTIC
-				printf("ehci_alloc_sqtd_chain: i=%d\n", i);
-#endif
-				goto nomem;
-			}
-			cur->qtd.qtd_buffer[i] = htole32(a);
-			cur->qtd.qtd_buffer_hi[i] = 0;
+		/* Find number of pages we'll be using, insert dma addresses */
+		pages = EHCI_PAGE(curlen + EHCI_PAGE_SIZE -1) >> 12;
+		KASSERT(pages <= EHCI_QTD_NBUFFERS);
+		pageoffs = EHCI_PAGE(curoffs);
+		for (i = 0; i < pages; i++) {
+			a = DMAADDR(dma, pageoffs + i * EHCI_PAGE_SIZE);
+			cur->qtd.qtd_buffer[i] = htole32(a & 0xFFFFF000);
+			/* Cast up to avoid compiler warnings */
+			cur->qtd.qtd_buffer_hi[i] = htole32((uint64_t)a >> 32);
 		}
+
+		/* First buffer pointer requires a page offset to start at */
+		va = (vaddr_t)KERNADDR(dma, curoffs);
+		cur->qtd.qtd_buffer[0] |= htole32(EHCI_PAGE_OFFSET(va));
+
 		cur->nextqtd = next;
 		cur->qtd.qtd_next = cur->qtd.qtd_altnext = nextphys;
 		cur->qtd.qtd_status =
@@ -2832,7 +2829,8 @@ ehci_alloc_sqtd_chain(struct ehci_pipe *
 		cur->len = curlen;
 
 		DPRINTFN(10,("ehci_alloc_sqtd_chain: cbp=0x%08x end=0x%08x\n",
-			    dataphys, dataphys + curlen));
+			    curoffs, curoffs + curlen));
+
 		/* adjust the toggle based on the number of packets in this
 		   qtd */
 		if (((curlen + mps - 1) / mps) & 1) {
@@ -2845,7 +2843,7 @@ ehci_alloc_sqtd_chain(struct ehci_pipe *
 		    BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD);
 		DPRINTFN(10,("ehci_alloc_sqtd_chain: extend chain\n"));
 		if (len)
-			dataphys += curlen;
+			curoffs += curlen;
 		cur = next;
 	}
 	cur->qtd.qtd_status |= htole32(EHCI_QTD_IOC);

Index: src/sys/dev/usb/usb_mem.c
diff -u src/sys/dev/usb/usb_mem.c:1.54 src/sys/dev/usb/usb_mem.c:1.55
--- src/sys/dev/usb/usb_mem.c:1.54	Sat Jan  5 23:34:20 2013
+++ src/sys/dev/usb/usb_mem.c	Mon Jan  7 15:07:41 2013
@@ -1,4 +1,4 @@
-/*	$NetBSD: usb_mem.c,v 1.54 2013/01/05 23:34:20 christos Exp $	*/
+/*	$NetBSD: usb_mem.c,v 1.55 2013/01/07 15:07:41 prlw1 Exp $	*/
 
 /*
  * Copyright (c) 1998 The NetBSD Foundation, Inc.
@@ -38,12 +38,12 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: usb_mem.c,v 1.54 2013/01/05 23:34:20 christos Exp $");
+__KERNEL_RCSID(0, "$NetBSD: usb_mem.c,v 1.55 2013/01/07 15:07:41 prlw1 Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/kernel.h>
-#include <sys/malloc.h>
+#include <sys/kmem.h>
 #include <sys/queue.h>
 #include <sys/device.h>		/* for usbdivar.h */
 #include <sys/bus.h>
@@ -82,7 +82,7 @@ struct usb_frag_dma {
 };
 
 Static usbd_status	usb_block_allocmem(bus_dma_tag_t, size_t, size_t,
-					   usb_dma_block_t **);
+					   usb_dma_block_t **, bool);
 Static void		usb_block_freemem(usb_dma_block_t *);
 
 LIST_HEAD(usb_dma_block_qh, usb_dma_block);
@@ -113,13 +113,20 @@ usb_mem_init(void)
 
 Static usbd_status
 usb_block_allocmem(bus_dma_tag_t tag, size_t size, size_t align,
-		   usb_dma_block_t **dmap)
+		   usb_dma_block_t **dmap, bool multiseg)
 {
 	usb_dma_block_t *b;
 	int error;
 
 	DPRINTFN(5, ("usb_block_allocmem: size=%zu align=%zu\n", size, align));
 
+	if (size == 0) {
+#ifdef DIAGNOSTIC
+		printf("usb_block_allocmem: called with size==0\n");
+#endif
+		return USBD_INVAL;
+	}
+
 #ifdef DIAGNOSTIC
 	if (cpu_intr_p()) {
 		printf("usb_block_allocmem: in interrupt context, size=%lu\n",
@@ -131,6 +138,9 @@ usb_block_allocmem(bus_dma_tag_t tag, si
 
 	/* First check the free list. */
 	LIST_FOREACH(b, &usb_blk_freelist, next) {
+		/* Don't allocate multiple segments to unwilling callers */
+		if (b->nsegs != 1 && !multiseg)
+			continue;
 		if (b->tag == tag && b->size >= size && b->align >= align) {
 			LIST_REMOVE(b, next);
 			usb_blk_nfree--;
@@ -149,15 +159,27 @@ usb_block_allocmem(bus_dma_tag_t tag, si
 #endif
 
 	DPRINTFN(6, ("usb_block_allocmem: no free\n"));
-	b = malloc(sizeof *b, M_USB, M_NOWAIT | M_ZERO);
+	b = kmem_zalloc(sizeof *b, KM_SLEEP);
 	if (b == NULL)
 		return (USBD_NOMEM);
 
 	b->tag = tag;
 	b->size = size;
 	b->align = align;
+
+	b->nsegs = (size + (PAGE_SIZE-1)) / PAGE_SIZE;
+	if (!multiseg)
+		/* Caller wants one segment */
+		b->nsegs = 1;
+
+	b->segs = kmem_alloc(b->nsegs * sizeof(*b->segs), KM_SLEEP);
+	if (b->segs == NULL) {
+		kmem_free(b, sizeof *b);
+		return USBD_NOMEM;
+	}
+
 	error = bus_dmamem_alloc(tag, b->size, align, 0,
-				 b->segs, __arraycount(b->segs),
+				 b->segs, b->nsegs,
 				 &b->nsegs, BUS_DMA_NOWAIT);
 	if (error)
 		goto free0;
@@ -167,7 +189,7 @@ usb_block_allocmem(bus_dma_tag_t tag, si
 	if (error)
 		goto free1;
 
-	error = bus_dmamap_create(tag, b->size, 1, b->size,
+	error = bus_dmamap_create(tag, b->size, b->nsegs, b->size,
 				  0, BUS_DMA_NOWAIT, &b->map);
 	if (error)
 		goto unmap;
@@ -181,6 +203,7 @@ usb_block_allocmem(bus_dma_tag_t tag, si
 #ifdef USB_FRAG_DMA_WORKAROUND
 	memset(b->kaddr, 0, b->size);
 #endif
+
 	return (USBD_NORMAL_COMPLETION);
 
  destroy:
@@ -190,7 +213,8 @@ usb_block_allocmem(bus_dma_tag_t tag, si
  free1:
 	bus_dmamem_free(tag, b->segs, b->nsegs);
  free0:
-	free(b, M_USB);
+	kmem_free(b->segs, b->nsegs * sizeof(*b->segs));
+	kmem_free(b, sizeof *b);
 	return (USBD_NOMEM);
 }
 
@@ -208,7 +232,8 @@ usb_block_real_freemem(usb_dma_block_t *
 	bus_dmamap_destroy(b->tag, b->map);
 	bus_dmamem_unmap(b->tag, b->kaddr, b->size);
 	bus_dmamem_free(b->tag, b->segs, b->nsegs);
-	free(p, M_USB);
+	kmem_free(b->segs, b->nsegs * sizeof(*b->segs));
+	kmem_free(b, sizeof *b);
 }
 #endif
 
@@ -247,21 +272,31 @@ usb_block_freemem(usb_dma_block_t *b)
 usbd_status
 usb_allocmem(usbd_bus_handle bus, size_t size, size_t align, usb_dma_t *p)
 {
+	return usb_allocmem_flags(bus, size, align, p, 0);
+}
+
+usbd_status
+usb_allocmem_flags(usbd_bus_handle bus, size_t size, size_t align, usb_dma_t *p,
+		   int flags)
+{
 	bus_dma_tag_t tag = bus->dmatag;
 	usbd_status err;
 	struct usb_frag_dma *f;
 	usb_dma_block_t *b;
 	int i;
 	static ONCE_DECL(init_control);
+	bool frag;
 
 	RUN_ONCE(&init_control, usb_mem_init);
 
+	frag = (flags & USBMALLOC_MULTISEG);
+
 	/* If the request is large then just use a full block. */
 	if (size > USB_MEM_SMALL || align > USB_MEM_SMALL) {
 		DPRINTFN(1, ("usb_allocmem: large alloc %d\n", (int)size));
 		size = (size + USB_MEM_BLOCK - 1) & ~(USB_MEM_BLOCK - 1);
 		mutex_enter(&usb_blk_lock);
-		err = usb_block_allocmem(tag, size, align, &p->block);
+		err = usb_block_allocmem(tag, size, align, &p->block, frag);
 		if (!err) {
 #ifdef DEBUG
 			LIST_INSERT_HEAD(&usb_blk_fulllist, p->block, next);
@@ -284,7 +319,8 @@ usb_allocmem(usbd_bus_handle bus, size_t
 	}
 	if (f == NULL) {
 		DPRINTFN(1, ("usb_allocmem: adding fragments\n"));
-		err = usb_block_allocmem(tag, USB_MEM_BLOCK, USB_MEM_SMALL,&b);
+		err = usb_block_allocmem(tag, USB_MEM_BLOCK, USB_MEM_SMALL, &b,
+					 false);
 		if (err) {
 			mutex_exit(&usb_blk_lock);
 			return (err);
@@ -313,6 +349,7 @@ usb_allocmem(usbd_bus_handle bus, size_t
 	LIST_REMOVE(f, next);
 	mutex_exit(&usb_blk_lock);
 	DPRINTFN(5, ("usb_allocmem: use frag=%p size=%d\n", f, (int)size));
+
 	return (USBD_NORMAL_COMPLETION);
 }
 
@@ -349,6 +386,38 @@ usb_freemem(usbd_bus_handle bus, usb_dma
 	DPRINTFN(5, ("usb_freemem: frag=%p\n", f));
 }
 
+bus_addr_t
+usb_dmaaddr(usb_dma_t *dma, unsigned int offset)
+{
+	unsigned int i;
+	bus_size_t seg_offs;
+
+	offset += dma->offs;
+
+	KASSERT(offset < dma->block->size);
+
+	if (dma->block->nsegs == 1) {
+		KASSERT(dma->block->map->dm_segs[0].ds_len > offset);
+		return dma->block->map->dm_segs[0].ds_addr + offset;
+	}
+
+	/* Search for a bus_segment_t corresponding to this offset. With no
+	 * record of the offset in the map to a particular dma_segment_t, we
+	 * have to iterate from the start of the list each time. Could be
+	 * improved */
+	seg_offs = 0;
+	for (i = 0; i < dma->block->nsegs; i++) {
+		if (seg_offs + dma->block->map->dm_segs[i].ds_len > offset)
+			break;
+
+		seg_offs += dma->block->map->dm_segs[i].ds_len;
+	}
+
+	KASSERT(i != dma->block->nsegs);
+	offset -= seg_offs;
+	return dma->block->map->dm_segs[i].ds_addr + offset;
+}
+
 void
 usb_syncmem(usb_dma_t *p, bus_addr_t offset, bus_size_t len, int ops)
 {
@@ -367,7 +436,7 @@ usb_reserve_allocm(struct usb_dma_reserv
 	if (rs->vaddr == 0 || size > USB_MEM_RESERVE)
 		return USBD_NOMEM;
 
-	dma->block = malloc(sizeof *dma->block, M_USB, M_ZERO | M_NOWAIT);
+	dma->block = kmem_zalloc(sizeof *dma->block, KM_SLEEP);
 	if (dma->block == NULL)
 		return USBD_NOMEM;
 
@@ -403,7 +472,8 @@ usb_reserve_freem(struct usb_dma_reserve
 
 	error = extent_free(rs->extent,
 	    (u_long)(rs->paddr + dma->offs), dma->block->size, 0);
-	free(dma->block, M_USB);
+	/* XXXPW correct that segs[0] is not used? */
+	kmem_free(dma->block, dma->block->size);
 }
 
 int

Index: src/sys/dev/usb/usb_mem.h
diff -u src/sys/dev/usb/usb_mem.h:1.28 src/sys/dev/usb/usb_mem.h:1.29
--- src/sys/dev/usb/usb_mem.h:1.28	Fri Feb 24 06:48:28 2012
+++ src/sys/dev/usb/usb_mem.h	Mon Jan  7 15:07:41 2013
@@ -1,4 +1,4 @@
-/*	$NetBSD: usb_mem.h,v 1.28 2012/02/24 06:48:28 mrg Exp $	*/
+/*	$NetBSD: usb_mem.h,v 1.29 2013/01/07 15:07:41 prlw1 Exp $	*/
 /*	$FreeBSD: src/sys/dev/usb/usb_mem.h,v 1.9 1999/11/17 22:33:47 n_hibma Exp $	*/
 
 /*
@@ -35,7 +35,7 @@ typedef struct usb_dma_block {
 	bus_dma_tag_t tag;
 	bus_dmamap_t map;
         void *kaddr;
-        bus_dma_segment_t segs[1];
+        bus_dma_segment_t *segs;
         int nsegs;
         size_t size;
         size_t align;
@@ -45,14 +45,20 @@ typedef struct usb_dma_block {
 	LIST_ENTRY(usb_dma_block) next;
 } usb_dma_block_t;
 
-#define DMAADDR(dma, o) ((dma)->block->map->dm_segs[0].ds_addr + (dma)->offs + (o))
-#define KERNADDR(dma, o) \
-	((void *)((char *)(dma)->block->kaddr + (dma)->offs + (o)))
+#define USBMALLOC_MULTISEG	1
 
-usbd_status	usb_allocmem(usbd_bus_handle,size_t,size_t, usb_dma_t *);
+usbd_status	usb_allocmem(usbd_bus_handle, size_t, size_t, usb_dma_t *);
+usbd_status	usb_allocmem_flags(usbd_bus_handle, size_t, size_t, usb_dma_t *,
+			int);
 void		usb_freemem(usbd_bus_handle, usb_dma_t *);
 void		usb_syncmem(usb_dma_t *, bus_addr_t, bus_size_t, int ops);
 
+bus_addr_t	usb_dmaaddr(usb_dma_t *, unsigned int);
+
+#define DMAADDR(dma, o) usb_dmaaddr((dma), (o))
+#define KERNADDR(dma, o) \
+	((void *)((char *)(dma)->block->kaddr + (dma)->offs + (o)))
+
 struct extent;
 
 struct usb_dma_reserve {

Reply via email to