xen_bus_dma.c revision 1.28.10.1 1 /* $NetBSD: xen_bus_dma.c,v 1.28.10.1 2020/04/20 11:29:01 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.28.10.1 2020/04/20 11:29:01 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 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