xen_bus_dma.c revision 1.31 1 /* $NetBSD: xen_bus_dma.c,v 1.31 2020/04/25 15:26:17 bouyer 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.31 2020/04/25 15:26:17 bouyer 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 #include "opt_xen.h"
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 #ifdef XENPV
60
61 extern paddr_t avail_end;
62
63 /* Pure 2^n version of get_order */
64 static inline int get_order(unsigned long size)
65 {
66 int order = -1;
67 size = (size - 1) >> (PAGE_SHIFT - 1);
68 do {
69 size >>= 1;
70 order++;
71 } while (size);
72 return order;
73 }
74
75 static int
76 _xen_alloc_contig(bus_size_t size, bus_size_t alignment,
77 struct pglist *mlistp, int flags, bus_addr_t low, bus_addr_t high)
78 {
79 int order, i;
80 unsigned long npagesreq, npages, mfn;
81 bus_addr_t pa;
82 struct vm_page *pg, *pgnext;
83 int s, error;
84 struct xen_memory_reservation res;
85
86 /*
87 * When requesting a contigous memory region, the hypervisor will
88 * return a memory range aligned on size.
89 * The only way to enforce alignment is to request a memory region
90 * of size max(alignment, size).
91 */
92 order = uimax(get_order(size), get_order(alignment));
93 npages = (1 << order);
94 npagesreq = (size >> PAGE_SHIFT);
95 KASSERT(npages >= npagesreq);
96
97 /* get npages from UVM, and give them back to the hypervisor */
98 error = uvm_pglistalloc(((psize_t)npages) << PAGE_SHIFT,
99 0, avail_end, 0, 0, mlistp, npages, (flags & BUS_DMA_NOWAIT) == 0);
100 if (error)
101 return (error);
102
103 for (pg = mlistp->tqh_first; pg != NULL; pg = pg->pageq.queue.tqe_next) {
104 pa = VM_PAGE_TO_PHYS(pg);
105 mfn = xpmap_ptom(pa) >> PAGE_SHIFT;
106 xpmap_ptom_unmap(pa);
107 set_xen_guest_handle(res.extent_start, &mfn);
108 res.nr_extents = 1;
109 res.extent_order = 0;
110 res.mem_flags = 0;
111 res.domid = DOMID_SELF;
112 error = HYPERVISOR_memory_op(XENMEM_decrease_reservation, &res);
113 if (error != 1) {
114 #ifdef DEBUG
115 printf("xen_alloc_contig: XENMEM_decrease_reservation "
116 "failed: err %d (pa %#" PRIxPADDR " mfn %#lx)\n",
117 error, pa, mfn);
118 #endif
119 xpmap_ptom_map(pa, ptoa(mfn));
120
121 error = ENOMEM;
122 goto failed;
123 }
124 }
125 /* Get the new contiguous memory extent */
126 set_xen_guest_handle(res.extent_start, &mfn);
127 res.nr_extents = 1;
128 res.extent_order = order;
129 res.mem_flags = XENMEMF_address_bits(get_order(high) + PAGE_SHIFT);
130 res.domid = DOMID_SELF;
131 error = HYPERVISOR_memory_op(XENMEM_increase_reservation, &res);
132 if (error != 1) {
133 #ifdef DEBUG
134 printf("xen_alloc_contig: XENMEM_increase_reservation "
135 "failed: %d (order %d mem_flags %d)\n",
136 error, order, res.mem_flags);
137 #endif
138 error = ENOMEM;
139 pg = NULL;
140 goto failed;
141 }
142 s = splvm(); /* XXXSMP */
143 /* Map the new extent in place of the old pages */
144 for (pg = mlistp->tqh_first, i = 0; pg != NULL; pg = pgnext, i++) {
145 pgnext = pg->pageq.queue.tqe_next;
146 pa = VM_PAGE_TO_PHYS(pg);
147 xpmap_ptom_map(pa, ptoa(mfn+i));
148 xpq_queue_machphys_update(((paddr_t)(mfn+i)) << PAGE_SHIFT, pa);
149 /* while here, give extra pages back to UVM */
150 if (i >= npagesreq) {
151 TAILQ_REMOVE(mlistp, pg, pageq.queue);
152 uvm_pagefree(pg);
153 }
154 }
155 /* Flush updates through and flush the TLB */
156 xpq_queue_tlb_flush();
157 splx(s);
158 return 0;
159
160 failed:
161 /*
162 * Attempt to recover from a failed decrease or increase reservation:
163 * if decrease_reservation failed, we don't have given all pages
164 * back to Xen; give them back to UVM, and get the missing pages
165 * from Xen.
166 * if increase_reservation failed, we expect pg to be NULL and we just
167 * get back the missing pages from Xen one by one.
168 */
169 /* give back remaining pages to UVM */
170 for (; pg != NULL; pg = pgnext) {
171 pgnext = pg->pageq.queue.tqe_next;
172 TAILQ_REMOVE(mlistp, pg, pageq.queue);
173 uvm_pagefree(pg);
174 }
175 /* remplace the pages that we already gave to Xen */
176 s = splvm(); /* XXXSMP */
177 for (pg = mlistp->tqh_first; pg != NULL; pg = pgnext) {
178 pgnext = pg->pageq.queue.tqe_next;
179 set_xen_guest_handle(res.extent_start, &mfn);
180 res.nr_extents = 1;
181 res.extent_order = 0;
182 res.mem_flags = XENMEMF_address_bits(32);
183 res.domid = DOMID_SELF;
184 if (HYPERVISOR_memory_op(XENMEM_increase_reservation, &res)
185 < 0) {
186 printf("xen_alloc_contig: recovery "
187 "XENMEM_increase_reservation failed!\n");
188 break;
189 }
190 pa = VM_PAGE_TO_PHYS(pg);
191 xpmap_ptom_map(pa, ptoa(mfn));
192 xpq_queue_machphys_update(((paddr_t)mfn) << PAGE_SHIFT, pa);
193 TAILQ_REMOVE(mlistp, pg, pageq.queue);
194 uvm_pagefree(pg);
195 }
196 /* Flush updates through and flush the TLB */
197 xpq_queue_tlb_flush();
198 splx(s);
199 return error;
200 }
201
202
203 /*
204 * Allocate physical memory from the given physical address range.
205 * Called by DMA-safe memory allocation methods.
206 * We need our own version to deal with physical vs machine addresses.
207 */
208 int
209 _xen_bus_dmamem_alloc_range(bus_dma_tag_t t, bus_size_t size,
210 bus_size_t alignment, bus_size_t boundary, bus_dma_segment_t *segs,
211 int nsegs, int *rsegs, int flags, bus_addr_t low, bus_addr_t high)
212 {
213 bus_addr_t curaddr, lastaddr;
214 struct vm_page *m;
215 struct pglist mlist;
216 int curseg, error;
217 int doingrealloc = 0;
218 bus_size_t uboundary;
219
220 /* Always round the size. */
221 size = round_page(size);
222
223 KASSERT((alignment & (alignment - 1)) == 0);
224 KASSERT((boundary & (boundary - 1)) == 0);
225 KASSERT(boundary >= PAGE_SIZE || boundary == 0);
226
227 if (alignment < PAGE_SIZE)
228 alignment = PAGE_SIZE;
229
230 /*
231 * Allocate pages from the VM system.
232 * We accept boundaries < size, splitting in multiple segments
233 * if needed. uvm_pglistalloc does not, so compute an appropriate
234 * boundary: next power of 2 >= size
235 */
236 if (boundary == 0)
237 uboundary = 0;
238 else {
239 uboundary = boundary;
240 while (uboundary < size)
241 uboundary = uboundary << 1;
242 }
243 error = uvm_pglistalloc(size, 0, avail_end, alignment, uboundary,
244 &mlist, nsegs, (flags & BUS_DMA_NOWAIT) == 0);
245 if (error)
246 return (error);
247 again:
248
249 /*
250 * Compute the location, size, and number of segments actually
251 * returned by the VM code.
252 */
253 m = mlist.tqh_first;
254 curseg = 0;
255 curaddr = lastaddr = segs[curseg].ds_addr = _BUS_VM_PAGE_TO_BUS(m);
256 if (curaddr < low || curaddr >= high)
257 goto badaddr;
258 segs[curseg].ds_len = PAGE_SIZE;
259 m = m->pageq.queue.tqe_next;
260 if ((segs[curseg].ds_addr & (alignment - 1)) != 0)
261 goto dorealloc;
262
263 for (; m != NULL; m = m->pageq.queue.tqe_next) {
264 curaddr = _BUS_VM_PAGE_TO_BUS(m);
265 if (curaddr < low || curaddr >= high)
266 goto badaddr;
267 if (curaddr == (lastaddr + PAGE_SIZE) &&
268 (lastaddr & boundary) == (curaddr & boundary)) {
269 segs[curseg].ds_len += PAGE_SIZE;
270 } else {
271 curseg++;
272 if (curseg >= nsegs ||
273 (curaddr & (alignment - 1)) != 0) {
274 if (doingrealloc)
275 return EFBIG;
276 else
277 goto dorealloc;
278 }
279 segs[curseg].ds_addr = curaddr;
280 segs[curseg].ds_len = PAGE_SIZE;
281 }
282 lastaddr = curaddr;
283 }
284
285 *rsegs = curseg + 1;
286 return (0);
287
288 badaddr:
289 if (doingrealloc == 0)
290 goto dorealloc;
291 if (curaddr < low) {
292 /* no way to enforce this */
293 printf("_xen_bus_dmamem_alloc_range: no way to "
294 "enforce address range (0x%" PRIx64 " - 0x%" PRIx64 ")\n",
295 (uint64_t)low, (uint64_t)high);
296 uvm_pglistfree(&mlist);
297 return EINVAL;
298 }
299 printf("xen_bus_dmamem_alloc_range: "
300 "curraddr=0x%lx > high=0x%lx\n",
301 (u_long)curaddr, (u_long)high);
302 panic("xen_bus_dmamem_alloc_range 1");
303 dorealloc:
304 if (doingrealloc == 1)
305 panic("_xen_bus_dmamem_alloc_range: "
306 "xen_alloc_contig returned "
307 "too much segments");
308 doingrealloc = 1;
309 /*
310 * Too much segments, or memory doesn't fit
311 * constraints. Free this memory and
312 * get a contigous segment from the hypervisor.
313 */
314 uvm_pglistfree(&mlist);
315 for (curseg = 0; curseg < nsegs; curseg++) {
316 segs[curseg].ds_addr = 0;
317 segs[curseg].ds_len = 0;
318 }
319 error = _xen_alloc_contig(size, alignment,
320 &mlist, flags, low, high);
321 if (error)
322 return error;
323 goto again;
324 }
325 #endif /* XENPV */
326