Line data Source code
1 : /* $OpenBSD: usb_mem.c,v 1.30 2018/03/07 04:39:54 jmatthew Exp $ */
2 : /* $NetBSD: usb_mem.c,v 1.26 2003/02/01 06:23:40 thorpej Exp $ */
3 :
4 : /*
5 : * Copyright (c) 1998 The NetBSD Foundation, Inc.
6 : * All rights reserved.
7 : *
8 : * This code is derived from software contributed to The NetBSD Foundation
9 : * by Lennart Augustsson (lennart@augustsson.net) at
10 : * Carlstedt Research & Technology.
11 : *
12 : * Redistribution and use in source and binary forms, with or without
13 : * modification, are permitted provided that the following conditions
14 : * are met:
15 : * 1. Redistributions of source code must retain the above copyright
16 : * notice, this list of conditions and the following disclaimer.
17 : * 2. Redistributions in binary form must reproduce the above copyright
18 : * notice, this list of conditions and the following disclaimer in the
19 : * documentation and/or other materials provided with the distribution.
20 : *
21 : * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
22 : * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23 : * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 : * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
25 : * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 : * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 : * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 : * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 : * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 : * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 : * POSSIBILITY OF SUCH DAMAGE.
32 : */
33 :
34 : /*
35 : * USB DMA memory allocation.
36 : * We need to allocate a lot of small (many 8 byte, some larger)
37 : * memory blocks that can be used for DMA. Using the bus_dma
38 : * routines directly would incur large overheads in space and time.
39 : */
40 :
41 : #include <sys/param.h>
42 : #include <sys/systm.h>
43 : #include <sys/kernel.h>
44 : #include <sys/malloc.h>
45 : #include <sys/queue.h>
46 : #include <sys/timeout.h>
47 : #include <sys/device.h> /* for usbdivar.h */
48 : #include <machine/bus.h>
49 :
50 : #include <dev/usb/usb.h>
51 : #include <dev/usb/usbdi.h>
52 : #include <dev/usb/usbdivar.h> /* just for struct usb_dma */
53 : #include <dev/usb/usb_mem.h>
54 :
55 : #ifdef USB_DEBUG
56 : #define DPRINTF(x) do { if (usbdebug) printf x; } while (0)
57 : #define DPRINTFN(n,x) do { if (usbdebug>(n)) printf x; } while (0)
58 : extern int usbdebug;
59 : #else
60 : #define DPRINTF(x)
61 : #define DPRINTFN(n,x)
62 : #endif
63 :
64 : #define USB_MEM_SMALL 64
65 : #define USB_MEM_CHUNKS 64
66 : #define USB_MEM_BLOCK (USB_MEM_SMALL * USB_MEM_CHUNKS)
67 :
68 : struct usb_frag_dma {
69 : struct usb_dma_block *block;
70 : u_int offs;
71 : LIST_ENTRY(usb_frag_dma) next;
72 : };
73 :
74 : usbd_status usb_block_allocmem(bus_dma_tag_t, size_t, size_t,
75 : struct usb_dma_block **);
76 : void usb_block_freemem(struct usb_dma_block *);
77 :
78 : LIST_HEAD(, usb_dma_block) usb_blk_freelist =
79 : LIST_HEAD_INITIALIZER(usb_blk_freelist);
80 : int usb_blk_nfree = 0;
81 : /* XXX should have different free list for different tags (for speed) */
82 : LIST_HEAD(, usb_frag_dma) usb_frag_freelist =
83 : LIST_HEAD_INITIALIZER(usb_frag_freelist);
84 :
85 : usbd_status
86 0 : usb_block_allocmem(bus_dma_tag_t tag, size_t size, size_t align,
87 : struct usb_dma_block **dmap)
88 : {
89 : int error;
90 : struct usb_dma_block *p;
91 : int s;
92 :
93 : DPRINTFN(5, ("usb_block_allocmem: size=%lu align=%lu\n",
94 : (u_long)size, (u_long)align));
95 :
96 0 : s = splusb();
97 : /* First check the free list. */
98 0 : for (p = LIST_FIRST(&usb_blk_freelist); p; p = LIST_NEXT(p, next)) {
99 0 : if (p->tag == tag && p->size >= size && p->align >= align) {
100 0 : LIST_REMOVE(p, next);
101 0 : usb_blk_nfree--;
102 0 : splx(s);
103 0 : *dmap = p;
104 : DPRINTFN(6,("usb_block_allocmem: free list size=%lu\n",
105 : (u_long)p->size));
106 0 : return (USBD_NORMAL_COMPLETION);
107 : }
108 : }
109 0 : splx(s);
110 :
111 0 : assertwaitok();
112 :
113 : DPRINTFN(6, ("usb_block_allocmem: no free\n"));
114 0 : p = malloc(sizeof *p, M_USB, M_NOWAIT);
115 0 : if (p == NULL)
116 0 : return (USBD_NOMEM);
117 :
118 0 : p->tag = tag;
119 0 : p->size = size;
120 0 : p->align = align;
121 0 : error = bus_dmamem_alloc(tag, p->size, align, 0,
122 : p->segs, nitems(p->segs),
123 : &p->nsegs, BUS_DMA_NOWAIT);
124 0 : if (error)
125 : goto free0;
126 :
127 0 : error = bus_dmamem_map(tag, p->segs, p->nsegs, p->size,
128 : &p->kaddr, BUS_DMA_NOWAIT|BUS_DMA_COHERENT);
129 0 : if (error)
130 : goto free1;
131 :
132 0 : error = bus_dmamap_create(tag, p->size, 1, p->size,
133 : 0, BUS_DMA_NOWAIT, &p->map);
134 0 : if (error)
135 : goto unmap;
136 :
137 0 : error = bus_dmamap_load(tag, p->map, p->kaddr, p->size, NULL,
138 : BUS_DMA_NOWAIT);
139 0 : if (error)
140 : goto destroy;
141 :
142 0 : *dmap = p;
143 0 : return (USBD_NORMAL_COMPLETION);
144 :
145 : destroy:
146 0 : bus_dmamap_destroy(tag, p->map);
147 : unmap:
148 0 : bus_dmamem_unmap(tag, p->kaddr, p->size);
149 : free1:
150 0 : bus_dmamem_free(tag, p->segs, p->nsegs);
151 : free0:
152 0 : free(p, M_USB, sizeof *p);
153 0 : return (USBD_NOMEM);
154 0 : }
155 :
156 : #if 0
157 : void
158 : usb_block_real_freemem(struct usb_dma_block *p)
159 : {
160 : assertwaitok();
161 : bus_dmamap_unload(p->tag, p->map);
162 : bus_dmamap_destroy(p->tag, p->map);
163 : bus_dmamem_unmap(p->tag, p->kaddr, p->size);
164 : bus_dmamem_free(p->tag, p->segs, p->nsegs);
165 : free(p, M_USB, sizeof *p);
166 : }
167 : #endif
168 :
169 : /*
170 : * Do not free the memory unconditionally since we might be called
171 : * from an interrupt context and that is BAD.
172 : * XXX when should we really free?
173 : */
174 : void
175 0 : usb_block_freemem(struct usb_dma_block *p)
176 : {
177 : int s;
178 :
179 : DPRINTFN(6, ("usb_block_freemem: size=%lu\n", (u_long)p->size));
180 0 : s = splusb();
181 0 : LIST_INSERT_HEAD(&usb_blk_freelist, p, next);
182 0 : usb_blk_nfree++;
183 0 : splx(s);
184 0 : }
185 :
186 : usbd_status
187 0 : usb_allocmem(struct usbd_bus *bus, size_t size, size_t align, struct usb_dma *p)
188 : {
189 0 : bus_dma_tag_t tag = bus->dmatag;
190 : usbd_status err;
191 : struct usb_frag_dma *f;
192 0 : struct usb_dma_block *b;
193 : int i;
194 : int s;
195 :
196 : /* If the request is large then just use a full block. */
197 0 : if (size > USB_MEM_SMALL || align > USB_MEM_SMALL) {
198 : DPRINTFN(1, ("usb_allocmem: large alloc %d\n", (int)size));
199 0 : size = (size + USB_MEM_BLOCK - 1) & ~(USB_MEM_BLOCK - 1);
200 0 : err = usb_block_allocmem(tag, size, align, &p->block);
201 0 : if (!err) {
202 0 : p->block->frags = NULL;
203 0 : p->offs = 0;
204 0 : }
205 0 : return (err);
206 : }
207 :
208 0 : s = splusb();
209 : /* Check for free fragments. */
210 0 : for (f = LIST_FIRST(&usb_frag_freelist); f; f = LIST_NEXT(f, next))
211 0 : if (f->block->tag == tag)
212 : break;
213 0 : if (f == NULL) {
214 : DPRINTFN(1, ("usb_allocmem: adding fragments\n"));
215 0 : err = usb_block_allocmem(tag, USB_MEM_BLOCK, USB_MEM_SMALL,&b);
216 0 : if (err) {
217 0 : splx(s);
218 0 : return (err);
219 : }
220 0 : b->frags = mallocarray(USB_MEM_CHUNKS, sizeof(*f), M_USB,
221 : M_NOWAIT);
222 0 : if (b->frags == NULL) {
223 0 : splx(s);
224 0 : usb_block_freemem(b);
225 0 : return (USBD_NOMEM);
226 : }
227 0 : for (i = 0; i < USB_MEM_CHUNKS; i++) {
228 0 : f = &b->frags[i];
229 0 : f->block = b;
230 0 : f->offs = USB_MEM_SMALL * i;
231 0 : LIST_INSERT_HEAD(&usb_frag_freelist, f, next);
232 : }
233 0 : f = LIST_FIRST(&usb_frag_freelist);
234 0 : }
235 0 : p->block = f->block;
236 0 : p->offs = f->offs;
237 0 : LIST_REMOVE(f, next);
238 0 : splx(s);
239 : DPRINTFN(5, ("usb_allocmem: use frag=%p size=%d\n", f, (int)size));
240 0 : return (USBD_NORMAL_COMPLETION);
241 0 : }
242 :
243 : void
244 0 : usb_freemem(struct usbd_bus *bus, struct usb_dma *p)
245 : {
246 : struct usb_frag_dma *f;
247 : int s;
248 :
249 0 : if (p->block->frags == NULL) {
250 : DPRINTFN(1, ("usb_freemem: large free\n"));
251 0 : usb_block_freemem(p->block);
252 0 : return;
253 : }
254 0 : s = splusb();
255 0 : f = &p->block->frags[p->offs / USB_MEM_SMALL];
256 0 : LIST_INSERT_HEAD(&usb_frag_freelist, f, next);
257 0 : splx(s);
258 : DPRINTFN(5, ("usb_freemem: frag=%p block=%p\n", f, f->block));
259 0 : }
260 :
261 : void
262 0 : usb_syncmem(struct usb_dma *p, bus_addr_t offset, bus_size_t len, int ops)
263 : {
264 0 : bus_dmamap_sync(p->block->tag, p->block->map, p->offs + offset,
265 : len, ops);
266 0 : }
|