xref: /netbsd-src/sys/arch/xen/x86/xen_bus_dma.c (revision e4ebea9efd33d7fbff602d6288b15240e56427d2)
1*e4ebea9eSandvar /*	$NetBSD: xen_bus_dma.c,v 1.34 2024/05/14 19:00:44 andvar Exp $	*/
20944c0faSbouyer /*	NetBSD bus_dma.c,v 1.21 2005/04/16 07:53:35 yamt Exp */
30944c0faSbouyer 
40944c0faSbouyer /*-
50944c0faSbouyer  * Copyright (c) 1996, 1997, 1998 The NetBSD Foundation, Inc.
60944c0faSbouyer  * All rights reserved.
70944c0faSbouyer  *
80944c0faSbouyer  * This code is derived from software contributed to The NetBSD Foundation
90944c0faSbouyer  * by Charles M. Hannum and by Jason R. Thorpe of the Numerical Aerospace
100944c0faSbouyer  * Simulation Facility, NASA Ames Research Center.
110944c0faSbouyer  *
120944c0faSbouyer  * Redistribution and use in source and binary forms, with or without
130944c0faSbouyer  * modification, are permitted provided that the following conditions
140944c0faSbouyer  * are met:
150944c0faSbouyer  * 1. Redistributions of source code must retain the above copyright
160944c0faSbouyer  *    notice, this list of conditions and the following disclaimer.
170944c0faSbouyer  * 2. Redistributions in binary form must reproduce the above copyright
180944c0faSbouyer  *    notice, this list of conditions and the following disclaimer in the
190944c0faSbouyer  *    documentation and/or other materials provided with the distribution.
200944c0faSbouyer  *
210944c0faSbouyer  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
220944c0faSbouyer  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
230944c0faSbouyer  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
240944c0faSbouyer  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
250944c0faSbouyer  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
260944c0faSbouyer  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
270944c0faSbouyer  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
280944c0faSbouyer  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
290944c0faSbouyer  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
300944c0faSbouyer  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
310944c0faSbouyer  * POSSIBILITY OF SUCH DAMAGE.
320944c0faSbouyer  */
330944c0faSbouyer 
340944c0faSbouyer #include <sys/cdefs.h>
35*e4ebea9eSandvar __KERNEL_RCSID(0, "$NetBSD: xen_bus_dma.c,v 1.34 2024/05/14 19:00:44 andvar Exp $");
360944c0faSbouyer 
370944c0faSbouyer #include <sys/param.h>
380944c0faSbouyer #include <sys/systm.h>
390944c0faSbouyer #include <sys/kernel.h>
400944c0faSbouyer #include <sys/mbuf.h>
410944c0faSbouyer #include <sys/proc.h>
42e265f67bSdyoung #include <sys/bus.h>
438f18579dSriastradh 
440944c0faSbouyer #include <machine/bus_private.h>
458f18579dSriastradh #include <machine/pmap_private.h>
460944c0faSbouyer 
4775ff44eeSnjoly #include <uvm/uvm.h>
480944c0faSbouyer 
49c24c993fSbouyer #include "opt_xen.h"
500944c0faSbouyer 
51010da6cfSjdolecek /* No special needs */
52010da6cfSjdolecek struct x86_bus_dma_tag xenbus_bus_dma_tag = {
53010da6cfSjdolecek 	._tag_needs_free	= 0,
54010da6cfSjdolecek 	._bounce_thresh		= 0,
55010da6cfSjdolecek 	._bounce_alloc_lo	= 0,
56010da6cfSjdolecek 	._bounce_alloc_hi	= 0,
57010da6cfSjdolecek 	._may_bounce		= NULL,
58010da6cfSjdolecek };
59010da6cfSjdolecek 
60c24c993fSbouyer #ifdef XENPV
61c24c993fSbouyer 
62c24c993fSbouyer extern paddr_t avail_end;
63c24c993fSbouyer 
640944c0faSbouyer /* Pure 2^n version of get_order */
get_order(unsigned long size)6550a256a3Sperry static inline int get_order(unsigned long size)
660944c0faSbouyer {
670944c0faSbouyer 	int order = -1;
680944c0faSbouyer 	size = (size - 1) >> (PAGE_SHIFT - 1);
690944c0faSbouyer 	do {
700944c0faSbouyer 		size >>= 1;
710944c0faSbouyer 		order++;
720944c0faSbouyer 	} while (size);
730944c0faSbouyer 	return order;
740944c0faSbouyer }
750944c0faSbouyer 
760944c0faSbouyer static int
_xen_alloc_contig(bus_size_t size,bus_size_t alignment,struct pglist * mlistp,int flags,bus_addr_t low,bus_addr_t high)775b2d9a3cSbouyer _xen_alloc_contig(bus_size_t size, bus_size_t alignment,
78dd27ab28Sbouyer     struct pglist *mlistp, int flags, bus_addr_t low, bus_addr_t high)
790944c0faSbouyer {
800944c0faSbouyer 	int order, i;
810944c0faSbouyer 	unsigned long npagesreq, npages, mfn;
820944c0faSbouyer 	bus_addr_t pa;
830944c0faSbouyer 	struct vm_page *pg, *pgnext;
840944c0faSbouyer 	int s, error;
858f162b7eSbouyer 	struct xen_memory_reservation res;
860944c0faSbouyer 
870944c0faSbouyer 	/*
88*e4ebea9eSandvar 	 * When requesting a contiguous memory region, the hypervisor will
895b2d9a3cSbouyer 	 * return a memory range aligned on size.
905b2d9a3cSbouyer 	 * The only way to enforce alignment is to request a memory region
915b2d9a3cSbouyer 	 * of size max(alignment, size).
920944c0faSbouyer 	 */
93d1579b2dSriastradh 	order = uimax(get_order(size), get_order(alignment));
940944c0faSbouyer 	npages = (1 << order);
950944c0faSbouyer 	npagesreq = (size >> PAGE_SHIFT);
960944c0faSbouyer 	KASSERT(npages >= npagesreq);
970944c0faSbouyer 
9813da824dSjym 	/* get npages from UVM, and give them back to the hypervisor */
9951023c61Scegger 	error = uvm_pglistalloc(((psize_t)npages) << PAGE_SHIFT,
10051023c61Scegger             0, avail_end, 0, 0, mlistp, npages, (flags & BUS_DMA_NOWAIT) == 0);
1010944c0faSbouyer 	if (error)
1020944c0faSbouyer 		return (error);
1030944c0faSbouyer 
10406c343acSad 	for (pg = mlistp->tqh_first; pg != NULL; pg = pg->pageq.queue.tqe_next) {
1050944c0faSbouyer 		pa = VM_PAGE_TO_PHYS(pg);
1060944c0faSbouyer 		mfn = xpmap_ptom(pa) >> PAGE_SHIFT;
1074b5a093dSjym 		xpmap_ptom_unmap(pa);
1086f78ccc1Sjym 		set_xen_guest_handle(res.extent_start, &mfn);
1098f162b7eSbouyer 		res.nr_extents = 1;
1108f162b7eSbouyer 		res.extent_order = 0;
111e1b03486Sjdolecek 		res.mem_flags = 0;
1128f162b7eSbouyer 		res.domid = DOMID_SELF;
11313da824dSjym 		error = HYPERVISOR_memory_op(XENMEM_decrease_reservation, &res);
11413da824dSjym 		if (error != 1) {
115caeb885aSbouyer #ifdef DEBUG
1168f162b7eSbouyer 			printf("xen_alloc_contig: XENMEM_decrease_reservation "
1174077310cSjym 			    "failed: err %d (pa %#" PRIxPADDR " mfn %#lx)\n",
1184077310cSjym 			    error, pa, mfn);
119caeb885aSbouyer #endif
1204b5a093dSjym 			xpmap_ptom_map(pa, ptoa(mfn));
121aeeb0b33Sbouyer 
122aeeb0b33Sbouyer 			error = ENOMEM;
123aeeb0b33Sbouyer 			goto failed;
1248f162b7eSbouyer 		}
1250944c0faSbouyer 	}
1260944c0faSbouyer 	/* Get the new contiguous memory extent */
1276f78ccc1Sjym 	set_xen_guest_handle(res.extent_start, &mfn);
1288f162b7eSbouyer 	res.nr_extents = 1;
1298f162b7eSbouyer 	res.extent_order = order;
130e1b03486Sjdolecek 	res.mem_flags = XENMEMF_address_bits(get_order(high) + PAGE_SHIFT);
1318f162b7eSbouyer 	res.domid = DOMID_SELF;
132e89f6402Sbouyer 	error = HYPERVISOR_memory_op(XENMEM_increase_reservation, &res);
133e89f6402Sbouyer 	if (error != 1) {
134caeb885aSbouyer #ifdef DEBUG
1358f162b7eSbouyer 		printf("xen_alloc_contig: XENMEM_increase_reservation "
136e1b03486Sjdolecek 		    "failed: %d (order %d mem_flags %d)\n",
137e1b03486Sjdolecek 		    error, order, res.mem_flags);
138caeb885aSbouyer #endif
139aeeb0b33Sbouyer 		error = ENOMEM;
140aeeb0b33Sbouyer 		pg = NULL;
141aeeb0b33Sbouyer 		goto failed;
1428f162b7eSbouyer 	}
1438c3f6558Sjdolecek 	s = splvm(); /* XXXSMP */
1440944c0faSbouyer 	/* Map the new extent in place of the old pages */
1450944c0faSbouyer 	for (pg = mlistp->tqh_first, i = 0; pg != NULL; pg = pgnext, i++) {
14606c343acSad 		pgnext = pg->pageq.queue.tqe_next;
1470944c0faSbouyer 		pa = VM_PAGE_TO_PHYS(pg);
1484b5a093dSjym 		xpmap_ptom_map(pa, ptoa(mfn+i));
14951023c61Scegger 		xpq_queue_machphys_update(((paddr_t)(mfn+i)) << PAGE_SHIFT, pa);
1503705635cSbouyer 	}
1513705635cSbouyer 	/* Flush updates through and flush the TLB */
1523705635cSbouyer 	xpq_queue_tlb_flush();
1533705635cSbouyer 	splx(s);
1543705635cSbouyer 	/* now that ptom/mtop are valid, give the extra pages back to UVM */
1553705635cSbouyer 	for (pg = mlistp->tqh_first, i = 0; pg != NULL; pg = pgnext, i++) {
1563705635cSbouyer 		pgnext = pg->pageq.queue.tqe_next;
1570944c0faSbouyer 		if (i >= npagesreq) {
15806c343acSad 			TAILQ_REMOVE(mlistp, pg, pageq.queue);
1590944c0faSbouyer 			uvm_pagefree(pg);
1600944c0faSbouyer 		}
1610944c0faSbouyer 	}
1620944c0faSbouyer 	return 0;
163aeeb0b33Sbouyer 
164aeeb0b33Sbouyer failed:
165aeeb0b33Sbouyer 	/*
166aeeb0b33Sbouyer 	 * Attempt to recover from a failed decrease or increase reservation:
167aeeb0b33Sbouyer 	 * if decrease_reservation failed, we don't have given all pages
168aeeb0b33Sbouyer 	 * back to Xen; give them back to UVM, and get the missing pages
169aeeb0b33Sbouyer 	 * from Xen.
170aeeb0b33Sbouyer 	 * if increase_reservation failed, we expect pg to be NULL and we just
171aeeb0b33Sbouyer 	 * get back the missing pages from Xen one by one.
172aeeb0b33Sbouyer 	 */
173aeeb0b33Sbouyer 	/* give back remaining pages to UVM */
174aeeb0b33Sbouyer 	for (; pg != NULL; pg = pgnext) {
17506c343acSad 		pgnext = pg->pageq.queue.tqe_next;
17606c343acSad 		TAILQ_REMOVE(mlistp, pg, pageq.queue);
177aeeb0b33Sbouyer 		uvm_pagefree(pg);
178aeeb0b33Sbouyer 	}
179aeeb0b33Sbouyer 	/* remplace the pages that we already gave to Xen */
1808c3f6558Sjdolecek 	s = splvm(); /* XXXSMP */
181aeeb0b33Sbouyer 	for (pg = mlistp->tqh_first; pg != NULL; pg = pgnext) {
18206c343acSad 		pgnext = pg->pageq.queue.tqe_next;
1836f78ccc1Sjym 		set_xen_guest_handle(res.extent_start, &mfn);
184aeeb0b33Sbouyer 		res.nr_extents = 1;
185aeeb0b33Sbouyer 		res.extent_order = 0;
186e1b03486Sjdolecek 		res.mem_flags = XENMEMF_address_bits(32);
187aeeb0b33Sbouyer 		res.domid = DOMID_SELF;
188aeeb0b33Sbouyer 		if (HYPERVISOR_memory_op(XENMEM_increase_reservation, &res)
189aeeb0b33Sbouyer 		    < 0) {
190aeeb0b33Sbouyer 			printf("xen_alloc_contig: recovery "
191aeeb0b33Sbouyer 			    "XENMEM_increase_reservation failed!\n");
192aeeb0b33Sbouyer 			break;
193aeeb0b33Sbouyer 		}
194aeeb0b33Sbouyer 		pa = VM_PAGE_TO_PHYS(pg);
1954b5a093dSjym 		xpmap_ptom_map(pa, ptoa(mfn));
19651023c61Scegger 		xpq_queue_machphys_update(((paddr_t)mfn) << PAGE_SHIFT, pa);
1973705635cSbouyer 		/* slow but we don't care */
1983705635cSbouyer 		xpq_queue_tlb_flush();
19906c343acSad 		TAILQ_REMOVE(mlistp, pg, pageq.queue);
200aeeb0b33Sbouyer 		uvm_pagefree(pg);
201aeeb0b33Sbouyer 	}
202aeeb0b33Sbouyer 	splx(s);
203aeeb0b33Sbouyer 	return error;
2040944c0faSbouyer }
2050944c0faSbouyer 
2060944c0faSbouyer 
2070944c0faSbouyer /*
2080944c0faSbouyer  * Allocate physical memory from the given physical address range.
2090944c0faSbouyer  * Called by DMA-safe memory allocation methods.
2100944c0faSbouyer  * We need our own version to deal with physical vs machine addresses.
2110944c0faSbouyer  */
2120944c0faSbouyer int
_xen_bus_dmamem_alloc_range(bus_dma_tag_t t,bus_size_t size,bus_size_t alignment,bus_size_t boundary,bus_dma_segment_t * segs,int nsegs,int * rsegs,int flags,bus_addr_t low,bus_addr_t high)2130944c0faSbouyer _xen_bus_dmamem_alloc_range(bus_dma_tag_t t, bus_size_t size,
2140944c0faSbouyer     bus_size_t alignment, bus_size_t boundary, bus_dma_segment_t *segs,
2150944c0faSbouyer     int nsegs, int *rsegs, int flags, bus_addr_t low, bus_addr_t high)
2160944c0faSbouyer {
217aeeb0b33Sbouyer 	bus_addr_t curaddr, lastaddr;
2180944c0faSbouyer 	struct vm_page *m;
2190944c0faSbouyer 	struct pglist mlist;
2200944c0faSbouyer 	int curseg, error;
2210944c0faSbouyer 	int doingrealloc = 0;
2225b2d9a3cSbouyer 	bus_size_t uboundary;
2230944c0faSbouyer 
2240944c0faSbouyer 	/* Always round the size. */
2250944c0faSbouyer 	size = round_page(size);
2260944c0faSbouyer 
227cd1cc781Sbouyer 	KASSERT((alignment & (alignment - 1)) == 0);
228cd1cc781Sbouyer 	KASSERT((boundary & (boundary - 1)) == 0);
2295b2d9a3cSbouyer 	KASSERT(boundary >= PAGE_SIZE || boundary == 0);
2305b2d9a3cSbouyer 
231cd1cc781Sbouyer 	if (alignment < PAGE_SIZE)
232cd1cc781Sbouyer 		alignment = PAGE_SIZE;
233cd1cc781Sbouyer 
2340944c0faSbouyer 	/*
2350944c0faSbouyer 	 * Allocate pages from the VM system.
2365b2d9a3cSbouyer 	 * We accept boundaries < size, splitting in multiple segments
2375b2d9a3cSbouyer 	 * if needed. uvm_pglistalloc does not, so compute an appropriate
2385b2d9a3cSbouyer 	 * boundary: next power of 2 >= size
2390944c0faSbouyer 	 */
2405b2d9a3cSbouyer 	if (boundary == 0)
2415b2d9a3cSbouyer 		uboundary = 0;
2425b2d9a3cSbouyer 	else {
2435b2d9a3cSbouyer 		uboundary = boundary;
2445b2d9a3cSbouyer 		while (uboundary < size)
2455b2d9a3cSbouyer 			uboundary = uboundary << 1;
2465b2d9a3cSbouyer 	}
2475b2d9a3cSbouyer 	error = uvm_pglistalloc(size, 0, avail_end, alignment, uboundary,
2480944c0faSbouyer 	    &mlist, nsegs, (flags & BUS_DMA_NOWAIT) == 0);
2490944c0faSbouyer 	if (error)
2500944c0faSbouyer 		return (error);
2510944c0faSbouyer again:
2520944c0faSbouyer 
2530944c0faSbouyer 	/*
2540944c0faSbouyer 	 * Compute the location, size, and number of segments actually
2550944c0faSbouyer 	 * returned by the VM code.
2560944c0faSbouyer 	 */
2570944c0faSbouyer 	m = mlist.tqh_first;
2580944c0faSbouyer 	curseg = 0;
259dd27ab28Sbouyer 	curaddr = lastaddr = segs[curseg].ds_addr = _BUS_VM_PAGE_TO_BUS(m);
260dd27ab28Sbouyer 	if (curaddr < low || curaddr >= high)
261dd27ab28Sbouyer 		goto badaddr;
2620944c0faSbouyer 	segs[curseg].ds_len = PAGE_SIZE;
26306c343acSad 	m = m->pageq.queue.tqe_next;
264aeeb0b33Sbouyer 	if ((segs[curseg].ds_addr & (alignment - 1)) != 0)
265cd1cc781Sbouyer 		goto dorealloc;
2660944c0faSbouyer 
26706c343acSad 	for (; m != NULL; m = m->pageq.queue.tqe_next) {
268aeeb0b33Sbouyer 		curaddr = _BUS_VM_PAGE_TO_BUS(m);
269dd27ab28Sbouyer 		if (curaddr < low || curaddr >= high)
270dd27ab28Sbouyer 			goto badaddr;
2715b2d9a3cSbouyer 		if (curaddr == (lastaddr + PAGE_SIZE) &&
2725b2d9a3cSbouyer 		    (lastaddr & boundary) == (curaddr & boundary)) {
273dd27ab28Sbouyer 			segs[curseg].ds_len += PAGE_SIZE;
274dd27ab28Sbouyer 		} else {
275dd27ab28Sbouyer 			curseg++;
2765b2d9a3cSbouyer 			if (curseg >= nsegs ||
2775b2d9a3cSbouyer 			    (curaddr & (alignment - 1)) != 0) {
2785b2d9a3cSbouyer 				if (doingrealloc)
2795b2d9a3cSbouyer 					return EFBIG;
2805b2d9a3cSbouyer 				else
281dd27ab28Sbouyer 					goto dorealloc;
2825b2d9a3cSbouyer 			}
283dd27ab28Sbouyer 			segs[curseg].ds_addr = curaddr;
284dd27ab28Sbouyer 			segs[curseg].ds_len = PAGE_SIZE;
285dd27ab28Sbouyer 		}
286dd27ab28Sbouyer 		lastaddr = curaddr;
287dd27ab28Sbouyer 	}
288dd27ab28Sbouyer 
289dd27ab28Sbouyer 	*rsegs = curseg + 1;
290dd27ab28Sbouyer 	return (0);
291dd27ab28Sbouyer 
292dd27ab28Sbouyer badaddr:
293dd27ab28Sbouyer 	if (doingrealloc == 0)
294dd27ab28Sbouyer 		goto dorealloc;
295dd27ab28Sbouyer 	if (curaddr < low) {
296dd27ab28Sbouyer 		/* no way to enforce this */
297dd27ab28Sbouyer 		printf("_xen_bus_dmamem_alloc_range: no way to "
298e89f6402Sbouyer 		    "enforce address range (0x%" PRIx64 " - 0x%" PRIx64 ")\n",
299e89f6402Sbouyer 		    (uint64_t)low, (uint64_t)high);
300dd27ab28Sbouyer 		uvm_pglistfree(&mlist);
301dd27ab28Sbouyer 		return EINVAL;
302dd27ab28Sbouyer 	}
303dd27ab28Sbouyer 	printf("xen_bus_dmamem_alloc_range: "
304dd27ab28Sbouyer 	    "curraddr=0x%lx > high=0x%lx\n",
305dd27ab28Sbouyer 	    (u_long)curaddr, (u_long)high);
306dd27ab28Sbouyer 	panic("xen_bus_dmamem_alloc_range 1");
307cd1cc781Sbouyer dorealloc:
3080944c0faSbouyer 	if (doingrealloc == 1)
3090944c0faSbouyer 		panic("_xen_bus_dmamem_alloc_range: "
3100944c0faSbouyer 		   "xen_alloc_contig returned "
3110944c0faSbouyer 		   "too much segments");
3120944c0faSbouyer 	doingrealloc = 1;
3130944c0faSbouyer 	/*
314dd27ab28Sbouyer 	 * Too much segments, or memory doesn't fit
315dd27ab28Sbouyer 	 * constraints. Free this memory and
316*e4ebea9eSandvar 	 * get a contiguous segment from the hypervisor.
3170944c0faSbouyer 	 */
3180944c0faSbouyer 	uvm_pglistfree(&mlist);
3190944c0faSbouyer 	for (curseg = 0; curseg < nsegs; curseg++) {
3200944c0faSbouyer 		segs[curseg].ds_addr = 0;
3210944c0faSbouyer 		segs[curseg].ds_len = 0;
3220944c0faSbouyer 	}
3230944c0faSbouyer 	error = _xen_alloc_contig(size, alignment,
3245b2d9a3cSbouyer 	    &mlist, flags, low, high);
3250944c0faSbouyer 	if (error)
3260944c0faSbouyer 		return error;
3270944c0faSbouyer 	goto again;
3280944c0faSbouyer }
329c24c993fSbouyer #endif /* XENPV */
330