KVM: s390: fix mismatch between user and in-kernel guest limit
authorDominik Dingel <dingel@linux.vnet.ibm.com>
Mon, 1 Dec 2014 16:24:42 +0000 (17:24 +0100)
committerChristian Borntraeger <borntraeger@de.ibm.com>
Tue, 15 Dec 2015 16:08:21 +0000 (17:08 +0100)
While the userspace interface requests the maximum size the gmap code
expects to get a maximum address.

This error resulted in bigger page tables than necessary for some guest
sizes, e.g. a 2GB guest used 3 levels instead of 2.

At the same time we introduce KVM_S390_NO_MEM_LIMIT, which allows in a
bright future that a guest spans the complete 64 bit address space.

We also switch to TASK_MAX_SIZE for the initial memory size, this is a
cosmetic change as the previous size also resulted in a 4 level pagetable
creation.

Reported-by: David Hildenbrand <dahi@linux.vnet.ibm.com>
Reviewed-by: Cornelia Huck <cornelia.huck@de.ibm.com>
Signed-off-by: Dominik Dingel <dingel@linux.vnet.ibm.com>
Signed-off-by: Christian Borntraeger <borntraeger@de.ibm.com>
Documentation/virtual/kvm/devices/vm.txt
arch/s390/include/asm/kvm_host.h
arch/s390/include/uapi/asm/kvm.h
arch/s390/kvm/kvm-s390.c
arch/s390/mm/pgtable.c

index 2d09d1ed86d02d99046d62b1a772589e3de70c4d..f083a168eb350b80895f25a23621b87a131be84b 100644 (file)
@@ -37,7 +37,8 @@ Returns: -EFAULT if the given address is not accessible
 Allows userspace to query the actual limit and set a new limit for
 the maximum guest memory size. The limit will be rounded up to
 2048 MB, 4096 GB, 8192 TB respectively, as this limit is governed by
-the number of page table levels.
+the number of page table levels. In the case that there is no limit we will set
+the limit to KVM_S390_NO_MEM_LIMIT (U64_MAX).
 
 2. GROUP: KVM_S390_VM_CPU_MODEL
 Architectures: s390
index 12e9291da2673ff8a65c6500f5f384349accf97f..c83144110ea9006db00c0772abe8eff37a51861c 100644 (file)
@@ -627,6 +627,7 @@ struct kvm_arch{
        struct kvm_s390_float_interrupt float_int;
        struct kvm_device *flic;
        struct gmap *gmap;
+       unsigned long mem_limit;
        int css_support;
        int use_irqchip;
        int use_cmma;
index ef1a5fcc6c66bbf5705173b41371378c4b541483..d2aea31252f2ee9200a0cc8f0eeeeb54895c6235 100644 (file)
@@ -66,6 +66,8 @@ struct kvm_s390_io_adapter_req {
 #define KVM_S390_VM_MEM_CLR_CMMA       1
 #define KVM_S390_VM_MEM_LIMIT_SIZE     2
 
+#define KVM_S390_NO_MEM_LIMIT          U64_MAX
+
 /* kvm attributes for KVM_S390_VM_TOD */
 #define KVM_S390_VM_TOD_LOW            0
 #define KVM_S390_VM_TOD_HIGH           1
index c14845c3a6f8ccfaeab8cfceeffadcf3ced71cf7..8aa5e55a8ecd07abb395a74fe7189f1b940b0147 100644 (file)
@@ -378,8 +378,8 @@ static int kvm_s390_get_mem_control(struct kvm *kvm, struct kvm_device_attr *att
        case KVM_S390_VM_MEM_LIMIT_SIZE:
                ret = 0;
                VM_EVENT(kvm, 3, "QUERY: max guest memory: %lu bytes",
-                        kvm->arch.gmap->asce_end);
-               if (put_user(kvm->arch.gmap->asce_end, (u64 __user *)attr->addr))
+                        kvm->arch.mem_limit);
+               if (put_user(kvm->arch.mem_limit, (u64 __user *)attr->addr))
                        ret = -EFAULT;
                break;
        default:
@@ -431,9 +431,17 @@ static int kvm_s390_set_mem_control(struct kvm *kvm, struct kvm_device_attr *att
                if (get_user(new_limit, (u64 __user *)attr->addr))
                        return -EFAULT;
 
-               if (new_limit > kvm->arch.gmap->asce_end)
+               if (kvm->arch.mem_limit != KVM_S390_NO_MEM_LIMIT &&
+                   new_limit > kvm->arch.mem_limit)
                        return -E2BIG;
 
+               if (!new_limit)
+                       return -EINVAL;
+
+               /* gmap_alloc takes last usable address */
+               if (new_limit != KVM_S390_NO_MEM_LIMIT)
+                       new_limit -= 1;
+
                ret = -EBUSY;
                mutex_lock(&kvm->lock);
                if (atomic_read(&kvm->online_vcpus) == 0) {
@@ -450,7 +458,9 @@ static int kvm_s390_set_mem_control(struct kvm *kvm, struct kvm_device_attr *att
                        }
                }
                mutex_unlock(&kvm->lock);
-               VM_EVENT(kvm, 3, "SET: max guest memory: %lu bytes", new_limit);
+               VM_EVENT(kvm, 3, "SET: max guest address: %lu", new_limit);
+               VM_EVENT(kvm, 3, "New guest asce: 0x%pK",
+                        (void *) kvm->arch.gmap->asce);
                break;
        }
        default:
@@ -1172,8 +1182,10 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
 
        if (type & KVM_VM_S390_UCONTROL) {
                kvm->arch.gmap = NULL;
+               kvm->arch.mem_limit = KVM_S390_NO_MEM_LIMIT;
        } else {
-               kvm->arch.gmap = gmap_alloc(current->mm, (1UL << 44) - 1);
+               kvm->arch.mem_limit = TASK_MAX_SIZE;
+               kvm->arch.gmap = gmap_alloc(current->mm, kvm->arch.mem_limit - 1);
                if (!kvm->arch.gmap)
                        goto out_err;
                kvm->arch.gmap->private = kvm;
@@ -2829,6 +2841,9 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm,
        if (mem->memory_size & 0xffffful)
                return -EINVAL;
 
+       if (mem->guest_phys_addr + mem->memory_size > kvm->arch.mem_limit)
+               return -EINVAL;
+
        return 0;
 }
 
index 54ef3bc01b43c361a47d6a9112b9bb7bfcdc4501..63b039899a5ed1d70c56445b28f18b6d5389df28 100644 (file)
@@ -133,7 +133,7 @@ void crst_table_downgrade(struct mm_struct *mm, unsigned long limit)
 /**
  * gmap_alloc - allocate a guest address space
  * @mm: pointer to the parent mm_struct
- * @limit: maximum size of the gmap address space
+ * @limit: maximum address of the gmap address space
  *
  * Returns a guest address space structure.
  */
@@ -402,7 +402,7 @@ int gmap_map_segment(struct gmap *gmap, unsigned long from,
        if ((from | to | len) & (PMD_SIZE - 1))
                return -EINVAL;
        if (len == 0 || from + len < from || to + len < to ||
-           from + len > TASK_MAX_SIZE || to + len > gmap->asce_end)
+           from + len - 1 > TASK_MAX_SIZE || to + len - 1 > gmap->asce_end)
                return -EINVAL;
 
        flush = 0;