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