| Commit | Line | Data |
|---|---|---|
| 1550dfd9 MD |
1 | /* |
| 2 | * $NetBSD: usb_mem.c,v 1.26 2003/02/01 06:23:40 thorpej Exp $ | |
| 3 | * $FreeBSD: src/sys/dev/usb/usb_mem.c,v 1.5 2003/10/04 22:13:21 joe Exp $ | |
| 1550dfd9 MD |
4 | */ |
| 5 | /* | |
| 6 | * Copyright (c) 1998 The NetBSD Foundation, Inc. | |
| 7 | * All rights reserved. | |
| 8 | * | |
| 9 | * This code is derived from software contributed to The NetBSD Foundation | |
| 10 | * by Lennart Augustsson (lennart@augustsson.net) at | |
| 11 | * Carlstedt Research & Technology. | |
| 12 | * | |
| 13 | * Redistribution and use in source and binary forms, with or without | |
| 14 | * modification, are permitted provided that the following conditions | |
| 15 | * are met: | |
| 16 | * 1. Redistributions of source code must retain the above copyright | |
| 17 | * notice, this list of conditions and the following disclaimer. | |
| 18 | * 2. Redistributions in binary form must reproduce the above copyright | |
| 19 | * notice, this list of conditions and the following disclaimer in the | |
| 20 | * documentation and/or other materials provided with the distribution. | |
| 21 | * 3. All advertising materials mentioning features or use of this software | |
| 22 | * must display the following acknowledgement: | |
| 23 | * This product includes software developed by the NetBSD | |
| 24 | * Foundation, Inc. and its contributors. | |
| 25 | * 4. Neither the name of The NetBSD Foundation nor the names of its | |
| 26 | * contributors may be used to endorse or promote products derived | |
| 27 | * from this software without specific prior written permission. | |
| 28 | * | |
| 29 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS | |
| 30 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED | |
| 31 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
| 32 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS | |
| 33 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| 34 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| 35 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| 36 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| 37 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| 38 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
| 39 | * POSSIBILITY OF SUCH DAMAGE. | |
| 40 | */ | |
| 41 | ||
| 42 | /* | |
| 43 | * USB DMA memory allocation. | |
| 44 | * We need to allocate a lot of small (many 8 byte, some larger) | |
| 45 | * memory blocks that can be used for DMA. Using the bus_dma | |
| 46 | * routines directly would incur large overheads in space and time. | |
| 47 | */ | |
| 48 | #include <sys/param.h> | |
| 49 | #include <sys/systm.h> | |
| 50 | #include <sys/malloc.h> | |
| 51 | #include <sys/kernel.h> | |
| 1550dfd9 MD |
52 | #include <sys/endian.h> |
| 53 | #include <sys/module.h> | |
| 54 | #include <sys/bus.h> | |
| 1550dfd9 | 55 | #include <sys/queue.h> |
| 05bd2b9e MD |
56 | #include <sys/sysctl.h> |
| 57 | #include <sys/ktr.h> | |
| 1550dfd9 | 58 | |
| 1550dfd9 MD |
59 | #include <machine/endian.h> |
| 60 | ||
| 61 | #ifdef DIAGNOSTIC | |
| 62 | #include <sys/proc.h> | |
| 63 | #endif | |
| 4e01b467 | 64 | #include <sys/thread2.h> |
| 1550dfd9 MD |
65 | |
| 66 | #include <bus/usb/usb.h> | |
| 67 | #include <bus/usb/usbdi.h> | |
| 68 | #include <bus/usb/usbdivar.h> /* just for usb_dma_t */ | |
| 69 | #include <bus/usb/usb_mem.h> | |
| 70 | ||
| 5bf48697 AE |
71 | #define USBKTR_STRING "ptr=%p bus=%p size=%zu align=%zu" |
| 72 | #define USBKTR_ARGS void *p, usbd_bus_handle bus, size_t size, size_t align | |
| 05bd2b9e MD |
73 | |
| 74 | #if !defined(KTR_USB_MEMORY) | |
| 75 | #define KTR_USB_MEMORY KTR_ALL | |
| 76 | #endif | |
| 77 | KTR_INFO_MASTER(usbmem); | |
| 5bf48697 AE |
78 | KTR_INFO(KTR_USB_MEMORY, usbmem, alloc_full, 0, USBKTR_STRING, USBKTR_ARGS); |
| 79 | KTR_INFO(KTR_USB_MEMORY, usbmem, alloc_frag, 0, USBKTR_STRING, USBKTR_ARGS); | |
| 80 | KTR_INFO(KTR_USB_MEMORY, usbmem, free_full, 0, USBKTR_STRING, USBKTR_ARGS); | |
| 81 | KTR_INFO(KTR_USB_MEMORY, usbmem, free_frag, 0, USBKTR_STRING, USBKTR_ARGS); | |
| 82 | KTR_INFO(KTR_USB_MEMORY, usbmem, blkalloc, 0, USBKTR_STRING, USBKTR_ARGS); | |
| 83 | KTR_INFO(KTR_USB_MEMORY, usbmem, blkalloc2, 0, USBKTR_STRING, USBKTR_ARGS); | |
| 84 | KTR_INFO(KTR_USB_MEMORY, usbmem, blkfree, 0, USBKTR_STRING, USBKTR_ARGS); | |
| 05bd2b9e MD |
85 | |
| 86 | #define logmemory(name, ptr, bus, size, align) \ | |
| 5bf48697 | 87 | KTR_LOG(usbmem_ ## name, ptr, bus, size, align); |
| 05bd2b9e | 88 | |
| 1550dfd9 | 89 | #ifdef USB_DEBUG |
| fc1ef497 HT |
90 | #define DPRINTF(x) if (usbdebug) kprintf x |
| 91 | #define DPRINTFN(n,x) if (usbdebug>(n)) kprintf x | |
| 1550dfd9 MD |
92 | extern int usbdebug; |
| 93 | #else | |
| 94 | #define DPRINTF(x) | |
| 95 | #define DPRINTFN(n,x) | |
| 96 | #endif | |
| 97 | ||
| 98 | #define USB_MEM_SMALL 64 | |
| 99 | #define USB_MEM_CHUNKS (PAGE_SIZE / USB_MEM_SMALL) | |
| 100 | #define USB_MEM_BLOCK (USB_MEM_SMALL * USB_MEM_CHUNKS) | |
| 101 | ||
| 102 | /* This struct is overlayed on free fragments. */ | |
| 103 | struct usb_frag_dma { | |
| 104 | usb_dma_block_t *block; | |
| 105 | u_int offs; | |
| 106 | LIST_ENTRY(usb_frag_dma) next; | |
| 107 | }; | |
| 108 | ||
| 6ed427ca HT |
109 | static bus_dmamap_callback_t usbmem_callback; |
| 110 | static usbd_status usb_block_allocmem(bus_dma_tag_t, size_t, size_t, | |
| 1550dfd9 | 111 | usb_dma_block_t **); |
| 6ed427ca | 112 | static void usb_block_freemem(usb_dma_block_t *); |
| 1550dfd9 | 113 | |
| 6ed427ca | 114 | static LIST_HEAD(, usb_dma_block) usb_blk_freelist = |
| 1550dfd9 | 115 | LIST_HEAD_INITIALIZER(usb_blk_freelist); |
| 6ed427ca | 116 | static int usb_blk_nfree = 0; |
| 1550dfd9 | 117 | /* XXX should have different free list for different tags (for speed) */ |
| 6ed427ca | 118 | static LIST_HEAD(, usb_frag_dma) usb_frag_freelist = |
| 1550dfd9 MD |
119 | LIST_HEAD_INITIALIZER(usb_frag_freelist); |
| 120 | ||
| 6ed427ca | 121 | static void |
| 1550dfd9 MD |
122 | usbmem_callback(void *arg, bus_dma_segment_t *segs, int nseg, int error) |
| 123 | { | |
| 124 | int i; | |
| 125 | usb_dma_block_t *p = arg; | |
| 126 | ||
| 127 | if (error == EFBIG) { | |
| 85f8e2ea | 128 | kprintf("usb: mapping to large\n"); |
| 1550dfd9 MD |
129 | return; |
| 130 | } | |
| 131 | ||
| 132 | p->nsegs = nseg; | |
| 133 | for (i = 0; i < nseg && i < sizeof p->segs / sizeof *p->segs; i++) | |
| 134 | p->segs[i] = segs[i]; | |
| 135 | } | |
| 136 | ||
| 6ed427ca | 137 | static usbd_status |
| 1550dfd9 MD |
138 | usb_block_allocmem(bus_dma_tag_t tag, size_t size, size_t align, |
| 139 | usb_dma_block_t **dmap) | |
| 140 | { | |
| 141 | usb_dma_block_t *p; | |
| 1550dfd9 MD |
142 | |
| 143 | DPRINTFN(5, ("usb_block_allocmem: size=%lu align=%lu\n", | |
| 144 | (u_long)size, (u_long)align)); | |
| 145 | ||
| 4e01b467 | 146 | crit_enter(); |
| 1550dfd9 MD |
147 | /* First check the free list. */ |
| 148 | for (p = LIST_FIRST(&usb_blk_freelist); p; p = LIST_NEXT(p, next)) { | |
| 149 | if (p->tag == tag && p->size >= size && p->align >= align) { | |
| 150 | LIST_REMOVE(p, next); | |
| 151 | usb_blk_nfree--; | |
| 4e01b467 | 152 | crit_exit(); |
| 1550dfd9 MD |
153 | *dmap = p; |
| 154 | DPRINTFN(6,("usb_block_allocmem: free list size=%lu\n", | |
| 155 | (u_long)p->size)); | |
| 05bd2b9e | 156 | logmemory(blkalloc2, p, NULL, size, align); |
| 1550dfd9 MD |
157 | return (USBD_NORMAL_COMPLETION); |
| 158 | } | |
| 159 | } | |
| 4e01b467 | 160 | crit_exit(); |
| 1550dfd9 | 161 | |
| 1550dfd9 | 162 | DPRINTFN(6, ("usb_block_allocmem: no free\n")); |
| efda3bd0 | 163 | p = kmalloc(sizeof *p, M_USB, M_INTWAIT); |
| 05bd2b9e | 164 | logmemory(blkalloc, p, NULL, size, align); |
| 1550dfd9 | 165 | |
| 1550dfd9 MD |
166 | if (bus_dma_tag_create(tag, align, 0, |
| 167 | BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, | |
| c157ff7a | 168 | size, NELEM(p->segs), size, BUS_DMA_ALLOCNOW, &p->tag) == ENOMEM) { |
| 1550dfd9 MD |
169 | goto free; |
| 170 | } | |
| 171 | ||
| 172 | p->size = size; | |
| 173 | p->align = align; | |
| 174 | if (bus_dmamem_alloc(p->tag, &p->kaddr, | |
| 175 | BUS_DMA_NOWAIT|BUS_DMA_COHERENT, &p->map)) | |
| 176 | goto tagfree; | |
| 177 | ||
| 178 | if (bus_dmamap_load(p->tag, p->map, p->kaddr, p->size, | |
| 179 | usbmem_callback, p, 0)) | |
| 180 | goto memfree; | |
| 181 | ||
| 182 | /* XXX - override the tag, ok since we never free it */ | |
| 183 | p->tag = tag; | |
| 184 | *dmap = p; | |
| 185 | return (USBD_NORMAL_COMPLETION); | |
| 186 | ||
| 187 | /* | |
| 188 | * XXX - do we need to _unload? is the order of _free and _destroy | |
| 189 | * correct? | |
| 190 | */ | |
| 191 | memfree: | |
| 192 | bus_dmamem_free(p->tag, p->kaddr, p->map); | |
| 193 | tagfree: | |
| 194 | bus_dma_tag_destroy(p->tag); | |
| 195 | free: | |
| efda3bd0 | 196 | kfree(p, M_USB); |
| 1550dfd9 MD |
197 | return (USBD_NOMEM); |
| 198 | } | |
| 199 | ||
| 200 | /* | |
| 201 | * Do not free the memory unconditionally since we might be called | |
| 202 | * from an interrupt context and that is BAD. | |
| 203 | * XXX when should we really free? | |
| 204 | */ | |
| 6ed427ca | 205 | static void |
| 1550dfd9 MD |
206 | usb_block_freemem(usb_dma_block_t *p) |
| 207 | { | |
| 1550dfd9 | 208 | DPRINTFN(6, ("usb_block_freemem: size=%lu\n", (u_long)p->size)); |
| 05bd2b9e | 209 | logmemory(blkfree, p, NULL, p->size, p->align); |
| 4e01b467 | 210 | crit_enter(); |
| 1550dfd9 MD |
211 | LIST_INSERT_HEAD(&usb_blk_freelist, p, next); |
| 212 | usb_blk_nfree++; | |
| 4e01b467 | 213 | crit_exit(); |
| 1550dfd9 MD |
214 | } |
| 215 | ||
| 216 | usbd_status | |
| 217 | usb_allocmem(usbd_bus_handle bus, size_t size, size_t align, usb_dma_t *p) | |
| 218 | { | |
| 219 | bus_dma_tag_t tag = bus->dmatag; | |
| 220 | usbd_status err; | |
| 221 | struct usb_frag_dma *f; | |
| 222 | usb_dma_block_t *b; | |
| 223 | int i; | |
| 1550dfd9 MD |
224 | |
| 225 | /* compat w/ Net/OpenBSD */ | |
| 226 | if (align == 0) | |
| 227 | align = 1; | |
| 228 | ||
| 229 | /* If the request is large then just use a full block. */ | |
| 230 | if (size > USB_MEM_SMALL || align > USB_MEM_SMALL) { | |
| 231 | DPRINTFN(1, ("usb_allocmem: large alloc %d\n", (int)size)); | |
| 232 | size = (size + USB_MEM_BLOCK - 1) & ~(USB_MEM_BLOCK - 1); | |
| 233 | err = usb_block_allocmem(tag, size, align, &p->block); | |
| 234 | if (!err) { | |
| 235 | p->block->fullblock = 1; | |
| 236 | p->offs = 0; | |
| 237 | p->len = size; | |
| 05bd2b9e | 238 | logmemory(alloc_full, p, NULL, size, align); |
| 1550dfd9 MD |
239 | } |
| 240 | return (err); | |
| 241 | } | |
| 242 | ||
| 4e01b467 | 243 | crit_enter(); |
| 1550dfd9 MD |
244 | /* Check for free fragments. */ |
| 245 | for (f = LIST_FIRST(&usb_frag_freelist); f; f = LIST_NEXT(f, next)) | |
| 246 | if (f->block->tag == tag) | |
| 247 | break; | |
| 248 | if (f == NULL) { | |
| 249 | DPRINTFN(1, ("usb_allocmem: adding fragments\n")); | |
| 250 | err = usb_block_allocmem(tag, USB_MEM_BLOCK, USB_MEM_SMALL,&b); | |
| 251 | if (err) { | |
| 4e01b467 | 252 | crit_exit(); |
| 1550dfd9 MD |
253 | return (err); |
| 254 | } | |
| 255 | b->fullblock = 0; | |
| 256 | /* XXX - override the tag, ok since we never free it */ | |
| 257 | b->tag = tag; | |
| 258 | KASSERT(sizeof *f <= USB_MEM_SMALL, ("USB_MEM_SMALL(%d) is too small for struct usb_frag_dma(%zd)\n", | |
| 259 | USB_MEM_SMALL, sizeof *f)); | |
| 260 | for (i = 0; i < USB_MEM_BLOCK; i += USB_MEM_SMALL) { | |
| 261 | f = (struct usb_frag_dma *)((char *)b->kaddr + i); | |
| 262 | f->block = b; | |
| 263 | f->offs = i; | |
| 264 | LIST_INSERT_HEAD(&usb_frag_freelist, f, next); | |
| 265 | } | |
| 266 | f = LIST_FIRST(&usb_frag_freelist); | |
| 267 | } | |
| 268 | p->block = f->block; | |
| 269 | p->offs = f->offs; | |
| 270 | p->len = USB_MEM_SMALL; | |
| 271 | LIST_REMOVE(f, next); | |
| 4e01b467 | 272 | crit_exit(); |
| 05bd2b9e | 273 | logmemory(alloc_frag, p, NULL, size, align); |
| 1550dfd9 MD |
274 | DPRINTFN(5, ("usb_allocmem: use frag=%p size=%d\n", f, (int)size)); |
| 275 | return (USBD_NORMAL_COMPLETION); | |
| 276 | } | |
| 277 | ||
| 278 | void | |
| 279 | usb_freemem(usbd_bus_handle bus, usb_dma_t *p) | |
| 280 | { | |
| 281 | struct usb_frag_dma *f; | |
| 1550dfd9 MD |
282 | |
| 283 | if (p->block->fullblock) { | |
| 284 | DPRINTFN(1, ("usb_freemem: large free\n")); | |
| 285 | usb_block_freemem(p->block); | |
| 5bf48697 | 286 | logmemory(free_full, p, bus, (size_t)0, (size_t)0); |
| 1550dfd9 MD |
287 | return; |
| 288 | } | |
| 5bf48697 | 289 | logmemory(free_frag, p, bus, (size_t)0, (size_t)0); |
| 1550dfd9 MD |
290 | f = KERNADDR(p, 0); |
| 291 | f->block = p->block; | |
| 292 | f->offs = p->offs; | |
| 4e01b467 | 293 | crit_enter(); |
| 1550dfd9 | 294 | LIST_INSERT_HEAD(&usb_frag_freelist, f, next); |
| 4e01b467 | 295 | crit_exit(); |
| 1550dfd9 MD |
296 | DPRINTFN(5, ("usb_freemem: frag=%p\n", f)); |
| 297 | } |