]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - arch/powerpc/kvm/book3s_64_vio.c
KVM: PPC: Add support for multiple-TCE hcalls
[karo-tx-linux.git] / arch / powerpc / kvm / book3s_64_vio.c
index 54cf9bc94dadfe3a1debf64f03cea511fc0aa97b..94c8e7e9b58cdfc32cf164a84113515c5daa6a4d 100644 (file)
@@ -14,6 +14,7 @@
  *
  * Copyright 2010 Paul Mackerras, IBM Corp. <paulus@au1.ibm.com>
  * Copyright 2011 David Gibson, IBM Corporation <dwg@au1.ibm.com>
+ * Copyright 2016 Alexey Kardashevskiy, IBM Corporation <aik@au1.ibm.com>
  */
 
 #include <linux/types.h>
 #include <asm/ppc-opcode.h>
 #include <asm/kvm_host.h>
 #include <asm/udbg.h>
+#include <asm/iommu.h>
+#include <asm/tce.h>
 
-#define TCES_PER_PAGE  (PAGE_SIZE / sizeof(u64))
-
-static long kvmppc_stt_npages(unsigned long window_size)
+static unsigned long kvmppc_tce_pages(unsigned long window_size)
 {
-       return ALIGN((window_size >> SPAPR_TCE_SHIFT)
+       return ALIGN((window_size >> IOMMU_PAGE_SHIFT_4K)
                     * sizeof(u64), PAGE_SIZE) / PAGE_SIZE;
 }
 
-static void release_spapr_tce_table(struct kvmppc_spapr_tce_table *stt)
+static unsigned long kvmppc_stt_pages(unsigned long tce_pages)
+{
+       unsigned long stt_bytes = sizeof(struct kvmppc_spapr_tce_table) +
+                       (tce_pages * sizeof(struct page *));
+
+       return tce_pages + ALIGN(stt_bytes, PAGE_SIZE) / PAGE_SIZE;
+}
+
+static long kvmppc_account_memlimit(unsigned long stt_pages, bool inc)
+{
+       long ret = 0;
+
+       if (!current || !current->mm)
+               return ret; /* process exited */
+
+       down_write(&current->mm->mmap_sem);
+
+       if (inc) {
+               unsigned long locked, lock_limit;
+
+               locked = current->mm->locked_vm + stt_pages;
+               lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
+               if (locked > lock_limit && !capable(CAP_IPC_LOCK))
+                       ret = -ENOMEM;
+               else
+                       current->mm->locked_vm += stt_pages;
+       } else {
+               if (WARN_ON_ONCE(stt_pages > current->mm->locked_vm))
+                       stt_pages = current->mm->locked_vm;
+
+               current->mm->locked_vm -= stt_pages;
+       }
+
+       pr_debug("[%d] RLIMIT_MEMLOCK KVM %c%ld %ld/%ld%s\n", current->pid,
+                       inc ? '+' : '-',
+                       stt_pages << PAGE_SHIFT,
+                       current->mm->locked_vm << PAGE_SHIFT,
+                       rlimit(RLIMIT_MEMLOCK),
+                       ret ? " - exceeded" : "");
+
+       up_write(&current->mm->mmap_sem);
+
+       return ret;
+}
+
+static void release_spapr_tce_table(struct rcu_head *head)
 {
-       struct kvm *kvm = stt->kvm;
+       struct kvmppc_spapr_tce_table *stt = container_of(head,
+                       struct kvmppc_spapr_tce_table, rcu);
        int i;
+       unsigned long npages = kvmppc_tce_pages(stt->window_size);
 
-       mutex_lock(&kvm->lock);
-       list_del(&stt->list);
-       for (i = 0; i < kvmppc_stt_npages(stt->window_size); i++)
+       for (i = 0; i < npages; i++)
                __free_page(stt->pages[i]);
-       kfree(stt);
-       mutex_unlock(&kvm->lock);
 
-       kvm_put_kvm(kvm);
+       kfree(stt);
 }
 
 static int kvm_spapr_tce_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
@@ -65,7 +109,7 @@ static int kvm_spapr_tce_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
        struct kvmppc_spapr_tce_table *stt = vma->vm_file->private_data;
        struct page *page;
 
-       if (vmf->pgoff >= kvmppc_stt_npages(stt->window_size))
+       if (vmf->pgoff >= kvmppc_tce_pages(stt->window_size))
                return VM_FAULT_SIGBUS;
 
        page = stt->pages[vmf->pgoff];
@@ -88,7 +132,14 @@ static int kvm_spapr_tce_release(struct inode *inode, struct file *filp)
 {
        struct kvmppc_spapr_tce_table *stt = filp->private_data;
 
-       release_spapr_tce_table(stt);
+       list_del_rcu(&stt->list);
+
+       kvm_put_kvm(stt->kvm);
+
+       kvmppc_account_memlimit(
+               kvmppc_stt_pages(kvmppc_tce_pages(stt->window_size)), false);
+       call_rcu(&stt->rcu, release_spapr_tce_table);
+
        return 0;
 }
 
@@ -101,7 +152,7 @@ long kvm_vm_ioctl_create_spapr_tce(struct kvm *kvm,
                                   struct kvm_create_spapr_tce *args)
 {
        struct kvmppc_spapr_tce_table *stt = NULL;
-       long npages;
+       unsigned long npages;
        int ret = -ENOMEM;
        int i;
 
@@ -111,7 +162,12 @@ long kvm_vm_ioctl_create_spapr_tce(struct kvm *kvm,
                        return -EBUSY;
        }
 
-       npages = kvmppc_stt_npages(args->window_size);
+       npages = kvmppc_tce_pages(args->window_size);
+       ret = kvmppc_account_memlimit(kvmppc_stt_pages(npages), true);
+       if (ret) {
+               stt = NULL;
+               goto fail;
+       }
 
        stt = kzalloc(sizeof(*stt) + npages * sizeof(struct page *),
                      GFP_KERNEL);
@@ -131,7 +187,7 @@ long kvm_vm_ioctl_create_spapr_tce(struct kvm *kvm,
        kvm_get_kvm(kvm);
 
        mutex_lock(&kvm->lock);
-       list_add(&stt->list, &kvm->arch.spapr_tce_tables);
+       list_add_rcu(&stt->list, &kvm->arch.spapr_tce_tables);
 
        mutex_unlock(&kvm->lock);
 
@@ -148,3 +204,59 @@ fail:
        }
        return ret;
 }
+
+long kvmppc_h_put_tce_indirect(struct kvm_vcpu *vcpu,
+               unsigned long liobn, unsigned long ioba,
+               unsigned long tce_list, unsigned long npages)
+{
+       struct kvmppc_spapr_tce_table *stt;
+       long i, ret = H_SUCCESS, idx;
+       unsigned long entry, ua = 0;
+       u64 __user *tces, tce;
+
+       stt = kvmppc_find_table(vcpu, liobn);
+       if (!stt)
+               return H_TOO_HARD;
+
+       entry = ioba >> IOMMU_PAGE_SHIFT_4K;
+       /*
+        * SPAPR spec says that the maximum size of the list is 512 TCEs
+        * so the whole table fits in 4K page
+        */
+       if (npages > 512)
+               return H_PARAMETER;
+
+       if (tce_list & (SZ_4K - 1))
+               return H_PARAMETER;
+
+       ret = kvmppc_ioba_validate(stt, ioba, npages);
+       if (ret != H_SUCCESS)
+               return ret;
+
+       idx = srcu_read_lock(&vcpu->kvm->srcu);
+       if (kvmppc_gpa_to_ua(vcpu->kvm, tce_list, &ua, NULL)) {
+               ret = H_TOO_HARD;
+               goto unlock_exit;
+       }
+       tces = (u64 __user *) ua;
+
+       for (i = 0; i < npages; ++i) {
+               if (get_user(tce, tces + i)) {
+                       ret = H_TOO_HARD;
+                       goto unlock_exit;
+               }
+               tce = be64_to_cpu(tce);
+
+               ret = kvmppc_tce_validate(stt, tce);
+               if (ret != H_SUCCESS)
+                       goto unlock_exit;
+
+               kvmppc_tce_put(stt, entry + i, tce);
+       }
+
+unlock_exit:
+       srcu_read_unlock(&vcpu->kvm->srcu, idx);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(kvmppc_h_put_tce_indirect);