KVM: set_memory_region: Disallow changing read-only attribute later
[pandora-kernel.git] / virt / kvm / kvm_main.c
index a83ca63..2e93630 100644 (file)
@@ -718,6 +718,24 @@ static struct kvm_memslots *install_new_memslots(struct kvm *kvm,
        return old_memslots; 
 }
 
+/*
+ * KVM_SET_USER_MEMORY_REGION ioctl allows the following operations:
+ * - create a new memory slot
+ * - delete an existing memory slot
+ * - modify an existing memory slot
+ *   -- move it in the guest physical memory space
+ *   -- just change its flags
+ *
+ * Since flags can be changed by some of these operations, the following
+ * differentiation is the best we can do for __kvm_set_memory_region():
+ */
+enum kvm_mr_change {
+       KVM_MR_CREATE,
+       KVM_MR_DELETE,
+       KVM_MR_MOVE,
+       KVM_MR_FLAGS_ONLY,
+};
+
 /*
  * Allocate some memory and give it an address in the guest physical address
  * space.
@@ -736,7 +754,7 @@ int __kvm_set_memory_region(struct kvm *kvm,
        struct kvm_memory_slot *slot;
        struct kvm_memory_slot old, new;
        struct kvm_memslots *slots = NULL, *old_memslots;
-       bool old_iommu_mapped;
+       enum kvm_mr_change change;
 
        r = check_memory_region_flags(mem);
        if (r)
@@ -778,19 +796,31 @@ int __kvm_set_memory_region(struct kvm *kvm,
        new.npages = npages;
        new.flags = mem->flags;
 
-       old_iommu_mapped = old.npages;
-
-       /*
-        * Disallow changing a memory slot's size or changing anything about
-        * zero sized slots that doesn't involve making them non-zero.
-        */
        r = -EINVAL;
-       if (npages && old.npages && npages != old.npages)
-               goto out;
-       if (!npages && !old.npages)
+       if (npages) {
+               if (!old.npages)
+                       change = KVM_MR_CREATE;
+               else { /* Modify an existing slot. */
+                       if ((mem->userspace_addr != old.userspace_addr) ||
+                           (npages != old.npages) ||
+                           ((new.flags ^ old.flags) & KVM_MEM_READONLY))
+                               goto out;
+
+                       if (base_gfn != old.base_gfn)
+                               change = KVM_MR_MOVE;
+                       else if (new.flags != old.flags)
+                               change = KVM_MR_FLAGS_ONLY;
+                       else { /* Nothing to change. */
+                               r = 0;
+                               goto out;
+                       }
+               }
+       } else if (old.npages) {
+               change = KVM_MR_DELETE;
+       } else /* Modify a non-existent slot: disallowed. */
                goto out;
 
-       if ((npages && !old.npages) || (base_gfn != old.base_gfn)) {
+       if ((change == KVM_MR_CREATE) || (change == KVM_MR_MOVE)) {
                /* Check for overlaps */
                r = -EEXIST;
                kvm_for_each_memslot(slot, kvm->memslots) {
@@ -808,20 +838,12 @@ int __kvm_set_memory_region(struct kvm *kvm,
                new.dirty_bitmap = NULL;
 
        r = -ENOMEM;
-
-       /*
-        * Allocate if a slot is being created.  If modifying a slot,
-        * the userspace_addr cannot change.
-        */
-       if (!old.npages) {
+       if (change == KVM_MR_CREATE) {
                new.user_alloc = user_alloc;
                new.userspace_addr = mem->userspace_addr;
 
                if (kvm_arch_create_memslot(&new, npages))
                        goto out_free;
-       } else if (npages && mem->userspace_addr != old.userspace_addr) {
-               r = -EINVAL;
-               goto out_free;
        }
 
        /* Allocate page dirty bitmap if needed */
@@ -830,7 +852,7 @@ int __kvm_set_memory_region(struct kvm *kvm,
                        goto out_free;
        }
 
-       if (!npages || base_gfn != old.base_gfn) {
+       if ((change == KVM_MR_DELETE) || (change == KVM_MR_MOVE)) {
                r = -ENOMEM;
                slots = kmemdup(kvm->memslots, sizeof(struct kvm_memslots),
                                GFP_KERNEL);
@@ -843,7 +865,6 @@ int __kvm_set_memory_region(struct kvm *kvm,
 
                /* slot was deleted or moved, clear iommu mapping */
                kvm_iommu_unmap_pages(kvm, &old);
-               old_iommu_mapped = false;
                /* From this point no new shadow pages pointing to a deleted,
                 * or moved, memslot will be created.
                 *
@@ -874,29 +895,21 @@ int __kvm_set_memory_region(struct kvm *kvm,
 
        /*
         * IOMMU mapping:  New slots need to be mapped.  Old slots need to be
-        * un-mapped and re-mapped if their base changes or if flags that the
-        * iommu cares about change (read-only).  Base change unmapping is
-        * handled above with slot deletion, so we only unmap incompatible
-        * flags here.  Anything else the iommu might care about for existing
-        * slots (size changes, userspace addr changes) is disallowed above,
-        * so any other attribute changes getting here can be skipped.
+        * un-mapped and re-mapped if their base changes.  Since base change
+        * unmapping is handled above with slot deletion, mapping alone is
+        * needed here.  Anything else the iommu might care about for existing
+        * slots (size changes, userspace addr changes and read-only flag
+        * changes) is disallowed above, so any other attribute changes getting
+        * here can be skipped.
         */
-       if (npages) {
-               if (old_iommu_mapped &&
-                   ((new.flags ^ old.flags) & KVM_MEM_READONLY)) {
-                       kvm_iommu_unmap_pages(kvm, &old);
-                       old_iommu_mapped = false;
-               }
-
-               if (!old_iommu_mapped) {
-                       r = kvm_iommu_map_pages(kvm, &new);
-                       if (r)
-                               goto out_slots;
-               }
+       if ((change == KVM_MR_CREATE) || (change == KVM_MR_MOVE)) {
+               r = kvm_iommu_map_pages(kvm, &new);
+               if (r)
+                       goto out_slots;
        }
 
        /* actual memory is freed via old in kvm_free_physmem_slot below */
-       if (!npages) {
+       if (change == KVM_MR_DELETE) {
                new.dirty_bitmap = NULL;
                memset(&new.arch, 0, sizeof(new.arch));
        }