diff options
Diffstat (limited to 'drivers/xen/gntdev.c')
| -rw-r--r-- | drivers/xen/gntdev.c | 130 |
1 files changed, 88 insertions, 42 deletions
diff --git a/drivers/xen/gntdev.c b/drivers/xen/gntdev.c index 2e22df2f7a3f..3c8803feba26 100644 --- a/drivers/xen/gntdev.c +++ b/drivers/xen/gntdev.c | |||
| @@ -56,10 +56,15 @@ MODULE_PARM_DESC(limit, "Maximum number of grants that may be mapped by " | |||
| 56 | static atomic_t pages_mapped = ATOMIC_INIT(0); | 56 | static atomic_t pages_mapped = ATOMIC_INIT(0); |
| 57 | 57 | ||
| 58 | static int use_ptemod; | 58 | static int use_ptemod; |
| 59 | #define populate_freeable_maps use_ptemod | ||
| 59 | 60 | ||
| 60 | struct gntdev_priv { | 61 | struct gntdev_priv { |
| 62 | /* maps with visible offsets in the file descriptor */ | ||
| 61 | struct list_head maps; | 63 | struct list_head maps; |
| 62 | /* lock protects maps from concurrent changes */ | 64 | /* maps that are not visible; will be freed on munmap. |
| 65 | * Only populated if populate_freeable_maps == 1 */ | ||
| 66 | struct list_head freeable_maps; | ||
| 67 | /* lock protects maps and freeable_maps */ | ||
| 63 | spinlock_t lock; | 68 | spinlock_t lock; |
| 64 | struct mm_struct *mm; | 69 | struct mm_struct *mm; |
| 65 | struct mmu_notifier mn; | 70 | struct mmu_notifier mn; |
| @@ -193,7 +198,7 @@ static struct grant_map *gntdev_find_map_index(struct gntdev_priv *priv, | |||
| 193 | return NULL; | 198 | return NULL; |
| 194 | } | 199 | } |
| 195 | 200 | ||
| 196 | static void gntdev_put_map(struct grant_map *map) | 201 | static void gntdev_put_map(struct gntdev_priv *priv, struct grant_map *map) |
| 197 | { | 202 | { |
| 198 | if (!map) | 203 | if (!map) |
| 199 | return; | 204 | return; |
| @@ -208,6 +213,12 @@ static void gntdev_put_map(struct grant_map *map) | |||
| 208 | evtchn_put(map->notify.event); | 213 | evtchn_put(map->notify.event); |
| 209 | } | 214 | } |
| 210 | 215 | ||
| 216 | if (populate_freeable_maps && priv) { | ||
| 217 | spin_lock(&priv->lock); | ||
| 218 | list_del(&map->next); | ||
| 219 | spin_unlock(&priv->lock); | ||
| 220 | } | ||
| 221 | |||
| 211 | if (map->pages && !use_ptemod) | 222 | if (map->pages && !use_ptemod) |
| 212 | unmap_grant_pages(map, 0, map->count); | 223 | unmap_grant_pages(map, 0, map->count); |
| 213 | gntdev_free_map(map); | 224 | gntdev_free_map(map); |
| @@ -301,17 +312,10 @@ static int __unmap_grant_pages(struct grant_map *map, int offset, int pages) | |||
| 301 | 312 | ||
| 302 | if (map->notify.flags & UNMAP_NOTIFY_CLEAR_BYTE) { | 313 | if (map->notify.flags & UNMAP_NOTIFY_CLEAR_BYTE) { |
| 303 | int pgno = (map->notify.addr >> PAGE_SHIFT); | 314 | int pgno = (map->notify.addr >> PAGE_SHIFT); |
| 304 | if (pgno >= offset && pgno < offset + pages && use_ptemod) { | 315 | if (pgno >= offset && pgno < offset + pages) { |
| 305 | void __user *tmp = (void __user *) | 316 | /* No need for kmap, pages are in lowmem */ |
| 306 | map->vma->vm_start + map->notify.addr; | 317 | uint8_t *tmp = pfn_to_kaddr(page_to_pfn(map->pages[pgno])); |
| 307 | err = copy_to_user(tmp, &err, 1); | ||
| 308 | if (err) | ||
| 309 | return -EFAULT; | ||
| 310 | map->notify.flags &= ~UNMAP_NOTIFY_CLEAR_BYTE; | ||
| 311 | } else if (pgno >= offset && pgno < offset + pages) { | ||
| 312 | uint8_t *tmp = kmap(map->pages[pgno]); | ||
| 313 | tmp[map->notify.addr & (PAGE_SIZE-1)] = 0; | 318 | tmp[map->notify.addr & (PAGE_SIZE-1)] = 0; |
| 314 | kunmap(map->pages[pgno]); | ||
| 315 | map->notify.flags &= ~UNMAP_NOTIFY_CLEAR_BYTE; | 319 | map->notify.flags &= ~UNMAP_NOTIFY_CLEAR_BYTE; |
| 316 | } | 320 | } |
| 317 | } | 321 | } |
| @@ -376,11 +380,24 @@ static void gntdev_vma_open(struct vm_area_struct *vma) | |||
| 376 | static void gntdev_vma_close(struct vm_area_struct *vma) | 380 | static void gntdev_vma_close(struct vm_area_struct *vma) |
| 377 | { | 381 | { |
| 378 | struct grant_map *map = vma->vm_private_data; | 382 | struct grant_map *map = vma->vm_private_data; |
| 383 | struct file *file = vma->vm_file; | ||
| 384 | struct gntdev_priv *priv = file->private_data; | ||
| 379 | 385 | ||
| 380 | pr_debug("gntdev_vma_close %p\n", vma); | 386 | pr_debug("gntdev_vma_close %p\n", vma); |
| 381 | map->vma = NULL; | 387 | if (use_ptemod) { |
| 388 | /* It is possible that an mmu notifier could be running | ||
| 389 | * concurrently, so take priv->lock to ensure that the vma won't | ||
| 390 | * vanishing during the unmap_grant_pages call, since we will | ||
| 391 | * spin here until that completes. Such a concurrent call will | ||
| 392 | * not do any unmapping, since that has been done prior to | ||
| 393 | * closing the vma, but it may still iterate the unmap_ops list. | ||
| 394 | */ | ||
| 395 | spin_lock(&priv->lock); | ||
| 396 | map->vma = NULL; | ||
| 397 | spin_unlock(&priv->lock); | ||
| 398 | } | ||
| 382 | vma->vm_private_data = NULL; | 399 | vma->vm_private_data = NULL; |
| 383 | gntdev_put_map(map); | 400 | gntdev_put_map(priv, map); |
| 384 | } | 401 | } |
| 385 | 402 | ||
| 386 | static struct vm_operations_struct gntdev_vmops = { | 403 | static struct vm_operations_struct gntdev_vmops = { |
| @@ -390,33 +407,43 @@ static struct vm_operations_struct gntdev_vmops = { | |||
| 390 | 407 | ||
| 391 | /* ------------------------------------------------------------------ */ | 408 | /* ------------------------------------------------------------------ */ |
| 392 | 409 | ||
| 410 | static void unmap_if_in_range(struct grant_map *map, | ||
| 411 | unsigned long start, unsigned long end) | ||
| 412 | { | ||
| 413 | unsigned long mstart, mend; | ||
| 414 | int err; | ||
| 415 | |||
| 416 | if (!map->vma) | ||
| 417 | return; | ||
| 418 | if (map->vma->vm_start >= end) | ||
| 419 | return; | ||
| 420 | if (map->vma->vm_end <= start) | ||
| 421 | return; | ||
| 422 | mstart = max(start, map->vma->vm_start); | ||
| 423 | mend = min(end, map->vma->vm_end); | ||
| 424 | pr_debug("map %d+%d (%lx %lx), range %lx %lx, mrange %lx %lx\n", | ||
| 425 | map->index, map->count, | ||
| 426 | map->vma->vm_start, map->vma->vm_end, | ||
| 427 | start, end, mstart, mend); | ||
| 428 | err = unmap_grant_pages(map, | ||
| 429 | (mstart - map->vma->vm_start) >> PAGE_SHIFT, | ||
| 430 | (mend - mstart) >> PAGE_SHIFT); | ||
| 431 | WARN_ON(err); | ||
| 432 | } | ||
| 433 | |||
| 393 | static void mn_invl_range_start(struct mmu_notifier *mn, | 434 | static void mn_invl_range_start(struct mmu_notifier *mn, |
| 394 | struct mm_struct *mm, | 435 | struct mm_struct *mm, |
| 395 | unsigned long start, unsigned long end) | 436 | unsigned long start, unsigned long end) |
| 396 | { | 437 | { |
| 397 | struct gntdev_priv *priv = container_of(mn, struct gntdev_priv, mn); | 438 | struct gntdev_priv *priv = container_of(mn, struct gntdev_priv, mn); |
| 398 | struct grant_map *map; | 439 | struct grant_map *map; |
| 399 | unsigned long mstart, mend; | ||
| 400 | int err; | ||
| 401 | 440 | ||
| 402 | spin_lock(&priv->lock); | 441 | spin_lock(&priv->lock); |
| 403 | list_for_each_entry(map, &priv->maps, next) { | 442 | list_for_each_entry(map, &priv->maps, next) { |
| 404 | if (!map->vma) | 443 | unmap_if_in_range(map, start, end); |
| 405 | continue; | 444 | } |
| 406 | if (map->vma->vm_start >= end) | 445 | list_for_each_entry(map, &priv->freeable_maps, next) { |
| 407 | continue; | 446 | unmap_if_in_range(map, start, end); |
| 408 | if (map->vma->vm_end <= start) | ||
| 409 | continue; | ||
| 410 | mstart = max(start, map->vma->vm_start); | ||
| 411 | mend = min(end, map->vma->vm_end); | ||
| 412 | pr_debug("map %d+%d (%lx %lx), range %lx %lx, mrange %lx %lx\n", | ||
| 413 | map->index, map->count, | ||
| 414 | map->vma->vm_start, map->vma->vm_end, | ||
| 415 | start, end, mstart, mend); | ||
| 416 | err = unmap_grant_pages(map, | ||
| 417 | (mstart - map->vma->vm_start) >> PAGE_SHIFT, | ||
| 418 | (mend - mstart) >> PAGE_SHIFT); | ||
| 419 | WARN_ON(err); | ||
| 420 | } | 447 | } |
| 421 | spin_unlock(&priv->lock); | 448 | spin_unlock(&priv->lock); |
| 422 | } | 449 | } |
| @@ -445,6 +472,15 @@ static void mn_release(struct mmu_notifier *mn, | |||
| 445 | err = unmap_grant_pages(map, /* offset */ 0, map->count); | 472 | err = unmap_grant_pages(map, /* offset */ 0, map->count); |
| 446 | WARN_ON(err); | 473 | WARN_ON(err); |
| 447 | } | 474 | } |
| 475 | list_for_each_entry(map, &priv->freeable_maps, next) { | ||
| 476 | if (!map->vma) | ||
| 477 | continue; | ||
| 478 | pr_debug("map %d+%d (%lx %lx)\n", | ||
| 479 | map->index, map->count, | ||
| 480 | map->vma->vm_start, map->vma->vm_end); | ||
| 481 | err = unmap_grant_pages(map, /* offset */ 0, map->count); | ||
| 482 | WARN_ON(err); | ||
| 483 | } | ||
| 448 | spin_unlock(&priv->lock); | 484 | spin_unlock(&priv->lock); |
| 449 | } | 485 | } |
| 450 | 486 | ||
| @@ -466,6 +502,7 @@ static int gntdev_open(struct inode *inode, struct file *flip) | |||
| 466 | return -ENOMEM; | 502 | return -ENOMEM; |
| 467 | 503 | ||
| 468 | INIT_LIST_HEAD(&priv->maps); | 504 | INIT_LIST_HEAD(&priv->maps); |
| 505 | INIT_LIST_HEAD(&priv->freeable_maps); | ||
| 469 | spin_lock_init(&priv->lock); | 506 | spin_lock_init(&priv->lock); |
| 470 | 507 | ||
| 471 | if (use_ptemod) { | 508 | if (use_ptemod) { |
| @@ -500,8 +537,9 @@ static int gntdev_release(struct inode *inode, struct file *flip) | |||
| 500 | while (!list_empty(&priv->maps)) { | 537 | while (!list_empty(&priv->maps)) { |
| 501 | map = list_entry(priv->maps.next, struct grant_map, next); | 538 | map = list_entry(priv->maps.next, struct grant_map, next); |
| 502 | list_del(&map->next); | 539 | list_del(&map->next); |
| 503 | gntdev_put_map(map); | 540 | gntdev_put_map(NULL /* already removed */, map); |
| 504 | } | 541 | } |
| 542 | WARN_ON(!list_empty(&priv->freeable_maps)); | ||
| 505 | 543 | ||
| 506 | if (use_ptemod) | 544 | if (use_ptemod) |
| 507 | mmu_notifier_unregister(&priv->mn, priv->mm); | 545 | mmu_notifier_unregister(&priv->mn, priv->mm); |
| @@ -529,14 +567,14 @@ static long gntdev_ioctl_map_grant_ref(struct gntdev_priv *priv, | |||
| 529 | 567 | ||
| 530 | if (unlikely(atomic_add_return(op.count, &pages_mapped) > limit)) { | 568 | if (unlikely(atomic_add_return(op.count, &pages_mapped) > limit)) { |
| 531 | pr_debug("can't map: over limit\n"); | 569 | pr_debug("can't map: over limit\n"); |
| 532 | gntdev_put_map(map); | 570 | gntdev_put_map(NULL, map); |
| 533 | return err; | 571 | return err; |
| 534 | } | 572 | } |
| 535 | 573 | ||
| 536 | if (copy_from_user(map->grants, &u->refs, | 574 | if (copy_from_user(map->grants, &u->refs, |
| 537 | sizeof(map->grants[0]) * op.count) != 0) { | 575 | sizeof(map->grants[0]) * op.count) != 0) { |
| 538 | gntdev_put_map(map); | 576 | gntdev_put_map(NULL, map); |
| 539 | return err; | 577 | return -EFAULT; |
| 540 | } | 578 | } |
| 541 | 579 | ||
| 542 | spin_lock(&priv->lock); | 580 | spin_lock(&priv->lock); |
| @@ -565,11 +603,13 @@ static long gntdev_ioctl_unmap_grant_ref(struct gntdev_priv *priv, | |||
| 565 | map = gntdev_find_map_index(priv, op.index >> PAGE_SHIFT, op.count); | 603 | map = gntdev_find_map_index(priv, op.index >> PAGE_SHIFT, op.count); |
| 566 | if (map) { | 604 | if (map) { |
| 567 | list_del(&map->next); | 605 | list_del(&map->next); |
| 606 | if (populate_freeable_maps) | ||
| 607 | list_add_tail(&map->next, &priv->freeable_maps); | ||
| 568 | err = 0; | 608 | err = 0; |
| 569 | } | 609 | } |
| 570 | spin_unlock(&priv->lock); | 610 | spin_unlock(&priv->lock); |
| 571 | if (map) | 611 | if (map) |
| 572 | gntdev_put_map(map); | 612 | gntdev_put_map(priv, map); |
| 573 | return err; | 613 | return err; |
| 574 | } | 614 | } |
| 575 | 615 | ||
| @@ -579,25 +619,31 @@ static long gntdev_ioctl_get_offset_for_vaddr(struct gntdev_priv *priv, | |||
| 579 | struct ioctl_gntdev_get_offset_for_vaddr op; | 619 | struct ioctl_gntdev_get_offset_for_vaddr op; |
| 580 | struct vm_area_struct *vma; | 620 | struct vm_area_struct *vma; |
| 581 | struct grant_map *map; | 621 | struct grant_map *map; |
| 622 | int rv = -EINVAL; | ||
| 582 | 623 | ||
| 583 | if (copy_from_user(&op, u, sizeof(op)) != 0) | 624 | if (copy_from_user(&op, u, sizeof(op)) != 0) |
| 584 | return -EFAULT; | 625 | return -EFAULT; |
| 585 | pr_debug("priv %p, offset for vaddr %lx\n", priv, (unsigned long)op.vaddr); | 626 | pr_debug("priv %p, offset for vaddr %lx\n", priv, (unsigned long)op.vaddr); |
| 586 | 627 | ||
| 628 | down_read(¤t->mm->mmap_sem); | ||
| 587 | vma = find_vma(current->mm, op.vaddr); | 629 | vma = find_vma(current->mm, op.vaddr); |
| 588 | if (!vma || vma->vm_ops != &gntdev_vmops) | 630 | if (!vma || vma->vm_ops != &gntdev_vmops) |
| 589 | return -EINVAL; | 631 | goto out_unlock; |
| 590 | 632 | ||
| 591 | map = vma->vm_private_data; | 633 | map = vma->vm_private_data; |
| 592 | if (!map) | 634 | if (!map) |
| 593 | return -EINVAL; | 635 | goto out_unlock; |
| 594 | 636 | ||
| 595 | op.offset = map->index << PAGE_SHIFT; | 637 | op.offset = map->index << PAGE_SHIFT; |
| 596 | op.count = map->count; | 638 | op.count = map->count; |
| 639 | rv = 0; | ||
| 597 | 640 | ||
| 598 | if (copy_to_user(u, &op, sizeof(op)) != 0) | 641 | out_unlock: |
| 642 | up_read(¤t->mm->mmap_sem); | ||
| 643 | |||
| 644 | if (rv == 0 && copy_to_user(u, &op, sizeof(op)) != 0) | ||
| 599 | return -EFAULT; | 645 | return -EFAULT; |
| 600 | return 0; | 646 | return rv; |
| 601 | } | 647 | } |
| 602 | 648 | ||
| 603 | static long gntdev_ioctl_notify(struct gntdev_priv *priv, void __user *u) | 649 | static long gntdev_ioctl_notify(struct gntdev_priv *priv, void __user *u) |
| @@ -778,7 +824,7 @@ out_unlock_put: | |||
| 778 | out_put_map: | 824 | out_put_map: |
| 779 | if (use_ptemod) | 825 | if (use_ptemod) |
| 780 | map->vma = NULL; | 826 | map->vma = NULL; |
| 781 | gntdev_put_map(map); | 827 | gntdev_put_map(priv, map); |
| 782 | return err; | 828 | return err; |
| 783 | } | 829 | } |
| 784 | 830 | ||
