xen_bus_dma.c revision 1.32 1 /* $NetBSD: xen_bus_dma.c,v 1.32 2020/05/06 19:50:26 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.32 2020/05/06 19:50:26 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 }
150 /* Flush updates through and flush the TLB */
151 xpq_queue_tlb_flush();
152 splx(s);
153 /* now that ptom/mtop are valid, give the extra pages back to UVM */
154 for (pg = mlistp->tqh_first, i = 0; pg != NULL; pg = pgnext, i++) {
155 pgnext = pg->pageq.queue.tqe_next;
156 if (i >= npagesreq) {
157 TAILQ_REMOVE(mlistp, pg, pageq.queue);
158 uvm_pagefree(pg);
159 }
160 }
161 return 0;
162
163 failed:
164 /*
165 * Attempt to recover from a failed decrease or increase reservation:
166 * if decrease_reservation failed, we don't have given all pages
167 * back to Xen; give them back to UVM, and get the missing pages
168 * from Xen.
169 * if increase_reservation failed, we expect pg to be NULL and we just
170 * get back the missing pages from Xen one by one.
171 */
172 /* give back remaining pages to UVM */
173 for (; pg != NULL; pg = pgnext) {
174 pgnext = pg->pageq.queue.tqe_next;
175 TAILQ_REMOVE(mlistp, pg, pageq.queue);
176 uvm_pagefree(pg);
177 }
178 /* remplace the pages that we already gave to Xen */
179 s = splvm(); /* XXXSMP */
180 for (pg = mlistp->tqh_first; pg != NULL; pg = pgnext) {
181 pgnext = pg->pageq.queue.tqe_next;
182 set_xen_guest_handle(res.extent_start, &mfn);
183 res.nr_extents = 1;
184 res.extent_order = 0;
185 res.mem_flags = XENMEMF_address_bits(32);
186 res.domid = DOMID_SELF;
187 if (HYPERVISOR_memory_op(XENMEM_increase_reservation, &res)
188 < 0) {
189 printf("xen_alloc_contig: recovery "
190 "XENMEM_increase_reservation failed!\n");
191 break;
192 }
193 pa = VM_PAGE_TO_PHYS(pg);
194 xpmap_ptom_map(pa, ptoa(mfn));
195 xpq_queue_machphys_update(((paddr_t)mfn) << PAGE_SHIFT, pa);
196 /* slow but we don't care */
197 xpq_queue_tlb_flush();
198 TAILQ_REMOVE(mlistp, pg, pageq.queue);
199 uvm_pagefree(pg);
200 }
201 splx(s);
202 return error;
203 }
204
205
206 /*
207 * Allocate physical memory from the given physical address range.
208 * Called by DMA-safe memory allocation methods.
209 * We need our own version to deal with physical vs machine addresses.
210 */
211 int
212 _xen_bus_dmamem_alloc_range(bus_dma_tag_t t, bus_size_t size,
213 bus_size_t alignment, bus_size_t boundary, bus_dma_segment_t *segs,
214 int nsegs, int *rsegs, int flags, bus_addr_t low, bus_addr_t high)
215 {
216 bus_addr_t curaddr, lastaddr;
217 struct vm_page *m;
218 struct pglist mlist;
219 int curseg, error;
220 int doingrealloc = 0;
221 bus_size_t uboundary;
222
223 /* Always round the size. */
224 size = round_page(size);
225
226 KASSERT((alignment & (alignment - 1)) == 0);
227 KASSERT((boundary & (boundary - 1)) == 0);
228 KASSERT(boundary >= PAGE_SIZE || boundary == 0);
229
230 if (alignment < PAGE_SIZE)
231 alignment = PAGE_SIZE;
232
233 /*
234 * Allocate pages from the VM system.
235 * We accept boundaries < size, splitting in multiple segments
236 * if needed. uvm_pglistalloc does not, so compute an appropriate
237 * boundary: next power of 2 >= size
238 */
239 if (boundary == 0)
240 uboundary = 0;
241 else {
242 uboundary = boundary;
243 while (uboundary < size)
244 uboundary = uboundary << 1;
245 }
246 error = uvm_pglistalloc(size, 0, avail_end, alignment, uboundary,
247 &mlist, nsegs, (flags & BUS_DMA_NOWAIT) == 0);
248 if (error)
249 return (error);
250 again:
251
252 /*
253 * Compute the location, size, and number of segments actually
254 * returned by the VM code.
255 */
256 m = mlist.tqh_first;
257 curseg = 0;
258 curaddr = lastaddr = segs[curseg].ds_addr = _BUS_VM_PAGE_TO_BUS(m);
259 if (curaddr < low || curaddr >= high)
260 goto badaddr;
261 segs[curseg].ds_len = PAGE_SIZE;
262 m = m->pageq.queue.tqe_next;
263 if ((segs[curseg].ds_addr & (alignment - 1)) != 0)
264 goto dorealloc;
265
266 for (; m != NULL; m = m->pageq.queue.tqe_next) {
267 curaddr = _BUS_VM_PAGE_TO_BUS(m);
268 if (curaddr < low || curaddr >= high)
269 goto badaddr;
270 if (curaddr == (lastaddr + PAGE_SIZE) &&
271 (lastaddr & boundary) == (curaddr & boundary)) {
272 segs[curseg].ds_len += PAGE_SIZE;
273 } else {
274 curseg++;
275 if (curseg >= nsegs ||
276 (curaddr & (alignment - 1)) != 0) {
277 if (doingrealloc)
278 return EFBIG;
279 else
280 goto dorealloc;
281 }
282 segs[curseg].ds_addr = curaddr;
283 segs[curseg].ds_len = PAGE_SIZE;
284 }
285 lastaddr = curaddr;
286 }
287
288 *rsegs = curseg + 1;
289 return (0);
290
291 badaddr:
292 if (doingrealloc == 0)
293 goto dorealloc;
294 if (curaddr < low) {
295 /* no way to enforce this */
296 printf("_xen_bus_dmamem_alloc_range: no way to "
297 "enforce address range (0x%" PRIx64 " - 0x%" PRIx64 ")\n",
298 (uint64_t)low, (uint64_t)high);
299 uvm_pglistfree(&mlist);
300 return EINVAL;
301 }
302 printf("xen_bus_dmamem_alloc_range: "
303 "curraddr=0x%lx > high=0x%lx\n",
304 (u_long)curaddr, (u_long)high);
305 panic("xen_bus_dmamem_alloc_range 1");
306 dorealloc:
307 if (doingrealloc == 1)
308 panic("_xen_bus_dmamem_alloc_range: "
309 "xen_alloc_contig returned "
310 "too much segments");
311 doingrealloc = 1;
312 /*
313 * Too much segments, or memory doesn't fit
314 * constraints. Free this memory and
315 * get a contigous segment from the hypervisor.
316 */
317 uvm_pglistfree(&mlist);
318 for (curseg = 0; curseg < nsegs; curseg++) {
319 segs[curseg].ds_addr = 0;
320 segs[curseg].ds_len = 0;
321 }
322 error = _xen_alloc_contig(size, alignment,
323 &mlist, flags, low, high);
324 if (error)
325 return error;
326 goto again;
327 }
328 #endif /* XENPV */
329