xen_bus_dma.c revision 1.6.6.1 1 /* $NetBSD: xen_bus_dma.c,v 1.6.6.1 2006/09/03 15:23:37 yamt 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 * 3. All advertising materials mentioning features or use of this software
21 * must display the following acknowledgement:
22 * This product includes software developed by the NetBSD
23 * Foundation, Inc. and its contributors.
24 * 4. Neither the name of The NetBSD Foundation nor the names of its
25 * contributors may be used to endorse or promote products derived
26 * from this software without specific prior written permission.
27 *
28 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
29 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
30 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
31 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
32 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
33 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
34 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
35 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
36 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
37 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
38 * POSSIBILITY OF SUCH DAMAGE.
39 */
40
41 #include <sys/cdefs.h>
42 __KERNEL_RCSID(0, "$NetBSD: xen_bus_dma.c,v 1.6.6.1 2006/09/03 15:23:37 yamt Exp $");
43
44 #include <sys/param.h>
45 #include <sys/systm.h>
46 #include <sys/kernel.h>
47 #include <sys/malloc.h>
48 #include <sys/mbuf.h>
49 #include <sys/proc.h>
50
51 #include <machine/bus.h>
52 #include <machine/bus_private.h>
53
54 #include <uvm/uvm_extern.h>
55
56 extern paddr_t avail_end;
57
58 /* Pure 2^n version of get_order */
59 static inline int get_order(unsigned long size)
60 {
61 int order = -1;
62 size = (size - 1) >> (PAGE_SHIFT - 1);
63 do {
64 size >>= 1;
65 order++;
66 } while (size);
67 return order;
68 }
69
70 static int
71 _xen_alloc_contig(bus_size_t size, bus_size_t alignment, bus_size_t boundary,
72 struct pglist *mlistp, int flags)
73 {
74 int order, i;
75 unsigned long npagesreq, npages, mfn;
76 bus_addr_t pa;
77 struct vm_page *pg, *pgnext;
78 int s, error;
79 #ifdef XEN3
80 struct xen_memory_reservation res;
81 #endif
82
83 /*
84 * When requesting a contigous memory region, the hypervisor will
85 * return a memory range aligned on size. This will automagically
86 * handle "boundary", but the only way to enforce alignment
87 * is to request a memory region of size max(alignment, size).
88 */
89 order = max(get_order(size), get_order(alignment));
90 npages = (1 << order);
91 npagesreq = (size >> PAGE_SHIFT);
92 KASSERT(npages >= npagesreq);
93
94 /* get npages from UWM, and give them back to the hypervisor */
95 error = uvm_pglistalloc(npages << PAGE_SHIFT, 0, avail_end, 0, 0,
96 mlistp, npages, (flags & BUS_DMA_NOWAIT) == 0);
97 if (error)
98 return (error);
99
100 for (pg = mlistp->tqh_first; pg != NULL; pg = pg->pageq.tqe_next) {
101 pa = VM_PAGE_TO_PHYS(pg);
102 mfn = xpmap_ptom(pa) >> PAGE_SHIFT;
103 xpmap_phys_to_machine_mapping[
104 (pa - XPMAP_OFFSET) >> PAGE_SHIFT] = INVALID_P2M_ENTRY;
105 #ifdef XEN3
106 res.extent_start = &mfn;
107 res.nr_extents = 1;
108 res.extent_order = 0;
109 res.domid = DOMID_SELF;
110 if (HYPERVISOR_memory_op(XENMEM_decrease_reservation, &res)
111 < 0) {
112 printf("xen_alloc_contig: XENMEM_decrease_reservation "
113 "failed!\n");
114 xpmap_phys_to_machine_mapping[
115 (pa - XPMAP_OFFSET) >> PAGE_SHIFT] = mfn;
116
117 error = ENOMEM;
118 goto failed;
119 }
120 #else
121 if (HYPERVISOR_dom_mem_op(MEMOP_decrease_reservation,
122 &mfn, 1, 0) != 1) {
123 printf("xen_alloc_contig: MEMOP_decrease_reservation "
124 "failed!\n");
125 xpmap_phys_to_machine_mapping[
126 (pa - XPMAP_OFFSET) >> PAGE_SHIFT] = mfn;
127 error = ENOMEM;
128 goto failed;
129 }
130 #endif
131 }
132 /* Get the new contiguous memory extent */
133 #ifdef XEN3
134 res.extent_start = &mfn;
135 res.nr_extents = 1;
136 res.extent_order = order;
137 res.address_bits = 31;
138 res.domid = DOMID_SELF;
139 if (HYPERVISOR_memory_op(XENMEM_increase_reservation, &res) < 0) {
140 printf("xen_alloc_contig: XENMEM_increase_reservation "
141 "failed!\n");
142 error = ENOMEM;
143 pg = NULL;
144 goto failed;
145 }
146 #else
147 if (HYPERVISOR_dom_mem_op(MEMOP_increase_reservation,
148 &mfn, 1, order) != 1) {
149 printf("xen_alloc_contig: MEMOP_increase_reservation "
150 "failed!\n");
151 error = ENOMEM;
152 pg = NULL;
153 goto failed;
154 }
155 #endif
156 s = splvm();
157 /* Map the new extent in place of the old pages */
158 for (pg = mlistp->tqh_first, i = 0; pg != NULL; pg = pgnext, i++) {
159 pgnext = pg->pageq.tqe_next;
160 pa = VM_PAGE_TO_PHYS(pg);
161 xpmap_phys_to_machine_mapping[
162 (pa - XPMAP_OFFSET) >> PAGE_SHIFT] = mfn+i;
163 xpq_queue_machphys_update((mfn+i) << PAGE_SHIFT, pa);
164 /* while here, give extra pages back to UVM */
165 if (i >= npagesreq) {
166 TAILQ_REMOVE(mlistp, pg, pageq);
167 uvm_pagefree(pg);
168 }
169
170 }
171 /* Flush updates through and flush the TLB */
172 xpq_queue_tlb_flush();
173 xpq_flush_queue();
174 splx(s);
175 return 0;
176
177 failed:
178 /*
179 * Attempt to recover from a failed decrease or increase reservation:
180 * if decrease_reservation failed, we don't have given all pages
181 * back to Xen; give them back to UVM, and get the missing pages
182 * from Xen.
183 * if increase_reservation failed, we expect pg to be NULL and we just
184 * get back the missing pages from Xen one by one.
185 */
186 /* give back remaining pages to UVM */
187 for (; pg != NULL; pg = pgnext) {
188 pgnext = pg->pageq.tqe_next;
189 TAILQ_REMOVE(mlistp, pg, pageq);
190 uvm_pagefree(pg);
191 }
192 /* remplace the pages that we already gave to Xen */
193 s = splvm();
194 for (pg = mlistp->tqh_first; pg != NULL; pg = pgnext) {
195 pgnext = pg->pageq.tqe_next;
196 #ifdef XEN3
197 res.extent_start = &mfn;
198 res.nr_extents = 1;
199 res.extent_order = 0;
200 res.address_bits = 31;
201 res.domid = DOMID_SELF;
202 if (HYPERVISOR_memory_op(XENMEM_increase_reservation, &res)
203 < 0) {
204 printf("xen_alloc_contig: recovery "
205 "XENMEM_increase_reservation failed!\n");
206 break;
207 }
208 #else
209 if (HYPERVISOR_dom_mem_op(MEMOP_increase_reservation,
210 &mfn, 1, 0) != 1) {
211 printf("xen_alloc_contig: recovery "
212 "MEMOP_increase_reservation failed!\n");
213 break;
214 }
215 #endif
216 pa = VM_PAGE_TO_PHYS(pg);
217 xpmap_phys_to_machine_mapping[
218 (pa - XPMAP_OFFSET) >> PAGE_SHIFT] = mfn;
219 xpq_queue_machphys_update((mfn) << PAGE_SHIFT, pa);
220 TAILQ_REMOVE(mlistp, pg, pageq);
221 uvm_pagefree(pg);
222 }
223 /* Flush updates through and flush the TLB */
224 xpq_queue_tlb_flush();
225 xpq_flush_queue();
226 splx(s);
227 return error;
228 }
229
230
231 /*
232 * Allocate physical memory from the given physical address range.
233 * Called by DMA-safe memory allocation methods.
234 * We need our own version to deal with physical vs machine addresses.
235 */
236 int
237 _xen_bus_dmamem_alloc_range(bus_dma_tag_t t, bus_size_t size,
238 bus_size_t alignment, bus_size_t boundary, bus_dma_segment_t *segs,
239 int nsegs, int *rsegs, int flags, bus_addr_t low, bus_addr_t high)
240 {
241 bus_addr_t curaddr, lastaddr;
242 struct vm_page *m;
243 struct pglist mlist;
244 int curseg, error;
245 int doingrealloc = 0;
246
247 /* Always round the size. */
248 size = round_page(size);
249
250 KASSERT((alignment & (alignment - 1)) == 0);
251 KASSERT((boundary & (boundary - 1)) == 0);
252 if (alignment < PAGE_SIZE)
253 alignment = PAGE_SIZE;
254 if (boundary != 0 && boundary < size)
255 return (EINVAL);
256
257 /*
258 * Allocate pages from the VM system.
259 */
260 error = uvm_pglistalloc(size, 0, avail_end, alignment, boundary,
261 &mlist, nsegs, (flags & BUS_DMA_NOWAIT) == 0);
262 if (error)
263 return (error);
264 again:
265
266 /*
267 * Compute the location, size, and number of segments actually
268 * returned by the VM code.
269 */
270 m = mlist.tqh_first;
271 curseg = 0;
272 lastaddr = segs[curseg].ds_addr = _BUS_VM_PAGE_TO_BUS(m);
273 segs[curseg].ds_len = PAGE_SIZE;
274 m = m->pageq.tqe_next;
275 if ((segs[curseg].ds_addr & (alignment - 1)) != 0)
276 goto dorealloc;
277
278 for (; m != NULL; m = m->pageq.tqe_next) {
279 curaddr = _BUS_VM_PAGE_TO_BUS(m);
280 if ((lastaddr < low || lastaddr >= high) ||
281 (curaddr < low || curaddr >= high)) {
282 /*
283 * If machine addresses are outside the allowed
284 * range we have to bail. Xen2 doesn't offer an
285 * interface to get memory in a specific address
286 * range.
287 */
288 printf("_xen_bus_dmamem_alloc_range: no way to "
289 "enforce address range\n");
290 uvm_pglistfree(&mlist);
291 return EINVAL;
292 }
293 if (curaddr == (lastaddr + PAGE_SIZE)) {
294 segs[curseg].ds_len += PAGE_SIZE;
295 if ((lastaddr & boundary) !=
296 (curaddr & boundary))
297 goto dorealloc;
298 } else {
299 curseg++;
300 if (curseg >= nsegs ||
301 (curaddr & (alignment - 1)) != 0) {
302 dorealloc:
303 if (doingrealloc == 1)
304 panic("_xen_bus_dmamem_alloc_range: "
305 "xen_alloc_contig returned "
306 "too much segments");
307 doingrealloc = 1;
308 /*
309 * Too much segments. Free this memory and
310 * get a contigous segment from the hypervisor.
311 */
312 uvm_pglistfree(&mlist);
313 for (curseg = 0; curseg < nsegs; curseg++) {
314 segs[curseg].ds_addr = 0;
315 segs[curseg].ds_len = 0;
316 }
317 error = _xen_alloc_contig(size, alignment,
318 boundary, &mlist, flags);
319 if (error)
320 return error;
321 goto again;
322 }
323 segs[curseg].ds_addr = curaddr;
324 segs[curseg].ds_len = PAGE_SIZE;
325 }
326 lastaddr = curaddr;
327 }
328
329 *rsegs = curseg + 1;
330
331 return (0);
332 }
333