Home | History | Annotate | Line # | Download | only in x86
xen_bus_dma.c revision 1.27.2.2
      1 /*	$NetBSD: xen_bus_dma.c,v 1.27.2.2 2020/04/13 08:04:12 martin Exp $	*/
      2 /*	NetBSD bus_dma.c,v 1.21 2005/04/16 07:53:35 yamt Exp */
      3 
      4 /*-
      5  * Copyright (c) 1996, 1997, 1998 The NetBSD Foundation, Inc.
      6  * All rights reserved.
      7  *
      8  * This code is derived from software contributed to The NetBSD Foundation
      9  * by Charles M. Hannum and by Jason R. Thorpe of the Numerical Aerospace
     10  * Simulation Facility, NASA Ames Research Center.
     11  *
     12  * Redistribution and use in source and binary forms, with or without
     13  * modification, are permitted provided that the following conditions
     14  * are met:
     15  * 1. Redistributions of source code must retain the above copyright
     16  *    notice, this list of conditions and the following disclaimer.
     17  * 2. Redistributions in binary form must reproduce the above copyright
     18  *    notice, this list of conditions and the following disclaimer in the
     19  *    documentation and/or other materials provided with the distribution.
     20  *
     21  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     23  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     24  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     25  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     28  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     29  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     31  * POSSIBILITY OF SUCH DAMAGE.
     32  */
     33 
     34 #include <sys/cdefs.h>
     35 __KERNEL_RCSID(0, "$NetBSD: xen_bus_dma.c,v 1.27.2.2 2020/04/13 08:04:12 martin Exp $");
     36 
     37 #include <sys/param.h>
     38 #include <sys/systm.h>
     39 #include <sys/kernel.h>
     40 #include <sys/mbuf.h>
     41 #include <sys/proc.h>
     42 
     43 #include <sys/bus.h>
     44 #include <machine/bus_private.h>
     45 
     46 #include <uvm/uvm.h>
     47 
     48 extern paddr_t avail_end;
     49 
     50 /* No special needs */
     51 struct x86_bus_dma_tag xenbus_bus_dma_tag = {
     52 	._tag_needs_free	= 0,
     53 	._bounce_thresh		= 0,
     54 	._bounce_alloc_lo	= 0,
     55 	._bounce_alloc_hi	= 0,
     56 	._may_bounce		= NULL,
     57 };
     58 
     59 /* Pure 2^n version of get_order */
     60 static inline int get_order(unsigned long size)
     61 {
     62 	int order = -1;
     63 	size = (size - 1) >> (PAGE_SHIFT - 1);
     64 	do {
     65 		size >>= 1;
     66 		order++;
     67 	} while (size);
     68 	return order;
     69 }
     70 
     71 static int
     72 _xen_alloc_contig(bus_size_t size, bus_size_t alignment,
     73     struct pglist *mlistp, int flags, bus_addr_t low, bus_addr_t high)
     74 {
     75 	int order, i;
     76 	unsigned long npagesreq, npages, mfn;
     77 	bus_addr_t pa;
     78 	struct vm_page *pg, *pgnext;
     79 	int s, error;
     80 	struct xen_memory_reservation res;
     81 
     82 	/*
     83 	 * When requesting a contigous memory region, the hypervisor will
     84 	 * return a memory range aligned on size.
     85 	 * The only way to enforce alignment is to request a memory region
     86 	 * of size max(alignment, size).
     87 	 */
     88 	order = uimax(get_order(size), get_order(alignment));
     89 	npages = (1 << order);
     90 	npagesreq = (size >> PAGE_SHIFT);
     91 	KASSERT(npages >= npagesreq);
     92 
     93 	/* get npages from UVM, and give them back to the hypervisor */
     94 	error = uvm_pglistalloc(((psize_t)npages) << PAGE_SHIFT,
     95             0, avail_end, 0, 0, mlistp, npages, (flags & BUS_DMA_NOWAIT) == 0);
     96 	if (error)
     97 		return (error);
     98 
     99 	for (pg = mlistp->tqh_first; pg != NULL; pg = pg->pageq.queue.tqe_next) {
    100 		pa = VM_PAGE_TO_PHYS(pg);
    101 		mfn = xpmap_ptom(pa) >> PAGE_SHIFT;
    102 		xpmap_ptom_unmap(pa);
    103 		set_xen_guest_handle(res.extent_start, &mfn);
    104 		res.nr_extents = 1;
    105 		res.extent_order = 0;
    106 		res.mem_flags = 0;
    107 		res.domid = DOMID_SELF;
    108 		error = HYPERVISOR_memory_op(XENMEM_decrease_reservation, &res);
    109 		if (error != 1) {
    110 #ifdef DEBUG
    111 			printf("xen_alloc_contig: XENMEM_decrease_reservation "
    112 			    "failed: err %d (pa %#" PRIxPADDR " mfn %#lx)\n",
    113 			    error, pa, mfn);
    114 #endif
    115 			xpmap_ptom_map(pa, ptoa(mfn));
    116 
    117 			error = ENOMEM;
    118 			goto failed;
    119 		}
    120 	}
    121 	/* Get the new contiguous memory extent */
    122 	set_xen_guest_handle(res.extent_start, &mfn);
    123 	res.nr_extents = 1;
    124 	res.extent_order = order;
    125 	res.mem_flags = XENMEMF_address_bits(get_order(high) + PAGE_SHIFT);
    126 	res.domid = DOMID_SELF;
    127 	error = HYPERVISOR_memory_op(XENMEM_increase_reservation, &res);
    128 	if (error != 1) {
    129 #ifdef DEBUG
    130 		printf("xen_alloc_contig: XENMEM_increase_reservation "
    131 		    "failed: %d (order %d mem_flags %d)\n",
    132 		    error, order, res.mem_flags);
    133 #endif
    134 		error = ENOMEM;
    135 		pg = NULL;
    136 		goto failed;
    137 	}
    138 	s = splvm(); /* XXXSMP */
    139 	/* Map the new extent in place of the old pages */
    140 	for (pg = mlistp->tqh_first, i = 0; pg != NULL; pg = pgnext, i++) {
    141 		pgnext = pg->pageq.queue.tqe_next;
    142 		pa = VM_PAGE_TO_PHYS(pg);
    143 		xpmap_ptom_map(pa, ptoa(mfn+i));
    144 		xpq_queue_machphys_update(((paddr_t)(mfn+i)) << PAGE_SHIFT, pa);
    145 		/* while here, give extra pages back to UVM */
    146 		if (i >= npagesreq) {
    147 			TAILQ_REMOVE(mlistp, pg, pageq.queue);
    148 			uvm_pagefree(pg);
    149 		}
    150 	}
    151 	/* Flush updates through and flush the TLB */
    152 	xpq_queue_tlb_flush();
    153 	splx(s);
    154 	return 0;
    155 
    156 failed:
    157 	/*
    158 	 * Attempt to recover from a failed decrease or increase reservation:
    159 	 * if decrease_reservation failed, we don't have given all pages
    160 	 * back to Xen; give them back to UVM, and get the missing pages
    161 	 * from Xen.
    162 	 * if increase_reservation failed, we expect pg to be NULL and we just
    163 	 * get back the missing pages from Xen one by one.
    164 	 */
    165 	/* give back remaining pages to UVM */
    166 	for (; pg != NULL; pg = pgnext) {
    167 		pgnext = pg->pageq.queue.tqe_next;
    168 		TAILQ_REMOVE(mlistp, pg, pageq.queue);
    169 		uvm_pagefree(pg);
    170 	}
    171 	/* remplace the pages that we already gave to Xen */
    172 	s = splvm(); /* XXXSMP */
    173 	for (pg = mlistp->tqh_first; pg != NULL; pg = pgnext) {
    174 		pgnext = pg->pageq.queue.tqe_next;
    175 		set_xen_guest_handle(res.extent_start, &mfn);
    176 		res.nr_extents = 1;
    177 		res.extent_order = 0;
    178 		res.mem_flags = XENMEMF_address_bits(32);
    179 		res.domid = DOMID_SELF;
    180 		if (HYPERVISOR_memory_op(XENMEM_increase_reservation, &res)
    181 		    < 0) {
    182 			printf("xen_alloc_contig: recovery "
    183 			    "XENMEM_increase_reservation failed!\n");
    184 			break;
    185 		}
    186 		pa = VM_PAGE_TO_PHYS(pg);
    187 		xpmap_ptom_map(pa, ptoa(mfn));
    188 		xpq_queue_machphys_update(((paddr_t)mfn) << PAGE_SHIFT, pa);
    189 		TAILQ_REMOVE(mlistp, pg, pageq.queue);
    190 		uvm_pagefree(pg);
    191 	}
    192 	/* Flush updates through and flush the TLB */
    193 	xpq_queue_tlb_flush();
    194 	splx(s);
    195 	return error;
    196 }
    197 
    198 
    199 /*
    200  * Allocate physical memory from the given physical address range.
    201  * Called by DMA-safe memory allocation methods.
    202  * We need our own version to deal with physical vs machine addresses.
    203  */
    204 int
    205 _xen_bus_dmamem_alloc_range(bus_dma_tag_t t, bus_size_t size,
    206     bus_size_t alignment, bus_size_t boundary, bus_dma_segment_t *segs,
    207     int nsegs, int *rsegs, int flags, bus_addr_t low, bus_addr_t high)
    208 {
    209 	bus_addr_t curaddr, lastaddr;
    210 	struct vm_page *m;
    211 	struct pglist mlist;
    212 	int curseg, error;
    213 	int doingrealloc = 0;
    214 	bus_size_t uboundary;
    215 
    216 	/* Always round the size. */
    217 	size = round_page(size);
    218 
    219 	KASSERT((alignment & (alignment - 1)) == 0);
    220 	KASSERT((boundary & (boundary - 1)) == 0);
    221 	KASSERT(boundary >= PAGE_SIZE || boundary == 0);
    222 
    223 	if (alignment < PAGE_SIZE)
    224 		alignment = PAGE_SIZE;
    225 
    226 	/*
    227 	 * Allocate pages from the VM system.
    228 	 * We accept boundaries < size, splitting in multiple segments
    229 	 * if needed. uvm_pglistalloc does not, so compute an appropriate
    230 	 * boundary: next power of 2 >= size
    231 	 */
    232 	if (boundary == 0)
    233 		uboundary = 0;
    234 	else {
    235 		uboundary = boundary;
    236 		while (uboundary < size)
    237 			uboundary = uboundary << 1;
    238 	}
    239 	error = uvm_pglistalloc(size, 0, avail_end, alignment, uboundary,
    240 	    &mlist, nsegs, (flags & BUS_DMA_NOWAIT) == 0);
    241 	if (error)
    242 		return (error);
    243 again:
    244 
    245 	/*
    246 	 * Compute the location, size, and number of segments actually
    247 	 * returned by the VM code.
    248 	 */
    249 	m = mlist.tqh_first;
    250 	curseg = 0;
    251 	curaddr = lastaddr = segs[curseg].ds_addr = _BUS_VM_PAGE_TO_BUS(m);
    252 	if (curaddr < low || curaddr >= high)
    253 		goto badaddr;
    254 	segs[curseg].ds_len = PAGE_SIZE;
    255 	m = m->pageq.queue.tqe_next;
    256 	if ((segs[curseg].ds_addr & (alignment - 1)) != 0)
    257 		goto dorealloc;
    258 
    259 	for (; m != NULL; m = m->pageq.queue.tqe_next) {
    260 		curaddr = _BUS_VM_PAGE_TO_BUS(m);
    261 		if (curaddr < low || curaddr >= high)
    262 			goto badaddr;
    263 		if (curaddr == (lastaddr + PAGE_SIZE) &&
    264 		    (lastaddr & boundary) == (curaddr & boundary)) {
    265 			segs[curseg].ds_len += PAGE_SIZE;
    266 		} else {
    267 			curseg++;
    268 			if (curseg >= nsegs ||
    269 			    (curaddr & (alignment - 1)) != 0) {
    270 				if (doingrealloc)
    271 					return EFBIG;
    272 				else
    273 					goto dorealloc;
    274 			}
    275 			segs[curseg].ds_addr = curaddr;
    276 			segs[curseg].ds_len = PAGE_SIZE;
    277 		}
    278 		lastaddr = curaddr;
    279 	}
    280 
    281 	*rsegs = curseg + 1;
    282 	return (0);
    283 
    284 badaddr:
    285 	if (doingrealloc == 0)
    286 		goto dorealloc;
    287 	if (curaddr < low) {
    288 		/* no way to enforce this */
    289 		printf("_xen_bus_dmamem_alloc_range: no way to "
    290 		    "enforce address range (0x%" PRIx64 " - 0x%" PRIx64 ")\n",
    291 		    (uint64_t)low, (uint64_t)high);
    292 		uvm_pglistfree(&mlist);
    293 		return EINVAL;
    294 	}
    295 	printf("xen_bus_dmamem_alloc_range: "
    296 	    "curraddr=0x%lx > high=0x%lx\n",
    297 	    (u_long)curaddr, (u_long)high);
    298 	panic("xen_bus_dmamem_alloc_range 1");
    299 dorealloc:
    300 	if (doingrealloc == 1)
    301 		panic("_xen_bus_dmamem_alloc_range: "
    302 		   "xen_alloc_contig returned "
    303 		   "too much segments");
    304 	doingrealloc = 1;
    305 	/*
    306 	 * Too much segments, or memory doesn't fit
    307 	 * constraints. Free this memory and
    308 	 * get a contigous segment from the hypervisor.
    309 	 */
    310 	uvm_pglistfree(&mlist);
    311 	for (curseg = 0; curseg < nsegs; curseg++) {
    312 		segs[curseg].ds_addr = 0;
    313 		segs[curseg].ds_len = 0;
    314 	}
    315 	error = _xen_alloc_contig(size, alignment,
    316 	    &mlist, flags, low, high);
    317 	if (error)
    318 		return error;
    319 	goto again;
    320 }
    321