blob: 438223ae0fc3450c0aaf79864d2f377fa5038bc5 [file] [log] [blame]
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -08001/******************************************************************************
2 * privcmd.c
3 *
4 * Interface to privileged domain-0 commands.
5 *
6 * Copyright (c) 2002-2004, K A Fraser, B Dragovic
7 */
8
9#include <linux/kernel.h>
10#include <linux/sched.h>
11#include <linux/slab.h>
12#include <linux/string.h>
13#include <linux/errno.h>
14#include <linux/mm.h>
15#include <linux/mman.h>
16#include <linux/uaccess.h>
17#include <linux/swap.h>
18#include <linux/smp_lock.h>
19#include <linux/highmem.h>
20#include <linux/pagemap.h>
21#include <linux/seq_file.h>
22
23#include <asm/pgalloc.h>
24#include <asm/pgtable.h>
25#include <asm/tlb.h>
26#include <asm/xen/hypervisor.h>
27#include <asm/xen/hypercall.h>
28
29#include <xen/xen.h>
30#include <xen/privcmd.h>
31#include <xen/interface/xen.h>
32#include <xen/features.h>
33#include <xen/page.h>
34
Ian Campbellf020e292009-05-20 15:42:14 +010035#define REMAP_BATCH_SIZE 16
36
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -080037#ifndef HAVE_ARCH_PRIVCMD_MMAP
38static int privcmd_enforce_singleshot_mapping(struct vm_area_struct *vma);
39#endif
40
41struct remap_data {
42 unsigned long mfn;
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -080043 pgprot_t prot;
Ian Campbellf020e292009-05-20 15:42:14 +010044 struct mmu_update *mmu_update;
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -080045};
46
47static int remap_area_mfn_pte_fn(pte_t *ptep, pgtable_t token,
48 unsigned long addr, void *data)
49{
50 struct remap_data *rmd = data;
51 pte_t pte = pte_mkspecial(pfn_pte(rmd->mfn++, rmd->prot));
52
Ian Campbellf020e292009-05-20 15:42:14 +010053 rmd->mmu_update->ptr = arbitrary_virt_to_machine(ptep).maddr;
54 rmd->mmu_update->val = pte_val_ma(pte);
55 rmd->mmu_update++;
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -080056
57 return 0;
58}
59
Ian Campbellf020e292009-05-20 15:42:14 +010060static int remap_domain_mfn_range(struct vm_area_struct *vma,
61 unsigned long addr,
62 unsigned long mfn, int nr,
63 pgprot_t prot, unsigned domid)
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -080064{
65 struct remap_data rmd;
Ian Campbellf020e292009-05-20 15:42:14 +010066 struct mmu_update mmu_update[REMAP_BATCH_SIZE];
67 int batch;
68 unsigned long range;
69 int err = 0;
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -080070
71 prot = __pgprot(pgprot_val(prot) | _PAGE_IOMAP);
72
73 vma->vm_flags |= VM_IO | VM_RESERVED | VM_PFNMAP;
74
75 rmd.mfn = mfn;
76 rmd.prot = prot;
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -080077
Ian Campbellf020e292009-05-20 15:42:14 +010078 while (nr) {
79 batch = min(REMAP_BATCH_SIZE, nr);
80 range = (unsigned long)batch << PAGE_SHIFT;
81
82 rmd.mmu_update = mmu_update;
83 err = apply_to_page_range(vma->vm_mm, addr, range,
84 remap_area_mfn_pte_fn, &rmd);
85 if (err)
86 goto out;
87
88 err = -EFAULT;
89 if (HYPERVISOR_mmu_update(mmu_update, batch, NULL, domid) < 0)
90 goto out;
91
92 nr -= batch;
93 addr += range;
94 }
95
96 err = 0;
97out:
98
99 flush_tlb_all();
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -0800100
101 return err;
102}
103
104static long privcmd_ioctl_hypercall(void __user *udata)
105{
106 struct privcmd_hypercall hypercall;
107 long ret;
108
109 if (copy_from_user(&hypercall, udata, sizeof(hypercall)))
110 return -EFAULT;
111
112 ret = privcmd_call(hypercall.op,
113 hypercall.arg[0], hypercall.arg[1],
114 hypercall.arg[2], hypercall.arg[3],
115 hypercall.arg[4]);
116
117 return ret;
118}
119
120static void free_page_list(struct list_head *pages)
121{
122 struct page *p, *n;
123
124 list_for_each_entry_safe(p, n, pages, lru)
125 __free_page(p);
126
127 INIT_LIST_HEAD(pages);
128}
129
130/*
131 * Given an array of items in userspace, return a list of pages
132 * containing the data. If copying fails, either because of memory
133 * allocation failure or a problem reading user memory, return an
134 * error code; its up to the caller to dispose of any partial list.
135 */
136static int gather_array(struct list_head *pagelist,
137 unsigned nelem, size_t size,
138 void __user *data)
139{
140 unsigned pageidx;
141 void *pagedata;
142 int ret;
143
144 if (size > PAGE_SIZE)
145 return 0;
146
147 pageidx = PAGE_SIZE;
148 pagedata = NULL; /* quiet, gcc */
149 while (nelem--) {
150 if (pageidx > PAGE_SIZE-size) {
151 struct page *page = alloc_page(GFP_KERNEL);
152
153 ret = -ENOMEM;
154 if (page == NULL)
155 goto fail;
156
157 pagedata = page_address(page);
158
159 list_add_tail(&page->lru, pagelist);
160 pageidx = 0;
161 }
162
163 ret = -EFAULT;
164 if (copy_from_user(pagedata + pageidx, data, size))
165 goto fail;
166
167 data += size;
168 pageidx += size;
169 }
170
171 ret = 0;
172
173fail:
174 return ret;
175}
176
177/*
178 * Call function "fn" on each element of the array fragmented
179 * over a list of pages.
180 */
181static int traverse_pages(unsigned nelem, size_t size,
182 struct list_head *pos,
183 int (*fn)(void *data, void *state),
184 void *state)
185{
186 void *pagedata;
187 unsigned pageidx;
Ian Campbellf020e292009-05-20 15:42:14 +0100188 int ret = 0;
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -0800189
190 BUG_ON(size > PAGE_SIZE);
191
192 pageidx = PAGE_SIZE;
193 pagedata = NULL; /* hush, gcc */
194
195 while (nelem--) {
196 if (pageidx > PAGE_SIZE-size) {
197 struct page *page;
198 pos = pos->next;
199 page = list_entry(pos, struct page, lru);
200 pagedata = page_address(page);
201 pageidx = 0;
202 }
203
204 ret = (*fn)(pagedata + pageidx, state);
205 if (ret)
206 break;
207 pageidx += size;
208 }
209
210 return ret;
211}
212
213struct mmap_mfn_state {
214 unsigned long va;
215 struct vm_area_struct *vma;
216 domid_t domain;
217};
218
219static int mmap_mfn_range(void *data, void *state)
220{
221 struct privcmd_mmap_entry *msg = data;
222 struct mmap_mfn_state *st = state;
223 struct vm_area_struct *vma = st->vma;
224 int rc;
225
226 /* Do not allow range to wrap the address space. */
227 if ((msg->npages > (LONG_MAX >> PAGE_SHIFT)) ||
228 ((unsigned long)(msg->npages << PAGE_SHIFT) >= -st->va))
229 return -EINVAL;
230
231 /* Range chunks must be contiguous in va space. */
232 if ((msg->va != st->va) ||
233 ((msg->va+(msg->npages<<PAGE_SHIFT)) > vma->vm_end))
234 return -EINVAL;
235
236 rc = remap_domain_mfn_range(vma,
237 msg->va & PAGE_MASK,
Ian Campbellf020e292009-05-20 15:42:14 +0100238 msg->mfn, msg->npages,
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -0800239 vma->vm_page_prot,
240 st->domain);
241 if (rc < 0)
242 return rc;
243
244 st->va += msg->npages << PAGE_SHIFT;
245
246 return 0;
247}
248
249static long privcmd_ioctl_mmap(void __user *udata)
250{
251 struct privcmd_mmap mmapcmd;
252 struct mm_struct *mm = current->mm;
253 struct vm_area_struct *vma;
254 int rc;
255 LIST_HEAD(pagelist);
256 struct mmap_mfn_state state;
257
258 if (!xen_initial_domain())
259 return -EPERM;
260
261 if (copy_from_user(&mmapcmd, udata, sizeof(mmapcmd)))
262 return -EFAULT;
263
264 rc = gather_array(&pagelist,
265 mmapcmd.num, sizeof(struct privcmd_mmap_entry),
266 mmapcmd.entry);
267
268 if (rc || list_empty(&pagelist))
269 goto out;
270
271 down_write(&mm->mmap_sem);
272
273 {
274 struct page *page = list_first_entry(&pagelist,
275 struct page, lru);
276 struct privcmd_mmap_entry *msg = page_address(page);
277
278 vma = find_vma(mm, msg->va);
279 rc = -EINVAL;
280
281 if (!vma || (msg->va != vma->vm_start) ||
282 !privcmd_enforce_singleshot_mapping(vma))
283 goto out_up;
284 }
285
286 state.va = vma->vm_start;
287 state.vma = vma;
288 state.domain = mmapcmd.dom;
289
290 rc = traverse_pages(mmapcmd.num, sizeof(struct privcmd_mmap_entry),
291 &pagelist,
292 mmap_mfn_range, &state);
293
294
295out_up:
296 up_write(&mm->mmap_sem);
297
298out:
299 free_page_list(&pagelist);
300
301 return rc;
302}
303
304struct mmap_batch_state {
305 domid_t domain;
306 unsigned long va;
307 struct vm_area_struct *vma;
308 int err;
309
310 xen_pfn_t __user *user;
311};
312
313static int mmap_batch_fn(void *data, void *state)
314{
315 xen_pfn_t *mfnp = data;
316 struct mmap_batch_state *st = state;
317
318 if (remap_domain_mfn_range(st->vma, st->va & PAGE_MASK,
Ian Campbellf020e292009-05-20 15:42:14 +0100319 *mfnp, 1,
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -0800320 st->vma->vm_page_prot, st->domain) < 0) {
321 *mfnp |= 0xf0000000U;
322 st->err++;
323 }
324 st->va += PAGE_SIZE;
325
326 return 0;
327}
328
329static int mmap_return_errors(void *data, void *state)
330{
331 xen_pfn_t *mfnp = data;
332 struct mmap_batch_state *st = state;
333
334 put_user(*mfnp, st->user++);
335
336 return 0;
337}
338
Jeremy Fitzhardingef31fdf52009-03-08 04:10:00 -0700339static struct vm_operations_struct privcmd_vm_ops;
340
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -0800341static long privcmd_ioctl_mmap_batch(void __user *udata)
342{
343 int ret;
344 struct privcmd_mmapbatch m;
345 struct mm_struct *mm = current->mm;
346 struct vm_area_struct *vma;
347 unsigned long nr_pages;
348 LIST_HEAD(pagelist);
349 struct mmap_batch_state state;
350
351 if (!xen_initial_domain())
352 return -EPERM;
353
354 if (copy_from_user(&m, udata, sizeof(m)))
355 return -EFAULT;
356
357 nr_pages = m.num;
358 if ((m.num <= 0) || (nr_pages > (LONG_MAX >> PAGE_SHIFT)))
359 return -EINVAL;
360
361 ret = gather_array(&pagelist, m.num, sizeof(xen_pfn_t),
362 m.arr);
363
364 if (ret || list_empty(&pagelist))
365 goto out;
366
367 down_write(&mm->mmap_sem);
368
369 vma = find_vma(mm, m.addr);
370 ret = -EINVAL;
371 if (!vma ||
Jeremy Fitzhardingef31fdf52009-03-08 04:10:00 -0700372 vma->vm_ops != &privcmd_vm_ops ||
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -0800373 (m.addr != vma->vm_start) ||
374 ((m.addr + (nr_pages << PAGE_SHIFT)) != vma->vm_end) ||
375 !privcmd_enforce_singleshot_mapping(vma)) {
376 up_write(&mm->mmap_sem);
377 goto out;
378 }
379
380 state.domain = m.dom;
381 state.vma = vma;
382 state.va = m.addr;
383 state.err = 0;
384
385 ret = traverse_pages(m.num, sizeof(xen_pfn_t),
386 &pagelist, mmap_batch_fn, &state);
387
388 up_write(&mm->mmap_sem);
389
390 if (state.err > 0) {
Ian Campbellf020e292009-05-20 15:42:14 +0100391 ret = 0;
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -0800392
Ian Campbellf020e292009-05-20 15:42:14 +0100393 state.user = m.arr;
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -0800394 traverse_pages(m.num, sizeof(xen_pfn_t),
395 &pagelist,
396 mmap_return_errors, &state);
397 }
398
399out:
400 free_page_list(&pagelist);
401
402 return ret;
403}
404
405static long privcmd_ioctl(struct file *file,
406 unsigned int cmd, unsigned long data)
407{
408 int ret = -ENOSYS;
409 void __user *udata = (void __user *) data;
410
411 switch (cmd) {
412 case IOCTL_PRIVCMD_HYPERCALL:
413 ret = privcmd_ioctl_hypercall(udata);
414 break;
415
416 case IOCTL_PRIVCMD_MMAP:
417 ret = privcmd_ioctl_mmap(udata);
418 break;
419
420 case IOCTL_PRIVCMD_MMAPBATCH:
421 ret = privcmd_ioctl_mmap_batch(udata);
422 break;
423
424 default:
425 ret = -EINVAL;
426 break;
427 }
428
429 return ret;
430}
431
432#ifndef HAVE_ARCH_PRIVCMD_MMAP
433static int privcmd_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
434{
Jeremy Fitzhardinge441c7412009-03-06 09:56:59 -0800435 printk(KERN_DEBUG "privcmd_fault: vma=%p %lx-%lx, pgoff=%lx, uv=%p\n",
436 vma, vma->vm_start, vma->vm_end,
437 vmf->pgoff, vmf->virtual_address);
438
Jeremy Fitzhardinge1c5de192009-02-09 12:05:49 -0800439 return VM_FAULT_SIGBUS;
440}
441
442static struct vm_operations_struct privcmd_vm_ops = {
443 .fault = privcmd_fault
444};
445
446static int privcmd_mmap(struct file *file, struct vm_area_struct *vma)
447{
448 /* Unsupported for auto-translate guests. */
449 if (xen_feature(XENFEAT_auto_translated_physmap))
450 return -ENOSYS;
451
452 /* DONTCOPY is essential for Xen as copy_page_range is broken. */
453 vma->vm_flags |= VM_RESERVED | VM_IO | VM_DONTCOPY;
454 vma->vm_ops = &privcmd_vm_ops;
455 vma->vm_private_data = NULL;
456
457 return 0;
458}
459
460static int privcmd_enforce_singleshot_mapping(struct vm_area_struct *vma)
461{
462 return (xchg(&vma->vm_private_data, (void *)1) == NULL);
463}
464#endif
465
466const struct file_operations privcmd_file_ops = {
467 .unlocked_ioctl = privcmd_ioctl,
468 .mmap = privcmd_mmap,
469};