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