xref: /freebsd/sys/dev/usb/usb_busdma.c (revision 578d0eff90183f6bb6a9c529ca7ef67f01a17662)
102ac6454SAndrew Thompson /* $FreeBSD$ */
202ac6454SAndrew Thompson /*-
302ac6454SAndrew Thompson  * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
402ac6454SAndrew Thompson  *
502ac6454SAndrew Thompson  * Redistribution and use in source and binary forms, with or without
602ac6454SAndrew Thompson  * modification, are permitted provided that the following conditions
702ac6454SAndrew Thompson  * are met:
802ac6454SAndrew Thompson  * 1. Redistributions of source code must retain the above copyright
902ac6454SAndrew Thompson  *    notice, this list of conditions and the following disclaimer.
1002ac6454SAndrew Thompson  * 2. Redistributions in binary form must reproduce the above copyright
1102ac6454SAndrew Thompson  *    notice, this list of conditions and the following disclaimer in the
1202ac6454SAndrew Thompson  *    documentation and/or other materials provided with the distribution.
1302ac6454SAndrew Thompson  *
1402ac6454SAndrew Thompson  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1502ac6454SAndrew Thompson  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1602ac6454SAndrew Thompson  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1702ac6454SAndrew Thompson  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1802ac6454SAndrew Thompson  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1902ac6454SAndrew Thompson  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2002ac6454SAndrew Thompson  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2102ac6454SAndrew Thompson  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2202ac6454SAndrew Thompson  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2302ac6454SAndrew Thompson  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2402ac6454SAndrew Thompson  * SUCH DAMAGE.
2502ac6454SAndrew Thompson  */
2602ac6454SAndrew Thompson 
2702ac6454SAndrew Thompson #include <dev/usb/usb_mfunc.h>
2802ac6454SAndrew Thompson #include <dev/usb/usb_error.h>
2902ac6454SAndrew Thompson #include <dev/usb/usb.h>
3002ac6454SAndrew Thompson 
3102ac6454SAndrew Thompson #define	USB_DEBUG_VAR usb2_debug
3202ac6454SAndrew Thompson 
3302ac6454SAndrew Thompson #include <dev/usb/usb_core.h>
3402ac6454SAndrew Thompson #include <dev/usb/usb_busdma.h>
3502ac6454SAndrew Thompson #include <dev/usb/usb_process.h>
3602ac6454SAndrew Thompson #include <dev/usb/usb_transfer.h>
3702ac6454SAndrew Thompson #include <dev/usb/usb_device.h>
3802ac6454SAndrew Thompson #include <dev/usb/usb_util.h>
3902ac6454SAndrew Thompson #include <dev/usb/usb_debug.h>
4002ac6454SAndrew Thompson 
4102ac6454SAndrew Thompson #include <dev/usb/usb_controller.h>
4202ac6454SAndrew Thompson #include <dev/usb/usb_bus.h>
4302ac6454SAndrew Thompson 
44bdc081c6SAndrew Thompson #if USB_HAVE_BUSDMA
45578d0effSAndrew Thompson static void	usb2_dma_tag_create(struct usb2_dma_tag *, usb2_size_t, usb2_size_t);
4602ac6454SAndrew Thompson static void	usb2_dma_tag_destroy(struct usb2_dma_tag *);
47bdc081c6SAndrew Thompson #endif
4802ac6454SAndrew Thompson 
49bdc081c6SAndrew Thompson #if USB_HAVE_BUSDMA && defined(__FreeBSD__)
5002ac6454SAndrew Thompson static void	usb2_dma_lock_cb(void *, bus_dma_lock_op_t);
5102ac6454SAndrew Thompson static void	usb2_pc_alloc_mem_cb(void *, bus_dma_segment_t *, int, int);
5202ac6454SAndrew Thompson static void	usb2_pc_load_mem_cb(void *, bus_dma_segment_t *, int, int);
5302ac6454SAndrew Thompson static void	usb2_pc_common_mem_cb(void *, bus_dma_segment_t *, int, int,
5402ac6454SAndrew Thompson 		    uint8_t);
5502ac6454SAndrew Thompson #endif
5602ac6454SAndrew Thompson 
57bdc081c6SAndrew Thompson #if USB_HAVE_BUSDMA && defined(__NetBSD__)
5802ac6454SAndrew Thompson static void	usb2_pc_common_mem_cb(struct usb2_page_cache *,
5902ac6454SAndrew Thompson 		    bus_dma_segment_t *, int, int, uint8_t);
6002ac6454SAndrew Thompson #endif
6102ac6454SAndrew Thompson 
6202ac6454SAndrew Thompson /*------------------------------------------------------------------------*
6302ac6454SAndrew Thompson  *  usb2_get_page - lookup DMA-able memory for the given offset
6402ac6454SAndrew Thompson  *
6502ac6454SAndrew Thompson  * NOTE: Only call this function when the "page_cache" structure has
6602ac6454SAndrew Thompson  * been properly initialized !
6702ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
6802ac6454SAndrew Thompson void
69578d0effSAndrew Thompson usb2_get_page(struct usb2_page_cache *pc, usb2_frlength_t offset,
7002ac6454SAndrew Thompson     struct usb2_page_search *res)
7102ac6454SAndrew Thompson {
7202ac6454SAndrew Thompson 	struct usb2_page *page;
7302ac6454SAndrew Thompson 
74bdc081c6SAndrew Thompson #if USB_HAVE_BUSDMA
7502ac6454SAndrew Thompson 	if (pc->page_start) {
7602ac6454SAndrew Thompson 
7702ac6454SAndrew Thompson 		/* Case 1 - something has been loaded into DMA */
7802ac6454SAndrew Thompson 
7902ac6454SAndrew Thompson 		if (pc->buffer) {
8002ac6454SAndrew Thompson 
8102ac6454SAndrew Thompson 			/* Case 1a - Kernel Virtual Address */
8202ac6454SAndrew Thompson 
8302ac6454SAndrew Thompson 			res->buffer = USB_ADD_BYTES(pc->buffer, offset);
8402ac6454SAndrew Thompson 		}
8502ac6454SAndrew Thompson 		offset += pc->page_offset_buf;
8602ac6454SAndrew Thompson 
8702ac6454SAndrew Thompson 		/* compute destination page */
8802ac6454SAndrew Thompson 
8902ac6454SAndrew Thompson 		page = pc->page_start;
9002ac6454SAndrew Thompson 
9102ac6454SAndrew Thompson 		if (pc->ismultiseg) {
9202ac6454SAndrew Thompson 
9302ac6454SAndrew Thompson 			page += (offset / USB_PAGE_SIZE);
9402ac6454SAndrew Thompson 
9502ac6454SAndrew Thompson 			offset %= USB_PAGE_SIZE;
9602ac6454SAndrew Thompson 
9702ac6454SAndrew Thompson 			res->length = USB_PAGE_SIZE - offset;
9802ac6454SAndrew Thompson 			res->physaddr = page->physaddr + offset;
9902ac6454SAndrew Thompson 		} else {
10002ac6454SAndrew Thompson 			res->length = 0 - 1;
10102ac6454SAndrew Thompson 			res->physaddr = page->physaddr + offset;
10202ac6454SAndrew Thompson 		}
10302ac6454SAndrew Thompson 		if (!pc->buffer) {
10402ac6454SAndrew Thompson 
10502ac6454SAndrew Thompson 			/* Case 1b - Non Kernel Virtual Address */
10602ac6454SAndrew Thompson 
10702ac6454SAndrew Thompson 			res->buffer = USB_ADD_BYTES(page->buffer, offset);
10802ac6454SAndrew Thompson 		}
109bdc081c6SAndrew Thompson 		return;
110bdc081c6SAndrew Thompson 	}
111bdc081c6SAndrew Thompson #endif
11202ac6454SAndrew Thompson 	/* Case 2 - Plain PIO */
11302ac6454SAndrew Thompson 
11402ac6454SAndrew Thompson 	res->buffer = USB_ADD_BYTES(pc->buffer, offset);
11502ac6454SAndrew Thompson 	res->length = 0 - 1;
116bdc081c6SAndrew Thompson #if USB_HAVE_BUSDMA
11702ac6454SAndrew Thompson 	res->physaddr = 0;
118bdc081c6SAndrew Thompson #endif
11902ac6454SAndrew Thompson }
12002ac6454SAndrew Thompson 
12102ac6454SAndrew Thompson /*------------------------------------------------------------------------*
12202ac6454SAndrew Thompson  *  usb2_copy_in - copy directly to DMA-able memory
12302ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
12402ac6454SAndrew Thompson void
125578d0effSAndrew Thompson usb2_copy_in(struct usb2_page_cache *cache, usb2_frlength_t offset,
126578d0effSAndrew Thompson     const void *ptr, usb2_frlength_t len)
12702ac6454SAndrew Thompson {
12802ac6454SAndrew Thompson 	struct usb2_page_search buf_res;
12902ac6454SAndrew Thompson 
13002ac6454SAndrew Thompson 	while (len != 0) {
13102ac6454SAndrew Thompson 
13202ac6454SAndrew Thompson 		usb2_get_page(cache, offset, &buf_res);
13302ac6454SAndrew Thompson 
13402ac6454SAndrew Thompson 		if (buf_res.length > len) {
13502ac6454SAndrew Thompson 			buf_res.length = len;
13602ac6454SAndrew Thompson 		}
13702ac6454SAndrew Thompson 		bcopy(ptr, buf_res.buffer, buf_res.length);
13802ac6454SAndrew Thompson 
13902ac6454SAndrew Thompson 		offset += buf_res.length;
14002ac6454SAndrew Thompson 		len -= buf_res.length;
14102ac6454SAndrew Thompson 		ptr = USB_ADD_BYTES(ptr, buf_res.length);
14202ac6454SAndrew Thompson 	}
14302ac6454SAndrew Thompson }
14402ac6454SAndrew Thompson 
14502ac6454SAndrew Thompson /*------------------------------------------------------------------------*
14602ac6454SAndrew Thompson  *  usb2_copy_in_user - copy directly to DMA-able memory from userland
14702ac6454SAndrew Thompson  *
14802ac6454SAndrew Thompson  * Return values:
14902ac6454SAndrew Thompson  *    0: Success
15002ac6454SAndrew Thompson  * Else: Failure
15102ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
152bdc081c6SAndrew Thompson #if USB_HAVE_USER_IO
15302ac6454SAndrew Thompson int
154578d0effSAndrew Thompson usb2_copy_in_user(struct usb2_page_cache *cache, usb2_frlength_t offset,
155578d0effSAndrew Thompson     const void *ptr, usb2_frlength_t len)
15602ac6454SAndrew Thompson {
15702ac6454SAndrew Thompson 	struct usb2_page_search buf_res;
15802ac6454SAndrew Thompson 	int error;
15902ac6454SAndrew Thompson 
16002ac6454SAndrew Thompson 	while (len != 0) {
16102ac6454SAndrew Thompson 
16202ac6454SAndrew Thompson 		usb2_get_page(cache, offset, &buf_res);
16302ac6454SAndrew Thompson 
16402ac6454SAndrew Thompson 		if (buf_res.length > len) {
16502ac6454SAndrew Thompson 			buf_res.length = len;
16602ac6454SAndrew Thompson 		}
16702ac6454SAndrew Thompson 		error = copyin(ptr, buf_res.buffer, buf_res.length);
16802ac6454SAndrew Thompson 		if (error)
16902ac6454SAndrew Thompson 			return (error);
17002ac6454SAndrew Thompson 
17102ac6454SAndrew Thompson 		offset += buf_res.length;
17202ac6454SAndrew Thompson 		len -= buf_res.length;
17302ac6454SAndrew Thompson 		ptr = USB_ADD_BYTES(ptr, buf_res.length);
17402ac6454SAndrew Thompson 	}
17502ac6454SAndrew Thompson 	return (0);			/* success */
17602ac6454SAndrew Thompson }
177bdc081c6SAndrew Thompson #endif
17802ac6454SAndrew Thompson 
17902ac6454SAndrew Thompson /*------------------------------------------------------------------------*
18002ac6454SAndrew Thompson  *  usb2_m_copy_in - copy a mbuf chain directly into DMA-able memory
18102ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
182bdc081c6SAndrew Thompson #if USB_HAVE_MBUF
18302ac6454SAndrew Thompson struct usb2_m_copy_in_arg {
18402ac6454SAndrew Thompson 	struct usb2_page_cache *cache;
185578d0effSAndrew Thompson 	usb2_frlength_t dst_offset;
18602ac6454SAndrew Thompson };
18702ac6454SAndrew Thompson 
188578d0effSAndrew Thompson static int
18902ac6454SAndrew Thompson #ifdef __FreeBSD__
19002ac6454SAndrew Thompson usb2_m_copy_in_cb(void *arg, void *src, uint32_t count)
19102ac6454SAndrew Thompson #else
19202ac6454SAndrew Thompson usb2_m_copy_in_cb(void *arg, caddr_t src, uint32_t count)
19302ac6454SAndrew Thompson #endif
19402ac6454SAndrew Thompson {
19502ac6454SAndrew Thompson 	register struct usb2_m_copy_in_arg *ua = arg;
19602ac6454SAndrew Thompson 
19702ac6454SAndrew Thompson 	usb2_copy_in(ua->cache, ua->dst_offset, src, count);
19802ac6454SAndrew Thompson 	ua->dst_offset += count;
19902ac6454SAndrew Thompson 	return (0);
20002ac6454SAndrew Thompson }
20102ac6454SAndrew Thompson 
20202ac6454SAndrew Thompson void
203578d0effSAndrew Thompson usb2_m_copy_in(struct usb2_page_cache *cache, usb2_frlength_t dst_offset,
204578d0effSAndrew Thompson     struct mbuf *m, usb2_size_t src_offset, usb2_frlength_t src_len)
20502ac6454SAndrew Thompson {
20602ac6454SAndrew Thompson 	struct usb2_m_copy_in_arg arg = {cache, dst_offset};
207578d0effSAndrew Thompson 	int error;
20802ac6454SAndrew Thompson 
20902ac6454SAndrew Thompson 	error = m_apply(m, src_offset, src_len, &usb2_m_copy_in_cb, &arg);
21002ac6454SAndrew Thompson }
211bdc081c6SAndrew Thompson #endif
21202ac6454SAndrew Thompson 
21302ac6454SAndrew Thompson /*------------------------------------------------------------------------*
21402ac6454SAndrew Thompson  *  usb2_uiomove - factored out code
21502ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
216bdc081c6SAndrew Thompson #if USB_HAVE_USER_IO
21702ac6454SAndrew Thompson int
21802ac6454SAndrew Thompson usb2_uiomove(struct usb2_page_cache *pc, struct uio *uio,
219578d0effSAndrew Thompson     usb2_frlength_t pc_offset, usb2_frlength_t len)
22002ac6454SAndrew Thompson {
22102ac6454SAndrew Thompson 	struct usb2_page_search res;
22202ac6454SAndrew Thompson 	int error = 0;
22302ac6454SAndrew Thompson 
22402ac6454SAndrew Thompson 	while (len != 0) {
22502ac6454SAndrew Thompson 
22602ac6454SAndrew Thompson 		usb2_get_page(pc, pc_offset, &res);
22702ac6454SAndrew Thompson 
22802ac6454SAndrew Thompson 		if (res.length > len) {
22902ac6454SAndrew Thompson 			res.length = len;
23002ac6454SAndrew Thompson 		}
23102ac6454SAndrew Thompson 		/*
23202ac6454SAndrew Thompson 		 * "uiomove()" can sleep so one needs to make a wrapper,
23302ac6454SAndrew Thompson 		 * exiting the mutex and checking things
23402ac6454SAndrew Thompson 		 */
23502ac6454SAndrew Thompson 		error = uiomove(res.buffer, res.length, uio);
23602ac6454SAndrew Thompson 
23702ac6454SAndrew Thompson 		if (error) {
23802ac6454SAndrew Thompson 			break;
23902ac6454SAndrew Thompson 		}
24002ac6454SAndrew Thompson 		pc_offset += res.length;
24102ac6454SAndrew Thompson 		len -= res.length;
24202ac6454SAndrew Thompson 	}
24302ac6454SAndrew Thompson 	return (error);
24402ac6454SAndrew Thompson }
245bdc081c6SAndrew Thompson #endif
24602ac6454SAndrew Thompson 
24702ac6454SAndrew Thompson /*------------------------------------------------------------------------*
24802ac6454SAndrew Thompson  *  usb2_copy_out - copy directly from DMA-able memory
24902ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
25002ac6454SAndrew Thompson void
251578d0effSAndrew Thompson usb2_copy_out(struct usb2_page_cache *cache, usb2_frlength_t offset,
252578d0effSAndrew Thompson     void *ptr, usb2_frlength_t len)
25302ac6454SAndrew Thompson {
25402ac6454SAndrew Thompson 	struct usb2_page_search res;
25502ac6454SAndrew Thompson 
25602ac6454SAndrew Thompson 	while (len != 0) {
25702ac6454SAndrew Thompson 
25802ac6454SAndrew Thompson 		usb2_get_page(cache, offset, &res);
25902ac6454SAndrew Thompson 
26002ac6454SAndrew Thompson 		if (res.length > len) {
26102ac6454SAndrew Thompson 			res.length = len;
26202ac6454SAndrew Thompson 		}
26302ac6454SAndrew Thompson 		bcopy(res.buffer, ptr, res.length);
26402ac6454SAndrew Thompson 
26502ac6454SAndrew Thompson 		offset += res.length;
26602ac6454SAndrew Thompson 		len -= res.length;
26702ac6454SAndrew Thompson 		ptr = USB_ADD_BYTES(ptr, res.length);
26802ac6454SAndrew Thompson 	}
26902ac6454SAndrew Thompson }
27002ac6454SAndrew Thompson 
27102ac6454SAndrew Thompson /*------------------------------------------------------------------------*
27202ac6454SAndrew Thompson  *  usb2_copy_out_user - copy directly from DMA-able memory to userland
27302ac6454SAndrew Thompson  *
27402ac6454SAndrew Thompson  * Return values:
27502ac6454SAndrew Thompson  *    0: Success
27602ac6454SAndrew Thompson  * Else: Failure
27702ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
278bdc081c6SAndrew Thompson #if USB_HAVE_USER_IO
27902ac6454SAndrew Thompson int
280578d0effSAndrew Thompson usb2_copy_out_user(struct usb2_page_cache *cache, usb2_frlength_t offset,
281578d0effSAndrew Thompson     void *ptr, usb2_frlength_t len)
28202ac6454SAndrew Thompson {
28302ac6454SAndrew Thompson 	struct usb2_page_search res;
28402ac6454SAndrew Thompson 	int error;
28502ac6454SAndrew Thompson 
28602ac6454SAndrew Thompson 	while (len != 0) {
28702ac6454SAndrew Thompson 
28802ac6454SAndrew Thompson 		usb2_get_page(cache, offset, &res);
28902ac6454SAndrew Thompson 
29002ac6454SAndrew Thompson 		if (res.length > len) {
29102ac6454SAndrew Thompson 			res.length = len;
29202ac6454SAndrew Thompson 		}
29302ac6454SAndrew Thompson 		error = copyout(res.buffer, ptr, res.length);
29402ac6454SAndrew Thompson 		if (error)
29502ac6454SAndrew Thompson 			return (error);
29602ac6454SAndrew Thompson 
29702ac6454SAndrew Thompson 		offset += res.length;
29802ac6454SAndrew Thompson 		len -= res.length;
29902ac6454SAndrew Thompson 		ptr = USB_ADD_BYTES(ptr, res.length);
30002ac6454SAndrew Thompson 	}
30102ac6454SAndrew Thompson 	return (0);			/* success */
30202ac6454SAndrew Thompson }
303bdc081c6SAndrew Thompson #endif
30402ac6454SAndrew Thompson 
30502ac6454SAndrew Thompson /*------------------------------------------------------------------------*
30602ac6454SAndrew Thompson  *  usb2_bzero - zero DMA-able memory
30702ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
30802ac6454SAndrew Thompson void
309578d0effSAndrew Thompson usb2_bzero(struct usb2_page_cache *cache, usb2_frlength_t offset,
310578d0effSAndrew Thompson     usb2_frlength_t len)
31102ac6454SAndrew Thompson {
31202ac6454SAndrew Thompson 	struct usb2_page_search res;
31302ac6454SAndrew Thompson 
31402ac6454SAndrew Thompson 	while (len != 0) {
31502ac6454SAndrew Thompson 
31602ac6454SAndrew Thompson 		usb2_get_page(cache, offset, &res);
31702ac6454SAndrew Thompson 
31802ac6454SAndrew Thompson 		if (res.length > len) {
31902ac6454SAndrew Thompson 			res.length = len;
32002ac6454SAndrew Thompson 		}
32102ac6454SAndrew Thompson 		bzero(res.buffer, res.length);
32202ac6454SAndrew Thompson 
32302ac6454SAndrew Thompson 		offset += res.length;
32402ac6454SAndrew Thompson 		len -= res.length;
32502ac6454SAndrew Thompson 	}
32602ac6454SAndrew Thompson }
32702ac6454SAndrew Thompson 
328bdc081c6SAndrew Thompson #if USB_HAVE_BUSDMA && defined(__FreeBSD__)
32902ac6454SAndrew Thompson 
33002ac6454SAndrew Thompson /*------------------------------------------------------------------------*
33102ac6454SAndrew Thompson  *	usb2_dma_lock_cb - dummy callback
33202ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
33302ac6454SAndrew Thompson static void
33402ac6454SAndrew Thompson usb2_dma_lock_cb(void *arg, bus_dma_lock_op_t op)
33502ac6454SAndrew Thompson {
33602ac6454SAndrew Thompson 	/* we use "mtx_owned()" instead of this function */
33702ac6454SAndrew Thompson }
33802ac6454SAndrew Thompson 
33902ac6454SAndrew Thompson /*------------------------------------------------------------------------*
34002ac6454SAndrew Thompson  *	usb2_dma_tag_create - allocate a DMA tag
34102ac6454SAndrew Thompson  *
34202ac6454SAndrew Thompson  * NOTE: If the "align" parameter has a value of 1 the DMA-tag will
34302ac6454SAndrew Thompson  * allow multi-segment mappings. Else all mappings are single-segment.
34402ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
34502ac6454SAndrew Thompson static void
34602ac6454SAndrew Thompson usb2_dma_tag_create(struct usb2_dma_tag *udt,
347578d0effSAndrew Thompson     usb2_size_t size, usb2_size_t align)
34802ac6454SAndrew Thompson {
34902ac6454SAndrew Thompson 	bus_dma_tag_t tag;
35002ac6454SAndrew Thompson 
35102ac6454SAndrew Thompson 	if (bus_dma_tag_create
35202ac6454SAndrew Thompson 	    ( /* parent    */ udt->tag_parent->tag,
35302ac6454SAndrew Thompson 	     /* alignment */ align,
35402ac6454SAndrew Thompson 	     /* boundary  */ USB_PAGE_SIZE,
35502ac6454SAndrew Thompson 	     /* lowaddr   */ (2ULL << (udt->tag_parent->dma_bits - 1)) - 1,
35602ac6454SAndrew Thompson 	     /* highaddr  */ BUS_SPACE_MAXADDR,
35702ac6454SAndrew Thompson 	     /* filter    */ NULL,
35802ac6454SAndrew Thompson 	     /* filterarg */ NULL,
35902ac6454SAndrew Thompson 	     /* maxsize   */ size,
36002ac6454SAndrew Thompson 	     /* nsegments */ (align == 1) ?
36102ac6454SAndrew Thompson 	    (2 + (size / USB_PAGE_SIZE)) : 1,
36202ac6454SAndrew Thompson 	     /* maxsegsz  */ (align == 1) ?
36302ac6454SAndrew Thompson 	    USB_PAGE_SIZE : size,
36402ac6454SAndrew Thompson 	     /* flags     */ BUS_DMA_KEEP_PG_OFFSET,
36502ac6454SAndrew Thompson 	     /* lockfn    */ &usb2_dma_lock_cb,
36602ac6454SAndrew Thompson 	     /* lockarg   */ NULL,
36702ac6454SAndrew Thompson 	    &tag)) {
36802ac6454SAndrew Thompson 		tag = NULL;
36902ac6454SAndrew Thompson 	}
37002ac6454SAndrew Thompson 	udt->tag = tag;
37102ac6454SAndrew Thompson }
37202ac6454SAndrew Thompson 
37302ac6454SAndrew Thompson /*------------------------------------------------------------------------*
37402ac6454SAndrew Thompson  *	usb2_dma_tag_free - free a DMA tag
37502ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
37602ac6454SAndrew Thompson static void
37702ac6454SAndrew Thompson usb2_dma_tag_destroy(struct usb2_dma_tag *udt)
37802ac6454SAndrew Thompson {
37902ac6454SAndrew Thompson 	bus_dma_tag_destroy(udt->tag);
38002ac6454SAndrew Thompson }
38102ac6454SAndrew Thompson 
38202ac6454SAndrew Thompson /*------------------------------------------------------------------------*
38302ac6454SAndrew Thompson  *	usb2_pc_alloc_mem_cb - BUS-DMA callback function
38402ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
38502ac6454SAndrew Thompson static void
38602ac6454SAndrew Thompson usb2_pc_alloc_mem_cb(void *arg, bus_dma_segment_t *segs,
38702ac6454SAndrew Thompson     int nseg, int error)
38802ac6454SAndrew Thompson {
38902ac6454SAndrew Thompson 	usb2_pc_common_mem_cb(arg, segs, nseg, error, 0);
39002ac6454SAndrew Thompson }
39102ac6454SAndrew Thompson 
39202ac6454SAndrew Thompson /*------------------------------------------------------------------------*
39302ac6454SAndrew Thompson  *	usb2_pc_load_mem_cb - BUS-DMA callback function
39402ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
39502ac6454SAndrew Thompson static void
39602ac6454SAndrew Thompson usb2_pc_load_mem_cb(void *arg, bus_dma_segment_t *segs,
39702ac6454SAndrew Thompson     int nseg, int error)
39802ac6454SAndrew Thompson {
39902ac6454SAndrew Thompson 	usb2_pc_common_mem_cb(arg, segs, nseg, error, 1);
40002ac6454SAndrew Thompson }
40102ac6454SAndrew Thompson 
40202ac6454SAndrew Thompson /*------------------------------------------------------------------------*
40302ac6454SAndrew Thompson  *	usb2_pc_common_mem_cb - BUS-DMA callback function
40402ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
40502ac6454SAndrew Thompson static void
40602ac6454SAndrew Thompson usb2_pc_common_mem_cb(void *arg, bus_dma_segment_t *segs,
40702ac6454SAndrew Thompson     int nseg, int error, uint8_t isload)
40802ac6454SAndrew Thompson {
40902ac6454SAndrew Thompson 	struct usb2_dma_parent_tag *uptag;
41002ac6454SAndrew Thompson 	struct usb2_page_cache *pc;
41102ac6454SAndrew Thompson 	struct usb2_page *pg;
412578d0effSAndrew Thompson 	usb2_size_t rem;
41302ac6454SAndrew Thompson 	uint8_t owned;
41402ac6454SAndrew Thompson 
41502ac6454SAndrew Thompson 	pc = arg;
41602ac6454SAndrew Thompson 	uptag = pc->tag_parent;
41702ac6454SAndrew Thompson 
41802ac6454SAndrew Thompson 	/*
41902ac6454SAndrew Thompson 	 * XXX There is sometimes recursive locking here.
42002ac6454SAndrew Thompson 	 * XXX We should try to find a better solution.
42102ac6454SAndrew Thompson 	 * XXX Until further the "owned" variable does
42202ac6454SAndrew Thompson 	 * XXX the trick.
42302ac6454SAndrew Thompson 	 */
42402ac6454SAndrew Thompson 
42502ac6454SAndrew Thompson 	if (error) {
42602ac6454SAndrew Thompson 		goto done;
42702ac6454SAndrew Thompson 	}
42802ac6454SAndrew Thompson 	pg = pc->page_start;
42902ac6454SAndrew Thompson 	pg->physaddr = segs->ds_addr & ~(USB_PAGE_SIZE - 1);
43002ac6454SAndrew Thompson 	rem = segs->ds_addr & (USB_PAGE_SIZE - 1);
43102ac6454SAndrew Thompson 	pc->page_offset_buf = rem;
43202ac6454SAndrew Thompson 	pc->page_offset_end += rem;
43302ac6454SAndrew Thompson 	nseg--;
43402ac6454SAndrew Thompson #if (USB_DEBUG != 0)
43502ac6454SAndrew Thompson 	if (rem != (USB_P2U(pc->buffer) & (USB_PAGE_SIZE - 1))) {
43602ac6454SAndrew Thompson 		/*
43702ac6454SAndrew Thompson 		 * This check verifies that the physical address is correct:
43802ac6454SAndrew Thompson 		 */
43902ac6454SAndrew Thompson 		DPRINTFN(0, "Page offset was not preserved!\n");
44002ac6454SAndrew Thompson 		error = 1;
44102ac6454SAndrew Thompson 		goto done;
44202ac6454SAndrew Thompson 	}
44302ac6454SAndrew Thompson #endif
44402ac6454SAndrew Thompson 	while (nseg > 0) {
44502ac6454SAndrew Thompson 		nseg--;
44602ac6454SAndrew Thompson 		segs++;
44702ac6454SAndrew Thompson 		pg++;
44802ac6454SAndrew Thompson 		pg->physaddr = segs->ds_addr & ~(USB_PAGE_SIZE - 1);
44902ac6454SAndrew Thompson 	}
45002ac6454SAndrew Thompson 
45102ac6454SAndrew Thompson done:
45202ac6454SAndrew Thompson 	owned = mtx_owned(uptag->mtx);
45302ac6454SAndrew Thompson 	if (!owned)
45402ac6454SAndrew Thompson 		mtx_lock(uptag->mtx);
45502ac6454SAndrew Thompson 
45602ac6454SAndrew Thompson 	uptag->dma_error = (error ? 1 : 0);
45702ac6454SAndrew Thompson 	if (isload) {
45802ac6454SAndrew Thompson 		(uptag->func) (uptag);
45902ac6454SAndrew Thompson 	} else {
46002ac6454SAndrew Thompson 		usb2_cv_broadcast(uptag->cv);
46102ac6454SAndrew Thompson 	}
46202ac6454SAndrew Thompson 	if (!owned)
46302ac6454SAndrew Thompson 		mtx_unlock(uptag->mtx);
46402ac6454SAndrew Thompson }
46502ac6454SAndrew Thompson 
46602ac6454SAndrew Thompson /*------------------------------------------------------------------------*
46702ac6454SAndrew Thompson  *	usb2_pc_alloc_mem - allocate DMA'able memory
46802ac6454SAndrew Thompson  *
46902ac6454SAndrew Thompson  * Returns:
47002ac6454SAndrew Thompson  *    0: Success
47102ac6454SAndrew Thompson  * Else: Failure
47202ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
47302ac6454SAndrew Thompson uint8_t
47402ac6454SAndrew Thompson usb2_pc_alloc_mem(struct usb2_page_cache *pc, struct usb2_page *pg,
475578d0effSAndrew Thompson     usb2_size_t size, usb2_size_t align)
47602ac6454SAndrew Thompson {
47702ac6454SAndrew Thompson 	struct usb2_dma_parent_tag *uptag;
47802ac6454SAndrew Thompson 	struct usb2_dma_tag *utag;
47902ac6454SAndrew Thompson 	bus_dmamap_t map;
48002ac6454SAndrew Thompson 	void *ptr;
48102ac6454SAndrew Thompson 	int err;
48202ac6454SAndrew Thompson 
48302ac6454SAndrew Thompson 	uptag = pc->tag_parent;
48402ac6454SAndrew Thompson 
48502ac6454SAndrew Thompson 	if (align != 1) {
48602ac6454SAndrew Thompson 		/*
48702ac6454SAndrew Thompson 	         * The alignment must be greater or equal to the
48802ac6454SAndrew Thompson 	         * "size" else the object can be split between two
48902ac6454SAndrew Thompson 	         * memory pages and we get a problem!
49002ac6454SAndrew Thompson 	         */
49102ac6454SAndrew Thompson 		while (align < size) {
49202ac6454SAndrew Thompson 			align *= 2;
49302ac6454SAndrew Thompson 			if (align == 0) {
49402ac6454SAndrew Thompson 				goto error;
49502ac6454SAndrew Thompson 			}
49602ac6454SAndrew Thompson 		}
49702ac6454SAndrew Thompson #if 1
49802ac6454SAndrew Thompson 		/*
49902ac6454SAndrew Thompson 		 * XXX BUS-DMA workaround - FIXME later:
50002ac6454SAndrew Thompson 		 *
50102ac6454SAndrew Thompson 		 * We assume that that the aligment at this point of
50202ac6454SAndrew Thompson 		 * the code is greater than or equal to the size and
50302ac6454SAndrew Thompson 		 * less than two times the size, so that if we double
50402ac6454SAndrew Thompson 		 * the size, the size will be greater than the
50502ac6454SAndrew Thompson 		 * alignment.
50602ac6454SAndrew Thompson 		 *
50702ac6454SAndrew Thompson 		 * The bus-dma system has a check for "alignment"
50802ac6454SAndrew Thompson 		 * being less than "size". If that check fails we end
50902ac6454SAndrew Thompson 		 * up using contigmalloc which is page based even for
51002ac6454SAndrew Thompson 		 * small allocations. Try to avoid that to save
51102ac6454SAndrew Thompson 		 * memory, hence we sometimes to a large number of
51202ac6454SAndrew Thompson 		 * small allocations!
51302ac6454SAndrew Thompson 		 */
51402ac6454SAndrew Thompson 		if (size <= (USB_PAGE_SIZE / 2)) {
51502ac6454SAndrew Thompson 			size *= 2;
51602ac6454SAndrew Thompson 		}
51702ac6454SAndrew Thompson #endif
51802ac6454SAndrew Thompson 	}
51902ac6454SAndrew Thompson 	/* get the correct DMA tag */
52002ac6454SAndrew Thompson 	utag = usb2_dma_tag_find(uptag, size, align);
52102ac6454SAndrew Thompson 	if (utag == NULL) {
52202ac6454SAndrew Thompson 		goto error;
52302ac6454SAndrew Thompson 	}
52402ac6454SAndrew Thompson 	/* allocate memory */
52502ac6454SAndrew Thompson 	if (bus_dmamem_alloc(
52602ac6454SAndrew Thompson 	    utag->tag, &ptr, (BUS_DMA_WAITOK | BUS_DMA_COHERENT), &map)) {
52702ac6454SAndrew Thompson 		goto error;
52802ac6454SAndrew Thompson 	}
52902ac6454SAndrew Thompson 	/* setup page cache */
53002ac6454SAndrew Thompson 	pc->buffer = ptr;
53102ac6454SAndrew Thompson 	pc->page_start = pg;
53202ac6454SAndrew Thompson 	pc->page_offset_buf = 0;
53302ac6454SAndrew Thompson 	pc->page_offset_end = size;
53402ac6454SAndrew Thompson 	pc->map = map;
53502ac6454SAndrew Thompson 	pc->tag = utag->tag;
53602ac6454SAndrew Thompson 	pc->ismultiseg = (align == 1);
53702ac6454SAndrew Thompson 
53802ac6454SAndrew Thompson 	mtx_lock(uptag->mtx);
53902ac6454SAndrew Thompson 
54002ac6454SAndrew Thompson 	/* load memory into DMA */
54102ac6454SAndrew Thompson 	err = bus_dmamap_load(
54202ac6454SAndrew Thompson 	    utag->tag, map, ptr, size, &usb2_pc_alloc_mem_cb,
54302ac6454SAndrew Thompson 	    pc, (BUS_DMA_WAITOK | BUS_DMA_COHERENT));
54402ac6454SAndrew Thompson 
54502ac6454SAndrew Thompson 	if (err == EINPROGRESS) {
54602ac6454SAndrew Thompson 		usb2_cv_wait(uptag->cv, uptag->mtx);
54702ac6454SAndrew Thompson 		err = 0;
54802ac6454SAndrew Thompson 	}
54902ac6454SAndrew Thompson 	mtx_unlock(uptag->mtx);
55002ac6454SAndrew Thompson 
55102ac6454SAndrew Thompson 	if (err || uptag->dma_error) {
55202ac6454SAndrew Thompson 		bus_dmamem_free(utag->tag, ptr, map);
55302ac6454SAndrew Thompson 		goto error;
55402ac6454SAndrew Thompson 	}
55502ac6454SAndrew Thompson 	bzero(ptr, size);
55602ac6454SAndrew Thompson 
55702ac6454SAndrew Thompson 	usb2_pc_cpu_flush(pc);
55802ac6454SAndrew Thompson 
55902ac6454SAndrew Thompson 	return (0);
56002ac6454SAndrew Thompson 
56102ac6454SAndrew Thompson error:
56202ac6454SAndrew Thompson 	/* reset most of the page cache */
56302ac6454SAndrew Thompson 	pc->buffer = NULL;
56402ac6454SAndrew Thompson 	pc->page_start = NULL;
56502ac6454SAndrew Thompson 	pc->page_offset_buf = 0;
56602ac6454SAndrew Thompson 	pc->page_offset_end = 0;
56702ac6454SAndrew Thompson 	pc->map = NULL;
56802ac6454SAndrew Thompson 	pc->tag = NULL;
56902ac6454SAndrew Thompson 	return (1);
57002ac6454SAndrew Thompson }
57102ac6454SAndrew Thompson 
57202ac6454SAndrew Thompson /*------------------------------------------------------------------------*
57302ac6454SAndrew Thompson  *	usb2_pc_free_mem - free DMA memory
57402ac6454SAndrew Thompson  *
57502ac6454SAndrew Thompson  * This function is NULL safe.
57602ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
57702ac6454SAndrew Thompson void
57802ac6454SAndrew Thompson usb2_pc_free_mem(struct usb2_page_cache *pc)
57902ac6454SAndrew Thompson {
58002ac6454SAndrew Thompson 	if (pc && pc->buffer) {
58102ac6454SAndrew Thompson 
58202ac6454SAndrew Thompson 		bus_dmamap_unload(pc->tag, pc->map);
58302ac6454SAndrew Thompson 
58402ac6454SAndrew Thompson 		bus_dmamem_free(pc->tag, pc->buffer, pc->map);
58502ac6454SAndrew Thompson 
58602ac6454SAndrew Thompson 		pc->buffer = NULL;
58702ac6454SAndrew Thompson 	}
58802ac6454SAndrew Thompson }
58902ac6454SAndrew Thompson 
59002ac6454SAndrew Thompson /*------------------------------------------------------------------------*
59102ac6454SAndrew Thompson  *	usb2_pc_load_mem - load virtual memory into DMA
59202ac6454SAndrew Thompson  *
59302ac6454SAndrew Thompson  * Return values:
59402ac6454SAndrew Thompson  * 0: Success
59502ac6454SAndrew Thompson  * Else: Error
59602ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
59702ac6454SAndrew Thompson uint8_t
598578d0effSAndrew Thompson usb2_pc_load_mem(struct usb2_page_cache *pc, usb2_size_t size, uint8_t sync)
59902ac6454SAndrew Thompson {
60002ac6454SAndrew Thompson 	/* setup page cache */
60102ac6454SAndrew Thompson 	pc->page_offset_buf = 0;
60202ac6454SAndrew Thompson 	pc->page_offset_end = size;
60302ac6454SAndrew Thompson 	pc->ismultiseg = 1;
60402ac6454SAndrew Thompson 
60502ac6454SAndrew Thompson 	mtx_assert(pc->tag_parent->mtx, MA_OWNED);
60602ac6454SAndrew Thompson 
60702ac6454SAndrew Thompson 	if (size > 0) {
60802ac6454SAndrew Thompson 		if (sync) {
60902ac6454SAndrew Thompson 			struct usb2_dma_parent_tag *uptag;
61002ac6454SAndrew Thompson 			int err;
61102ac6454SAndrew Thompson 
61202ac6454SAndrew Thompson 			uptag = pc->tag_parent;
61302ac6454SAndrew Thompson 
61402ac6454SAndrew Thompson 			/*
61502ac6454SAndrew Thompson 			 * We have to unload the previous loaded DMA
61602ac6454SAndrew Thompson 			 * pages before trying to load a new one!
61702ac6454SAndrew Thompson 			 */
61802ac6454SAndrew Thompson 			bus_dmamap_unload(pc->tag, pc->map);
61902ac6454SAndrew Thompson 
62002ac6454SAndrew Thompson 			/*
62102ac6454SAndrew Thompson 			 * Try to load memory into DMA.
62202ac6454SAndrew Thompson 			 */
62302ac6454SAndrew Thompson 			err = bus_dmamap_load(
62402ac6454SAndrew Thompson 			    pc->tag, pc->map, pc->buffer, size,
62502ac6454SAndrew Thompson 			    &usb2_pc_alloc_mem_cb, pc, BUS_DMA_WAITOK);
62602ac6454SAndrew Thompson 			if (err == EINPROGRESS) {
62702ac6454SAndrew Thompson 				usb2_cv_wait(uptag->cv, uptag->mtx);
62802ac6454SAndrew Thompson 				err = 0;
62902ac6454SAndrew Thompson 			}
63002ac6454SAndrew Thompson 			if (err || uptag->dma_error) {
63102ac6454SAndrew Thompson 				return (1);
63202ac6454SAndrew Thompson 			}
63302ac6454SAndrew Thompson 		} else {
63402ac6454SAndrew Thompson 
63502ac6454SAndrew Thompson 			/*
63602ac6454SAndrew Thompson 			 * We have to unload the previous loaded DMA
63702ac6454SAndrew Thompson 			 * pages before trying to load a new one!
63802ac6454SAndrew Thompson 			 */
63902ac6454SAndrew Thompson 			bus_dmamap_unload(pc->tag, pc->map);
64002ac6454SAndrew Thompson 
64102ac6454SAndrew Thompson 			/*
64202ac6454SAndrew Thompson 			 * Try to load memory into DMA. The callback
64302ac6454SAndrew Thompson 			 * will be called in all cases:
64402ac6454SAndrew Thompson 			 */
64502ac6454SAndrew Thompson 			if (bus_dmamap_load(
64602ac6454SAndrew Thompson 			    pc->tag, pc->map, pc->buffer, size,
64702ac6454SAndrew Thompson 			    &usb2_pc_load_mem_cb, pc, BUS_DMA_WAITOK)) {
64802ac6454SAndrew Thompson 			}
64902ac6454SAndrew Thompson 		}
65002ac6454SAndrew Thompson 	} else {
65102ac6454SAndrew Thompson 		if (!sync) {
65202ac6454SAndrew Thompson 			/*
65302ac6454SAndrew Thompson 			 * Call callback so that refcount is decremented
65402ac6454SAndrew Thompson 			 * properly:
65502ac6454SAndrew Thompson 			 */
65602ac6454SAndrew Thompson 			pc->tag_parent->dma_error = 0;
65702ac6454SAndrew Thompson 			(pc->tag_parent->func) (pc->tag_parent);
65802ac6454SAndrew Thompson 		}
65902ac6454SAndrew Thompson 	}
66002ac6454SAndrew Thompson 	return (0);
66102ac6454SAndrew Thompson }
66202ac6454SAndrew Thompson 
66302ac6454SAndrew Thompson /*------------------------------------------------------------------------*
66402ac6454SAndrew Thompson  *	usb2_pc_cpu_invalidate - invalidate CPU cache
66502ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
66602ac6454SAndrew Thompson void
66702ac6454SAndrew Thompson usb2_pc_cpu_invalidate(struct usb2_page_cache *pc)
66802ac6454SAndrew Thompson {
66902ac6454SAndrew Thompson 	if (pc->page_offset_end == pc->page_offset_buf) {
67002ac6454SAndrew Thompson 		/* nothing has been loaded into this page cache! */
67102ac6454SAndrew Thompson 		return;
67202ac6454SAndrew Thompson 	}
67302ac6454SAndrew Thompson 	bus_dmamap_sync(pc->tag, pc->map,
67402ac6454SAndrew Thompson 	    BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_POSTREAD);
67502ac6454SAndrew Thompson }
67602ac6454SAndrew Thompson 
67702ac6454SAndrew Thompson /*------------------------------------------------------------------------*
67802ac6454SAndrew Thompson  *	usb2_pc_cpu_flush - flush CPU cache
67902ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
68002ac6454SAndrew Thompson void
68102ac6454SAndrew Thompson usb2_pc_cpu_flush(struct usb2_page_cache *pc)
68202ac6454SAndrew Thompson {
68302ac6454SAndrew Thompson 	if (pc->page_offset_end == pc->page_offset_buf) {
68402ac6454SAndrew Thompson 		/* nothing has been loaded into this page cache! */
68502ac6454SAndrew Thompson 		return;
68602ac6454SAndrew Thompson 	}
68702ac6454SAndrew Thompson 	bus_dmamap_sync(pc->tag, pc->map,
68802ac6454SAndrew Thompson 	    BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD);
68902ac6454SAndrew Thompson }
69002ac6454SAndrew Thompson 
69102ac6454SAndrew Thompson /*------------------------------------------------------------------------*
69202ac6454SAndrew Thompson  *	usb2_pc_dmamap_create - create a DMA map
69302ac6454SAndrew Thompson  *
69402ac6454SAndrew Thompson  * Returns:
69502ac6454SAndrew Thompson  *    0: Success
69602ac6454SAndrew Thompson  * Else: Failure
69702ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
69802ac6454SAndrew Thompson uint8_t
699578d0effSAndrew Thompson usb2_pc_dmamap_create(struct usb2_page_cache *pc, usb2_size_t size)
70002ac6454SAndrew Thompson {
70102ac6454SAndrew Thompson 	struct usb2_xfer_root *info;
70202ac6454SAndrew Thompson 	struct usb2_dma_tag *utag;
70302ac6454SAndrew Thompson 
70402ac6454SAndrew Thompson 	/* get info */
705bdc081c6SAndrew Thompson 	info = USB_DMATAG_TO_XROOT(pc->tag_parent);
70602ac6454SAndrew Thompson 
70702ac6454SAndrew Thompson 	/* sanity check */
70802ac6454SAndrew Thompson 	if (info == NULL) {
70902ac6454SAndrew Thompson 		goto error;
71002ac6454SAndrew Thompson 	}
71102ac6454SAndrew Thompson 	utag = usb2_dma_tag_find(pc->tag_parent, size, 1);
71202ac6454SAndrew Thompson 	if (utag == NULL) {
71302ac6454SAndrew Thompson 		goto error;
71402ac6454SAndrew Thompson 	}
71502ac6454SAndrew Thompson 	/* create DMA map */
71602ac6454SAndrew Thompson 	if (bus_dmamap_create(utag->tag, 0, &pc->map)) {
71702ac6454SAndrew Thompson 		goto error;
71802ac6454SAndrew Thompson 	}
71902ac6454SAndrew Thompson 	pc->tag = utag->tag;
72002ac6454SAndrew Thompson 	return 0;			/* success */
72102ac6454SAndrew Thompson 
72202ac6454SAndrew Thompson error:
72302ac6454SAndrew Thompson 	pc->map = NULL;
72402ac6454SAndrew Thompson 	pc->tag = NULL;
72502ac6454SAndrew Thompson 	return 1;			/* failure */
72602ac6454SAndrew Thompson }
72702ac6454SAndrew Thompson 
72802ac6454SAndrew Thompson /*------------------------------------------------------------------------*
72902ac6454SAndrew Thompson  *	usb2_pc_dmamap_destroy
73002ac6454SAndrew Thompson  *
73102ac6454SAndrew Thompson  * This function is NULL safe.
73202ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
73302ac6454SAndrew Thompson void
73402ac6454SAndrew Thompson usb2_pc_dmamap_destroy(struct usb2_page_cache *pc)
73502ac6454SAndrew Thompson {
73602ac6454SAndrew Thompson 	if (pc && pc->tag) {
73702ac6454SAndrew Thompson 		bus_dmamap_destroy(pc->tag, pc->map);
73802ac6454SAndrew Thompson 		pc->tag = NULL;
73902ac6454SAndrew Thompson 		pc->map = NULL;
74002ac6454SAndrew Thompson 	}
74102ac6454SAndrew Thompson }
74202ac6454SAndrew Thompson 
74302ac6454SAndrew Thompson #endif
74402ac6454SAndrew Thompson 
745bdc081c6SAndrew Thompson #if USB_HAVE_BUSDMA && defined(__NetBSD__)
74602ac6454SAndrew Thompson 
74702ac6454SAndrew Thompson /*------------------------------------------------------------------------*
74802ac6454SAndrew Thompson  *	usb2_dma_tag_create - allocate a DMA tag
74902ac6454SAndrew Thompson  *
75002ac6454SAndrew Thompson  * NOTE: If the "align" parameter has a value of 1 the DMA-tag will
75102ac6454SAndrew Thompson  * allow multi-segment mappings. Else all mappings are single-segment.
75202ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
75302ac6454SAndrew Thompson static void
75402ac6454SAndrew Thompson usb2_dma_tag_create(struct usb2_dma_tag *udt,
755578d0effSAndrew Thompson     usb2_size_t size, usb2_size_t align)
75602ac6454SAndrew Thompson {
757578d0effSAndrew Thompson 	usb2_size_t nseg;
75802ac6454SAndrew Thompson 
75902ac6454SAndrew Thompson 	if (align == 1) {
76002ac6454SAndrew Thompson 		nseg = (2 + (size / USB_PAGE_SIZE));
76102ac6454SAndrew Thompson 	} else {
76202ac6454SAndrew Thompson 		nseg = 1;
76302ac6454SAndrew Thompson 	}
76402ac6454SAndrew Thompson 
76502ac6454SAndrew Thompson 	udt->p_seg = malloc(nseg * sizeof(*(udt->p_seg)),
76602ac6454SAndrew Thompson 	    M_USB, M_WAITOK | M_ZERO);
76702ac6454SAndrew Thompson 
76802ac6454SAndrew Thompson 	if (udt->p_seg == NULL) {
76902ac6454SAndrew Thompson 		return;
77002ac6454SAndrew Thompson 	}
77102ac6454SAndrew Thompson 	udt->tag = udt->tag_parent->tag;
77202ac6454SAndrew Thompson 	udt->n_seg = nseg;
77302ac6454SAndrew Thompson }
77402ac6454SAndrew Thompson 
77502ac6454SAndrew Thompson /*------------------------------------------------------------------------*
77602ac6454SAndrew Thompson  *	usb2_dma_tag_free - free a DMA tag
77702ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
77802ac6454SAndrew Thompson static void
77902ac6454SAndrew Thompson usb2_dma_tag_destroy(struct usb2_dma_tag *udt)
78002ac6454SAndrew Thompson {
78102ac6454SAndrew Thompson 	free(udt->p_seg, M_USB);
78202ac6454SAndrew Thompson }
78302ac6454SAndrew Thompson 
78402ac6454SAndrew Thompson /*------------------------------------------------------------------------*
78502ac6454SAndrew Thompson  *	usb2_pc_common_mem_cb - BUS-DMA callback function
78602ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
78702ac6454SAndrew Thompson static void
78802ac6454SAndrew Thompson usb2_pc_common_mem_cb(struct usb2_page_cache *pc, bus_dma_segment_t *segs,
78902ac6454SAndrew Thompson     int nseg, int error, uint8_t isload, uint8_t dolock)
79002ac6454SAndrew Thompson {
79102ac6454SAndrew Thompson 	struct usb2_dma_parent_tag *uptag;
79202ac6454SAndrew Thompson 	struct usb2_page *pg;
793578d0effSAndrew Thompson 	usb2_size_t rem;
79402ac6454SAndrew Thompson 	uint8_t ext_seg;		/* extend last segment */
79502ac6454SAndrew Thompson 
79602ac6454SAndrew Thompson 	uptag = pc->tag_parent;
79702ac6454SAndrew Thompson 
79802ac6454SAndrew Thompson 	if (error) {
79902ac6454SAndrew Thompson 		goto done;
80002ac6454SAndrew Thompson 	}
80102ac6454SAndrew Thompson 	pg = pc->page_start;
80202ac6454SAndrew Thompson 	pg->physaddr = segs->ds_addr & ~(USB_PAGE_SIZE - 1);
80302ac6454SAndrew Thompson 	rem = segs->ds_addr & (USB_PAGE_SIZE - 1);
80402ac6454SAndrew Thompson 	pc->page_offset_buf = rem;
80502ac6454SAndrew Thompson 	pc->page_offset_end += rem;
80602ac6454SAndrew Thompson 	if (nseg < ((pc->page_offset_end +
80702ac6454SAndrew Thompson 	    (USB_PAGE_SIZE - 1)) / USB_PAGE_SIZE)) {
80802ac6454SAndrew Thompson 		ext_seg = 1;
80902ac6454SAndrew Thompson 	} else {
81002ac6454SAndrew Thompson 		ext_seg = 0;
81102ac6454SAndrew Thompson 	}
81202ac6454SAndrew Thompson 	nseg--;
81302ac6454SAndrew Thompson #if (USB_DEBUG != 0)
81402ac6454SAndrew Thompson 	if (rem != (USB_P2U(pc->buffer) & (USB_PAGE_SIZE - 1))) {
81502ac6454SAndrew Thompson 		/*
81602ac6454SAndrew Thompson 		 * This check verifies that the physical address is correct:
81702ac6454SAndrew Thompson 		 */
81802ac6454SAndrew Thompson 		DPRINTFN(0, "Page offset was not preserved!\n");
81902ac6454SAndrew Thompson 		error = 1;
82002ac6454SAndrew Thompson 		goto done;
82102ac6454SAndrew Thompson 	}
82202ac6454SAndrew Thompson #endif
82302ac6454SAndrew Thompson 	while (nseg > 0) {
82402ac6454SAndrew Thompson 		nseg--;
82502ac6454SAndrew Thompson 		segs++;
82602ac6454SAndrew Thompson 		pg++;
82702ac6454SAndrew Thompson 		pg->physaddr = segs->ds_addr & ~(USB_PAGE_SIZE - 1);
82802ac6454SAndrew Thompson 	}
82902ac6454SAndrew Thompson 
83002ac6454SAndrew Thompson 	/*
83102ac6454SAndrew Thompson 	 * XXX The segments we get from BUS-DMA are not aligned,
83202ac6454SAndrew Thompson 	 * XXX so we need to extend the last segment if we are
83302ac6454SAndrew Thompson 	 * XXX unaligned and cross the segment boundary!
83402ac6454SAndrew Thompson 	 */
83502ac6454SAndrew Thompson 	if (ext_seg && pc->ismultiseg) {
83602ac6454SAndrew Thompson 		(pg + 1)->physaddr = pg->physaddr + USB_PAGE_SIZE;
83702ac6454SAndrew Thompson 	}
83802ac6454SAndrew Thompson done:
83902ac6454SAndrew Thompson 	if (dolock)
84002ac6454SAndrew Thompson 		mtx_lock(uptag->mtx);
84102ac6454SAndrew Thompson 
84202ac6454SAndrew Thompson 	uptag->dma_error = (error ? 1 : 0);
84302ac6454SAndrew Thompson 	if (isload) {
84402ac6454SAndrew Thompson 		(uptag->func) (uptag);
84502ac6454SAndrew Thompson 	}
84602ac6454SAndrew Thompson 	if (dolock)
84702ac6454SAndrew Thompson 		mtx_unlock(uptag->mtx);
84802ac6454SAndrew Thompson }
84902ac6454SAndrew Thompson 
85002ac6454SAndrew Thompson /*------------------------------------------------------------------------*
85102ac6454SAndrew Thompson  *	usb2_pc_alloc_mem - allocate DMA'able memory
85202ac6454SAndrew Thompson  *
85302ac6454SAndrew Thompson  * Returns:
85402ac6454SAndrew Thompson  *    0: Success
85502ac6454SAndrew Thompson  * Else: Failure
85602ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
85702ac6454SAndrew Thompson uint8_t
85802ac6454SAndrew Thompson usb2_pc_alloc_mem(struct usb2_page_cache *pc, struct usb2_page *pg,
859578d0effSAndrew Thompson     usb2_size_t size, usb2_size_t align)
86002ac6454SAndrew Thompson {
86102ac6454SAndrew Thompson 	struct usb2_dma_parent_tag *uptag;
86202ac6454SAndrew Thompson 	struct usb2_dma_tag *utag;
86302ac6454SAndrew Thompson 	caddr_t ptr = NULL;
86402ac6454SAndrew Thompson 	bus_dmamap_t map;
86502ac6454SAndrew Thompson 	int seg_count;
86602ac6454SAndrew Thompson 
86702ac6454SAndrew Thompson 	uptag = pc->tag_parent;
86802ac6454SAndrew Thompson 
86902ac6454SAndrew Thompson 	if (align != 1) {
87002ac6454SAndrew Thompson 		/*
87102ac6454SAndrew Thompson 	         * The alignment must be greater or equal to the
87202ac6454SAndrew Thompson 	         * "size" else the object can be split between two
87302ac6454SAndrew Thompson 	         * memory pages and we get a problem!
87402ac6454SAndrew Thompson 	         */
87502ac6454SAndrew Thompson 		while (align < size) {
87602ac6454SAndrew Thompson 			align *= 2;
87702ac6454SAndrew Thompson 			if (align == 0) {
87802ac6454SAndrew Thompson 				goto done_5;
87902ac6454SAndrew Thompson 			}
88002ac6454SAndrew Thompson 		}
88102ac6454SAndrew Thompson 	}
88202ac6454SAndrew Thompson 	/* get the correct DMA tag */
88302ac6454SAndrew Thompson 	utag = usb2_dma_tag_find(pc->tag_parent, size, align);
88402ac6454SAndrew Thompson 	if (utag == NULL) {
88502ac6454SAndrew Thompson 		goto done_5;
88602ac6454SAndrew Thompson 	}
88702ac6454SAndrew Thompson 	if (bus_dmamem_alloc(utag->tag, size, align, 0, utag->p_seg,
88802ac6454SAndrew Thompson 	    utag->n_seg, &seg_count, BUS_DMA_WAITOK)) {
88902ac6454SAndrew Thompson 		goto done_4;
89002ac6454SAndrew Thompson 	}
89102ac6454SAndrew Thompson 	if (bus_dmamem_map(utag->tag, utag->p_seg, seg_count, size,
89202ac6454SAndrew Thompson 	    &ptr, BUS_DMA_WAITOK | BUS_DMA_COHERENT)) {
89302ac6454SAndrew Thompson 		goto done_3;
89402ac6454SAndrew Thompson 	}
89502ac6454SAndrew Thompson 	if (bus_dmamap_create(utag->tag, size, utag->n_seg, (align == 1) ?
89602ac6454SAndrew Thompson 	    USB_PAGE_SIZE : size, 0, BUS_DMA_WAITOK, &map)) {
89702ac6454SAndrew Thompson 		goto done_2;
89802ac6454SAndrew Thompson 	}
89902ac6454SAndrew Thompson 	if (bus_dmamap_load(utag->tag, map, ptr, size, NULL,
90002ac6454SAndrew Thompson 	    BUS_DMA_WAITOK)) {
90102ac6454SAndrew Thompson 		goto done_1;
90202ac6454SAndrew Thompson 	}
90302ac6454SAndrew Thompson 	pc->p_seg = malloc(seg_count * sizeof(*(pc->p_seg)),
90402ac6454SAndrew Thompson 	    M_USB, M_WAITOK | M_ZERO);
90502ac6454SAndrew Thompson 	if (pc->p_seg == NULL) {
90602ac6454SAndrew Thompson 		goto done_0;
90702ac6454SAndrew Thompson 	}
90802ac6454SAndrew Thompson 	/* store number if actual segments used */
90902ac6454SAndrew Thompson 	pc->n_seg = seg_count;
91002ac6454SAndrew Thompson 
91102ac6454SAndrew Thompson 	/* make a copy of the segments */
91202ac6454SAndrew Thompson 	bcopy(utag->p_seg, pc->p_seg,
91302ac6454SAndrew Thompson 	    seg_count * sizeof(*(pc->p_seg)));
91402ac6454SAndrew Thompson 
91502ac6454SAndrew Thompson 	/* setup page cache */
91602ac6454SAndrew Thompson 	pc->buffer = ptr;
91702ac6454SAndrew Thompson 	pc->page_start = pg;
91802ac6454SAndrew Thompson 	pc->page_offset_buf = 0;
91902ac6454SAndrew Thompson 	pc->page_offset_end = size;
92002ac6454SAndrew Thompson 	pc->map = map;
92102ac6454SAndrew Thompson 	pc->tag = utag->tag;
92202ac6454SAndrew Thompson 	pc->ismultiseg = (align == 1);
92302ac6454SAndrew Thompson 
92402ac6454SAndrew Thompson 	usb2_pc_common_mem_cb(pc, utag->p_seg, seg_count, 0, 0, 1);
92502ac6454SAndrew Thompson 
92602ac6454SAndrew Thompson 	bzero(ptr, size);
92702ac6454SAndrew Thompson 
92802ac6454SAndrew Thompson 	usb2_pc_cpu_flush(pc);
92902ac6454SAndrew Thompson 
93002ac6454SAndrew Thompson 	return (0);
93102ac6454SAndrew Thompson 
93202ac6454SAndrew Thompson done_0:
93302ac6454SAndrew Thompson 	bus_dmamap_unload(utag->tag, map);
93402ac6454SAndrew Thompson done_1:
93502ac6454SAndrew Thompson 	bus_dmamap_destroy(utag->tag, map);
93602ac6454SAndrew Thompson done_2:
93702ac6454SAndrew Thompson 	bus_dmamem_unmap(utag->tag, ptr, size);
93802ac6454SAndrew Thompson done_3:
93902ac6454SAndrew Thompson 	bus_dmamem_free(utag->tag, utag->p_seg, seg_count);
94002ac6454SAndrew Thompson done_4:
94102ac6454SAndrew Thompson 	/* utag is destroyed later */
94202ac6454SAndrew Thompson done_5:
94302ac6454SAndrew Thompson 	/* reset most of the page cache */
94402ac6454SAndrew Thompson 	pc->buffer = NULL;
94502ac6454SAndrew Thompson 	pc->page_start = NULL;
94602ac6454SAndrew Thompson 	pc->page_offset_buf = 0;
94702ac6454SAndrew Thompson 	pc->page_offset_end = 0;
94802ac6454SAndrew Thompson 	pc->map = NULL;
94902ac6454SAndrew Thompson 	pc->tag = NULL;
95002ac6454SAndrew Thompson 	pc->n_seg = 0;
95102ac6454SAndrew Thompson 	pc->p_seg = NULL;
95202ac6454SAndrew Thompson 	return (1);
95302ac6454SAndrew Thompson }
95402ac6454SAndrew Thompson 
95502ac6454SAndrew Thompson /*------------------------------------------------------------------------*
95602ac6454SAndrew Thompson  *	usb2_pc_free_mem - free DMA memory
95702ac6454SAndrew Thompson  *
95802ac6454SAndrew Thompson  * This function is NULL safe.
95902ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
96002ac6454SAndrew Thompson void
96102ac6454SAndrew Thompson usb2_pc_free_mem(struct usb2_page_cache *pc)
96202ac6454SAndrew Thompson {
96302ac6454SAndrew Thompson 	if (pc && pc->buffer) {
96402ac6454SAndrew Thompson 		bus_dmamap_unload(pc->tag, pc->map);
96502ac6454SAndrew Thompson 		bus_dmamap_destroy(pc->tag, pc->map);
96602ac6454SAndrew Thompson 		bus_dmamem_unmap(pc->tag, pc->buffer,
96702ac6454SAndrew Thompson 		    pc->page_offset_end - pc->page_offset_buf);
96802ac6454SAndrew Thompson 		bus_dmamem_free(pc->tag, pc->p_seg, pc->n_seg);
96902ac6454SAndrew Thompson 		free(pc->p_seg, M_USB);
97002ac6454SAndrew Thompson 		pc->buffer = NULL;
97102ac6454SAndrew Thompson 	}
97202ac6454SAndrew Thompson }
97302ac6454SAndrew Thompson 
97402ac6454SAndrew Thompson /*------------------------------------------------------------------------*
97502ac6454SAndrew Thompson  *	usb2_pc_load_mem - load virtual memory into DMA
97602ac6454SAndrew Thompson  *
97702ac6454SAndrew Thompson  * Return values:
97802ac6454SAndrew Thompson  * 0: Success
97902ac6454SAndrew Thompson  * Else: Error
98002ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
98102ac6454SAndrew Thompson uint8_t
982578d0effSAndrew Thompson usb2_pc_load_mem(struct usb2_page_cache *pc, usb2_size_t size, uint8_t sync)
98302ac6454SAndrew Thompson {
98402ac6454SAndrew Thompson 	int error;
98502ac6454SAndrew Thompson 
98602ac6454SAndrew Thompson 	/* setup page cache */
98702ac6454SAndrew Thompson 	pc->page_offset_buf = 0;
98802ac6454SAndrew Thompson 	pc->page_offset_end = size;
98902ac6454SAndrew Thompson 	pc->ismultiseg = 1;
99002ac6454SAndrew Thompson 
99102ac6454SAndrew Thompson 	if (size > 0) {
99202ac6454SAndrew Thompson 
99302ac6454SAndrew Thompson 		/*
99402ac6454SAndrew Thompson 		 * We have to unload the previous loaded DMA
99502ac6454SAndrew Thompson 		 * pages before trying to load a new one!
99602ac6454SAndrew Thompson 		 */
99702ac6454SAndrew Thompson 		bus_dmamap_unload(pc->tag, pc->map);
99802ac6454SAndrew Thompson 
99902ac6454SAndrew Thompson 		/* try to load memory into DMA using using no wait option */
100002ac6454SAndrew Thompson 		if (bus_dmamap_load(pc->tag, pc->map, pc->buffer,
100102ac6454SAndrew Thompson 		    size, NULL, BUS_DMA_NOWAIT)) {
100202ac6454SAndrew Thompson 			error = ENOMEM;
100302ac6454SAndrew Thompson 		} else {
100402ac6454SAndrew Thompson 			error = 0;
100502ac6454SAndrew Thompson 		}
100602ac6454SAndrew Thompson 
100702ac6454SAndrew Thompson 		usb2_pc_common_mem_cb(pc, pc->map->dm_segs,
100802ac6454SAndrew Thompson 		    pc->map->dm_nsegs, error, !sync);
100902ac6454SAndrew Thompson 
101002ac6454SAndrew Thompson 		if (error) {
101102ac6454SAndrew Thompson 			return (1);
101202ac6454SAndrew Thompson 		}
101302ac6454SAndrew Thompson 	} else {
101402ac6454SAndrew Thompson 		if (!sync) {
101502ac6454SAndrew Thompson 			/*
101602ac6454SAndrew Thompson 			 * Call callback so that refcount is decremented
101702ac6454SAndrew Thompson 			 * properly:
101802ac6454SAndrew Thompson 			 */
101902ac6454SAndrew Thompson 			pc->tag_parent->dma_error = 0;
102002ac6454SAndrew Thompson 			(pc->tag_parent->func) (pc->tag_parent);
102102ac6454SAndrew Thompson 		}
102202ac6454SAndrew Thompson 	}
102302ac6454SAndrew Thompson 	return (0);
102402ac6454SAndrew Thompson }
102502ac6454SAndrew Thompson 
102602ac6454SAndrew Thompson /*------------------------------------------------------------------------*
102702ac6454SAndrew Thompson  *	usb2_pc_cpu_invalidate - invalidate CPU cache
102802ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
102902ac6454SAndrew Thompson void
103002ac6454SAndrew Thompson usb2_pc_cpu_invalidate(struct usb2_page_cache *pc)
103102ac6454SAndrew Thompson {
1032578d0effSAndrew Thompson 	usb2_size_t len;
103302ac6454SAndrew Thompson 
103402ac6454SAndrew Thompson 	len = pc->page_offset_end - pc->page_offset_buf;
103502ac6454SAndrew Thompson 
103602ac6454SAndrew Thompson 	if (len == 0) {
103702ac6454SAndrew Thompson 		/* nothing has been loaded into this page cache */
103802ac6454SAndrew Thompson 		return;
103902ac6454SAndrew Thompson 	}
104002ac6454SAndrew Thompson 	bus_dmamap_sync(pc->tag, pc->map, 0, len,
104102ac6454SAndrew Thompson 	    BUS_DMASYNC_POSTWRITE | BUS_DMASYNC_POSTREAD);
104202ac6454SAndrew Thompson }
104302ac6454SAndrew Thompson 
104402ac6454SAndrew Thompson /*------------------------------------------------------------------------*
104502ac6454SAndrew Thompson  *	usb2_pc_cpu_flush - flush CPU cache
104602ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
104702ac6454SAndrew Thompson void
104802ac6454SAndrew Thompson usb2_pc_cpu_flush(struct usb2_page_cache *pc)
104902ac6454SAndrew Thompson {
1050578d0effSAndrew Thompson 	usb2_size_t len;
105102ac6454SAndrew Thompson 
105202ac6454SAndrew Thompson 	len = pc->page_offset_end - pc->page_offset_buf;
105302ac6454SAndrew Thompson 
105402ac6454SAndrew Thompson 	if (len == 0) {
105502ac6454SAndrew Thompson 		/* nothing has been loaded into this page cache */
105602ac6454SAndrew Thompson 		return;
105702ac6454SAndrew Thompson 	}
105802ac6454SAndrew Thompson 	bus_dmamap_sync(pc->tag, pc->map, 0, len,
105902ac6454SAndrew Thompson 	    BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD);
106002ac6454SAndrew Thompson }
106102ac6454SAndrew Thompson 
106202ac6454SAndrew Thompson /*------------------------------------------------------------------------*
106302ac6454SAndrew Thompson  *	usb2_pc_dmamap_create - create a DMA map
106402ac6454SAndrew Thompson  *
106502ac6454SAndrew Thompson  * Returns:
106602ac6454SAndrew Thompson  *    0: Success
106702ac6454SAndrew Thompson  * Else: Failure
106802ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
106902ac6454SAndrew Thompson uint8_t
1070578d0effSAndrew Thompson usb2_pc_dmamap_create(struct usb2_page_cache *pc, usb2_size_t size)
107102ac6454SAndrew Thompson {
107202ac6454SAndrew Thompson 	struct usb2_xfer_root *info;
107302ac6454SAndrew Thompson 	struct usb2_dma_tag *utag;
107402ac6454SAndrew Thompson 
107502ac6454SAndrew Thompson 	/* get info */
1076bdc081c6SAndrew Thompson 	info = USB_DMATAG_TO_XROOT(pc->tag_parent);
107702ac6454SAndrew Thompson 
107802ac6454SAndrew Thompson 	/* sanity check */
107902ac6454SAndrew Thompson 	if (info == NULL) {
108002ac6454SAndrew Thompson 		goto error;
108102ac6454SAndrew Thompson 	}
108202ac6454SAndrew Thompson 	utag = usb2_dma_tag_find(pc->tag_parent, size, 1);
108302ac6454SAndrew Thompson 	if (utag == NULL) {
108402ac6454SAndrew Thompson 		goto error;
108502ac6454SAndrew Thompson 	}
108602ac6454SAndrew Thompson 	if (bus_dmamap_create(utag->tag, size, utag->n_seg,
108702ac6454SAndrew Thompson 	    USB_PAGE_SIZE, 0, BUS_DMA_WAITOK, &pc->map)) {
108802ac6454SAndrew Thompson 		goto error;
108902ac6454SAndrew Thompson 	}
109002ac6454SAndrew Thompson 	pc->tag = utag->tag;
109102ac6454SAndrew Thompson 	pc->p_seg = utag->p_seg;
109202ac6454SAndrew Thompson 	pc->n_seg = utag->n_seg;
109302ac6454SAndrew Thompson 	return 0;			/* success */
109402ac6454SAndrew Thompson 
109502ac6454SAndrew Thompson error:
109602ac6454SAndrew Thompson 	pc->map = NULL;
109702ac6454SAndrew Thompson 	pc->tag = NULL;
109802ac6454SAndrew Thompson 	pc->p_seg = NULL;
109902ac6454SAndrew Thompson 	pc->n_seg = 0;
110002ac6454SAndrew Thompson 	return 1;			/* failure */
110102ac6454SAndrew Thompson }
110202ac6454SAndrew Thompson 
110302ac6454SAndrew Thompson /*------------------------------------------------------------------------*
110402ac6454SAndrew Thompson  *	usb2_pc_dmamap_destroy
110502ac6454SAndrew Thompson  *
110602ac6454SAndrew Thompson  * This function is NULL safe.
110702ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
110802ac6454SAndrew Thompson void
110902ac6454SAndrew Thompson usb2_pc_dmamap_destroy(struct usb2_page_cache *pc)
111002ac6454SAndrew Thompson {
111102ac6454SAndrew Thompson 	if (pc && pc->tag) {
111202ac6454SAndrew Thompson 		bus_dmamap_destroy(pc->tag, pc->map);
111302ac6454SAndrew Thompson 		pc->tag = NULL;
111402ac6454SAndrew Thompson 		pc->map = NULL;
111502ac6454SAndrew Thompson 	}
111602ac6454SAndrew Thompson }
111702ac6454SAndrew Thompson 
111802ac6454SAndrew Thompson #endif
111902ac6454SAndrew Thompson 
1120bdc081c6SAndrew Thompson #if USB_HAVE_BUSDMA
1121bdc081c6SAndrew Thompson 
112202ac6454SAndrew Thompson /*------------------------------------------------------------------------*
112302ac6454SAndrew Thompson  *	usb2_dma_tag_find - factored out code
112402ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
112502ac6454SAndrew Thompson struct usb2_dma_tag *
112602ac6454SAndrew Thompson usb2_dma_tag_find(struct usb2_dma_parent_tag *udpt,
1127578d0effSAndrew Thompson     usb2_size_t size, usb2_size_t align)
112802ac6454SAndrew Thompson {
112902ac6454SAndrew Thompson 	struct usb2_dma_tag *udt;
113002ac6454SAndrew Thompson 	uint8_t nudt;
113102ac6454SAndrew Thompson 
113202ac6454SAndrew Thompson 	USB_ASSERT(align > 0, ("Invalid parameter align = 0!\n"));
113302ac6454SAndrew Thompson 	USB_ASSERT(size > 0, ("Invalid parameter size = 0!\n"));
113402ac6454SAndrew Thompson 
113502ac6454SAndrew Thompson 	udt = udpt->utag_first;
113602ac6454SAndrew Thompson 	nudt = udpt->utag_max;
113702ac6454SAndrew Thompson 
113802ac6454SAndrew Thompson 	while (nudt--) {
113902ac6454SAndrew Thompson 
114002ac6454SAndrew Thompson 		if (udt->align == 0) {
114102ac6454SAndrew Thompson 			usb2_dma_tag_create(udt, size, align);
114202ac6454SAndrew Thompson 			if (udt->tag == NULL) {
114302ac6454SAndrew Thompson 				return (NULL);
114402ac6454SAndrew Thompson 			}
114502ac6454SAndrew Thompson 			udt->align = align;
114602ac6454SAndrew Thompson 			udt->size = size;
114702ac6454SAndrew Thompson 			return (udt);
114802ac6454SAndrew Thompson 		}
114902ac6454SAndrew Thompson 		if ((udt->align == align) && (udt->size == size)) {
115002ac6454SAndrew Thompson 			return (udt);
115102ac6454SAndrew Thompson 		}
115202ac6454SAndrew Thompson 		udt++;
115302ac6454SAndrew Thompson 	}
115402ac6454SAndrew Thompson 	return (NULL);
115502ac6454SAndrew Thompson }
115602ac6454SAndrew Thompson 
115702ac6454SAndrew Thompson /*------------------------------------------------------------------------*
115802ac6454SAndrew Thompson  *	usb2_dma_tag_setup - initialise USB DMA tags
115902ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
116002ac6454SAndrew Thompson void
116102ac6454SAndrew Thompson usb2_dma_tag_setup(struct usb2_dma_parent_tag *udpt,
116202ac6454SAndrew Thompson     struct usb2_dma_tag *udt, bus_dma_tag_t dmat,
116302ac6454SAndrew Thompson     struct mtx *mtx, usb2_dma_callback_t *func,
1164bdc081c6SAndrew Thompson     uint8_t ndmabits, uint8_t nudt)
116502ac6454SAndrew Thompson {
116602ac6454SAndrew Thompson 	bzero(udpt, sizeof(*udpt));
116702ac6454SAndrew Thompson 
116802ac6454SAndrew Thompson 	/* sanity checking */
116902ac6454SAndrew Thompson 	if ((nudt == 0) ||
117002ac6454SAndrew Thompson 	    (ndmabits == 0) ||
117102ac6454SAndrew Thompson 	    (mtx == NULL)) {
117202ac6454SAndrew Thompson 		/* something is corrupt */
117302ac6454SAndrew Thompson 		return;
117402ac6454SAndrew Thompson 	}
117502ac6454SAndrew Thompson #ifdef __FreeBSD__
117602ac6454SAndrew Thompson 	/* initialise condition variable */
117702ac6454SAndrew Thompson 	usb2_cv_init(udpt->cv, "USB DMA CV");
117802ac6454SAndrew Thompson #endif
117902ac6454SAndrew Thompson 
118002ac6454SAndrew Thompson 	/* store some information */
118102ac6454SAndrew Thompson 	udpt->mtx = mtx;
118202ac6454SAndrew Thompson 	udpt->func = func;
118302ac6454SAndrew Thompson 	udpt->tag = dmat;
118402ac6454SAndrew Thompson 	udpt->utag_first = udt;
118502ac6454SAndrew Thompson 	udpt->utag_max = nudt;
118602ac6454SAndrew Thompson 	udpt->dma_bits = ndmabits;
118702ac6454SAndrew Thompson 
118802ac6454SAndrew Thompson 	while (nudt--) {
118902ac6454SAndrew Thompson 		bzero(udt, sizeof(*udt));
119002ac6454SAndrew Thompson 		udt->tag_parent = udpt;
119102ac6454SAndrew Thompson 		udt++;
119202ac6454SAndrew Thompson 	}
119302ac6454SAndrew Thompson }
119402ac6454SAndrew Thompson 
119502ac6454SAndrew Thompson /*------------------------------------------------------------------------*
119602ac6454SAndrew Thompson  *	usb2_bus_tag_unsetup - factored out code
119702ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
119802ac6454SAndrew Thompson void
119902ac6454SAndrew Thompson usb2_dma_tag_unsetup(struct usb2_dma_parent_tag *udpt)
120002ac6454SAndrew Thompson {
120102ac6454SAndrew Thompson 	struct usb2_dma_tag *udt;
120202ac6454SAndrew Thompson 	uint8_t nudt;
120302ac6454SAndrew Thompson 
120402ac6454SAndrew Thompson 	udt = udpt->utag_first;
120502ac6454SAndrew Thompson 	nudt = udpt->utag_max;
120602ac6454SAndrew Thompson 
120702ac6454SAndrew Thompson 	while (nudt--) {
120802ac6454SAndrew Thompson 
120902ac6454SAndrew Thompson 		if (udt->align) {
121002ac6454SAndrew Thompson 			/* destroy the USB DMA tag */
121102ac6454SAndrew Thompson 			usb2_dma_tag_destroy(udt);
121202ac6454SAndrew Thompson 			udt->align = 0;
121302ac6454SAndrew Thompson 		}
121402ac6454SAndrew Thompson 		udt++;
121502ac6454SAndrew Thompson 	}
121602ac6454SAndrew Thompson 
121702ac6454SAndrew Thompson 	if (udpt->utag_max) {
121802ac6454SAndrew Thompson #ifdef __FreeBSD__
121902ac6454SAndrew Thompson 		/* destroy the condition variable */
122002ac6454SAndrew Thompson 		usb2_cv_destroy(udpt->cv);
122102ac6454SAndrew Thompson #endif
122202ac6454SAndrew Thompson 	}
122302ac6454SAndrew Thompson }
122402ac6454SAndrew Thompson 
122502ac6454SAndrew Thompson /*------------------------------------------------------------------------*
122602ac6454SAndrew Thompson  *	usb2_bdma_work_loop
122702ac6454SAndrew Thompson  *
122802ac6454SAndrew Thompson  * This function handles loading of virtual buffers into DMA and is
122902ac6454SAndrew Thompson  * only called when "dma_refcount" is zero.
123002ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
123102ac6454SAndrew Thompson void
123202ac6454SAndrew Thompson usb2_bdma_work_loop(struct usb2_xfer_queue *pq)
123302ac6454SAndrew Thompson {
123402ac6454SAndrew Thompson 	struct usb2_xfer_root *info;
123502ac6454SAndrew Thompson 	struct usb2_xfer *xfer;
1236578d0effSAndrew Thompson 	usb2_frcount_t nframes;
123702ac6454SAndrew Thompson 
123802ac6454SAndrew Thompson 	xfer = pq->curr;
123902ac6454SAndrew Thompson 	info = xfer->xroot;
124002ac6454SAndrew Thompson 
124102ac6454SAndrew Thompson 	mtx_assert(info->xfer_mtx, MA_OWNED);
124202ac6454SAndrew Thompson 
124302ac6454SAndrew Thompson 	if (xfer->error) {
124402ac6454SAndrew Thompson 		/* some error happened */
124502ac6454SAndrew Thompson 		USB_BUS_LOCK(info->bus);
124602ac6454SAndrew Thompson 		usb2_transfer_done(xfer, 0);
124702ac6454SAndrew Thompson 		USB_BUS_UNLOCK(info->bus);
124802ac6454SAndrew Thompson 		return;
124902ac6454SAndrew Thompson 	}
125002ac6454SAndrew Thompson 	if (!xfer->flags_int.bdma_setup) {
125102ac6454SAndrew Thompson 		struct usb2_page *pg;
1252578d0effSAndrew Thompson 		usb2_frlength_t frlength_0;
125302ac6454SAndrew Thompson 		uint8_t isread;
125402ac6454SAndrew Thompson 
125502ac6454SAndrew Thompson 		xfer->flags_int.bdma_setup = 1;
125602ac6454SAndrew Thompson 
125702ac6454SAndrew Thompson 		/* reset BUS-DMA load state */
125802ac6454SAndrew Thompson 
125902ac6454SAndrew Thompson 		info->dma_error = 0;
126002ac6454SAndrew Thompson 
126102ac6454SAndrew Thompson 		if (xfer->flags_int.isochronous_xfr) {
126202ac6454SAndrew Thompson 			/* only one frame buffer */
126302ac6454SAndrew Thompson 			nframes = 1;
126402ac6454SAndrew Thompson 			frlength_0 = xfer->sumlen;
126502ac6454SAndrew Thompson 		} else {
126602ac6454SAndrew Thompson 			/* can be multiple frame buffers */
126702ac6454SAndrew Thompson 			nframes = xfer->nframes;
126802ac6454SAndrew Thompson 			frlength_0 = xfer->frlengths[0];
126902ac6454SAndrew Thompson 		}
127002ac6454SAndrew Thompson 
127102ac6454SAndrew Thompson 		/*
127202ac6454SAndrew Thompson 		 * Set DMA direction first. This is needed to
127302ac6454SAndrew Thompson 		 * select the correct cache invalidate and cache
127402ac6454SAndrew Thompson 		 * flush operations.
127502ac6454SAndrew Thompson 		 */
127602ac6454SAndrew Thompson 		isread = USB_GET_DATA_ISREAD(xfer);
127702ac6454SAndrew Thompson 		pg = xfer->dma_page_ptr;
127802ac6454SAndrew Thompson 
127902ac6454SAndrew Thompson 		if (xfer->flags_int.control_xfr &&
128002ac6454SAndrew Thompson 		    xfer->flags_int.control_hdr) {
128102ac6454SAndrew Thompson 			/* special case */
128202ac6454SAndrew Thompson 			if (xfer->flags_int.usb2_mode == USB_MODE_DEVICE) {
128302ac6454SAndrew Thompson 				/* The device controller writes to memory */
128402ac6454SAndrew Thompson 				xfer->frbuffers[0].isread = 1;
128502ac6454SAndrew Thompson 			} else {
128602ac6454SAndrew Thompson 				/* The host controller reads from memory */
128702ac6454SAndrew Thompson 				xfer->frbuffers[0].isread = 0;
128802ac6454SAndrew Thompson 			}
128902ac6454SAndrew Thompson 		} else {
129002ac6454SAndrew Thompson 			/* default case */
129102ac6454SAndrew Thompson 			xfer->frbuffers[0].isread = isread;
129202ac6454SAndrew Thompson 		}
129302ac6454SAndrew Thompson 
129402ac6454SAndrew Thompson 		/*
129502ac6454SAndrew Thompson 		 * Setup the "page_start" pointer which points to an array of
129602ac6454SAndrew Thompson 		 * USB pages where information about the physical address of a
129702ac6454SAndrew Thompson 		 * page will be stored. Also initialise the "isread" field of
129802ac6454SAndrew Thompson 		 * the USB page caches.
129902ac6454SAndrew Thompson 		 */
130002ac6454SAndrew Thompson 		xfer->frbuffers[0].page_start = pg;
130102ac6454SAndrew Thompson 
130202ac6454SAndrew Thompson 		info->dma_nframes = nframes;
130302ac6454SAndrew Thompson 		info->dma_currframe = 0;
130402ac6454SAndrew Thompson 		info->dma_frlength_0 = frlength_0;
130502ac6454SAndrew Thompson 
130602ac6454SAndrew Thompson 		pg += (frlength_0 / USB_PAGE_SIZE);
130702ac6454SAndrew Thompson 		pg += 2;
130802ac6454SAndrew Thompson 
130902ac6454SAndrew Thompson 		while (--nframes > 0) {
131002ac6454SAndrew Thompson 			xfer->frbuffers[nframes].isread = isread;
131102ac6454SAndrew Thompson 			xfer->frbuffers[nframes].page_start = pg;
131202ac6454SAndrew Thompson 
131302ac6454SAndrew Thompson 			pg += (xfer->frlengths[nframes] / USB_PAGE_SIZE);
131402ac6454SAndrew Thompson 			pg += 2;
131502ac6454SAndrew Thompson 		}
131602ac6454SAndrew Thompson 
131702ac6454SAndrew Thompson 	}
131802ac6454SAndrew Thompson 	if (info->dma_error) {
131902ac6454SAndrew Thompson 		USB_BUS_LOCK(info->bus);
132002ac6454SAndrew Thompson 		usb2_transfer_done(xfer, USB_ERR_DMA_LOAD_FAILED);
132102ac6454SAndrew Thompson 		USB_BUS_UNLOCK(info->bus);
132202ac6454SAndrew Thompson 		return;
132302ac6454SAndrew Thompson 	}
132402ac6454SAndrew Thompson 	if (info->dma_currframe != info->dma_nframes) {
132502ac6454SAndrew Thompson 
132602ac6454SAndrew Thompson 		if (info->dma_currframe == 0) {
132702ac6454SAndrew Thompson 			/* special case */
132802ac6454SAndrew Thompson 			usb2_pc_load_mem(xfer->frbuffers,
132902ac6454SAndrew Thompson 			    info->dma_frlength_0, 0);
133002ac6454SAndrew Thompson 		} else {
133102ac6454SAndrew Thompson 			/* default case */
133202ac6454SAndrew Thompson 			nframes = info->dma_currframe;
133302ac6454SAndrew Thompson 			usb2_pc_load_mem(xfer->frbuffers + nframes,
133402ac6454SAndrew Thompson 			    xfer->frlengths[nframes], 0);
133502ac6454SAndrew Thompson 		}
133602ac6454SAndrew Thompson 
133702ac6454SAndrew Thompson 		/* advance frame index */
133802ac6454SAndrew Thompson 		info->dma_currframe++;
133902ac6454SAndrew Thompson 
134002ac6454SAndrew Thompson 		return;
134102ac6454SAndrew Thompson 	}
134202ac6454SAndrew Thompson 	/* go ahead */
134302ac6454SAndrew Thompson 	usb2_bdma_pre_sync(xfer);
134402ac6454SAndrew Thompson 
134502ac6454SAndrew Thompson 	/* start loading next USB transfer, if any */
134602ac6454SAndrew Thompson 	usb2_command_wrapper(pq, NULL);
134702ac6454SAndrew Thompson 
134802ac6454SAndrew Thompson 	/* finally start the hardware */
134902ac6454SAndrew Thompson 	usb2_pipe_enter(xfer);
135002ac6454SAndrew Thompson }
135102ac6454SAndrew Thompson 
135202ac6454SAndrew Thompson /*------------------------------------------------------------------------*
135302ac6454SAndrew Thompson  *	usb2_bdma_done_event
135402ac6454SAndrew Thompson  *
135502ac6454SAndrew Thompson  * This function is called when the BUS-DMA has loaded virtual memory
135602ac6454SAndrew Thompson  * into DMA, if any.
135702ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
135802ac6454SAndrew Thompson void
135902ac6454SAndrew Thompson usb2_bdma_done_event(struct usb2_dma_parent_tag *udpt)
136002ac6454SAndrew Thompson {
136102ac6454SAndrew Thompson 	struct usb2_xfer_root *info;
136202ac6454SAndrew Thompson 
1363bdc081c6SAndrew Thompson 	info = USB_DMATAG_TO_XROOT(udpt);
136402ac6454SAndrew Thompson 
136502ac6454SAndrew Thompson 	mtx_assert(info->xfer_mtx, MA_OWNED);
136602ac6454SAndrew Thompson 
136702ac6454SAndrew Thompson 	/* copy error */
136802ac6454SAndrew Thompson 	info->dma_error = udpt->dma_error;
136902ac6454SAndrew Thompson 
137002ac6454SAndrew Thompson 	/* enter workloop again */
137102ac6454SAndrew Thompson 	usb2_command_wrapper(&info->dma_q,
137202ac6454SAndrew Thompson 	    info->dma_q.curr);
137302ac6454SAndrew Thompson }
137402ac6454SAndrew Thompson 
137502ac6454SAndrew Thompson /*------------------------------------------------------------------------*
137602ac6454SAndrew Thompson  *	usb2_bdma_pre_sync
137702ac6454SAndrew Thompson  *
137802ac6454SAndrew Thompson  * This function handles DMA synchronisation that must be done before
137902ac6454SAndrew Thompson  * an USB transfer is started.
138002ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
138102ac6454SAndrew Thompson void
138202ac6454SAndrew Thompson usb2_bdma_pre_sync(struct usb2_xfer *xfer)
138302ac6454SAndrew Thompson {
138402ac6454SAndrew Thompson 	struct usb2_page_cache *pc;
1385578d0effSAndrew Thompson 	usb2_frcount_t nframes;
138602ac6454SAndrew Thompson 
138702ac6454SAndrew Thompson 	if (xfer->flags_int.isochronous_xfr) {
138802ac6454SAndrew Thompson 		/* only one frame buffer */
138902ac6454SAndrew Thompson 		nframes = 1;
139002ac6454SAndrew Thompson 	} else {
139102ac6454SAndrew Thompson 		/* can be multiple frame buffers */
139202ac6454SAndrew Thompson 		nframes = xfer->nframes;
139302ac6454SAndrew Thompson 	}
139402ac6454SAndrew Thompson 
139502ac6454SAndrew Thompson 	pc = xfer->frbuffers;
139602ac6454SAndrew Thompson 
139702ac6454SAndrew Thompson 	while (nframes--) {
139802ac6454SAndrew Thompson 
139902ac6454SAndrew Thompson 		if (pc->isread) {
140002ac6454SAndrew Thompson 			usb2_pc_cpu_invalidate(pc);
140102ac6454SAndrew Thompson 		} else {
140202ac6454SAndrew Thompson 			usb2_pc_cpu_flush(pc);
140302ac6454SAndrew Thompson 		}
140402ac6454SAndrew Thompson 		pc++;
140502ac6454SAndrew Thompson 	}
140602ac6454SAndrew Thompson }
140702ac6454SAndrew Thompson 
140802ac6454SAndrew Thompson /*------------------------------------------------------------------------*
140902ac6454SAndrew Thompson  *	usb2_bdma_post_sync
141002ac6454SAndrew Thompson  *
141102ac6454SAndrew Thompson  * This function handles DMA synchronisation that must be done after
141202ac6454SAndrew Thompson  * an USB transfer is complete.
141302ac6454SAndrew Thompson  *------------------------------------------------------------------------*/
141402ac6454SAndrew Thompson void
141502ac6454SAndrew Thompson usb2_bdma_post_sync(struct usb2_xfer *xfer)
141602ac6454SAndrew Thompson {
141702ac6454SAndrew Thompson 	struct usb2_page_cache *pc;
1418578d0effSAndrew Thompson 	usb2_frcount_t nframes;
141902ac6454SAndrew Thompson 
142002ac6454SAndrew Thompson 	if (xfer->flags_int.isochronous_xfr) {
142102ac6454SAndrew Thompson 		/* only one frame buffer */
142202ac6454SAndrew Thompson 		nframes = 1;
142302ac6454SAndrew Thompson 	} else {
142402ac6454SAndrew Thompson 		/* can be multiple frame buffers */
142502ac6454SAndrew Thompson 		nframes = xfer->nframes;
142602ac6454SAndrew Thompson 	}
142702ac6454SAndrew Thompson 
142802ac6454SAndrew Thompson 	pc = xfer->frbuffers;
142902ac6454SAndrew Thompson 
143002ac6454SAndrew Thompson 	while (nframes--) {
143102ac6454SAndrew Thompson 		if (pc->isread) {
143202ac6454SAndrew Thompson 			usb2_pc_cpu_invalidate(pc);
143302ac6454SAndrew Thompson 		}
143402ac6454SAndrew Thompson 		pc++;
143502ac6454SAndrew Thompson 	}
143602ac6454SAndrew Thompson }
1437bdc081c6SAndrew Thompson 
1438bdc081c6SAndrew Thompson #endif
1439