KVM: PPC: Book3S HV: Outline of KVM-HV HPT resizing implementation
authorDavid Gibson <david@gibson.dropbear.id.au>
Tue, 20 Dec 2016 05:49:05 +0000 (16:49 +1100)
committerPaul Mackerras <paulus@ozlabs.org>
Tue, 31 Jan 2017 10:59:56 +0000 (21:59 +1100)
This adds a not yet working outline of the HPT resizing PAPR
extension.  Specifically it adds the necessary ioctl() functions,
their basic steps, the work function which will handle preparation for
the resize, and synchronization between these, the guest page fault
path and guest HPT update path.

The actual guts of the implementation isn't here yet, so for now the
calls will always fail.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
Signed-off-by: Paul Mackerras <paulus@ozlabs.org>
arch/powerpc/include/asm/kvm_host.h
arch/powerpc/include/asm/kvm_ppc.h
arch/powerpc/kvm/book3s_64_mmu_hv.c
arch/powerpc/kvm/book3s_hv.c

index 0aa0f22d775a14f95a04d818ffde701e607a46b3..7bba8f41562705c5eb6c01161979e33ae816ddb9 100644 (file)
@@ -252,6 +252,8 @@ struct kvm_hpt_info {
        int cma;
 };
 
+struct kvm_resize_hpt;
+
 struct kvm_arch {
        unsigned int lpid;
 #ifdef CONFIG_KVM_BOOK3S_HV_POSSIBLE
@@ -276,6 +278,7 @@ struct kvm_arch {
        u64 process_table;
        struct dentry *debugfs_dir;
        struct dentry *htab_dentry;
+       struct kvm_resize_hpt *resize_hpt; /* protected by kvm->lock */
 #endif /* CONFIG_KVM_BOOK3S_HV_POSSIBLE */
 #ifdef CONFIG_KVM_BOOK3S_PR_POSSIBLE
        struct mutex hpt_mutex;
index cf3ef8d759107b09bb96214d49927bba9714f6d4..dd11c4c8c56a0cc93bef66c5fe8791bcfbb9699a 100644 (file)
@@ -215,6 +215,10 @@ extern void kvmppc_bookehv_exit(void);
 extern int kvmppc_prepare_to_enter(struct kvm_vcpu *vcpu);
 
 extern int kvm_vm_ioctl_get_htab_fd(struct kvm *kvm, struct kvm_get_htab_fd *);
+extern long kvm_vm_ioctl_resize_hpt_prepare(struct kvm *kvm,
+                                           struct kvm_ppc_resize_hpt *rhpt);
+extern long kvm_vm_ioctl_resize_hpt_commit(struct kvm *kvm,
+                                          struct kvm_ppc_resize_hpt *rhpt);
 
 int kvm_vcpu_ioctl_interrupt(struct kvm_vcpu *vcpu, struct kvm_interrupt *irq);
 
index 6d70989686a7875721fd79e7b0a36b552a174f32..323287fc0c01e056a5ce1470b257b7cd4040da48 100644 (file)
 
 #include "trace_hv.h"
 
+//#define DEBUG_RESIZE_HPT     1
+
+#ifdef DEBUG_RESIZE_HPT
+#define resize_hpt_debug(resize, ...)                          \
+       do {                                                    \
+               printk(KERN_DEBUG "RESIZE HPT %p: ", resize);   \
+               printk(__VA_ARGS__);                            \
+       } while (0)
+#else
+#define resize_hpt_debug(resize, ...)                          \
+       do { } while (0)
+#endif
+
 static long kvmppc_virtmode_do_h_enter(struct kvm *kvm, unsigned long flags,
                                long pte_index, unsigned long pteh,
                                unsigned long ptel, unsigned long *pte_idx_ret);
+
+struct kvm_resize_hpt {
+       /* These fields read-only after init */
+       struct kvm *kvm;
+       struct work_struct work;
+       u32 order;
+
+       /* These fields protected by kvm->lock */
+       int error;
+       bool prepare_done;
+};
+
 static void kvmppc_rmap_reset(struct kvm *kvm);
 
 int kvmppc_allocate_hpt(struct kvm_hpt_info *info, u32 order)
@@ -1179,6 +1204,172 @@ void kvmppc_unpin_guest_page(struct kvm *kvm, void *va, unsigned long gpa,
        srcu_read_unlock(&kvm->srcu, srcu_idx);
 }
 
+/*
+ * HPT resizing
+ */
+static int resize_hpt_allocate(struct kvm_resize_hpt *resize)
+{
+       return 0;
+}
+
+static int resize_hpt_rehash(struct kvm_resize_hpt *resize)
+{
+       return -EIO;
+}
+
+static void resize_hpt_pivot(struct kvm_resize_hpt *resize)
+{
+}
+
+static void resize_hpt_release(struct kvm *kvm, struct kvm_resize_hpt *resize)
+{
+       BUG_ON(kvm->arch.resize_hpt != resize);
+       kvm->arch.resize_hpt = NULL;
+       kfree(resize);
+}
+
+static void resize_hpt_prepare_work(struct work_struct *work)
+{
+       struct kvm_resize_hpt *resize = container_of(work,
+                                                    struct kvm_resize_hpt,
+                                                    work);
+       struct kvm *kvm = resize->kvm;
+       int err;
+
+       resize_hpt_debug(resize, "resize_hpt_prepare_work(): order = %d\n",
+                        resize->order);
+
+       err = resize_hpt_allocate(resize);
+
+       mutex_lock(&kvm->lock);
+
+       resize->error = err;
+       resize->prepare_done = true;
+
+       mutex_unlock(&kvm->lock);
+}
+
+long kvm_vm_ioctl_resize_hpt_prepare(struct kvm *kvm,
+                                    struct kvm_ppc_resize_hpt *rhpt)
+{
+       unsigned long flags = rhpt->flags;
+       unsigned long shift = rhpt->shift;
+       struct kvm_resize_hpt *resize;
+       int ret;
+
+       if (flags != 0)
+               return -EINVAL;
+
+       if (shift && ((shift < 18) || (shift > 46)))
+               return -EINVAL;
+
+       mutex_lock(&kvm->lock);
+
+       resize = kvm->arch.resize_hpt;
+
+       if (resize) {
+               if (resize->order == shift) {
+                       /* Suitable resize in progress */
+                       if (resize->prepare_done) {
+                               ret = resize->error;
+                               if (ret != 0)
+                                       resize_hpt_release(kvm, resize);
+                       } else {
+                               ret = 100; /* estimated time in ms */
+                       }
+
+                       goto out;
+               }
+
+               /* not suitable, cancel it */
+               resize_hpt_release(kvm, resize);
+       }
+
+       ret = 0;
+       if (!shift)
+               goto out; /* nothing to do */
+
+       /* start new resize */
+
+       resize = kzalloc(sizeof(*resize), GFP_KERNEL);
+       resize->order = shift;
+       resize->kvm = kvm;
+       INIT_WORK(&resize->work, resize_hpt_prepare_work);
+       kvm->arch.resize_hpt = resize;
+
+       schedule_work(&resize->work);
+
+       ret = 100; /* estimated time in ms */
+
+out:
+       mutex_unlock(&kvm->lock);
+       return ret;
+}
+
+static void resize_hpt_boot_vcpu(void *opaque)
+{
+       /* Nothing to do, just force a KVM exit */
+}
+
+long kvm_vm_ioctl_resize_hpt_commit(struct kvm *kvm,
+                                   struct kvm_ppc_resize_hpt *rhpt)
+{
+       unsigned long flags = rhpt->flags;
+       unsigned long shift = rhpt->shift;
+       struct kvm_resize_hpt *resize;
+       long ret;
+
+       if (flags != 0)
+               return -EINVAL;
+
+       if (shift && ((shift < 18) || (shift > 46)))
+               return -EINVAL;
+
+       mutex_lock(&kvm->lock);
+
+       resize = kvm->arch.resize_hpt;
+
+       /* This shouldn't be possible */
+       ret = -EIO;
+       if (WARN_ON(!kvm->arch.hpte_setup_done))
+               goto out_no_hpt;
+
+       /* Stop VCPUs from running while we mess with the HPT */
+       kvm->arch.hpte_setup_done = 0;
+       smp_mb();
+
+       /* Boot all CPUs out of the guest so they re-read
+        * hpte_setup_done */
+       on_each_cpu(resize_hpt_boot_vcpu, NULL, 1);
+
+       ret = -ENXIO;
+       if (!resize || (resize->order != shift))
+               goto out;
+
+       ret = -EBUSY;
+       if (!resize->prepare_done)
+               goto out;
+
+       ret = resize->error;
+       if (ret != 0)
+               goto out;
+
+       ret = resize_hpt_rehash(resize);
+       if (ret != 0)
+               goto out;
+
+       resize_hpt_pivot(resize);
+
+out:
+       /* Let VCPUs run again */
+       kvm->arch.hpte_setup_done = 1;
+       smp_mb();
+out_no_hpt:
+       resize_hpt_release(kvm, resize);
+       mutex_unlock(&kvm->lock);
+       return ret;
+}
+
 /*
  * Functions for reading and writing the hash table via reads and
  * writes on a file descriptor.
index fbc90174630403e8ac98b41a46d11912a6a5a6d8..1e107ece4e3701c51e997f989029aac77ff4c60d 100644 (file)
@@ -3422,6 +3422,9 @@ static int kvmppc_core_init_vm_hv(struct kvm *kvm)
 
        kvm->arch.lpcr = lpcr;
 
+       /* Initialization for future HPT resizes */
+       kvm->arch.resize_hpt = NULL;
+
        /*
         * Work out how many sets the TLB has, for the use of
         * the TLB invalidation loop in book3s_hv_rmhandlers.S.
@@ -3721,6 +3724,28 @@ static long kvm_arch_vm_ioctl_hv(struct file *filp,
                break;
        }
 
+       case KVM_PPC_RESIZE_HPT_PREPARE: {
+               struct kvm_ppc_resize_hpt rhpt;
+
+               r = -EFAULT;
+               if (copy_from_user(&rhpt, argp, sizeof(rhpt)))
+                       break;
+
+               r = kvm_vm_ioctl_resize_hpt_prepare(kvm, &rhpt);
+               break;
+       }
+
+       case KVM_PPC_RESIZE_HPT_COMMIT: {
+               struct kvm_ppc_resize_hpt rhpt;
+
+               r = -EFAULT;
+               if (copy_from_user(&rhpt, argp, sizeof(rhpt)))
+                       break;
+
+               r = kvm_vm_ioctl_resize_hpt_commit(kvm, &rhpt);
+               break;
+       }
+
        default:
                r = -ENOTTY;
        }