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 {