xref: /netbsd-src/sys/dev/usb/usb_mem.c (revision b757af438b42b93f8c6571f026d8b8ef3eaf5fc9)
1 /*	$NetBSD: usb_mem.c,v 1.52 2012/02/24 06:48:27 mrg Exp $	*/
2 
3 /*
4  * Copyright (c) 1998 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Lennart Augustsson (lennart@augustsson.net) at
9  * Carlstedt Research & Technology.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 /*
34  * USB DMA memory allocation.
35  * We need to allocate a lot of small (many 8 byte, some larger)
36  * memory blocks that can be used for DMA.  Using the bus_dma
37  * routines directly would incur large overheads in space and time.
38  */
39 
40 #include <sys/cdefs.h>
41 __KERNEL_RCSID(0, "$NetBSD: usb_mem.c,v 1.52 2012/02/24 06:48:27 mrg Exp $");
42 
43 #ifdef _KERNEL_OPT
44 #include "opt_usb.h"
45 #endif
46 
47 #include <sys/param.h>
48 #include <sys/systm.h>
49 #include <sys/kernel.h>
50 #include <sys/malloc.h>
51 #include <sys/queue.h>
52 #include <sys/device.h>		/* for usbdivar.h */
53 #include <sys/bus.h>
54 #include <sys/cpu.h>
55 #include <sys/extent.h>
56 
57 #ifdef DIAGNOSTIC
58 #include <sys/proc.h>
59 #endif
60 
61 #include <dev/usb/usb.h>
62 #include <dev/usb/usbdi.h>
63 #include <dev/usb/usbdivar.h>	/* just for usb_dma_t */
64 #include <dev/usb/usb_mem.h>
65 
66 #ifdef USB_DEBUG
67 #define DPRINTF(x)	if (usbdebug) printf x
68 #define DPRINTFN(n,x)	if (usbdebug>(n)) printf x
69 extern int usbdebug;
70 #else
71 #define DPRINTF(x)
72 #define DPRINTFN(n,x)
73 #endif
74 
75 #define USB_MEM_SMALL 64
76 #define USB_MEM_CHUNKS 64
77 #define USB_MEM_BLOCK (USB_MEM_SMALL * USB_MEM_CHUNKS)
78 
79 /* This struct is overlayed on free fragments. */
80 struct usb_frag_dma {
81 	usb_dma_block_t *block;
82 	u_int offs;
83 	LIST_ENTRY(usb_frag_dma) next;
84 };
85 
86 Static usbd_status	usb_block_allocmem(bus_dma_tag_t, size_t, size_t,
87 					   usb_dma_block_t **);
88 Static void		usb_block_freemem(usb_dma_block_t *);
89 
90 LIST_HEAD(usb_dma_block_qh, usb_dma_block);
91 Static struct usb_dma_block_qh usb_blk_freelist =
92 	LIST_HEAD_INITIALIZER(usb_blk_freelist);
93 #ifdef DEBUG
94 Static struct usb_dma_block_qh usb_blk_fraglist =
95 	LIST_HEAD_INITIALIZER(usb_blk_fraglist);
96 Static struct usb_dma_block_qh usb_blk_fulllist =
97 	LIST_HEAD_INITIALIZER(usb_blk_fulllist);
98 #endif
99 Static u_int usb_blk_nfree = 0;
100 /* XXX should have different free list for different tags (for speed) */
101 Static LIST_HEAD(, usb_frag_dma) usb_frag_freelist =
102 	LIST_HEAD_INITIALIZER(usb_frag_freelist);
103 
104 Static usbd_status
105 usb_block_allocmem(bus_dma_tag_t tag, size_t size, size_t align,
106 		   usb_dma_block_t **dmap)
107 {
108 	usb_dma_block_t *b;
109 	int error;
110 	int s;
111 
112 	DPRINTFN(5, ("usb_block_allocmem: size=%zu align=%zu\n", size, align));
113 
114 #ifdef DIAGNOSTIC
115 	if (cpu_intr_p()) {
116 		printf("usb_block_allocmem: in interrupt context, size=%lu\n",
117 		    (unsigned long) size);
118 	}
119 #endif
120 
121 	s = splusb();
122 	/* First check the free list. */
123 	LIST_FOREACH(b, &usb_blk_freelist, next) {
124 		if (b->tag == tag && b->size >= size && b->align >= align) {
125 			LIST_REMOVE(b, next);
126 			usb_blk_nfree--;
127 			splx(s);
128 			*dmap = b;
129 			DPRINTFN(6,("usb_block_allocmem: free list size=%zu\n",
130 			    b->size));
131 			return (USBD_NORMAL_COMPLETION);
132 		}
133 	}
134 	splx(s);
135 
136 #ifdef DIAGNOSTIC
137 	if (cpu_intr_p()) {
138 		printf("usb_block_allocmem: in interrupt context, failed\n");
139 		return (USBD_NOMEM);
140 	}
141 #endif
142 
143 	DPRINTFN(6, ("usb_block_allocmem: no free\n"));
144 	b = malloc(sizeof *b, M_USB, M_NOWAIT | M_ZERO);
145 	if (b == NULL)
146 		return (USBD_NOMEM);
147 
148 	b->tag = tag;
149 	b->size = size;
150 	b->align = align;
151 	error = bus_dmamem_alloc(tag, b->size, align, 0,
152 				 b->segs, __arraycount(b->segs),
153 				 &b->nsegs, BUS_DMA_NOWAIT);
154 	if (error)
155 		goto free0;
156 
157 	error = bus_dmamem_map(tag, b->segs, b->nsegs, b->size,
158 			       &b->kaddr, BUS_DMA_NOWAIT|BUS_DMA_COHERENT);
159 	if (error)
160 		goto free1;
161 
162 	error = bus_dmamap_create(tag, b->size, 1, b->size,
163 				  0, BUS_DMA_NOWAIT, &b->map);
164 	if (error)
165 		goto unmap;
166 
167 	error = bus_dmamap_load(tag, b->map, b->kaddr, b->size, NULL,
168 				BUS_DMA_NOWAIT);
169 	if (error)
170 		goto destroy;
171 
172 	*dmap = b;
173 #ifdef USB_FRAG_DMA_WORKAROUND
174 	memset(b->kaddr, 0, b->size);
175 #endif
176 	return (USBD_NORMAL_COMPLETION);
177 
178  destroy:
179 	bus_dmamap_destroy(tag, b->map);
180  unmap:
181 	bus_dmamem_unmap(tag, b->kaddr, b->size);
182  free1:
183 	bus_dmamem_free(tag, b->segs, b->nsegs);
184  free0:
185 	free(b, M_USB);
186 	return (USBD_NOMEM);
187 }
188 
189 #if 0
190 void
191 usb_block_real_freemem(usb_dma_block_t *b)
192 {
193 #ifdef DIAGNOSTIC
194 	if (cpu_intr_p()) {
195 		printf("usb_block_real_freemem: in interrupt context\n");
196 		return;
197 	}
198 #endif
199 	bus_dmamap_unload(b->tag, b->map);
200 	bus_dmamap_destroy(b->tag, b->map);
201 	bus_dmamem_unmap(b->tag, b->kaddr, b->size);
202 	bus_dmamem_free(b->tag, b->segs, b->nsegs);
203 	free(p, M_USB);
204 }
205 #endif
206 
207 #ifdef DEBUG
208 static bool
209 usb_valid_block_p(usb_dma_block_t *b, struct usb_dma_block_qh *qh)
210 {
211 	usb_dma_block_t *xb;
212 	LIST_FOREACH(xb, qh, next) {
213 		if (xb == b)
214 			return true;
215 	}
216 	return false;
217 }
218 #endif
219 
220 /*
221  * Do not free the memory unconditionally since we might be called
222  * from an interrupt context and that is BAD.
223  * XXX when should we really free?
224  */
225 Static void
226 usb_block_freemem(usb_dma_block_t *b)
227 {
228 	int s;
229 
230 	DPRINTFN(6, ("usb_block_freemem: size=%zu\n", b->size));
231 	s = splusb();
232 #ifdef DEBUG
233 	LIST_REMOVE(b, next);
234 #endif
235 	LIST_INSERT_HEAD(&usb_blk_freelist, b, next);
236 	usb_blk_nfree++;
237 	splx(s);
238 }
239 
240 usbd_status
241 usb_allocmem(usbd_bus_handle bus, size_t size, size_t align, usb_dma_t *p)
242 {
243 	bus_dma_tag_t tag = bus->dmatag;
244 	usbd_status err;
245 	struct usb_frag_dma *f;
246 	usb_dma_block_t *b;
247 	int i;
248 	int s;
249 
250 	/* If the request is large then just use a full block. */
251 	if (size > USB_MEM_SMALL || align > USB_MEM_SMALL) {
252 		DPRINTFN(1, ("usb_allocmem: large alloc %d\n", (int)size));
253 		size = (size + USB_MEM_BLOCK - 1) & ~(USB_MEM_BLOCK - 1);
254 		err = usb_block_allocmem(tag, size, align, &p->block);
255 		if (!err) {
256 #ifdef DEBUG
257 			LIST_INSERT_HEAD(&usb_blk_fulllist, p->block, next);
258 #endif
259 			p->block->flags = USB_DMA_FULLBLOCK;
260 			p->offs = 0;
261 		}
262 		return (err);
263 	}
264 
265 	s = splusb();
266 	/* Check for free fragments. */
267 	LIST_FOREACH(f, &usb_frag_freelist, next) {
268 		KDASSERTMSG(usb_valid_block_p(f->block, &usb_blk_fraglist),
269 		    "%s: usb frag %p: unknown block pointer %p",
270 		     __func__, f, f->block);
271 		if (f->block->tag == tag)
272 			break;
273 	}
274 	if (f == NULL) {
275 		DPRINTFN(1, ("usb_allocmem: adding fragments\n"));
276 		err = usb_block_allocmem(tag, USB_MEM_BLOCK, USB_MEM_SMALL,&b);
277 		if (err) {
278 			splx(s);
279 			return (err);
280 		}
281 #ifdef DEBUG
282 		LIST_INSERT_HEAD(&usb_blk_fraglist, b, next);
283 #endif
284 		b->flags = 0;
285 		for (i = 0; i < USB_MEM_BLOCK; i += USB_MEM_SMALL) {
286 			f = (struct usb_frag_dma *)((char *)b->kaddr + i);
287 			f->block = b;
288 			f->offs = i;
289 			LIST_INSERT_HEAD(&usb_frag_freelist, f, next);
290 #ifdef USB_FRAG_DMA_WORKAROUND
291 			i += 1 * USB_MEM_SMALL;
292 #endif
293 		}
294 		f = LIST_FIRST(&usb_frag_freelist);
295 	}
296 	p->block = f->block;
297 	p->offs = f->offs;
298 #ifdef USB_FRAG_DMA_WORKAROUND
299 	p->offs += USB_MEM_SMALL;
300 #endif
301 	p->block->flags &= ~USB_DMA_RESERVE;
302 	LIST_REMOVE(f, next);
303 	splx(s);
304 	DPRINTFN(5, ("usb_allocmem: use frag=%p size=%d\n", f, (int)size));
305 	return (USBD_NORMAL_COMPLETION);
306 }
307 
308 void
309 usb_freemem(usbd_bus_handle bus, usb_dma_t *p)
310 {
311 	struct usb_frag_dma *f;
312 	int s;
313 
314 	if (p->block->flags & USB_DMA_FULLBLOCK) {
315 		KDASSERTMSG(usb_valid_block_p(p->block, &usb_blk_fulllist),
316 		    "%s: dma %p: invalid block pointer %p",
317 		     __func__, p, p->block);
318 		DPRINTFN(1, ("usb_freemem: large free\n"));
319 		usb_block_freemem(p->block);
320 		return;
321 	}
322 	KDASSERTMSG(usb_valid_block_p(p->block, &usb_blk_fraglist),
323 	    "%s: dma %p: invalid block pointer %p",
324 	     __func__, p, p->block);
325 	//usb_syncmem(p, 0, USB_MEM_SMALL, BUS_DMASYNC_POSTREAD);
326 	f = KERNADDR(p, 0);
327 #ifdef USB_FRAG_DMA_WORKAROUND
328 	f = (void *)((uintptr_t)f - USB_MEM_SMALL);
329 #endif
330 	f->block = p->block;
331 	f->offs = p->offs;
332 #ifdef USB_FRAG_DMA_WORKAROUND
333 	f->offs -= USB_MEM_SMALL;
334 #endif
335 	s = splusb();
336 	LIST_INSERT_HEAD(&usb_frag_freelist, f, next);
337 	splx(s);
338 	DPRINTFN(5, ("usb_freemem: frag=%p\n", f));
339 }
340 
341 void
342 usb_syncmem(usb_dma_t *p, bus_addr_t offset, bus_size_t len, int ops)
343 {
344 	bus_dmamap_sync(p->block->tag, p->block->map, p->offs + offset,
345 	    len, ops);
346 }
347 
348 
349 usbd_status
350 usb_reserve_allocm(struct usb_dma_reserve *rs, usb_dma_t *dma, u_int32_t size)
351 {
352 	int error;
353 	u_long start;
354 	bus_addr_t baddr;
355 
356 	if (rs->vaddr == 0 || size > USB_MEM_RESERVE)
357 		return USBD_NOMEM;
358 
359 	dma->block = malloc(sizeof *dma->block, M_USB, M_ZERO | M_NOWAIT);
360 	if (dma->block == NULL)
361 		return USBD_NOMEM;
362 
363 	error = extent_alloc(rs->extent, size, PAGE_SIZE, 0,
364 	    EX_NOWAIT, &start);
365 
366 	if (error != 0) {
367 		aprint_error_dev(rs->dv,
368 		    "usb_reserve_allocm of size %u failed (error %d)\n",
369 		    size, error);
370 		return USBD_NOMEM;
371 	}
372 
373 	baddr = start;
374 	dma->offs = baddr - rs->paddr;
375 	dma->block->flags = USB_DMA_RESERVE;
376 	dma->block->align = PAGE_SIZE;
377 	dma->block->size = size;
378 	dma->block->nsegs = 1;
379 	/* XXX segs appears to be unused */
380 	dma->block->segs[0] = rs->map->dm_segs[0];
381 	dma->block->map = rs->map;
382 	dma->block->kaddr = rs->vaddr;
383 	dma->block->tag = rs->dtag;
384 
385 	return USBD_NORMAL_COMPLETION;
386 }
387 
388 void
389 usb_reserve_freem(struct usb_dma_reserve *rs, usb_dma_t *dma)
390 {
391 	int error;
392 
393 	error = extent_free(rs->extent,
394 	    (u_long)(rs->paddr + dma->offs), dma->block->size, 0);
395 	free(dma->block, M_USB);
396 }
397 
398 int
399 usb_setup_reserve(device_t dv, struct usb_dma_reserve *rs, bus_dma_tag_t dtag,
400 		  size_t size)
401 {
402 	int error, nseg;
403 	bus_dma_segment_t seg;
404 
405 	rs->dtag = dtag;
406 	rs->size = size;
407 	rs->dv = dv;
408 
409 	error = bus_dmamem_alloc(dtag, USB_MEM_RESERVE, PAGE_SIZE, 0,
410 	    &seg, 1, &nseg, BUS_DMA_NOWAIT);
411 	if (error != 0)
412 		return error;
413 
414 	error = bus_dmamem_map(dtag, &seg, nseg, USB_MEM_RESERVE,
415 	    &rs->vaddr, BUS_DMA_NOWAIT|BUS_DMA_COHERENT);
416 	if (error != 0)
417 		goto freeit;
418 
419 	error = bus_dmamap_create(dtag, USB_MEM_RESERVE, 1,
420 	    USB_MEM_RESERVE, 0, BUS_DMA_NOWAIT, &rs->map);
421 	if (error != 0)
422 		goto unmap;
423 
424 	error = bus_dmamap_load(dtag, rs->map, rs->vaddr, USB_MEM_RESERVE,
425 	    NULL, BUS_DMA_NOWAIT);
426 	if (error != 0)
427 		goto destroy;
428 
429 	rs->paddr = rs->map->dm_segs[0].ds_addr;
430 	rs->extent = extent_create(device_xname(dv), (u_long)rs->paddr,
431 	    (u_long)(rs->paddr + USB_MEM_RESERVE - 1), 0, 0, 0);
432 	if (rs->extent == NULL) {
433 		rs->vaddr = 0;
434 		return ENOMEM;
435 	}
436 
437 	return 0;
438 
439  destroy:
440 	bus_dmamap_destroy(dtag, rs->map);
441  unmap:
442 	bus_dmamem_unmap(dtag, rs->vaddr, size);
443  freeit:
444 	bus_dmamem_free(dtag, &seg, nseg);
445 
446 	rs->vaddr = 0;
447 
448 	return error;
449 }
450