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